springboot+shiro+redis(单机redis版)整合教程-续(添加动态角色权限控制)
相关教程:
2. springboot+shiro+redis(单机redis版)整合教程
3. springboot+shiro+redis(集群redis版)整合教程
参考此教程前请先阅读 2.springboot+shiro+redis(单机redis版)整合教程,此教程是在其基础上进行修改添加动态角色权限的。
本教程整合环境: java8 maven redis(单机)
开发工具: idea
版本: springboot 1.5.15.RELEASE
注:
1.本教程数据操作是模拟数据库操作,并没有真正进行持久化,自行修改即可。
项目结构,在 springboot+shiro+redis(单机redis版)整合教程 基础上进行的添加和修改结构如下:
首先添加角色权限实体类RolesPermissions.java
package webapp.model; import lombok.Data; import java.io.Serializable; @Data public class RolesPermissions implements Serializable { private static final long serialVersionUID = 1L; /** * @备注: * @字段:id BIGINT(19) */ private Long id; /** * @备注:创建时间 * @字段:create_time DATETIME(19) */ private java.util.Date createTime; /** * @备注:更新时间 * @字段:update_time DATETIME(19) */ private java.util.Date updateTime; /** * @备注:角色id * @字段:roles_id BIGINT(19) */ private Long rolesId; /** * @备注:权限id * @字段:permissions_id BIGINT(19) */ private Long permissionsId; /** * @备注:角色名称 * @字段:roles_name VARCHAR(100) */ private String rolesName; /** * @备注:api接口 * @字段:url VARCHAR(512) */ private String url; }
添加service层 RolesPermissionsService.java和 RolesPermissionsServiceImpl.java
RolesPermissionsService.java:
package webapp.service; import webapp.model.RolesPermissions; import java.util.List; public interface RolesPermissionsService { List<RolesPermissions> selectList(); List<RolesPermissions> selectByAuserName(String auserName); }
RolesPermissionsServiceImpl.java:
package webapp.service.impl; import org.springframework.stereotype.Service; import webapp.model.RolesPermissions; import webapp.service.RolesPermissionsService; import java.util.LinkedList; import java.util.List; @Service public class RolesPermissionsServiceImpl implements RolesPermissionsService { @Override public List<RolesPermissions> selectList() { List<RolesPermissions> rolesPermissionsList = new LinkedList<>(); RolesPermissions rolesPermissions = new RolesPermissions(); rolesPermissions.setId(1L); rolesPermissions.setPermissionsId(1L); rolesPermissions.setRolesId(1L); rolesPermissions.setRolesName("超级管理员"); rolesPermissions.setUrl("/index.html"); rolesPermissionsList.add(rolesPermissions); RolesPermissions rolesPermissions2 = new RolesPermissions(); rolesPermissions2.setId(2L); rolesPermissions2.setPermissionsId(1L); rolesPermissions2.setRolesId(2L); rolesPermissions2.setRolesName("管理员"); rolesPermissions2.setUrl("/test.html"); rolesPermissionsList.add(rolesPermissions2); RolesPermissions rolesPermissions3 = new RolesPermissions(); rolesPermissions3.setId(3L); rolesPermissions3.setPermissionsId(1L); rolesPermissions3.setRolesId(2L); rolesPermissions3.setRolesName("超级管理员"); rolesPermissions3.setUrl("/test.html"); rolesPermissionsList.add(rolesPermissions3); RolesPermissions rolesPermissions4 = new RolesPermissions(); rolesPermissions4.setId(4L); rolesPermissions4.setPermissionsId(3L); rolesPermissions4.setRolesId(1L); rolesPermissions4.setRolesName("超级管理员"); rolesPermissions4.setUrl("/core/user/findUser"); rolesPermissionsList.add(rolesPermissions4); return rolesPermissionsList; } @Override public List<RolesPermissions> selectByAuserName(String auserName) { List<RolesPermissions> rolesPermissionsList = new LinkedList<>(); RolesPermissions rolesPermissions = new RolesPermissions(); rolesPermissions.setId(1L); rolesPermissions.setPermissionsId(1L); rolesPermissions.setRolesId(1L); rolesPermissions.setRolesName("运营管理员"); rolesPermissions.setUrl("/index.html"); rolesPermissionsList.add(rolesPermissions); return rolesPermissionsList; } }
修改登录相关接口 UserController.java:
package webapp.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.session.Session; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.*; import webapp.service.UserService; import javax.annotation.Resource; import java.io.Serializable; /** * Created by Administrator on 2018/9/5. */ @RestController @RequestMapping("/core/user") public class UserController { @Autowired private UserService userService; /** * 登录 * @param * @return */ @GetMapping("/login") public String login(String userName, String password) { System.out.println("登录" + userName); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("admin-pc==" + userName, password); subject.login(token); Session session = subject.getSession(); Serializable sessionId = session.getId(); System.out.println("登录成功 -> " + sessionId); return userName + "[" + sessionId + "]"; } @GetMapping("/logout") public String logout() { SecurityUtils.getSubject().logout(); return "退出登录成功"; } /** * 获取当前登录用户 * @return */ @GetMapping("/findUser") public String findUser() { Subject subject = SecurityUtils.getSubject(); PrincipalCollection collection = subject.getPrincipals(); if (null != collection && !collection.isEmpty()) { String userName = (String) collection.iterator().next(); System.out.println("获取当前登录用户" + userName); return userService.findOneByUserName(userName).toString(); } return "{\n" + " \"codeEnum\": \"OVERTIME\",\n" + " \"code\": 0,\n" + " \"data\": null,\n" + " \"msg\": \"未登陆/登陆超时\",\n" + " \"success\": false\n" + "}"; } }
修改授权相关 UserShiroRealm.java:
package webapp.shiro; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.session.Session; import org.apache.shiro.session.mgt.eis.SessionDAO; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.support.DefaultSubjectContext; import org.springframework.beans.factory.annotation.Autowired; import webapp.model.RolesPermissions; import webapp.model.User; import webapp.service.RolesPermissionsService; import webapp.service.UserService; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; /** * Created by Administrator on 2018/9/5. */ public class UserShiroRealm extends AuthorizingRealm { @Autowired private UserService userService; @Autowired private SessionDAO sessionDAO; @Autowired private RolesPermissionsService rolesPermissionsService; /** * 角色权限和对应权限添加 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String userName = (String) principalCollection.getPrimaryPrincipal(); if (userName == null || "".equals(userName)) { return null; }else{ userName = userName.split("==")[1]; SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); try{ List<RolesPermissions> rolesPermissions = rolesPermissionsService.selectByAuserName(userName); //当前用户具有的权限 List<String> roles = rolesPermissions.stream().map(RolesPermissions::getRolesName).collect(Collectors.toList()); authorizationInfo.addRoles(roles); }catch(Exception e){ e.printStackTrace(); } return authorizationInfo; } } /** * 用户认证 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //加这一步的目的是在Post请求的时候会先进认证,然后在到请求 if (authenticationToken.getPrincipal() == null) { return null; } String userName = authenticationToken.getPrincipal().toString(); //只允许同一账户单个登录 Subject subject = SecurityUtils.getSubject(); Session nowSession = subject.getSession(); Collection<Session> sessions = sessionDAO.getActiveSessions(); if(sessions != null && sessions.size() > 0) { for (Session session : sessions) { if (!nowSession.getId().equals(session.getId()) && (session.getTimeout() == 0 || userName.equals(String.valueOf(session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY))))) { sessionDAO.delete(session); } } } User user = userService.findOneByUserName(userName.contains("admin-pc==") ? userName.split("==")[1] : userName); if (user == null) { return null; } else { //这里验证authenticationToken和simpleAuthenticationInfo的信息 return new SimpleAuthenticationInfo(userName, user.getPassword(), getName()); } } }
自定义角色权限校验器roleOrFilter(注:默认的roles为and关系,由于实际项目需要更多为或者关系,故自定义此类) CustomRolesAuthorizationFilter.java:
package webapp.shiro; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authz.AuthorizationFilter; import webapp.model.RolesPermissions; import webapp.redis.RedisCache; import webapp.service.RolesPermissionsService; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class CustomRolesAuthorizationFilter extends AuthorizationFilter { //项目名-用于多项目共用redis缓存时区分项目 private static final String PROJECTNAME = "shiro5"; private RolesPermissionsService rolesPermissionsService; private RedisCache redisCache; public CustomRolesAuthorizationFilter(RolesPermissionsService rolesPermissionsService, RedisCache redisCache) { this.rolesPermissionsService = rolesPermissionsService; this.redisCache = redisCache; } @Override public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { Subject subject = getSubject(request, response); PrincipalCollection principals = subject.getPrincipals(); //未登录情况 if (null == principals) { return false; } String userName = principals.toString(); String userName0 = userName.split("==")[0]; String[] rolesArray = (String[]) mappedValue; if (rolesArray == null || rolesArray.length == 0) { return true; } //当前请求URI所需要的角色 List<String> rolesList = Arrays.asList(rolesArray); String userName1 = ""; //redis缓存中当前登录用户的角色(注:更改用户角色时需要更新此缓存) Object cache = redisCache.getCache(PROJECTNAME + "==isAccessAllowed==" + userName); if ("admin-pc".equals(userName0)) { if (null != cache) { Set<String> cache1 = (Set<String>) cache; boolean disjoint = Collections.disjoint(cache1, rolesList); return !disjoint; } userName1 = userName.split("==")[1]; } List<RolesPermissions> rolesPermissions; if ("admin-pc".equals(userName0)) { rolesPermissions = rolesPermissionsService.selectByAuserName(userName1); } else { return true; } //当前用户具有的权限 Set<String> roles = rolesPermissions.stream().map(RolesPermissions::getRolesName).collect(Collectors.toSet()); //往redis缓存中存储当前登录用户的角色(注:更改用户角色时需要更新此缓存) redisCache.putCacheWithExpireTime(PROJECTNAME + "==isAccessAllowed==" + userName, roles, 86400); //24小时过期 boolean disjoint = Collections.disjoint(roles, rolesList); return !disjoint; } }
使用自定义的角色权限校验器roleOrFilter,ShiroPermissionFactory.java:
package webapp.shiro; import org.apache.shiro.config.Ini; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.config.IniFilterChainResolverFactory; import org.springframework.util.CollectionUtils; import webapp.model.RolesPermissions; import webapp.service.RolesPermissionsService; import java.util.*; public class ShiroPermissionFactory extends ShiroFilterFactoryBean { /**记录配置中的过滤链*/ public static String definition = ""; private RolesPermissionsService rolesPermissionsService; public ShiroPermissionFactory(RolesPermissionsService rolesPermissionsService){ this.rolesPermissionsService = rolesPermissionsService; } /** * 初始化设置过滤链 */ @Override public void setFilterChainDefinitions(String definitions) { definition = definitions;//记录配置的静态过滤链 List<RolesPermissions> rolesPermissions = rolesPermissionsService.selectList(); Set<String> urls = new LinkedHashSet<>(); for (RolesPermissions rolesPermission : rolesPermissions) { urls.add(rolesPermission.getUrl()); } Map<String,String> otherChains = new HashMap<>(); for (String url : urls) { StringBuilder roleOrFilters = new StringBuilder(); int j = 0; for (int i = 0; i < rolesPermissions.size(); i++) { if (Objects.equals(url, rolesPermissions.get(i).getUrl())) { if (j == 0) { j = 1; roleOrFilters.append(rolesPermissions.get(i).getRolesName()); } else { roleOrFilters.append(",").append(rolesPermissions.get(i).getRolesName()); } } } String rolesStr = roleOrFilters.toString(); if (!"".equals(rolesStr)) { otherChains.put(url, "roleOrFilter[" + rolesStr + "]"); } } //加载配置默认的过滤链 Ini ini = new Ini(); ini.load(definitions); Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS); if (CollectionUtils.isEmpty(section)) { section = ini.getSection(Ini.DEFAULT_SECTION_NAME); } //加上数据库中过滤链 section.putAll(otherChains); setFilterChainDefinitionMap(section); } }
shiro配置类ShiroConfig.java修改如下:
package webapp.conf; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import webapp.redis.RedisCache; import webapp.redis.RedisSessionDAO; import webapp.service.impl.RolesPermissionsServiceImpl; import webapp.shiro.CustomRolesAuthorizationFilter; import webapp.shiro.ShiroPermissionFactory; import webapp.shiro.UserShiroRealm; import javax.servlet.Filter; import java.util.HashMap; import java.util.Map; /** * shiro配置类 * Created by Administrator on 2018/9/5. */ @Configuration public class ShiroConfig { //将自己的验证方式加入容器 @Bean public UserShiroRealm userShiroRealm() { return new UserShiroRealm(); } @Bean public SimpleCookie getSimpleCookie() { SimpleCookie simpleCookie = new SimpleCookie(); simpleCookie.setName("SHRIOSESSIONID"); return simpleCookie; } //保证实现了Shiro内部lifecycle函数的bean执行 @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } //配置shiro session 的一个管理器 @Bean(name = "sessionManager") public DefaultWebSessionManager getDefaultWebSessionManager(RedisSessionDAO redisSessionDAO) { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionDAO(redisSessionDAO); sessionManager.setGlobalSessionTimeout(-1000); //session有效期 默认值1800000 30分钟 1800000毫秒 -1000表示永久 SimpleCookie simpleCookie = getSimpleCookie(); simpleCookie.setHttpOnly(true); //设置js不可读取此Cookie simpleCookie.setMaxAge(3 * 365 * 24 * 60 * 60); //3年 cookie有前期 sessionManager.setSessionIdCookie(simpleCookie); return sessionManager; } //配置核心安全事务管理器 @Bean(name="securityManager") public SecurityManager securityManager(@Qualifier("userShiroRealm") UserShiroRealm userShiroRealm, @Qualifier("sessionManager") DefaultWebSessionManager sessionManager) { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(userShiroRealm); manager.setSessionManager(sessionManager); return manager; } //权限管理,配置主要是Realm的管理认证 @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userShiroRealm()); return securityManager; } //Filter工厂,设置对应的过滤条件和跳转条件 @Bean public ShiroPermissionFactory shiroPermissionFactory(RedisCache redisCache) { ShiroPermissionFactory shiroFilter = new ShiroPermissionFactory(new RolesPermissionsServiceImpl()); shiroFilter.setSecurityManager(securityManager()); //登录认证不通过跳转 shiroFilter.setLoginUrl("/loginUnAuth"); //权限认证不通过跳转 shiroFilter.setUnauthorizedUrl("/authorUnAuth"); Map<String, Filter> filters = new HashMap<>(); filters.put("roleOrFilter", new CustomRolesAuthorizationFilter(new RolesPermissionsServiceImpl(), redisCache)); shiroFilter.setFilters(filters); shiroFilter.setFilterChainDefinitions("/###/@@@/** = authc"); return shiroFilter; } //加入注解的使用,不加入这个注解不生效 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; } }
角色权限刷新器FilterChainDefinitions.java:
package webapp.shiro; import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager; import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver; import org.apache.shiro.web.servlet.AbstractShiroFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.util.Map; /** * 当修改用户角色权限时候需要调用reloadFilterChains()来刷新角色权限 */ @Component public class FilterChainDefinitions { @Autowired private ShiroPermissionFactory permissFactory; public void reloadFilterChains() { synchronized (permissFactory) { //强制同步,控制线程安全 AbstractShiroFilter shiroFilter = null; try { shiroFilter = (AbstractShiroFilter) permissFactory.getObject(); PathMatchingFilterChainResolver resolver = (PathMatchingFilterChainResolver) shiroFilter .getFilterChainResolver(); // 过滤管理器 DefaultFilterChainManager manager = (DefaultFilterChainManager) resolver.getFilterChainManager(); // 清除权限配置 manager.getFilterChains().clear(); permissFactory.getFilterChainDefinitionMap().clear(); // 重新设置权限 permissFactory.setFilterChainDefinitions(ShiroPermissionFactory.definition);//传入配置中的filterchains Map<String, String> chains = permissFactory.getFilterChainDefinitionMap(); //重新生成过滤链 if (!CollectionUtils.isEmpty(chains)) { chains.forEach((url, definitionChains) -> { manager.createChain(url, definitionChains.trim().replace(" ", "")); }); } } catch (Exception e) { e.printStackTrace(); } } } }
角色权限刷新器FilterChainDefinitions.java的使用示例TestController.java:
package webapp.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import webapp.shiro.FilterChainDefinitions; @RestController @RequestMapping("/core/test") public class TestController { @Autowired private FilterChainDefinitions filterChainDefinitions; @GetMapping("/test") public void test() { //设置用户权限 //... //刷新权限 filterChainDefinitions.reloadFilterChains(); } }
然后启动项目,RolesPermissionsServiceImpl.java类中的获取数据库(模拟)中所有角色权限和获取用户角色权限可自行修改测试。实际应用于项目也只需要修改这个类为查询实际数据库数据即可。