目录
RBAC思想
实现方式
一图流实现思路
代码实现
导入相关依赖
实现登录与用户的token携带
编写登录controller接口
编写登录service业务
实现登录业务
实现登录后操作的权限验证
实现token拦截器,对所有操作进行身份验证
自定义注解,作为权限验证的切入点
在切面中编写通知
编写测试接口,测试登录后的用户操作
使用postman测试
登录测试
正常登录
密码或用户名有误
token拦截测试
使用后正常登录后获取的token
使用错误token
使用正常token但无访问权限
RBAC基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。这样做的好处是,不必在每次创建用户时都进行分配权限的操作,只要分配用户相应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限管理,减少系统的开销。
Springboot+AOP切面+自定义注解+redis+jwt+mybatis+token拦截器
注:为了简化操作和流程,没有真正地从mysql中读取数据,而是以写死的数据进行演示
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
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);
}
}
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);
}
}
}
测试用的权限:
拦截器不受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访问");
}
}
在写死的权限中只有test1、2、3的权限,而方法二权限为test10