SpringBoot项目自定义注解实现RBAC权限校验
SpringBoot项目可以集成Spring Security做权限校验框架,然后在Controller接口上直接使用@PreAuthorize注解来校验权限,那么我们能够自制简易的权限呢,通过看该博客能够自己自制一套简易的权限管理模式。...
SpringBoot项目自定义注解实现RBAC权限校验
文章目录
之前的博客介绍了RBAC的原理,
现在我们来介绍springboot如何基于RBAC自制简易的权限验证
1、前言
学过Spring Security的小伙伴都知道,SpringBoot项目可以集成Spring Security做权限校验框架,然后在Controller接口上直接使用@PreAuthorize注解来校验权限,但是如果我不想引入像Security、Shiro等第三方框架,也要实现权限校验的效果,该怎么做呢?
接下来就给大家介绍一种方案:拦截器+自定义注解做基于RBAC模型的权限校验
2、实现思路
这里做了一些简单的修改,
比如redis存入的key是token,value是对应的user对象
在登录成功之后,user对象里面有对应的list权限资源,从数据库中获取
这里没有对应的权限资源表,所以使用aop切面注解进行权限比较的时候,使用固定的list比较
正常应该通过账号,查询角色,通过角色查询对应的资源信息
3、代码实现
3.1、导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjtools -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.17</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.17</version>
</dependency>
3.2、登录认证
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 io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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(value = "/login")
public ResultJson index(@RequestBody RestSysUser restSysUser){
//登录以及登录成功存入token
return restAuthService.Login(restSysUser);
}
}
service层实现
@Override
public ResultJson Login(RestSysUser restSysUser) {
//账号密码校验,
if("admin".equals(restSysUser.getUsername()) && "123456".equals(restSysUser.getPassword())){
//账号密码正确
//登录成功
restSysUser.setResources(ResourceVerification.resource());
Map<String, Object> 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,60*60*24, TimeUnit.MINUTES);
return ResultJson.ok(token);
}else{
//账号密码不正确
return ResultJson.failure(ResultCode.LOGIN_ERROR);
}
}
其中 restSysUser.setResources(ResourceVerification.resource());
获取用户的权限资源:这里是写死了资源,正常是要从数据库获取
package com.melody.rest.util;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class ResourceVerification {
//权限比较
private List<String> resources = new ArrayList<>();
public Boolean compareResource(String temp){
initResources();
if (resources.contains(temp)){
return true;
}
return false;
}
public void initResources(){
//模拟用户权限
resources.add("/rest/test1");
resources.add("/rest/test2");
resources.add("/rest/test3");
resources.add("/rest/test4");
}
//模拟实现从数据库获取资源
public static List<String> resource(){
List<String> resourcesUser = new ArrayList<>();
resourcesUser.add("/testRest/t1");
resourcesUser.add("/testRest/t2");
resourcesUser.add("/testRest/t3");
resourcesUser.add("/testRest/t4");
return resourcesUser;
}
//是否包含该权限
public Boolean compareResourceRest(List<String> resources,String auth){
if (auth.length()!=0) {
if(resources.contains(auth)){
return true;
}
}
return false;
}
}
3.3、配置拦截器
这个拦截器拦截所有的请求,是查看有没有token以及该token绑定的用户账号是否正常,如果没有token则直接提示没有登录禁止访问。
MvcConfig(拦截器配置)
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);
}
}
LoginInterceptor
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");
System.out.println(token);
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<String, Object> 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;
}
//3、判断账号情况
if(restSysUser.getUsername()!=null){
//获取数据库账号情况
//比对,如果账号异常,则不能访问
//return false;
}
return true;
}
}
4、用自定义注解以及切面判断该用户有没有该方法的访问权限
下面,来介绍如何通过aop切面的方式来解决是否含有这个权限
其中aop依赖
<!-- aop相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
4.1、自定义注解
4.1、配置自定义注解接口
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 "";
}
- @Target,说明了Annotation所修饰的对象范围
- @Retention,定义了该Annotation生命周期(编译/运行)
- @Documented,是一个标记注解,没有成员
- @Inherited,阐述了某个被标注的类型是被继承的。
4.2、Aop切面:方法配置
@Aspect
表示这是一个切面类
@Around("@annotation(com.hmdp.annotation.MyPermission)")
里面表示在注解处环绕通知。
@Slf4j
是Lombok的关于slfj的简略写法。
方法配置
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}")
//@Value("authorization")
private String header;
@Autowired
private HttpServletRequest request;
@Resource
private RedisTemplate redisTemplate;
/**
* 目标方法
*/
@Pointcut("@annotation(com.melody.rest.annotion.AuthCheck)")
public void authPointCut(){
}
/**
* 目标方法调用之前执行
*/
@Before("authPointCut()")
public void doBefore() {
System.out.println("================== step 2: before ==================");
}
/**
* 目标方法调用之后执行
*/
@After("authPointCut()")
public void doAfter() {
System.out.println("================== step 4: after ==================");
}
/**
* 环绕
* 会将目标方法封装起来
* 具体验证业务数据
*/
@Around("authPointCut()")
public Object authCheck(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
try{
// 判断 TOKEN
String token = request.getHeader(header);
Map<String, Object> 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);
boolean flag = false;
if(ac != null) {
String auth = ac.value();
flag = restSysUser.getResources().stream().anyMatch(str -> str.equals(auth));
// way2:数据库中存放权限字段,根据注解的value确定请求所需权限判断是否有权限进行访问
}
if(!flag) {
//throw new AuthException(ResCode.BANED_REQUEST);
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 res;
}
}
4.3、测试controller类的编写
package com.melody.rest.restcontroller;
import com.melody.rest.annotion.AuthCheck;
import com.melody.rest.model.ResultJson;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Api(value = "测试",tags = {"测试"})
@RequestMapping("/testRest")
public class TestRestController {
//测试方法1
@AuthCheck("/testRest/t1")
@ApiOperation(value = "t1测试方法")
@PostMapping(value = "/t1")
public ResultJson test(){
return ResultJson.ok("test1访问成功");
}
//测试方法2
@AuthCheck("/testRest/t10")
@ApiOperation(value = "t10测试方法")
@PostMapping(value = "/t10")
public ResultJson test2(){
return ResultJson.ok("test10访问成功");
}
}
5、测试开始
从图中可以看出,我们模拟用户所拥有的权限是testRest/test1
用postman发起请求:
(1)一开始访问没有token,显示未登录
(2)登录获取token
(3)t1方法访问成功
(4)t10方法没有权限,不能访问
更多推荐
所有评论(0)