1、简介
本文章讲解的是基于RBAC模型的数据库设计的拦截器+自定义注解的形式实现权限验证
RBAC的数据库设计看:关于使用RBAC模型,实现用户权限管理_&波吉&的博客-CSDN博客
2、导入依赖
<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>
登入的controller层
@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层(主要的作用是获取用户相应的token,并存入redis)
@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);
}
}
拦截器的配置(拦截了所有的请求并,验证是否存在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");
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;
}
}
接下来,就是该用户是否有权限访问
自定义注解的形式
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}")
//@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;
}
}