SpringBoot整合Shiro(二)

一、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> resources = resourceService.selectAllResource();
        for(Map 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中"); 
        //添加一些不需权限的地址
        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();
        // 必须设置 SecurityManager  
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
        // 登录成功后要跳转的连接
        shiroFilterFactoryBean.setSuccessUrl("/user/loginSuccess");
        // 未授权界面,鉴权失败时返回信息给前端;
        shiroFilterFactoryBean.setUnauthorizedUrl("/user/returnUnauthorizedMsg");
        loadShiroFilterChain(shiroFilterFactoryBean,resourceService);
        return shiroFilterFactoryBean;
    }

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();// 没找到帐号
        }
    }

你可能感兴趣的:(SpringBoot整合Shiro(二))