• Springboot实现RBAC权限校验


    目录

    RBAC思想

    实现方式

    一图流实现思路

    代码实现

     导入相关依赖

    实现登录与用户的token携带

    编写登录controller接口

    编写登录service业务

    实现登录业务

    实现登录后操作的权限验证

     实现token拦截器,对所有操作进行身份验证

    自定义注解,作为权限验证的切入点

    在切面中编写通知

    编写测试接口,测试登录后的用户操作

    使用postman测试

    登录测试

    正常登录

     密码或用户名有误

     token拦截测试

    使用后正常登录后获取的token

    使用错误token

     使用正常token但无访问权限


    RBAC思想

            RBAC基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。这样做的好处是,不必在每次创建用户时都进行分配权限的操作,只要分配用户相应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限管理,减少系统的开销。

    实现方式

    Springboot+AOP切面+自定义注解+redis+jwt+mybatis+token拦截器

    注:为了简化操作和流程,没有真正地从mysql中读取数据,而是以写死的数据进行演示

    一图流实现思路

    代码实现

     导入相关依赖

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-webartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>org.springframework.bootgroupId>
    7. <artifactId>spring-boot-starter-testartifactId>
    8. <scope>testscope>
    9. dependency>
    10. <dependency>
    11. <groupId>org.projectlombokgroupId>
    12. <artifactId>lombokartifactId>
    13. <version>1.16.12version>
    14. dependency>
    15. <dependency>
    16. <groupId>org.aspectjgroupId>
    17. <artifactId>aspectjrtartifactId>
    18. <version>1.8.9version>
    19. dependency>
    20. <dependency>
    21. <groupId>org.aspectjgroupId>
    22. <artifactId>aspectjtoolsartifactId>
    23. <version>1.8.9version>
    24. dependency>
    25. <dependency>
    26. <groupId>org.aspectjgroupId>
    27. <artifactId>aspectjweaverartifactId>
    28. <version>1.7.4version>
    29. dependency>
    30. <dependency>
    31. <groupId>org.springframework.bootgroupId>
    32. <artifactId>spring-boot-starter-data-redisartifactId>
    33. dependency>
    34. <dependency>
    35. <groupId>cn.hutoolgroupId>
    36. <artifactId>hutool-allartifactId>
    37. <version>5.7.17version>
    38. dependency>
    39. <dependency>
    40. <groupId>com.alibabagroupId>
    41. <artifactId>fastjsonartifactId>
    42. <version>1.2.62version>
    43. dependency>
    44. <dependency>
    45. <groupId>cn.hutoolgroupId>
    46. <artifactId>hutool-allartifactId>
    47. <version>5.7.17version>
    48. dependency>

    实现登录与用户的token携带

    编写登录controller接口

    1. package com.melody.rest.restcontroller;
    2. import com.melody.rest.domain.RestSysUser;
    3. import com.melody.rest.model.ResultJson;
    4. import com.melody.rest.service.RestAuthService;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.web.bind.annotation.PostMapping;
    7. import org.springframework.web.bind.annotation.RequestBody;
    8. import org.springframework.web.bind.annotation.RequestMapping;
    9. import org.springframework.web.bind.annotation.RestController;
    10. @RestController
    11. @RequestMapping("/rest")
    12. public class LoginController {
    13. @Autowired
    14. private RestAuthService restAuthService;
    15. //登录
    16. @PostMapping("/login")
    17. public ResultJson index(@RequestBody RestSysUser restSysUser){
    18. //登录以及登录成功存入token
    19. return restAuthService.Login(restSysUser);
    20. }
    21. }

    编写登录service业务

    1. package com.melody.rest.service;
    2. import com.melody.rest.domain.RestSysUser;
    3. import com.melody.rest.model.ResultJson;
    4. public interface RestAuthService {
    5. //登录方法
    6. ResultJson Login(RestSysUser restSysUser);
    7. }

    实现登录业务

    在业务中判断账号密码的正确性,并将用户账户、密码、token存入redis,设置token一天有效期

    1. package com.melody.rest.service.impl;
    2. import cn.hutool.core.bean.BeanUtil;
    3. import cn.hutool.core.bean.copier.CopyOptions;
    4. import com.melody.rest.domain.RestSysUser;
    5. import com.melody.rest.model.ResultCode;
    6. import com.melody.rest.model.ResultJson;
    7. import com.melody.rest.service.RestAuthService;
    8. import com.melody.rest.util.ResourceVerification;
    9. import org.springframework.beans.factory.annotation.Autowired;
    10. import org.springframework.data.redis.core.RedisTemplate;
    11. import org.springframework.stereotype.Service;
    12. import java.util.HashMap;
    13. import java.util.Map;
    14. import java.util.UUID;
    15. import java.util.concurrent.TimeUnit;
    16. @Service
    17. public class RestAuthServiceImpl implements RestAuthService {
    18. @Autowired
    19. RedisTemplate redisTemplate;
    20. @Override
    21. public ResultJson Login(RestSysUser restSysUser) {
    22. //账号密码校验,
    23. if("admin".equals(restSysUser.getUsername()) && "123456".equals(restSysUser.getPassword())){
    24. //账号密码正确
    25. restSysUser.setResources(ResourceVerification.resource());//向用户权限中注入写死的权限
    26. Map userMap = BeanUtil.beanToMap(restSysUser, new HashMap<>(),
    27. CopyOptions.create()
    28. .setIgnoreNullValue(true)//忽略一些空值
    29. .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
    30. UUID uuid = UUID.randomUUID();
    31. String tokenKey= String.valueOf(uuid);
    32. String token="LoginUserKey "+tokenKey;
    33. //存储
    34. redisTemplate.opsForHash().putAll(token,userMap);
    35. //设置存值时间,expire默认秒,改为天数,设置1天
    36. redisTemplate.expire(token,1, TimeUnit.DAYS);
    37. return ResultJson.ok(token);
    38. }else{
    39. //账号密码不正确
    40. return ResultJson.failure(ResultCode.LOGIN_ERROR);
    41. }
    42. }
    43. }

    测试用的权限:

    实现登录后操作的权限验证

     实现token拦截器,对所有操作进行身份验证

    拦截器不受spring管理,因此需要在拦截器中注入redis模板类RedisTemplate,使用有参构造将RedisTemplate传递给token拦截类

    1. package com.melody.rest.config;
    2. import com.melody.rest.util.LoginInterceptor;
    3. import org.springframework.context.annotation.Configuration;
    4. import org.springframework.data.redis.core.RedisTemplate;
    5. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    6. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    7. import javax.annotation.Resource;
    8. @Configuration
    9. public class MvcConfig implements WebMvcConfigurer {
    10. @Resource
    11. RedisTemplate redisTemplate;
    12. @Override
    13. public void addInterceptors(InterceptorRegistry registry) {
    14. //配置登录查看是否有token拦截器
    15. registry.addInterceptor(new LoginInterceptor(redisTemplate)).addPathPatterns("/testRest/**").order(0);
    16. }
    17. }
    1. package com.melody.rest.util;
    2. import cn.hutool.core.bean.BeanUtil;
    3. import com.alibaba.fastjson.JSONObject;
    4. import com.melody.rest.domain.RestData;
    5. import com.melody.rest.domain.RestSysUser;
    6. import org.springframework.data.redis.core.RedisTemplate;
    7. import org.springframework.web.servlet.HandlerInterceptor;
    8. import javax.servlet.http.HttpServletRequest;
    9. import javax.servlet.http.HttpServletResponse;
    10. import java.util.Map;
    11. public class LoginInterceptor implements HandlerInterceptor {
    12. private RedisTemplate redisTemplate;
    13. public LoginInterceptor(RedisTemplate redisTemplate){
    14. this.redisTemplate=redisTemplate;
    15. }
    16. @Override
    17. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    18. //设置编码
    19. response.setCharacterEncoding("utf-8");
    20. response.setContentType("text/json;charset=utf-8");
    21. //1、判断是否携带token
    22. String token = request.getHeader("authorization");
    23. if(token==null || "".equals(token)){
    24. RestData restData = RestData.builder().code("401").msg("请先登录再操作!").build();
    25. String jsonRestData = JSONObject.toJSONString(restData);
    26. response.setStatus(401);
    27. response.getWriter().write(jsonRestData);
    28. return false;
    29. }
    30. Map userMap=redisTemplate.opsForHash().entries(token);
    31. RestSysUser restSysUser = BeanUtil.fillBeanWithMap(userMap, new RestSysUser(), false);
    32. //2、判断redis里面是否存在token
    33. if(userMap.isEmpty()){
    34. RestData restData = RestData.builder().code("401").msg("请先登录再操作!").build();
    35. String jsonRestData = JSONObject.toJSONString(restData);
    36. response.setStatus(401);
    37. response.getWriter().write(jsonRestData);
    38. return false;
    39. }
    40. return true;
    41. }
    42. }

    自定义注解,作为权限验证的切入点

    1. package com.melody.rest.annotion;
    2. import java.lang.annotation.*;
    3. @Target({ ElementType.PARAMETER, ElementType.METHOD })
    4. @Retention(RetentionPolicy.RUNTIME)
    5. @Documented
    6. public @interface AuthCheck {
    7. public String value() default "";
    8. }

    在切面中编写通知

    1. package com.melody.rest.aspect;
    2. import cn.hutool.core.bean.BeanUtil;
    3. import com.melody.rest.annotion.AuthCheck;
    4. import com.melody.rest.domain.RestSysUser;
    5. import com.melody.rest.exception.AuthException;
    6. import com.melody.rest.model.ResCode;
    7. import com.melody.rest.model.ResJson;
    8. import com.melody.rest.model.ResultCode;
    9. import com.melody.rest.model.ResultJson;
    10. import org.aspectj.lang.ProceedingJoinPoint;
    11. import org.aspectj.lang.annotation.Around;
    12. import org.aspectj.lang.annotation.Aspect;
    13. import org.aspectj.lang.annotation.Pointcut;
    14. import org.aspectj.lang.reflect.MethodSignature;
    15. import org.springframework.beans.factory.annotation.Autowired;
    16. import org.springframework.beans.factory.annotation.Value;
    17. import org.springframework.data.redis.core.RedisTemplate;
    18. import org.springframework.stereotype.Component;
    19. import javax.annotation.Resource;
    20. import javax.servlet.http.HttpServletRequest;
    21. import java.lang.reflect.Method;
    22. import java.util.Map;
    23. @Aspect
    24. @Component
    25. public class AuthAspect {
    26. @Value("${token.header}")
    27. private String header;
    28. @Autowired
    29. private HttpServletRequest request;
    30. @Resource
    31. private RedisTemplate redisTemplate;
    32. @Pointcut("@annotation(com.melody.rest.annotion.AuthCheck)")
    33. public void authPointCut(){
    34. }
    35. @Around("authPointCut()")
    36. public Object authCheck(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    37. try{
    38. // 判断 TOKEN
    39. String token = request.getHeader(header);
    40. System.err.println(token);
    41. Map userMap=redisTemplate.opsForHash().entries(token);
    42. RestSysUser restSysUser = BeanUtil.fillBeanWithMap(userMap, new RestSysUser(), false);
    43. if(restSysUser.getUsername() == null || restSysUser.getUsername().equals("")){
    44. throw new AuthException(ResCode.TOKEN_NOT_EXIST);
    45. } else {
    46. if(restSysUser.getResources()==null){
    47. throw new AuthException(ResCode.BANED_REQUEST);
    48. }
    49. //从切面织入点处通过反射机制获取织入点处的方法
    50. MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
    51. //获取切入点所在的方法
    52. Method method = signature.getMethod();
    53. AuthCheck ac = method.getAnnotation(AuthCheck.class);
    54. // System.err.println("=========================="+ac);
    55. boolean flag = false;
    56. if(ac != null) {
    57. //获取切入点方法的value值,该测试接口设置的value为权限字段
    58. String auth = ac.value();
    59. // System.err.println("-----------------------------"+auth);
    60. flag = restSysUser.getResources().stream().anyMatch(str -> str.equals(auth));
    61. // way2:数据库中存放权限字段,根据注解的value确定请求所需权限判断是否有权限进行访问
    62. }
    63. if(!flag) {
    64. return ResultJson.failure(ResultCode.FORBIDDEN);
    65. }
    66. }
    67. } catch(AuthException e) {
    68. System.err.println(e.getResCode().getCode() + ":" + e.getResCode().getMsg());
    69. return ResJson.no(e.getResCode());
    70. }
    71. // Object res = proceedingJoinPoint.proceed();
    72. return proceedingJoinPoint.proceed();
    73. }
    74. }

    编写测试接口,测试登录后的用户操作

    1. package com.melody.rest.restcontroller;
    2. import com.melody.rest.annotion.AuthCheck;
    3. import com.melody.rest.model.ResultJson;
    4. import org.springframework.web.bind.annotation.PostMapping;
    5. import org.springframework.web.bind.annotation.RequestMapping;
    6. import org.springframework.web.bind.annotation.RestController;
    7. @RestController
    8. @RequestMapping("/testRest")
    9. public class TestRestController {
    10. //测试方法1
    11. @AuthCheck("/testRest/t1")
    12. @PostMapping("/t1")
    13. public ResultJson test(){
    14. return ResultJson.ok("test1访问成功");
    15. }
    16. //测试方法2
    17. @AuthCheck("/testRest/t10")
    18. @PostMapping("/t10")
    19. public ResultJson test2(){
    20. return ResultJson.ok("test10访问");
    21. }
    22. }

    使用postman测试

    登录测试

    正常登录

     密码或用户名有误

     token拦截测试

    使用后正常登录后获取的token

    使用错误token

     

     使用正常token但无访问权限

    在写死的权限中只有test1、2、3的权限,而方法二权限为test10

  • 相关阅读:
    网络问题排障专题-AF网络问题排障
    初步学习http请求走私
    AutoJs学习-2048全自动
    Potplayer通过公网访问群晖WebDav,快速搭建远程办公环境
    JSP ssm 特殊人群防走失系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计
    【GitLab私有仓库】在Linux上用Gitlab搭建自己的私有库并配置cpolar内网穿透
    【小程序】微信小程序云开发笔记详细教程(建议收藏)
    核爆,字节跳动算法工程师,手写1000页数据算法笔记:Github已标星79k
    Spring的前置知识
    陌生人真的会传授你赚钱的技能吗?
  • 原文地址:https://blog.csdn.net/qq_57031340/article/details/126560425