基于shiro的通用鑒權(quán)組件Jarvis
大部分應(yīng)用都有鑒權(quán)的需求。如應(yīng)用認證岩榆,要求訪問者需要登錄才能訪問窥翩。再如應(yīng)用授權(quán),不僅要求訪問者登錄项贺,還需要訪問被授予權(quán)限才能訪問特定資源君躺。
為每個大大小小應(yīng)用開發(fā)安全模塊其實是一件繁瑣的事情。每個應(yīng)用的安全模塊都提供認證和授權(quán)功能开缎。
jarvis是一個通用的spring java應(yīng)用鑒權(quán)組件棕叫,具有如下特性:
- 組件化。需要配置的項目僅需引用組件的maven依賴奕删,提供簡單配置文件即可擁有鑒權(quán)模塊...
- 在線化俺泣。提供在線web配置,在線管理用戶完残,角色伏钠,權(quán)限。
- 實時化谨设。實時維護資源權(quán)限贝润,實時生效(通常項目的url資源訪問權(quán)限由配置文件進行定義,如果修改需要重啟項目)铝宵。
- url權(quán)限粒度
Apache Shiro
什么是shiro打掘?
日語發(fā)音,中文意思是“城堡”鹏秋,是一個功能強大且容易使用的java安全框架尊蚁。提供認證,授權(quán)侣夷,加密横朋,會話管理等功能,被廣泛使用百拓。
Servlet過濾器
Filter是在Servlet 2.3之后增加的新功能琴锭,當需要限制用戶訪問某些資源或者在處理請求時提前處理某些資源的時候,就可以使用過濾器完成衙传。
shiroFilter
Shiro對Servlet容器的FilterChain進行了代理,先執(zhí)行Shiro自己的Filter鏈,再執(zhí)行Servlet容器的Filter鏈(即原始的Filter)决帖。
shiro 核心概念
Subject、SecurityManager蓖捶、Realm
Subject
表示當前正在執(zhí)行操作的用戶地回,用戶通常是人,也可以是其他應(yīng)用進程。獲取當前用戶的代碼很簡單:
import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
...
Subject currentUser = SecurityUtils.getSubject();
一旦獲取到當前用戶實例對象刻像,就可以進行登錄登出畅买,權(quán)限檢查等操作...
SecurityManager
Subject代表當前用戶,SecurityManager代表所有用戶细睡,是shiro框架的核心谷羞。
當Subject進行l(wèi)ogin或logout或checkPermission時,其實都是交由SecurityManager進行溜徙,SecurityManager能協(xié)調(diào)這些組件完成工作洒宝。
- Authenticator 負責登錄驗證。獲取安全數(shù)據(jù)(通過realm)萌京,獲取當前Subject的信息,兩者比較宏浩,通過則登錄成功知残,失敗則拋出異常。
- Authorizer 負責登錄Subject的權(quán)限校驗比庄。
- SessionManager 創(chuàng)建求妹,維護,清除所有應(yīng)用session佳窑。
- CacheManager 緩存管理組件制恍,shiro需要可以獲取用戶安全數(shù)據(jù)用于認證,通常這些數(shù)據(jù)不會經(jīng)常變動神凑,對這些數(shù)據(jù)做緩存可以提升框架性能净神。
- Cryptography 加密組件,如密碼加密溉委。
通常開發(fā)者很少直接和SecurityManager進行交互...SecurityManager采用門面模式(外觀模式)鹃唯,可以當成一個容器類,包含各個安全組件瓣喊,及各個組件的get坡慌,set方法,因此也可以看成是一個java bean藻三,這種結(jié)構(gòu)的類可以很方便的進行xml配置...擴展性也強洪橘,可以配置自己實現(xiàn)的組件。SecurityManager不進行真正的Security操作棵帽,但知道何時進行并調(diào)用協(xié)調(diào)各個組件進行工作熄求。
- Realms
Realm
realm是Shiro和應(yīng)用安全數(shù)據(jù)的“橋梁”。進行登錄或訪問控制的時候逗概,shiro需要獲取應(yīng)用的用戶抡四,角色,權(quán)限等數(shù)據(jù),shiro通過配置好的realm來獲取這些數(shù)據(jù)指巡,從這點來看淑履,realm是一個DAO
(數(shù)據(jù)訪問對象)。shiro提供了一些內(nèi)置的realm組件藻雪,用于訪問不同數(shù)據(jù)源的安全數(shù)據(jù)秘噪,如關(guān)系型數(shù)據(jù)庫,文本配置文件等勉耀。開發(fā)者也可以實現(xiàn)自己的Realm指煎,如果這些內(nèi)置的realm不滿足需求。
Jarvis基于Apache Shiro的登錄流程
- controller接口便斥,接收登錄請求至壤,請求參數(shù)通常包含用戶名和密碼,封裝成shiro的 UsernamePasswordToken對象,獲取當前Subject枢纠,調(diào)用login(token)方法;像街,將用戶信息提交給認證authenticator組件。
@RequestMapping(value = "/login")
public String login(@Valid User user, BindingResult bindingResult, HttpServletRequest request, Map map) {
if (bindingResult.hasErrors()) {
return "index";
}
String username = user.getUsername();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword(), false);
// 獲取當前的Subject
Subject currentUser = SecurityUtils.getSubject();
try {
logger.info("對用戶[" + username + "]進行登錄驗證..驗證開始");
currentUser.login(token);
logger.info("對用戶[" + username + "]進行登錄驗證..驗證通過");
} catch (UnknownAccountException uae) {
logger.info("對用戶[" + username + "]進行登錄驗證..驗證未通過,未知賬戶");
.....
.....
- authenticator組件調(diào)用自定義的realm組件doGetAuthenticationInfo(AuthenticationToken token)方法晋渺。
其中doGetAuthenticationInfo(AuthenticationToken token)方法是登錄驗證的安全數(shù)據(jù)獲取方法镰绎,代碼邏輯如下:
- 根據(jù)當前Subject提供的用戶名稱查詢出用戶
- 用戶為null,返回null木西,subject.login(token)方法拋未知賬戶異常
- 如果用戶不為null畴栖,封裝成SimpleAuthenticationInfo實體對象并返回,shiro的Authentication組件會進行密碼校驗八千,校驗失敗則拋出異常B鹧取(校驗前按需進行密碼解密,這些shiro都會幫開發(fā)者完成)恋捆。
以下是realm組件doGetAuthenticationInfo(用戶數(shù)據(jù)獲取)的實現(xiàn)
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
// UsernamePasswordToken對象用來存放提交的登錄信息
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
logger.info("驗證當前Subject時獲取到token為:" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
// 查出是否有此用戶
User user = userService.findByName(token.getUsername());
if (user != null) {
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(), // account
user.getPassword(), // 密碼
ByteSource.Util.bytes(user.getCredentialsSalt()), // username+salt
getName() // realm name
);
// 若存在关翎,將此用戶存放到登錄認證info中,無需自己做密碼對比鸠信,Shiro會為我們進行密碼對比校驗
return info;
}
return null;
}
- 處理登錄校驗結(jié)果纵寝。shiro的authentication組件根據(jù)校驗產(chǎn)生的錯誤會拋出各種異常。
try {
logger.info("對用戶[" + username + "]進行登錄驗證..驗證開始");
currentUser.login(token);
logger.info("對用戶[" + username + "]進行登錄驗證..驗證通過");
} catch (UnknownAccountException uae) {
logger.info("對用戶[" + username + "]進行登錄驗證..驗證未通過,未知賬戶");
map.put("message", "未知賬戶");
} catch (IncorrectCredentialsException ice) {
logger.info("對用戶[" + username + "]進行登錄驗證..驗證未通過,錯誤的憑證");
map.put("message", "密碼不正確");
} catch (LockedAccountException lae) {
logger.info("對用戶[" + username + "]進行登錄驗證..驗證未通過,賬戶已鎖定");
map.put("message", "賬戶已鎖定");
} catch (ExcessiveAttemptsException eae) {
logger.info("對用戶[" + username + "]進行登錄驗證..驗證未通過,錯誤次數(shù)過多");
map.put("message", "用戶名或密碼錯誤次數(shù)過多");
} catch (AuthenticationException ae) {
// 通過處理Shiro的運行時AuthenticationException就可以控制用戶登錄失敗或密碼錯誤時的情景
logger.info("對用戶[" + username + "]進行登錄驗證..驗證未通過,堆棧軌跡如下");
ae.printStackTrace();
map.put("message", "用戶名或密碼不正確");
}
shiro權(quán)限配置
shiro權(quán)限配置一般是這樣的:
告訴shiro哪些資源需要哪些角色或哪些權(quán)限...
<property name="filterChainDefinitions" >
<value>
/** = anon
/page/login.jsp = anon
/page/register/* = anon
/page/index.jsp = authc
/page/addItem* = authc,roles[數(shù)據(jù)管理員]
/page/file* = authc,roles[普通用戶]
/page/listItems* = authc,perms[刪除數(shù)據(jù)]
/page/showItem* = authc,perms[添加數(shù)據(jù)]
/page/updateItem*=authc,roles[數(shù)據(jù)管理員]
</value>
</property>
當請求傳遞到PathMatchingFilter過濾器時星立,PathMatchingFilter根據(jù)請求url路徑解析對應(yīng)的配置(anon爽茴,authc),即mappedValue,然后調(diào)用onPreHandle(),進行權(quán)限校驗绰垂,和realm提供的安全數(shù)據(jù)比對室奏,檢查當前用戶是否擁有mappedValue所要求的權(quán)限。
boolean pathsMatch(String path, ServletRequest request)
boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception
動態(tài)url權(quán)限配置
大部分shiro應(yīng)用使用配置文件配置資源權(quán)限劲装,修改配置后需要重啟才能生效胧沫。
AbstractShiroFilter abstractShiroFilter = null;
try {
abstractShiroFilter = (AbstractShiroFilter)bean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
// 獲取過濾管理器
PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) abstractShiroFilter
.getFilterChainResolver();
DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();
// 清空初始權(quán)限配置
manager.getFilterChains().clear();
bean.getFilterChainDefinitionMap().clear();
// 重新構(gòu)建生成
set(bean,definitions);
Map<String, String> chains = bean.getFilterChainDefinitionMap();
for (Map.Entry<String, String> entry : chains.entrySet()) {
String url = entry.getKey();
String chainDefinition = entry.getValue().trim().replace(" ", "");
manager.createChain(url, chainDefinition);
}
Jarvis基于Apache Shiro的權(quán)限控制流程
Jarvis采用用戶-角色-權(quán)限的權(quán)限控制體系昌简。
表結(jié)構(gòu)設(shè)計:
user用戶表
role角色表
permission權(quán)限表
用戶角色關(guān)系表 多對多關(guān)系
角色權(quán)限關(guān)系表 多對多關(guān)系
談?wù)劷巧?/h4>
角色是權(quán)限的集合。
隱式角色和顯式角色(implicit role和explicit role)
- 隱式角色绒怨。使用隱式角色做權(quán)限校驗并不關(guān)心權(quán)限纯赎,僅僅關(guān)心角色本身。即能做什么事情南蹂,是因為擁有什么角色...
- 顯式角色犬金。 能做什么事情,是因為擁有什么角色六剥,而這個角色擁有這個權(quán)限去做這件事晚顷。
Jarvis即支持隱式角色也支持顯式角色,以下是realm組件doGetAuthorizationInfo(權(quán)限安全數(shù)據(jù)獲取)的實現(xiàn):
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("##################執(zhí)行Shiro權(quán)限認證##################");
String loginName = (String) super.getAvailablePrincipal(principalCollection);
// 到數(shù)據(jù)庫查是否有此對象
User user = userService.findByName(loginName);// 實際項目中疗疟,這里可以根據(jù)實際情況做緩存该默,如果不做,Shiro自己也是有時間間隔機制策彤,2分鐘內(nèi)不會重復(fù)執(zhí)行該方法
if (user != null) {
// 權(quán)限信息對象info,用來存放查出的用戶的所有的角色(role)及權(quán)限(permission)
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 用戶的角色集合
info.setRoles(user.getRolesName());
// 用戶的角色對應(yīng)的所有權(quán)限栓袖,如果只使用角色定義訪問權(quán)限,下面的四行可以不要 隱式角色
List<Role> roleList = user.getRoleList();
for (Role role : roleList) {
info.addStringPermissions(role.getPermissionsName());
}
return info;
}
// 返回null的話锅锨,就會導(dǎo)致任何用戶訪問被攔截的請求時,都會自動跳轉(zhuǎn)到unauthorizedUrl指定的地址
return null;
}
Jarvis集成超級詳情日志分析應(yīng)用
引入maven依賴
<dependency>
<groupId>com.hlg.bigdata</groupId>
<artifactId>Jarvis</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
配置文件application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/cjxqlog?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=5581653
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#logging.path=/Users/yangwq/cjxq/
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.properties.hibernate.hbm2ddl.auto=update
未來可以考慮集成data-plant恋沃,商品分類系統(tǒng)等需要鑒權(quán)的應(yīng)用必搞。