Springboot实现RBAC权限校验

目录

RBAC思想

实现方式

一图流实现思路

代码实现

 导入相关依赖

实现登录与用户的token携带

编写登录controller接口

编写登录service业务

实现登录业务

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

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

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

在切面中编写通知

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

使用postman测试

登录测试

正常登录

 密码或用户名有误

 token拦截测试

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

使用错误token

 使用正常token但无访问权限


RBAC思想

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

实现方式

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

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

一图流实现思路

Springboot实现RBAC权限校验_第1张图片

代码实现

 导入相关依赖

        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            org.projectlombok
            lombok
            1.16.12
        
        
            org.aspectj
            aspectjrt
            1.8.9
        
        
        
            org.aspectj
            aspectjtools
            1.8.9
        
        
            org.aspectj
            aspectjweaver
            1.7.4
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
        
        
            cn.hutool
            hutool-all
            5.7.17
        
        
            com.alibaba
            fastjson
            1.2.62
        
        
        
            cn.hutool
            hutool-all
            5.7.17
        

实现登录与用户的token携带

编写登录controller接口

package com.melody.rest.restcontroller;

import com.melody.rest.domain.RestSysUser;
import com.melody.rest.model.ResultJson;
import com.melody.rest.service.RestAuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/rest")
public class LoginController {

    @Autowired
    private RestAuthService restAuthService;


    //登录
    @PostMapping("/login")
    public ResultJson index(@RequestBody RestSysUser restSysUser){
        //登录以及登录成功存入token
        return restAuthService.Login(restSysUser);
    }

}

编写登录service业务

package com.melody.rest.service;


import com.melody.rest.domain.RestSysUser;
import com.melody.rest.model.ResultJson;

public interface RestAuthService {

    //登录方法
    ResultJson Login(RestSysUser restSysUser);
    

}

实现登录业务

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

package com.melody.rest.service.impl;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.melody.rest.domain.RestSysUser;
import com.melody.rest.model.ResultCode;
import com.melody.rest.model.ResultJson;
import com.melody.rest.service.RestAuthService;
import com.melody.rest.util.ResourceVerification;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;


import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


@Service
public class RestAuthServiceImpl implements RestAuthService {

    @Autowired
    RedisTemplate redisTemplate;


    @Override
    public ResultJson Login(RestSysUser restSysUser) {
        //账号密码校验,
        if("admin".equals(restSysUser.getUsername()) && "123456".equals(restSysUser.getPassword())){
            //账号密码正确
            restSysUser.setResources(ResourceVerification.resource());//向用户权限中注入写死的权限
            Map userMap = BeanUtil.beanToMap(restSysUser, new HashMap<>(),
                    CopyOptions.create()
                            .setIgnoreNullValue(true)//忽略一些空值
                            .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
            UUID uuid = UUID.randomUUID();
            String tokenKey= String.valueOf(uuid);
            String token="LoginUserKey "+tokenKey;
            //存储
            redisTemplate.opsForHash().putAll(token,userMap);
            //设置存值时间,expire默认秒,改为天数,设置1天
            redisTemplate.expire(token,1, TimeUnit.DAYS);
            return ResultJson.ok(token);
        }else{
            //账号密码不正确
            return ResultJson.failure(ResultCode.LOGIN_ERROR);
        }
    }
}

测试用的权限:

Springboot实现RBAC权限校验_第2张图片

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

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

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

package com.melody.rest.config;

import com.melody.rest.util.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


import javax.annotation.Resource;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    RedisTemplate redisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        //配置登录查看是否有token拦截器
        registry.addInterceptor(new LoginInterceptor(redisTemplate)).addPathPatterns("/testRest/**").order(0);

    }
}
package com.melody.rest.util;


import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSONObject;
import com.melody.rest.domain.RestData;
import com.melody.rest.domain.RestSysUser;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

public class LoginInterceptor implements HandlerInterceptor {

    private RedisTemplate redisTemplate;

    public LoginInterceptor(RedisTemplate redisTemplate){
        this.redisTemplate=redisTemplate;
    }


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //设置编码
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/json;charset=utf-8");

        //1、判断是否携带token
        String token = request.getHeader("authorization");
        if(token==null || "".equals(token)){
            RestData restData = RestData.builder().code("401").msg("请先登录再操作!").build();
            String jsonRestData = JSONObject.toJSONString(restData);
            response.setStatus(401);
            response.getWriter().write(jsonRestData);
            return false;
        }
        Map userMap=redisTemplate.opsForHash().entries(token);
        RestSysUser restSysUser = BeanUtil.fillBeanWithMap(userMap, new RestSysUser(), false);
        //2、判断redis里面是否存在token
        if(userMap.isEmpty()){
            RestData restData = RestData.builder().code("401").msg("请先登录再操作!").build();
            String jsonRestData = JSONObject.toJSONString(restData);
            response.setStatus(401);
            response.getWriter().write(jsonRestData);
            return false;
        }

        return true;
    }
}

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

package com.melody.rest.annotion;

import java.lang.annotation.*;

@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthCheck {

    public String value() default "";

}

在切面中编写通知

package com.melody.rest.aspect;


import cn.hutool.core.bean.BeanUtil;
import com.melody.rest.annotion.AuthCheck;
import com.melody.rest.domain.RestSysUser;
import com.melody.rest.exception.AuthException;
import com.melody.rest.model.ResCode;
import com.melody.rest.model.ResJson;
import com.melody.rest.model.ResultCode;
import com.melody.rest.model.ResultJson;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;


import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;

@Aspect
@Component
public class AuthAspect {

    @Value("${token.header}")
    private String header;

    @Autowired
    private HttpServletRequest request;

    @Resource
    private RedisTemplate redisTemplate;

    @Pointcut("@annotation(com.melody.rest.annotion.AuthCheck)")
    public void authPointCut(){

    }


    @Around("authPointCut()")
    public Object authCheck(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        try{
            // 判断 TOKEN
            String token = request.getHeader(header);
            System.err.println(token);
            Map userMap=redisTemplate.opsForHash().entries(token);
            RestSysUser restSysUser = BeanUtil.fillBeanWithMap(userMap, new RestSysUser(), false);
            if(restSysUser.getUsername() == null || restSysUser.getUsername().equals("")){
                throw new AuthException(ResCode.TOKEN_NOT_EXIST);
            } else {
                if(restSysUser.getResources()==null){
                    throw new AuthException(ResCode.BANED_REQUEST);
                }
                //从切面织入点处通过反射机制获取织入点处的方法
                MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
                //获取切入点所在的方法
                Method method = signature.getMethod();
                AuthCheck ac = method.getAnnotation(AuthCheck.class);
//                System.err.println("=========================="+ac);
                boolean flag = false;
                if(ac != null) {
                    //获取切入点方法的value值,该测试接口设置的value为权限字段
                    String auth = ac.value();
//                    System.err.println("-----------------------------"+auth);
                    flag = restSysUser.getResources().stream().anyMatch(str -> str.equals(auth));
                    // way2:数据库中存放权限字段,根据注解的value确定请求所需权限判断是否有权限进行访问
                }
                if(!flag) {
                    return ResultJson.failure(ResultCode.FORBIDDEN);
                }
            }
        } catch(AuthException e) {
            System.err.println(e.getResCode().getCode() + ":" + e.getResCode().getMsg());
            return ResJson.no(e.getResCode());
        }
//        Object res = proceedingJoinPoint.proceed();
        return proceedingJoinPoint.proceed();
    }

}

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

package com.melody.rest.restcontroller;

import com.melody.rest.annotion.AuthCheck;
import com.melody.rest.model.ResultJson;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;



@RestController
@RequestMapping("/testRest")
public class TestRestController {



    //测试方法1
    @AuthCheck("/testRest/t1")
    @PostMapping("/t1")
    public ResultJson test(){
        return ResultJson.ok("test1访问成功");
    }

    //测试方法2
    @AuthCheck("/testRest/t10")
    @PostMapping("/t10")
    public ResultJson test2(){
        return ResultJson.ok("test10访问");
    }

}

使用postman测试

登录测试

正常登录

Springboot实现RBAC权限校验_第3张图片

 密码或用户名有误

Springboot实现RBAC权限校验_第4张图片

 token拦截测试

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

Springboot实现RBAC权限校验_第5张图片

使用错误token

 Springboot实现RBAC权限校验_第6张图片

 使用正常token但无访问权限

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

Springboot实现RBAC权限校验_第7张图片

你可能感兴趣的:(java,spring,boot,aop,jwt,token)