Shiro集成SpringBoot
- 一般企业中使用
Shiro
都是配合 SpringBoot 进行集成使用。
项目集成思路
项目骨架
项目依赖
-
<dependencies> <!-- shiro-springboot --> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis-spring-boot-starter</artifactId> <version>3.3.1</version> </dependency> <!-- springboot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>2.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>2.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.1.6.RELEASE</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.2</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</version> </dependency> <!-- database --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.23</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.23</version> </dependency> <!-- hutool --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-crypto</artifactId> <version>5.6.0</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-http</artifactId> <version>5.6.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.73</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.10</version> <scope>provided</scope> </dependency> </dependencies> <!-- 设置阿里镜像仓库 --> <repositories> <repository> <!--远程仓库唯一标识符。可以用来匹配在settings.xml文件里配置的远程仓库 --> <id>nexus-aliyun</id> <!--远程仓库名称 --> <name>Nexus aliyun</name> <!--远程仓库URL,按protocol://hostname/path形式 --> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </repository> </repositories> <!-- 过滤xml文件 --> <build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>
ShiroDbRealm
-
主要创建自定义类 继承
AuthorizingRealm
实现自定义的 认证, 授权方法。 -
主要的权限数据从数据库中获取。
-
-
代码示例:
-
@Slf4j public class UserRealm extends AuthorizingRealm { @Autowired private UserMapper userMapper; @Autowired private ResourceMapper resourceMapper; @Autowired private RoleMapper roleMapper; /** * 这个方法只有会在登录时调用一次 * 认证方法 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UserLoginToken userLoginToken = (UserLoginToken) authenticationToken; // 1. 查询数据库数据是否匹配 String username = userLoginToken.getUsername(); char[] password = userLoginToken.getPassword(); User user = userMapper.queryByUserName(username); if (Objects.isNull(user)) { throw new UnknownAccountException("账号不存在!"); } // 2. 返回匹配方法,进行密码的校验 ByteSource source = ByteSource.Util.bytes(password); return new SimpleAuthenticationInfo(user , user.getPassWord() , source , "userRealm"); } /** * 这个方法在每次过滤器进行hasRole,HasPerm 等方法时 都会调用。 * 授权方法 , 一般只存储角色信息即可 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // 获取登录主体信息 User user = (User)principalCollection.getPrimaryPrincipal(); if (Objects.isNull(user)) { throw new RuntimeException("数据不存在!"); } SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); // 查询角色信息 List<Role> roleList = roleMapper.queryByUserId(user.getId()); List<String> roleIdList = roleList.stream().map(Role::getId).collect(Collectors.toList()); Set<String> LabelList = roleList.stream().map(Role::getLabel).collect(Collectors.toSet()); simpleAuthorizationInfo.setRoles(LabelList); // 查询资源信息 List<Resource> resourceList = resourceMapper.queryByRoleList(roleIdList); Set<String> permList = resourceList.stream().map(Resource::getLabel).collect(Collectors.toSet()); simpleAuthorizationInfo.setStringPermissions(permList); return simpleAuthorizationInfo; } }
ShiroConfig
-
通过
Configuration
创建 Shiro 的一系列配置,进行shiro的过滤权限等操作 -
Shiro的配置文件
-
-
@Configuration @Slf4j public class ShiroConfig { /** * 会话管理器 */ @Bean public DefaultWebSessionManager getDefaultWebSessionManager() { DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager(); // 会话过期时间,单位:毫秒(在无操作时开始计时) defaultWebSessionManager.setGlobalSessionTimeout(60 * 60 * 1000); // 开启自动校验会话过期定时 - 对性能有影响 defaultWebSessionManager.setSessionValidationSchedulerEnabled(true); // 生效cookie defaultWebSessionManager.setSessionIdCookieEnabled(true); // 设置session存储 defaultWebSessionManager.setSessionDAO(redisSessionDAO()); return defaultWebSessionManager; } /** * 核心权限管理器 */ @Bean public DefaultWebSecurityManager webSecurityManager(UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm securityManager.setRealm(userRealm); // 缓存管理器 securityManager.setCacheManager(redisCacheManager()); // 会话session管理 securityManager.setSessionManager(getDefaultWebSessionManager()); return securityManager; } /** * 过滤器拦截链路 */ @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); HashMap<String, String> listMap = new LinkedHashMap<>(); // anon默认授权 , authc登录鉴权 listMap.put("/static/**" , "anon"); listMap.put("/shiro-test/user/login" , "anon"); listMap.put("/user/update" , "customUser,customCheck,customRole[admin,SuperAdmin]"); listMap.put("/shiro-test/**" , "authc"); chainDefinition.addPathDefinitions(listMap); return chainDefinition; } /** * 过滤器 */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(ShiroFilterChainDefinition shiroFilterChainDefinition , DefaultWebSecurityManager securityManager) { // 使用默认过滤器 ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); // 自定义过滤器 // shiroFilter.setFilters(); // 设置过滤器拦截链路 shiroFilter.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap()); // 设置路径 shiroFilter.setLoginUrl("/"); shiroFilter.setSuccessUrl("/"); shiroFilter.setUnauthorizedUrl("/"); return shiroFilter; } /** * 自定义Realm */ @Bean public UserRealm userRealm() { UserRealm userRealm = new UserRealm(); // 设置密码校验器 userRealm.setCredentialsMatcher(credentialsMatcher()); return userRealm; } /** * 管理shiro bean生命周期 , 让Shiro 的配置类可以读取Spring 的bean */ @Bean public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 密码校验器 */ private HashedCredentialsMatcher credentialsMatcher() { HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); credentialsMatcher.setHashAlgorithmName("MD5"); //转换次数 credentialsMatcher.setHashIterations(1); return credentialsMatcher; } /** * RedisSessionDao session存储管理 */ private RedisSessionDAO redisSessionDAO() { RedisSessionDAO myRedisSessionDAO = new RedisSessionDAO(); RedisManager redisManager = new RedisManager(); myRedisSessionDAO.setRedisManager(redisManager); return myRedisSessionDAO; } /** * Redis 权限管理器缓存 */ private RedisCacheManager redisCacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(new RedisManager()); return redisCacheManager; } }
过滤器
-
Shiro内置很多业务默认的过滤器,比如:身份过滤器,授权过滤器等. 参考:org.apache.shiro.web.filter.mgt.DefaultFilter 中过滤器枚举.
-
过滤器是指 Shiro 对某一url进行的拦截处理,相当于web的过滤器,进行权限相关的判断处理。
-
自定义过滤器(继承
AuthorizationFilter
授权过滤器 ):- isAccessAllowed: 过滤方法 ,返回boolean类型
- onAccessDenied:过滤失败响应方法
-
权限校验过滤器:
-
/** * 授权权限校验过滤器 */ @Slf4j public class CustomRoleFilter extends AuthorizationFilter { /** * 校验失败方法 */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException { log.error("role check fail...."); Subject subject = this.getSubject(request, response); // 返回错误码 , 这里可以做国际化处理 HttpServletResponse httpServletResponse = (HttpServletResponse)response; Result<Void> result = Result.error("500", "没有权限"); httpServletResponse.setHeader("Content-Type" , "application/json;charset=utf-8"); httpServletResponse.getWriter().write(JSONObject.toJSONString(result)); return false; } /** * 校验方法 */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { log.info("role check start..."); Subject subject = getSubject(request, response); String[] rolesArray = (String[]) mappedValue; if (rolesArray == null || rolesArray.length == 0) { //no roles specified, so nothing to check - allow access. return true; } for (String role : rolesArray) { // Shiro的默认角色过滤器 这里用的是 hasAllRoles if (subject.hasRole(role)) { return true; } } return false; } }
-
用户登录过滤器:
-
@Slf4j public class CustomUserFilter extends UserFilter { /** * 没有登录过滤器响应报错 */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { log.error("role check fail...."); Subject subject = this.getSubject(request, response); // 返回错误码 , 这里可以做国际化处理 HttpServletResponse httpServletResponse = (HttpServletResponse)response; Result<Void> result = Result.error("500", "没有登录"); httpServletResponse.setHeader("Content-Type" , "application/json;charset=utf-8"); httpServletResponse.getWriter().write(JSONObject.toJSONString(result)); return false; } }
-
更新权限过滤器(可进行业务逻辑判断,是否需要在url权限判断前,更新权限信息):
-
@Slf4j public class PathCheckFilter extends PathMatchingFilter { @Override protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { log.info("更新权限...."); return true; } }
-
ShiroConfig中配置:
-
ShiroFilterFactoryBean 类设置自定义过滤器 // 自定义过滤器 shiroFilter.setFilters(getCustomFilter()); /** * 封装自定义过滤器 */ private Map<String, Filter> getCustomFilter() { HashMap<String, Filter> filterList = new LinkedHashMap<>(); filterList.put("customRole" , new CustomRoleFilter()); filterList.put("customUser" , new CustomUserFilter()); filterList.put("customCheck" , new PathCheckFilter()); return filterList; } // 设置url过滤器链路 listMap.put("/**","customUser,customCheck,customRole[admin,SuperAdmin]");
过滤器链路
-
过滤器进行拦截的路径的配置信息 , 一个 url 可以配置多个过滤器, 所以称为过滤器链(有前后顺序)。
-
按照定义的顺序,自上而下.
-
代码示例: listMap.put("/static/**" , "anon"); listMap.put("/shiro-test/user/login" , "anon"); listMap.put("/shiro-test/**" , "roles[user],authc"); --------------------------------- /shiro-test/**:? ? 表示过滤器执行的过滤器 , 以下是Shiro提供的默认常用filter过滤器 anon - 静默授权 authc - 登录 logout - 登出 authcBasic - noSessionCreation - perms["user:add,user:update"] - 权限过滤 port - 端口过滤器 rest - 请求的rest方法 GET / Post / put / delete roles["admin,user"] - 角色,多角色全匹配. ssl - 必须为https请求 user - 登录即可
PathMatchingFilter
-
单独讲一下
PathMatchingFilter
这个类。 -
-
其实就是各个过滤器的父类,一般继承这个接口,是为了实现
onPreHandle
方法,在下一步进行过滤器执行前,进行操作。返回true
往下执行过滤器。在这里可以做一些类似更新权限的操作,当权限表内容修改时,就先更新一下FilterChain
拦截url配置。 -
具体的作用体现就是:通过
onPreHandle
方法来判断是否执行之后的过滤器链,配置给url时,也遵循前后顺序。
注解鉴权方式
-
注解方式通过Shiro定义的一系列注解,一般写在 Controller 层方法上进行校验鉴权。
-
一般不会用注解方式,代码紊乱,扩展性差,不易管理。
-
常用注解:
-
@RequiresAuthentication - 表名当前用户是否经过认证 @RequiresGuest - 需要为 guest 用户才能访问 @RequiresPermissions - 当前用户需要指定权限 @RequiresRoles - 需要拥有XXX角色 @RequiresUser - 需要为已认证用户
-
Configuration配置信息:
-
/** * 注解方式鉴权生效器 */ @Bean public DefaultAdvisorAutoProxyCreator DefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor aas = new AuthorizationAttributeSourceAdvisor(); aas.setSecurityManager(securityManager); return aas; }
重要总结
- 在配置过滤器时 ("/**" , XXX) 一般在最后会配置这样一个全局路径的过滤,这种形式会重复走两边Shiro的授权流程代码。具体原因未知,不加这个全局流程就不会重复。
- ShiroConfig中可以不配置
LifecycleBeanPostProcessor
,需要给引入要使用的SpringBean
加上@Lazy注解
,因为ShiroConfig的执行顺序在,Shiro-Redis配置文件之前。
项目路径 shiro-springboot
- 链接:https://pan.baidu.com/s/1OotLAOubzxpZDpPLpjdFuw
提取码:bk3p