springboot + shiro 权限管理

编程入门 行业动态 更新时间:2024-10-25 02:21:17

springboot + shiro <a href=https://www.elefans.com/category/jswz/34/1771295.html style=权限管理"/>

springboot + shiro 权限管理

springboot shiro 权限管理

版本 :

  • springboot 2.3.4-RELEASE
  • shiro 1.6.0

注: 此版本基于 ssm 与 shiro 下的权限管理_XGLLHZ的博客-CSDN博客 文章(有些小问题,目前未修改),关联阅读便于理解。

核心组件 :

  • Subject 一般指用户
  • SecurityManager 安全管理器,管理所有 subject
  • SessionManager 会话管理器,管理用户登录后的 session
  • Authentication 认证
  • Authorization 鉴权
  • CacheManager 缓存管理器,管理缓存(缓存可自定义)

流程图 :

TokenFilter :

// token filter(请求先进入此拦截器)
public class TokenFilter extends AbstractFilter {private static final Logger logger = LogManager.getLogger(TokenFilter.class);private final SYSUserService userService;public TokenFilter(SYSUserService userService) {this.userService = userService;}/*** 此拦截器工作流程:*      1、判断是否携带 token*      2、判断 token 是否过期*      3、判断用户登录是否过期(即 session 中是否存在用户信息)*      4、在 token 未过期且用户登录过期的情况刷新用户 session(系统内部自动登录)** 此处做自动登录的目的:*      在 spring-shiro 中,用户登录信息是放在 session 中的,*      而 session 默认有效时间为半小时,也就是说默认情况下,*      用户每隔半小时就得登录一次,针对这个问题有两种解决方法:*          1、session 有效时长设置为无限长(缺点是当用户数量过大时会严重占用系统内存)*          2、利用 token 机制结合 session(有效时长可设两小时),当 token 有效 session*          失效时,刷新 session 中的用户信息,当 token 过期时提示用户重新登录*          * @param servletRequest* @param servletResponse* @return* @throws IOException*/@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain filterChain) throws ServletException, IOException {logger.info("enter TokenFilter.doFilter() params = {}", servletRequest.toString());HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;response.setContentType("application/json; charset=utf-8");String token = request.getHeader("token");// 若 token 为空则说明该请求为非法请求或不需要权限的请求if (StringUtils.isNotBlank(token)) {// 若 token 过期则返回响应体if (TokenUtil.checkToken(token) == 1) {ObjectMapper mapper = new ObjectMapper();PrintWriter writer = response.getWriter();writer.write(mapper.writeValueAsString(new APIResponse<>(ConstConfig.RE_LOGIN_EXPIRE_CODE,ConstConfig.RE_LOGIN_EXPIRE_MSG)));writer.flush();writer.close();return;}// 若 token 未过期则判断内存中是否有此用户身份验证信息Object object = null;try {object = SecurityUtils.getSubject().getPrincipal();} catch (Exception e) {logger.error("The userInfo is overdue in session!");}// 若没有此用户身份验证信息则根据 token 获取用户信息并调用 shiro 中的 subject.login()// 类似于系统内部自动登录(刷新 session 中的用户信息)if (object == null) {String username = userService.getUsernameByToken(token);if (StringUtils.isBlank(username)) {ObjectMapper mapper = new ObjectMapper();PrintWriter writer = response.getWriter();writer.write(mapper.writeValueAsString(new APIResponse<>(ConstConfig.RE_CHECK_TOKEN_ERROR_CODE,ConstConfig.RE_CHECK_TOKEN_ERROR_MSG)));writer.flush();writer.close();return;}SYSUserPo userPo = userService.getUserByUsername(username);if (userPo == null) {ObjectMapper mapper = new ObjectMapper();PrintWriter writer = response.getWriter();writer.write(mapper.writeValueAsString(new APIResponse<>(ConstConfig.SERVER_EXCEPTION_CODE,"User data does not exist")));writer.flush();writer.close();return;}// 调用 shiro 中的登录方法UsernamePasswordToken usernamePasswordToken =new UsernamePasswordToken(userPo.getUsername(), userPo.getPassword());Subject subject = SecurityUtils.getSubject();subject.login(usernamePasswordToken);}}filterChain.doFilter(servletRequest, servletResponse);}
}

ShiroRealm :

// 重写 shiro 父类中鉴权和认证的方法,以实现具体逻辑
public class ShiroRealm extends AuthorizingRealm {private static final Logger logger = LogManager.getLogger(ShiroRealm.class);private final SYSUserService userService;public ShiroRealm(SYSUserService userService) {this.userService = userService;}/*** 鉴权* 获取权限校验需要的信息(当前登录用户所具有的权限信息:权限、角色)* 调用时间: 1、默认情况下是每次请求资源(url)时都会调用;*          2、但可以将用户权限(资源)信息存放到缓存中,存放方式是在 subject.login()*              之后调用 subject.isPermitted("test")方法,在这个方法内部会调用下面的*              方法来获取用户权限(资源)信息,同时需要在 shiro 配置文件中配置相应的缓存管理器;*          3、放入缓存之后只在每次登录时调用;* 检验方式: shiro 的权限校验方式有两种,即基于权限(资源)、基于角色* 此项目采用基于权限(资源)的方式* @param principalCollection* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {logger.info("enter ShiroRealm.doGetAuthorizationInfo() authority");// 获取登录成功的用户名// 这里会从 session 中获取(登录执行 subject.login 方法时会将用户信息放入 session)String username = (String) principalCollection.fromRealm(getName()).iterator().next();// 根据用户名获取该用户所具有的权限列表SYSUserPo sysUserPo = new SYSUserPo();sysUserPo.setUsername(username);List<SYSPermPo> permList = userService.listPermByUser(sysUserPo);List<String> perms = null;if (permList != null && permList.size() != 0) {perms = permList.stream().map(SYSPermPo::getPermUrl).collect(Collectors.toList());}SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addStringPermissions(perms);return info;}/*** 认证* 获取身份验证需要的信息(数据库中的用户数据)* 调用时间: 登录时调用,即请求 /admin/user/login时(service 中 的 subject.login())* @param authenticationToken* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)throws AuthenticationException {logger.info("enter ShiroRealm.doGetAuthenticationInfo() authenticate");// 用户登录提交的信息UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;String username = token.getUsername();if (StringUtils.isBlank(username)) {throw new GlobalException(ConstConfig.RE_NAME_OR_PASSWORD_ERROR_CODE,ConstConfig.RE_NAME_OR_PASSWORD_ERROR_MSG);}// 根据用户名查询到的信息SYSUserPo userPo = userService.getUserByUsername(username);if (userPo == null) {throw new GlobalException(ConstConfig.SERVER_EXCEPTION_CODE, "User data does not exist");}return new SimpleAuthenticationInfo(userPo.getUsername(), userPo.getPassword(), getName());}
}

ShiroConfig :

// shiro config
@Component
public class ShiroConfig {public static final String PERMISSION_STRING = "perms[\"{0}\"]";private final SYSPermMapper permMapper;private final SYSUserService userService;public ShiroConfig(SYSPermMapper permMapper, SYSUserService userService) {this.permMapper = permMapper;this.userService = userService;}/*** 自定义实现 ShiroFilterFactoryBean** 关于 shiro 中的 filterChains(过滤链)* *      1、map 中的 key 表示要拦截的请求 url,value 表示处理此 url 所使用的拦截器,*          其中 value 的值可以是 shiro 提供的拦截器也可以是自定义拦截器*          *      2、value 的值可以为多个(以逗号隔开),表示该 url(key) 要被多个拦截器处理,*          其中执行顺序为 value 中的顺序,如: filterChains.put("/admin/user/list", "tokenFilter,authc")*          表示 /admin/user/list 请求会依次经过 tokenFilter(自定义)、authc 拦截器** @param securityManager* @return*/@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();factoryBean.setSecurityManager(securityManager);// loginUrl 在前后端分离下,此处应为返回登录提示的接口 api// 即,当系统发现当前请求地址需要授权才可访问时返回登录提示factoryBean.setLoginUrl("/admin/user/login_code");// unAuthorizedUrl 在前后端分离下,此处应为鉴权失败后返回权限不足的接口 api// 即,当鉴权失败后返回权限不足提示factoryBean.setUnauthorizedUrl("/admin/user/authorizingFail");// 自定义拦截器 token 校验Map<String, Filter> filters = new LinkedHashMap<>();filters.put("tokenFilter", new TokenFilter(userService));factoryBean.setFilters(filters);Map<String, String> filterChains = new LinkedHashMap<>();   // 承载过滤链的变量// 从数据库中获取权限(资源)url 及其对应的 roleList// 将存在 roleList 的 url 加入到过滤链中,因为 sys_perm 表中放的是所有资源的 url,// 其中有些资源不需要权限就可以访问,所以不需要放到过滤链中// 凡是放到过滤链中的 url 都会被拦截List<SYSPermPo> list = permMapper.allUrlRole();if (list != null && list.size() != 0) {list.stream().peek(a -> {if (StringUtils.isNoneBlank(a.getPermUrl()) && !a.getRoleList().isEmpty()) {filterChains.put(a.getPermUrl(), "tokenFilter,"+ MessageFormat.format(PERMISSION_STRING, a.getPermUrl()));}}).collect(Collectors.toList());}factoryBean.setFilterChainDefinitionMap(filterChains);return factoryBean;}@Beanpublic SecurityManager securityManager(ShiroRealm shiroRealm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(shiroRealm);return securityManager;}@Beanpublic ShiroRealm shiroRealm() {return new ShiroRealm(userService);}
}

shiro filter :

标识名称优先级说明对应类
anon匿名拦截器1不需要登录即可访问,一般用于静态资源AnonymousFilter
authc登录拦截器2需要登陆才可访问FormAuthenticationFilter
authcBasicHttpHttp拦截器3Http 拦截器 非常用类型BasicAuthenticationFilter
logout登出拦截器4用户登出拦截器 主要属性 redirectUrl 登出后重定向地址LogoutFilter
noSessionCreation不创建会话拦截器5没用过NoSessionCreationFilter
perms权限拦截器6验证用户是否有权限访问资源PermissionAuthenticationFilter
port端口拦截器7拦截端口,针对指定端口做出重定向PortFilter
restrest 风格拦截器8根据 rest 风格 url 构建权限 非常用HttpMethodPermissionFilter
roles角色拦截器9验证用户是否有角色访问资源RolesAuthorizationFilter
sslssl 拦截器10拦截非 https 请求,并跳转到 443SslFilter
user用户拦截器11用户认证通过或开启记住我功能SslFilter

RefreshFilterChains :

// shiro 过滤链是在服务启动时从数据库加载到内存中的
// 则当数据库权限数据发生变化时需要刷新内存中的过滤链
// 在添加修改角色权限相关方法中使用 refreshFilterChains.refreshFilterChains() 刷新过滤链
@Component
public class RefreshFilterChains {private static final Logger logger = LogManager.getLogger(RefreshFilterChains.class);public static final String PERMISSION_STRING = "perms[\"{0}\"]";private final ShiroFilterFactoryBean shiroFilterFactoryBean;private final SYSPermMapper permMapper;public RefreshFilterChains(ShiroFilterFactoryBean shiroFilterFactoryBean, SYSPermMapper permMapper) {this.shiroFilterFactoryBean = shiroFilterFactoryBean;this.permMapper = permMapper;}/*** 刷新过滤链(线程安全)*/public void refreshFilterChains() {logger.info("refresh shiro filter chains");synchronized (shiroFilterFactoryBean) {AbstractShiroFilter filter;try {// 获取 shiro 拦截器实例filter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject();// 获取路径匹配过滤链解析器实例PathMatchingFilterChainResolver resolver = (PathMatchingFilterChainResolver) filter.getFilterChainResolver();// 获取默认过滤链管理器DefaultFilterChainManager manager = (DefaultFilterChainManager) resolver.getFilterChainManager();// 清除过滤链manager.getFilterChains().clear();shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();Map<String, String> filterChains = new LinkedHashMap<>();List<SYSPermPo> list = permMapper.allUrlRole();if (list != null && list.size() != 0) {list.stream().peek(a -> {if (StringUtils.isNoneBlank(a.getPermUrl()) && !a.getRoleList().isEmpty()) {filterChains.put(a.getPermUrl(), "tokenFilter,"+ MessageFormat.format(PERMISSION_STRING, a.getPermUrl()));}}).collect(Collectors.toList());}shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChains);// 重新生成过滤链Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();if (!CollectionUtils.isEmpty(filterChainDefinitionMap)) {filterChains.forEach((key, value) -> {manager.createChain(key, value.replace(" ", ""));});}logger.info("The refreshed filter chains is {}", filterChainDefinitionMap.toString());} catch (Exception e) {logger.error("Failed refresh shiro filter chains");e.printStackTrace();}}}
}

完结 撒花 庆祝

人生何处不相逢.mp3-娴公主

更多推荐

springboot + shiro 权限管理

本文发布于:2024-02-11 19:26:50,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1682922.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:权限   springboot   shiro

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!