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);
        //<!-- 用户授权/认证信息Cache, 采用EhCache 缓存 --> 
        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<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中"); 
        //添加一些不需权限的地址
        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<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);
                    }
                    
                }           
            }
            //除了添加权限,还可以添加角色,在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();// 没找到帐号
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,657评论 18 139
  • 前言 Spring boot 是什么,网上的很多介绍,这里博客就不多介绍了。如果不明白Spring boot是什么...
    xuezhijian阅读 17,911评论 13 39
  • 文章转载自:http://blog.csdn.net/w1196726224/article/details/53...
    wangzaiplus阅读 3,397评论 0 3
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,631评论 18 399
  • 十月的南方,我身居的小镇,风中带着沁人心脾的凉意,刺入肌骨,阴冷如虫蚁般不动声色的腐蚀着,天已入秋。 毕业后,我没...
    Nancy暖昔阅读 304评论 0 0