当前位置:   article > 正文

Springboot权限管理

springboot权限管理

一、前言

最近在整合完单点登录后,又来了一个新活,好在这个任务已经比较成熟,实现的方式也比较多,也有比较成熟的框架已经实现了它,它就是权限管理。

二、实现过程

实现权限管理的方式有很多,我提出我通过查阅资料学习所知道的,如果有更多欢迎分享:

  1. 拦截器做鉴权
  2. AOP做鉴权
  3. shiro框架
  4. springsecurity框架

其中,1、2点都是基于在类或方法上加上一个自定义注解实现的,在通知或拦截器中通过获取类或方法上的注解的value值,来比较判断用户的权限,再决定是否放行执行我们的核心业务。

考虑到公司架构和其他原因,我使用到的是使用AOP进行鉴权认证。

实现过程如下:

2.1数据库设计   

2.1.1RBAC权限模型

RBAC是基于角色的访问控制,是用户通过角色与权限进行关联,为什么不直接给用户分配权限呢?简单来说,一个用户拥有多个角色,每个角色拥有若干权限。这样就构成了“用户-角色-权限”的授权模型。在这个模型中,用户与角色、角色与权限之间是多对多的关系。

根据角色授权的思想,我们需要设计五张表

​ 用户表(user)

​ 角色表(role)

​ 权限表(permission)

这三个表之间都是多对多的关系,所以衍生出两个关联表如下

 用户角色表(user_role)

​ 角色资源表(role_permission)

2.2.选择方案

2.2.1基于AOP的实现

1.定义注解

  1. @Retention(value = RetentionPolicy.RUNTIME)
  2. @Target({ElementType.METHOD,ElementType.TYPE})
  3. @Inherited
  4. @Documented
  5. public @interface PreAuthorize {
  6. String value();
  7. }

注解的value值代表的是方法所需的条件或权限

2.AuthorizeAspect切面定义

  1. @Aspect
  2. @Component
  3. public class AuthorizeAspect {
  4. @Autowired
  5. private ZsjScadaUserService userService;
  6. @Autowired
  7. private ZsjScadaUserRoleService userRoleService;
  8. @Autowired
  9. private ZsjScadaRolePermissionService rolePermissionService;
  10. @Autowired
  11. private ZsjScadaPermissionService permissionService;
  12. //定义切点
  13. @Pointcut("@annotation(com.metastar.vip.scada.service.annotation.PreAuthorize)")
  14. public void logPointCut(){
  15. }
  16. //鉴权通知
  17. //环绕通知选择原因:取消方法执行:在环绕通知中,我们可以选择不执行目标方法(JointPoint),从而取消方法的执行。这在鉴权过程中非常有用,如果用户没有相应的权限,我们可以直接返回错误信息,而不必执行目标方法。
  18. @Around("logPointCut()")
  19. public Object authAround(ProceedingJoinPoint joinPoint) throws Throwable{
  20. // TODO: 2023/8/31 获取目标方法中的HttpRequest -> 获取请求头携带的Cookie
  21. Object[] args = joinPoint.getArgs();
  22. String name = joinPoint.getSignature().getName();
  23. HttpServletRequest request = (HttpServletRequest) args[0];
  24. Object proceed = null;
  25. HttpSession session = request.getSession(false);
  26. if (session == null){
  27. return proceed = Result.fail("请先登录");
  28. }
  29. // TODO: 2023/8/31 根据cookie获取当前会话信息 -> 获取当前登录用户
  30. Assertion assertion = (Assertion) session.getAttribute("_const_cas_assertion_");
  31. Map<String, Object> attributes = assertion.getPrincipal().getAttributes();
  32. String user = attributes.get("USER").toString();
  33. JSONObject userJsonObject = JSON.parseObject(user);
  34. String userId = userJsonObject.getString("userId");
  35. // TODO: 2023/8/31 根据用户ID查询该用户角色
  36. QueryWrapper<UserDO> queryWrapper = new QueryWrapper<>();
  37. queryWrapper.eq("id",userId).eq("in_use",1);
  38. UserDO userDO = userService.getOne(queryWrapper);
  39. if (user == null){
  40. throw new RuntimeException("该用户不存在");
  41. }
  42. // TODO: 2023/8/31 根据该角色去查询所拥有的权限
  43. QueryWrapper<UserRoleDO> userRoleDOQueryWrapper = new QueryWrapper<>();
  44. userRoleDOQueryWrapper.eq("user_id",userDO.getId());
  45. UserRoleDO one = userRoleService.getOne(userRoleDOQueryWrapper);
  46. Long roleId = one.getRoleId();
  47. QueryWrapper<RolePermissionDO> rolePermissionDOQueryWrapper = new QueryWrapper<>();
  48. rolePermissionDOQueryWrapper.eq("role_id",roleId);
  49. List<Long> permissionIds = rolePermissionService.list(rolePermissionDOQueryWrapper).stream().map(RolePermissionDO::getPermissionId).collect(Collectors.toList());
  50. // TODO: 2023/8/31 查看该权限所能访问的资源uri
  51. // TODO: 2023/8/31 查看当前目标类+方法的URI是否存在与当前用户权限所对应的资源uri列表中
  52. // TODO: 2023/8/31 存在->继续指定控制器中的方法 不存在->返回错误信息
  53. Signature signature = joinPoint.getSignature();
  54. MethodSignature methodSignature = (MethodSignature) signature;
  55. Object target = joinPoint.getTarget();
  56. //获取注解标注的类(鉴权以类为单位)
  57. // PreAuthorize annotation1 = target.getClass().getAnnotation(PreAuthorize.class);
  58. // String value = annotation1.value();
  59. //获取注解标注的方法(鉴权以方法为单位)
  60. Method method = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
  61. //通过方法获取注解
  62. PreAuthorize annotation = method.getAnnotation(PreAuthorize.class);
  63. //获取注解中的值
  64. String permissionValue = annotation.value();
  65. Integer count = 0;
  66. for (Long permissionId : permissionIds) {
  67. String permissionName = permissionService.getById(permissionId).getPermissionName();
  68. if (permissionName.equals(permissionValue)){
  69. //该角色含有该权限 访问方法
  70. proceed = joinPoint.proceed(args);
  71. break;
  72. }
  73. count++;
  74. }
  75. if (count == permissionIds.size()){
  76. //没有该权限 抛出异常
  77. return proceed = Result.fail("权限不足,无法访问");
  78. }
  79. return proceed;
  80. }
  81. }

3.测试

  1. @RestController
  2. @RequestMapping("/auth")
  3. public class authTestController {
  4. @PostMapping("/test1")
  5. @PreAuthorize(value = "用户模块")
  6. public Result Test1(HttpServletRequest request,String userName){
  7. return Result.ok(userName);
  8. }
  9. }

Postman访问该url后,拦截后执行环绕通知进行鉴权,通过后访问该控制器中的Test1方法响应结果。

2.2.2基于拦截器的实现

1.同样是定义自定义注解

  1. @Target({ElementType.TYPE, ElementType.METHOD}) //注解的作用域,即此注解应该被用在什么地方
  2. @Retention(RetentionPolicy.RUNTIME) //注解的生命周期,即注解在什么范围内有效
  3. @Inherited //标识注解,允许子类继承
  4. @Documented //标识注解,生成javadoc文档时,会包含此注解
  5. public @interface RoleNum {
  6. // int value(); //default 1;
  7. String value();
  8. }

2.定义拦截器

  1. @Component
  2. public class LoginInterceptor implements HandlerInterceptor {
  3. @Autowired
  4. private StringRedisTemplate stringRedisTemplate;
  5. /**
  6. * 目标方法执行之前(Controller方法调用之前)
  7. * @param request
  8. * @param response
  9. * @param handler
  10. * @return
  11. * @throws Exception
  12. */
  13. @Override
  14. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  15. // TODO: 2023/7/21 获取前端Authorization的token值 访问redis
  16. // TODO: 2023/7/21 命中 放行 刷新redis中token的TTL
  17. // TODO: 2023/7/21 未命中(过期) 重新向登录页面 拦截
  18. // TODO: 2023/7/24 在验证到相应登陆的Token后 需要查看当前用户Role值 检验是否有该路径的访问权限
  19. String token = request.getHeader("Authorization");
  20. Long size = stringRedisTemplate.opsForHash().size(RedisConstant.LOGIN_INFO + token);
  21. if (size == 0){
  22. //重定向到登录页面
  23. // response.sendRedirect("/login");
  24. //拦截
  25. response.getWriter().write("{\"code\":-1,\"msg\":\"please login first\",\"data\":null}");
  26. return false;
  27. }else {
  28. String role = (String) stringRedisTemplate.opsForHash().get(RedisConstant.LOGIN_INFO + token, "role");
  29. if (hasPermission(handler,role)) {
  30. //权限足够
  31. //放行 并 刷新 用户信息token TTL
  32. stringRedisTemplate.expire(RedisConstant.LOGIN_INFO + token,RedisConstant.LOGIN_TTL, TimeUnit.MINUTES);
  33. return true;
  34. } else {
  35. //权限不足
  36. response.getWriter().write("{\"code\":-1,\"msg\":\"privilege is not enough\",\"data\":null}");
  37. //放行 并 刷新 用户信息token TTL
  38. stringRedisTemplate.expire(RedisConstant.LOGIN_INFO + token,RedisConstant.LOGIN_TTL, TimeUnit.MINUTES);
  39. return false;
  40. }
  41. }
  42. }
  43. /**
  44. * 验证权限是否足够
  45. * @param handler
  46. * @param role
  47. * @return
  48. */
  49. private boolean hasPermission(Object handler, String role) {
  50. if (handler instanceof HandlerMethod){
  51. HandlerMethod handlerMethod = (HandlerMethod) handler;
  52. //获取方法上的注解
  53. RoleNum roleNum = handlerMethod.getMethod().getAnnotation(RoleNum.class);
  54. //如果方法上的注解为空 则获取类的注解
  55. if (roleNum == null){
  56. roleNum = handlerMethod.getMethod().getDeclaringClass().getAnnotation(RoleNum.class);
  57. }
  58. //如果标记了注解,则判断权限
  59. if (roleNum != null){
  60. if (roleNum.value().equals("2")){ //该通用身份即可访问
  61. // System.err.println("权限足够,正在访问");
  62. return true;
  63. }
  64. if (roleNum.value().equals("1")) { //Normal 0
  65. if (role.equals("1")){
  66. return true;
  67. }else if (role.equals("0")){
  68. return true;
  69. }
  70. }
  71. if (roleNum.value().equals("0")){
  72. if (role.equals("0")){
  73. return true;
  74. }else {
  75. return false;
  76. }
  77. }
  78. }
  79. }
  80. return false;
  81. }

注意:上述用户的角色权限信息是存储在Redis中的。

三、最后

现在鉴权模块已经非常成熟,以上仅为个人心得和实践,还有很多改进的地方欢迎评论,后续还会补充通过Shiro框架实现的鉴权这一模块。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/437050
推荐阅读
相关标签
  

闽ICP备14008679号