一、shiro的工作流程
- 项目每次启动时,根据shiroConfig的配置,将相应权限url加载到shiro框架中
- 用户执行登录时,会自动执行doGetAuthenticationInfo和doGetAuthorizationInfo方法进行认证和鉴权
- 用户进行访问操作,若无权限或者未登录,会根据shiroConfig的配置,自动跳转到相应页面
使用用户账户名和密码生成令牌---->执行登录(shiro本身并不知令牌是否合法,通过用户自行实现Realm进行比对,常用的是数据库查询)
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("用户已经被锁定不能登录,请与管理员联系!");
return response;
} catch (ExcessiveAttemptsException e) {
token.clear();
response.setStatus(ResponseInfo.ERROR.getStatus());
response.setMsg(" 登录失败次数过多,锁定10分钟!");
return response;
} catch (AuthenticationException e) {
token.clear();
response.setStatus(ResponseInfo.ERROR.getStatus());
response.setMsg("用户或密码不正确!");
return response;
}
// 当验证都通过后,把用户信息放在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);//设置session过期时间1小时
}
注意:用户登录时,认证和鉴权都已完成,之后用户所有的操作相当于都在shiro的监控下
二、实现细节
备注:本案例所在项目是前后端分离,前端angularJs,后端springBoot,数据交互采用json,所有后台接口返回JsonResponse型数据。
1、实现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);
//
dwsm.setCacheManager(getEhCacheManager());
return dwsm;
}
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager);
return aasa;
}
// 加载shiroFilter权限控制规则(从数据库读取然后配置)
private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean,ResourceService resourceService){
Map filterChainDefinitionMap = new LinkedHashMap();
//遍历所有需要过滤的resource_url,逐个添加到filterChainDefinitionMap中
List
2、实现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("-----------------执行Shiro权限认证-----------------------");
//获取当前登录输入的用户名,等价于(String) principalCollection.fromRealm(getName()).iterator().next();
//String loginName = (String)super.getAvailablePrincipal(principals);
User loginUser = (User) SecurityUtils.getSubject().getSession().getAttribute(SessionUtil.SESSIONKEY);
if(loginUser!=null){
// 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
String userId = loginUser.getUserId();
UserRole ur = new UserRole();
ur.setUserId(userId);
List userRoles = userRoleMapper.select(ur);
Set resourceIdSet = new HashSet();
//查询出用户所有具有的资源,并加入到info中
if(userRoles!=null && userRoles.size()>0){
for(UserRole userRole : userRoles){
RoleResource rr = new RoleResource();
rr.setRoleId(userRole.getRoleId());
List roleResources = roleResourceMapper.select(rr);
if(roleResources!=null && roleResources.size()>0){
for(RoleResource roleResource : roleResources){
resourceIdSet.add(roleResource.getResourceId());
}
}
}
}
if(resourceIdSet.size()>0){
Iterator i = resourceIdSet.iterator();
while(i.hasNext()){
String resourceId = i.next();
if(StringUtils.isNotBlank(resourceId)){
info.addStringPermission(resourceId);
}
}
}
//除了添加权限,还可以添加角色,在filter中限定具有某种角色才可访问
//authorizationInfo.setRoles(...);
return info;
}
return null;
}
/**
* 登录认证
该方法主要执行以下操作:
1、检查提交的进行认证的令牌信息
2、根据令牌信息从数据源(通常为数据库)中获取用户信息
3、对用户信息进行匹配验证。
4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
5、验证失败则抛出AuthenticationException异常信息。
而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo
(),重写获取用户信息的方法。
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.info("------------执行Shiro身份认证--------------");
//UsernamePasswordToken对象用来存放提交的登录信息
UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
logger.info("验证当前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){
// 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验 // salt=username+salt
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getAccount(),user.getPassword(),ByteSource.Util.bytes(username+""+user.getCredentialssalt()), getName());
return simpleAuthenticationInfo;
}else {
throw new UnknownAccountException();// 没找到帐号
}
}