美文网首页
SpringBoot+Shiro

SpringBoot+Shiro

作者: 男卅_卅 | 来源:发表于2019-06-21 09:11 被阅读0次

    Shiro 是一款简单易用、功能强大的安全框架,帮助我们安全高效的构建企业级应用。

    Shiro 能做什么

    • 认证:用户登录的认证
    • 权限:基于角色和权限的访问(url权限),以及颗粒化权限控制(按钮权限)
    • 加密技术:Shiro的crypto包中包含了一系列的易于理解和使用发加密、哈希(aka摘要)辅助类
    • session管理:可在web容器以及EJB容器中使用session,可扩展(例如我们可以通过重写sessionDao将session存储到数据库中)
    • RememberMe:基于cookie的记住我的服务

    Shiro 常用组件介绍

    image.png
    • Subject:Subject其实代表的就是当前正在操作的用户,只不过因为“User”一般指代人,但是一个“Subject”可以是人,也可以是任何的第三方系统,服务账号等任何其他正在和当前系统交互的第三方软件系统。
      所有的Subject实例都被绑定到一个SecurityManager,如果你和一个Subject交互,所有的交互动作都会别转换成Subject与SecurityManager的交互。
    • SecurityManager: Shiro的核心,他主要用于协调Shiro内部各种安全组件,不过我们一般不用太关心SecurityManager,对于应用程序开发者来说,主要还是使用Subject的API来处理各种安全验证逻辑。
    • Reaim:这是用于连接Shiro和客户系统的用户数据的桥梁。一旦Shiro真正需要访问各种安全相关的数据(比如使用用户账号来做用户身份验证以及权限验证)时,他总是通过调用系统配置的各种Realm来读取数据。
    • 关于Shiro的其余核心组件参Shiro 官网Shiro的架构等文章。

    Shiro 是如何工作的

    简单来说,在Spring项目中

    1. Shiro会将他的所有组件注册到SecurityManager中。
    2. 再通过SecurityManager注册到ShiroFilterFactoryBean(这个类实现了Spring的BeanPostProcessor会预先加载)中。
    3. 最后以filter的形式注册到Spring容器(实现了Spring的FactoryBean,构造一个filter注册到Spring容器中),实现用户权限的管理。

    Shiro 如何集成

    • shiro所需依赖
    <!--shiro-->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.4.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-ehcache</artifactId>
        <version>1.4.0</version>
    </dependency>
    <!-- 基于thymeleaf的shiro扩展 -->
    <dependency>
        <groupId>com.github.theborakompanioni</groupId>
        <artifactId>thymeleaf-extras-shiro</artifactId>
        <version>2.0.0</version>
    </dependency>
    
    • ShiroConfig
    @Configuration
    public class ShiroConfig {
    
        private static final Logger log = LoggerFactory.getLogger(ShiroConfig.class);
    
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
            shiroFilter.setSecurityManager(securityManager);
    
            Map<String, String> chainDefinition = new LinkedHashMap<>();
            // 静态资源与登录请求不拦截
            chainDefinition.put("/js/**", "anon");
            chainDefinition.put("/css/**", "anon");
            chainDefinition.put("/img/**", "anon");
            chainDefinition.put("/layui/**", "anon");
            chainDefinition.put("/login", "anon");
            chainDefinition.put("/login.html", "anon");
            // 用户为授权通过认证 && 包含'admin'角色
            chainDefinition.put("/admin/**", "authc, roles[super_admin]");
            // 用户为授权通过认证或者RememberMe && 包含'document:read'权限
            chainDefinition.put("/docs/**", "user, perms[document:read]");
            // 用户访问所有请求 授权通过 || RememberMe
            chainDefinition.put("/**", "user");
    
            shiroFilter.setFilterChainDefinitionMap(chainDefinition);
            // 当 用户身份失效时重定向到 loginUrl
            shiroFilter.setLoginUrl("/login.html");
            // 用户登录后默认重定向请求
            shiroFilter.setSuccessUrl("/index.html");
            return shiroFilter;
        }
    
        @Bean
        public Realm realm() {
            ShiroRealm realm = new ShiroRealm();
            realm.setCredentialsMatcher(credentialsMatcher());
            realm.setCacheManager(ehCacheManager());
            return realm;
        }
    
        @Bean
        public CacheManager ehCacheManager() {
            EhCacheManager cacheManager = new EhCacheManager();
            cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
            return cacheManager;
        }
    
        @Bean
        public CredentialsMatcher credentialsMatcher() {
            AuthCredentialsMatcher credentialsMatcher = new AuthCredentialsMatcher(ehCacheManager());
            credentialsMatcher.setHashAlgorithmName(AuthCredentialsMatcher.HASH_ALGORITHM_NAME);
            credentialsMatcher.setHashIterations(AuthCredentialsMatcher.HASH_ITERATIONS);
            credentialsMatcher.setStoredCredentialsHexEncoded(true);
            return credentialsMatcher;
        }
    
        @Bean
        public DefaultWebSecurityManager securityManager() {
            log.debug("--------------shiro已经加载----------------");
            DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
            manager.setCacheManager(ehCacheManager());
            manager.setRealm(realm());
            manager.setRememberMeManager(rememberMeManager());
            return manager;
        }
    
        @Bean
        public RememberMeManager rememberMeManager() {
            CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
            //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
            cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
            cookieRememberMeManager.setCookie(rememberMeCookie());
            return cookieRememberMeManager;
        }
    
        @Bean
        public SimpleCookie rememberMeCookie(){
            //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
            SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
            //<!-- 记住我cookie生效时间30天 ,单位秒;-->
            simpleCookie.setMaxAge(259200);
            return simpleCookie;
        }
    
        /**
         * Shiro生命周期处理器:
         * 用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调(例如:UserRealm)
         * 在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调(例如:DefaultSecurityManager)
         */
        @Bean
        public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
            return new LifecycleBeanPostProcessor();
        }
    
        /**
         * 启用shrio授权注解拦截方式,AOP式方法级权限检查
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
                    new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    
        /**
         * thymeleaf的shiro扩展
         *
         * @return
         */
        @Bean
        public ShiroDialect shiroDialect() {
            return new ShiroDialect();
        }
    
    }
    

    以上基本是Spring项目集成Shiro的通用配置,下面针对上述的几个Bean聊一聊
    1. ShiroFilterFactoryBean:用于定义请求的拦截规则,Shiro为我们默认提供了一些项,常用如下

    • anon : 请求不拦截
    • authc:要求用户必须认证通过
    • user:要求用户为记住我状态
    • roles[xxx]:要求用户必须满足xxx角色
    • perms[xxx]:要求用户必须满足xxx权限
      其实上述每一个都对应了一个Shiro过滤器
      image.png
    1. Realm:上面提到过Realm是用于连接Shiro和客户系统的用户数据的桥梁,我们通过实现AuthorizingRealm来提供用户认证和授权两个API
    public class ShiroRealm extends AuthorizingRealm {
    
        private static final Logger log = LoggerFactory.getLogger(AuthorizingRealm.class);
    
        @Autowired
        @Lazy // 这里lazy 是有必要的, shiro组件会预先加载,导致依赖的bean 没有生成代理对象(AOP失效)
        private UserService userService;
    
        /**
         * 认证
         *
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            String username = (String) authenticationToken.getPrincipal();
    
            if (log.isDebugEnabled()) {
                log.debug(String.format("user:%s executing doGetAuthenticationInfo", username));
            }
    
            User user = userService.getUserByUsername(username);
    
            if (user == null) {
                throw new UnknownAccountException();
            }
    
            if (Constant.IS_LOCK.equals(user.getIsLock())) {
                throw new LockedAccountException();
            }
    
            // ShiroUser 作为实际的 principal
            ShiroUser shiroUser = new ShiroUser();
            BeanUtils.copyProperties(user, shiroUser);
    
            // SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)
            // principal 会被封装到 subject 中
            // shiro 默认会把我们的 credentials (也就是password) 和 token 中的作对比,所以我们可以不用做密码校验
            ByteSource salt = ByteSource.Util.bytes(user.getUsername());
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(shiroUser, user.getPassword(), salt, getName());
    
            if (log.isDebugEnabled()) {
                log.debug(String.format("user:%s executed doGetAuthenticationInfo", username));
            }
    
            return info;
        }
    
        /**
         * 授权
         *
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            ShiroUser shiroUser = (ShiroUser) principalCollection.getPrimaryPrincipal();
    
            if (log.isDebugEnabled()) {
                log.debug(String.format("user:%s executing doGetAuthorizationInfo", shiroUser.getUsername()));
            }
    
            AuthorizationDTO authorizationDTO = userService.getRolesAndPermissions(shiroUser.getId());
    
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.addRoles(authorizationDTO.getRoleCodeSet());
            info.addStringPermissions(authorizationDTO.getPermissionCodeSet());
    
            if (log.isDebugEnabled()) {
                log.debug(String.format("user:%s executed doGetAuthorizationInfo", shiroUser.getUsername()));
            }
    
            return info;
        }
    
    }
    
    • doGetAuthenticationInfo:认证方法,在执行subject.login(token);后,Shiro认证器会读取Realm中的该方法获取AuthenticationInfo对象(认证信息),包含principal(我们存储在shiro subject中的对象),credentials(密码)。
    • doGetAuthorizationInfo:授权方法,在需要校验用户访问权限的时候,Shiro授权器会读取Realm中的该方法获取AuthorizationInfo对象(授权信息)读取DB后,可以通过addRoles(roleCollection)和addStringPermissions(permCollection)设置当前用户的角色和权限。Shiro在拿到这个权限信息后,会去找缓存管理器。以当前subject的principal作为key缓存起来。
      3. CredentialsMatcher:密码匹配器,用于匹配doGetAuthenticationInfo方法返回的credentials和subject.login(token),时的token中的password是否一致。常用的实现有SimpleCredentialsMatcher(默认是该实现)、HashedCredentialsMatcher(该实现可以进行加密匹配)
      4. DefaultWebSecurityManger:如上述,用于协调Shiro内部各种安全组件,我们需要将我们扩展的bean注册到SecurityManager中
      5. RememberMeManager:开启该组件后使用记住服务,token中rememberMe为true时,登录成功之后会创建RememberMe cookie。

    关于thymeleaf-extras-shiro

    Shiro默认支持在jsp中使用shiro标签。但是想在thymeleaf中使用Shiro标签呢?
    使用thymeleaf-extras-shiro 完美解决 thymeleaf颗粒化权限控制

    你好, <span th:text="${principal}"></span><br>
    <p shiro:hasRole="super_admin">当前角色超级管理员</p>
    <button shiro:hasPermission="'sys:user:add'">添加</button>
    <button shiro:hasPermission="'sys:user:update'">编辑</button>
    <button shiro:hasPermission="'sys:user:lock'">冻结</button>
    <div shiro:hasAllPermissions="'sys:user:add, sys:user:update, sys:user:lock'">
        <span>满足所有权限时显示</span>
    </div>
    <div shiro:hasAnyPermissions="'sys:user:add, sys:user:update, sys:user:lock'">
        <span>满足一个权限即可显示</span>
    </div>
    

    相关文章

      网友评论

          本文标题:SpringBoot+Shiro

          本文链接:https://www.haomeiwen.com/subject/grfsqctx.html