Spring boot 集成shiro实现权限管理
1.maven集成所需关键jar包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 由于shiro很多地方都是靠aop的拦截器达到程序目的,所以需要引入AOP的包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- 由于要实现redis存储session会话,所以引入redis的包 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.8.24</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- mysql 数据库 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- For log4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2.配置文件
spring: datasource: driver-class-name: com.mysql.jdbc.Driver username: root password: 123456 url: jdbc:mysql://192.168.3.5:3306/platform?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC redis: host: 192.168.3.5 port: 6379 timeout: 360000 password: 123456 server: port: 8081
3. shiro的机制主要有两部分组成,ShiroConfig 配置shiro的参数配置,Realm 配置权限校验体系的规则,主要为两个函数一个是认证一个是授权
package com.example.demo.shiro; import com.example.demo.listener.ShiroSessionListener; import org.apache.shiro.cache.MemoryConstrainedCacheManager; import org.apache.shiro.session.SessionListener; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator; import org.apache.shiro.session.mgt.eis.SessionDAO; import org.apache.shiro.session.mgt.eis.SessionIdGenerator; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.crazycake.shiro.RedisCacheManager; import org.crazycake.shiro.RedisManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { @Value("${spring.redis.host}") private String redisHost; @Value("${spring.redis.port}") private int redisPort; @Value("${spring.redis.timeout}") private int redisTimeout; @Value("${spring.redis.password}") private String redisPassword; private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class); @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //安全管理器配置 shiroFilterFactoryBean.setSecurityManager(securityManager); //没有登录的用户请求需要登录的页面时自动跳转到登录页面。 //shiroFilterFactoryBean.setLoginUrl("/login"); //没有权限默认跳转的页面,登录的用户访问了没有被授权的资源自动跳转到的页面 //shiroFilterFactoryBean.setUnauthorizedUrl("/notRole"); //登录成功默认跳转页面,不配置则跳转至”/”,可以不配置,直接通过代码进行处理 //shiroFilterFactoryBean.setSuccessUrl("/"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/api/**", "anon"); //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证 filterChainDefinitionMap.put("/**", "authc"); //filterChainDefinitions 配置过滤规则,从上到下的顺序匹配 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 配置全局事务管理 * @return */ @Bean public SecurityManager securityManager() { DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager(); //设置自定义realm defaultSecurityManager.setRealm(myRealm()); //配置redis缓存 defaultSecurityManager.setCacheManager(cacheManager()); //配置自定义session管理,使用ehcache 或redis defaultSecurityManager.setSessionManager(sessionManager()); return defaultSecurityManager; } @Bean public MyRealm myRealm() { MyRealm myRealm = new MyRealm(); myRealm.setCachingEnabled(true); //启用身份验证缓存,即缓存AuthenticationInfo信息,默认false myRealm.setAuthenticationCachingEnabled(true); //缓存AuthenticationInfo信息的缓存名称 在ehcache-shiro.xml中有对应缓存的配置 myRealm.setAuthenticationCacheName("authenticationCache"); //启用授权缓存,即缓存AuthorizationInfo信息,默认false myRealm.setAuthorizationCachingEnabled(true); //缓存AuthorizationInfo信息的缓存名称 在ehcache-shiro.xml中有对应缓存的配置 myRealm.setAuthorizationCacheName("authorizationCache"); //配置自定义密码比较器 //shiroRealm.setCredentialsMatcher(retryLimitHashedCredentialsMatcher()); return myRealm; } /** * 配置会话管理器,设定会话超时及保存 * @return */ @Bean public SessionManager sessionManager() { DefaultWebSessionManager manager = new DefaultWebSessionManager(); manager.setCacheManager(new MemoryConstrainedCacheManager());// 加入缓存管理器 Collection<SessionListener> listeners = new ArrayList<SessionListener>(); listeners.add(sessionListener()); //配置监听 manager.setSessionListeners(listeners); manager.setSessionIdCookie(sessionIdCookie()); manager.setSessionDAO(sessionDAO());// 设置SessionDao manager.setCacheManager(cacheManager()); manager.setGlobalSessionTimeout(1800000);// 设置全局session超时时间,单位毫秒 manager.setDeleteInvalidSessions(true);// 删除过期的session //是否开启定时调度器进行检测过期session 默认为true manager.setSessionValidationSchedulerEnabled(true); return manager; } /** * shiro缓存管理器; * 需要添加到securityManager中 * @return */ @Bean public RedisCacheManager cacheManager(){ RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); return redisCacheManager; } /** * 配置session监听 * @return */ @Bean("sessionListener") public ShiroSessionListener sessionListener(){ ShiroSessionListener sessionListener = new ShiroSessionListener(); return sessionListener; } /** * 配置会话ID生成器 * @return */ @Bean public SessionIdGenerator sessionIdGenerator() { return new JavaUuidSessionIdGenerator(); } /** * SessionDAO的作用是为Session提供CRUD并进行持久化的一个shiro组件 * MemorySessionDAO 直接在内存中进行会话维护 * EnterpriseCacheSessionDAO 提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。 * @return */ @Bean public SessionDAO sessionDAO() { EnterpriseCacheSessionDAO enterpriseCacheSessionDAO = new EnterpriseCacheSessionDAO(); //使用redis缓存 enterpriseCacheSessionDAO.setCacheManager(cacheManager()); //设置session缓存的名字 默认为 shiro-activeSessionCache enterpriseCacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache"); //sessionId生成器 enterpriseCacheSessionDAO.setSessionIdGenerator(sessionIdGenerator()); return enterpriseCacheSessionDAO; } /** * 配置保存sessionId的cookie * 注意:这里的cookie 不是上面的记住我 cookie 记住我需要一个cookie session管理 也需要自己的cookie * @return */ @Bean("sessionIdCookie") public SimpleCookie sessionIdCookie(){ //这个参数是cookie的名称 SimpleCookie simpleCookie = new SimpleCookie("sid"); //setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。设为true后,只能通过http访问,javascript无法访问,防止xss读取cookie simpleCookie.setHttpOnly(true); simpleCookie.setPath("/"); //maxAge=-1表示浏览器关闭时失效此Cookie simpleCookie.setMaxAge(-1); return simpleCookie; } /** * 配置Redis服务器 * @return */ @Bean public RedisManager redisManager(){ RedisManager redisManager = new RedisManager(); redisManager.setHost(redisHost); redisManager.setPort(redisPort); redisManager.setPassword(redisPassword); redisManager.setTimeout(redisTimeout); return redisManager; } /** * 支持shiro注解权限控制 * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor(); sourceAdvisor.setSecurityManager(securityManager); return sourceAdvisor; } }
package com.example.demo.shiro; import com.example.demo.model.Role; import com.example.demo.model.User; import com.example.demo.service.PermissionService; import com.example.demo.service.RoleService; import com.example.demo.service.UserService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; import java.util.HashSet; import java.util.List; import java.util.Set; public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; @Autowired private RoleService roleService; @Autowired private PermissionService permissionService; /** * 为当前登录成功的用户授予权限和分配角色。 * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){ // 获取用户名 String username = (String) principalCollection.getPrimaryPrincipal(); User user = userService.getUserByUsername(username); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); List<String> roleList = roleService.getRoleListByUserId(user.getId()); Set<String> roleSet = new HashSet<>(); roleSet.addAll(roleList); authorizationInfo.setRoles(roleSet); List<String> permsList = permissionService.getPermsListByUserId(user.getId()); System.out.println(permsList.toString()); Set<String> permsSet = new HashSet<>(); permsSet.addAll(permsList); authorizationInfo.setStringPermissions(permsSet); return authorizationInfo; } /** * 验证当前登录的用户,获取认证信息 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //获取用户名/密码 UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken; String username = usernamePasswordToken.getUsername(); String password = new String(usernamePasswordToken.getPassword()); //从数据库查询用户信息 User user = userService.getUserByUsername(username); //可以在这里直接对用户名校验,或者调用 CredentialsMatcher 校验 if (user == null) { throw new UnknownAccountException("用户名或密码错误!"); } //这里将 密码对比 注销掉,否则 无法锁定 要将密码对比 交给 密码比较器 if (!password.equals(user.getPassword())) { throw new IncorrectCredentialsException("用户名或密码错误!"); } //调用 CredentialsMatcher 校验 还需要创建一个类 继承CredentialsMatcher 如果在上面校验了,这个就不需要了 //配置自定义权限登录器 参考博客: SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, user.getPassword(), getName()); return info; } /** * 重写方法,清除当前用户的的 授权缓存 * @param principals */ @Override public void clearCachedAuthorizationInfo(PrincipalCollection principals) { super.clearCachedAuthorizationInfo(principals); } /** * 重写方法,清除当前用户的 认证缓存 * @param principals */ @Override public void clearCachedAuthenticationInfo(PrincipalCollection principals) { super.clearCachedAuthenticationInfo(principals); } @Override public void clearCache(PrincipalCollection principals) { super.clearCache(principals); } /** * 自定义方法:清除所有 授权缓存 */ public void clearAllCachedAuthorizationInfo() { getAuthorizationCache().clear(); } /** * 自定义方法:清除所有 认证缓存 */ public void clearAllCachedAuthenticationInfo() { getAuthenticationCache().clear(); } /** * 自定义方法:清除所有的 认证缓存 和 授权缓存 */ public void clearAllCache() { clearAllCachedAuthenticationInfo(); clearAllCachedAuthorizationInfo(); } }
由于我们使用到了session的监听机制,所以还需建一个listener类
package com.example.demo.listener; import org.apache.shiro.session.Session; import org.apache.shiro.session.SessionListener; import java.util.concurrent.atomic.AtomicInteger; public class ShiroSessionListener implements SessionListener { /** * 统计在线人数 * juc包下线程安全自增 */ private final AtomicInteger sessionCount = new AtomicInteger(0); /** * 会话创建时触发 * @param session */ @Override public void onStart(Session session) { //会话创建,在线人数加一 sessionCount.incrementAndGet(); } /** * 退出会话时触发 * @param session */ @Override public void onStop(Session session) { //会话退出,在线人数减一 sessionCount.decrementAndGet(); } /** * 会话过期时触发 * @param session */ @Override public void onExpiration(Session session) { //会话过期,在线人数减一 sessionCount.decrementAndGet(); } /** * 获取在线人数使用 * @return */ public AtomicInteger getSessionCount() { return sessionCount; } }
到这里,基本上spring boot 集成shiro的关键配置就结束了,最主要的是ShiroConfig 和 MyRealm 这两个类,需要仔细的研究一下。其实多看几遍也很简单。
其他的service、serviceImpl、dao之类的类,就不再详细贴了。

浙公网安备 33010602011771号