一、shiro的工作流程
- 項目每次啟動時,根據(jù)shiroConfig的配置,將相應(yīng)權(quán)限url加載到shiro框架中
- 用戶執(zhí)行登錄時,會自動執(zhí)行doGetAuthenticationInfo和doGetAuthorizationInfo方法進(jìn)行認(rèn)證和鑒權(quán)
- 用戶進(jìn)行訪問操作,若無權(quán)限或者未登錄,會根據(jù)shiroConfig的配置,自動跳轉(zhuǎn)到相應(yīng)頁面
使用用戶賬戶名和密碼生成令牌---->執(zhí)行登錄(shiro本身并不知令牌是否合法,通過用戶自行實現(xiàn)Realm進(jìn)行比對,常用的是數(shù)據(jù)庫查詢)
Subject user = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
user.login(token);
} catch (LockedAccountException lae) {
token.clear();
response.setStatus(ResponseInfo.ERROR.getStatus());
response.setMsg("用戶已經(jīng)被鎖定不能登錄,請與管理員聯(lián)系!");
return response;
} catch (ExcessiveAttemptsException e) {
token.clear();
response.setStatus(ResponseInfo.ERROR.getStatus());
response.setMsg(" 登錄失敗次數(shù)過多,鎖定10分鐘!");
return response;
} catch (AuthenticationException e) {
token.clear();
response.setStatus(ResponseInfo.ERROR.getStatus());
response.setMsg("用戶或密碼不正確!");
return response;
}
// 當(dāng)驗證都通過后,把用戶信息放在session里
User loginUser = new User();
loginUser.setAccount(username);
loginUser = userMapper.selectOne(loginUser);
if(loginUser!=null){
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute(SessionUtil.SESSIONKEY, loginUser);
session.setTimeout(3600000);//設(shè)置session過期時間1小時
}
注意:用戶登錄時,認(rèn)證和鑒權(quán)都已完成,之后用戶所有的操作相當(dāng)于都在shiro的監(jiān)控下
二、實現(xiàn)細(xì)節(jié)
備注:本案例所在項目是前后端分離,前端angularJs,后端springBoot,數(shù)據(jù)交互采用json,所有后臺接口返回JsonResponse型數(shù)據(jù)。
1、實現(xiàn)ShiroConfig
/**Shiro 配置*/
@Configuration
public class ShiroConfig {
private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
@Bean
public EhCacheManager getEhCacheManager() {
EhCacheManager em = new EhCacheManager();
em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return em;
}
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName(PasswordHelper.ALGORITHMNAME);
hashedCredentialsMatcher.setHashIterations(PasswordHelper.HASHITERATIONS);
return hashedCredentialsMatcher;
}
@Bean(name = "egRealm")
public EgRealm myShiroRealm(EhCacheManager cacheManager,HashedCredentialsMatcher hashedCredentialsMatcher) {
EgRealm realm = new EgRealm();
realm.setCacheManager(cacheManager);
realm.setCredentialsMatcher(hashedCredentialsMatcher);
return realm;
}
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(EgRealm myShiroRealm) {
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
dwsm.setRealm(myShiroRealm);
//<!-- 用戶授權(quán)/認(rèn)證信息Cache, 采用EhCache 緩存 -->
dwsm.setCacheManager(getEhCacheManager());
return dwsm;
}
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager);
return aasa;
}
// 加載shiroFilter權(quán)限控制規(guī)則(從數(shù)據(jù)庫讀取然后配置)
private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean,ResourceService resourceService){
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//遍歷所有需要過濾的resource_url,逐個添加到filterChainDefinitionMap中
List<Map<String,Object>> resources = resourceService.selectAllResource();
for(Map<String,Object> resource : resources){
String resourceId = resource.get("resourceId").toString();
if(StringUtils.isNotBlank(resourceId) && resource.get("resourceUrl")!=null){
String permission = "perms["+resourceId+"]";
filterChainDefinitionMap.put(resource.get("resourceUrl").toString(),permission);
}
}
logger.info("加載resource.json文件中的資源路徑和id到shiroFilter中");
//添加一些不需權(quán)限的地址
filterChainDefinitionMap.put("/user/login", "anon");
filterChainDefinitionMap.put("/user/toLogin", "anon");
filterChainDefinitionMap.put("/user/getloginUserAccountName", "anon");
filterChainDefinitionMap.put("/metadata/**", "anon");
filterChainDefinitionMap.put("/menu/getMenuDataByUser", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
}
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager,ResourceService resourceService) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必須設(shè)置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不設(shè)置默認(rèn)會自動尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
// 登錄成功后要跳轉(zhuǎn)的連接
shiroFilterFactoryBean.setSuccessUrl("/user/loginSuccess");
// 未授權(quán)界面,鑒權(quán)失敗時返回信息給前端;
shiroFilterFactoryBean.setUnauthorizedUrl("/user/returnUnauthorizedMsg");
loadShiroFilterChain(shiroFilterFactoryBean,resourceService);
return shiroFilterFactoryBean;
}
2、實現(xiàn)Realm
public class EgRealm extends AuthorizingRealm {
private static final Logger logger = LoggerFactory.getLogger(EgRealm.class);
@Autowired
UserMapper userMapper;
@Autowired
UserRoleMapper userRoleMapper;
@Autowired
RoleResourceMapper roleResourceMapper;
@Autowired
ResourceMapper resourceMapper;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("-----------------執(zhí)行Shiro權(quán)限認(rèn)證-----------------------");
//獲取當(dāng)前登錄輸入的用戶名,等價于(String) principalCollection.fromRealm(getName()).iterator().next();
//String loginName = (String)super.getAvailablePrincipal(principals);
User loginUser = (User) SecurityUtils.getSubject().getSession().getAttribute(SessionUtil.SESSIONKEY);
if(loginUser!=null){
// 權(quán)限信息對象info,用來存放查出的用戶的所有的角色(role)及權(quán)限(permission)
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
String userId = loginUser.getUserId();
UserRole ur = new UserRole();
ur.setUserId(userId);
List<UserRole> userRoles = userRoleMapper.select(ur);
Set<String> resourceIdSet = new HashSet<String>();
//查詢出用戶所有具有的資源,并加入到info中
if(userRoles!=null && userRoles.size()>0){
for(UserRole userRole : userRoles){
RoleResource rr = new RoleResource();
rr.setRoleId(userRole.getRoleId());
List<RoleResource> roleResources = roleResourceMapper.select(rr);
if(roleResources!=null && roleResources.size()>0){
for(RoleResource roleResource : roleResources){
resourceIdSet.add(roleResource.getResourceId());
}
}
}
}
if(resourceIdSet.size()>0){
Iterator<String> i = resourceIdSet.iterator();
while(i.hasNext()){
String resourceId = i.next();
if(StringUtils.isNotBlank(resourceId)){
info.addStringPermission(resourceId);
}
}
}
//除了添加權(quán)限,還可以添加角色,在filter中限定具有某種角色才可訪問
//authorizationInfo.setRoles(...);
return info;
}
return null;
}
/**
* 登錄認(rèn)證
該方法主要執(zhí)行以下操作:
1、檢查提交的進(jìn)行認(rèn)證的令牌信息
2、根據(jù)令牌信息從數(shù)據(jù)源(通常為數(shù)據(jù)庫)中獲取用戶信息
3、對用戶信息進(jìn)行匹配驗證。
4、驗證通過將返回一個封裝了用戶信息的AuthenticationInfo實例。
5、驗證失敗則拋出AuthenticationException異常信息。
而在我們的應(yīng)用程序中要做的就是自定義一個Realm類,繼承AuthorizingRealm抽象類,重載doGetAuthenticationInfo
(),重寫獲取用戶信息的方法。
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.info("------------執(zhí)行Shiro身份認(rèn)證--------------");
//UsernamePasswordToken對象用來存放提交的登錄信息
UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
logger.info("驗證當(dāng)前Subject時獲取到token為:" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
//查出是否有此用戶
User user = new User();
String username = token.getUsername();
user.setAccount(username);
user=userMapper.selectOne(user);
if(user!=null){
// 若存在,將此用戶存放到登錄認(rèn)證info中,無需自己做密碼對比,Shiro會為我們進(jìn)行密碼對比校驗 // salt=username+salt
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getAccount(),user.getPassword(),ByteSource.Util.bytes(username+""+user.getCredentialssalt()), getName());
return simpleAuthenticationInfo;
}else {
throw new UnknownAccountException();// 沒找到帳號
}
}