前言
一、鉴权的流程
基于sso单点登录,用户登录后,为了保证用户可以访问其他微服务资源。来让一个长时间不过期的token放入到redis中 使用redis的设置过期时间的功能,给成功登录的token 让其一直刷新在redis中的过期时间。实现了只要用户还在操作服务,redis里面的token就一直存在,这样就能实现单点登录,处处使用。
对于鉴权,就是把用户的权限码放到token中 使用aop来比对前端传入的请求的权限码和该用户的权限码是否一样,一样的话就让其访问其具有的功能,没有就返回错误码。其通过自定义注解来让具有该注解的接口并且权限码一直的通过验证,然后如果当前用户没有该权限,抛出一个权限不足的异常,通过全局异常捕获来统一处理权限不足的异常。
二、实现步骤
2.1 权限工具模块
在common下新建一个security模块
引入所需依赖
<dependencies> <!--引入common-core--> <dependency> <groupId>com.xin</groupId> <artifactId>xin-common-core</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <!--依然aop面向切面的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!--servlet 请求依赖--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> </dependency> <!--soring web依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </dependency> </dependencies>
/**
* 有权限的自定义注解
* @author Ren
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HasPermission {
String value() default "";
}
自定义异常类
public class PermissionException extends RuntimeException {
public PermissionException(String msg){
super(msg);
}
public PermissionException() {
}
}
全局异常处理类
@RestControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(value = PermissionException.class)
public Result<String> handle01(PermissionException e){
return new Result<>(4001,e.getMessage());
}
}
aop切面类的实现
@Aspect
@Component
public class PermissionAspect {
/**
* 定义切点方法
*/
@Pointcut(value = "@annotation(com.rcg.security.annotation.HasPermission)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//获取方法对象。
MethodSignature methodSignatur= (MethodSignature) joinPoint.getSignature();
Method method = methodSignatur.getMethod();
//获取方法上HasPermission
HasPermission annotation = method.getAnnotation(HasPermission.class);
if(annotation!=null){
String permission = annotation.value();
//在springmvc任意唯一获取request对象。
HttpServletRequest request = WebUtils.getRequest();
String token = request.getHeader("token");
//解析token
Map<String, Object> map = JwtUtil.getTokenChaim(token);
List<String> authorities = (List<String>) map.get("authorities");
if(!authorities.contains(permission)){
throw new PermissionException("权限不足");
}
}
return joinPoint.proceed();
}
}
http请求工具类
public class WebUtils {
/** 获取request对象 **/
public static HttpServletRequest getRequest(){
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null){
return null;
}
return ((ServletRequestAttributes)requestAttributes).getRequest();
}
/** 获取response对象 **/
public static HttpServletResponse getResponse(){
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null){
return null;
}
return ((ServletRequestAttributes)requestAttributes).getResponse();
}
}
2.2 网关模块的修改
主要添加一个登陆过滤器,来处理token的时间刷新问题
引入依赖
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.xin</groupId> <artifactId>xin-common-core</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies>
配置文件application.yml
server:
port: 8086
spring:
application:
name: cai-gateway
redis:
host: localhost
port: 6379
cloud:
gateway:
routes:
- id: cai-sso
uri: lb://cai-sso
predicates:
- Path=/login
- id: cai-system
uri: lb://cai-system
predicates:
- Path=/system/**
登录过滤器类
@Component
public class LoginFilter implements GlobalFilter, Ordered {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取请求对象和响应对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//2.获取请求路径
String url = request.getPath().toString();
//2.1 判断放行请求为login的
if ("/login".equals(url)){
return chain.filter(exchange);
}
//3.获取请求头
String token = request.getHeaders().getFirst("token");
//判断token 不为空 有效 redis不过期
if (StringUtils.hasText(token)
&& JwtUtil.verifyToken(token)
&&stringRedisTemplate.hasKey(token)){
//刷新token时间 验证通过继续延长token在redis中的过期时间
stringRedisTemplate.expire(token,30, TimeUnit.MINUTES);
//放行
return chain.filter(exchange);
}
//所有验证都失败的情况
Result<String> result=new Result<>(5001,"登录失效");
//3.3作JSON转换
byte[] bytes = JSON.toJSONString(result).getBytes(StandardCharsets.UTF_8);
//3.4调用bufferFactory方法,生成DataBuffer对象
DataBuffer buffer = response.bufferFactory().wrap(bytes);
//4.调用Mono中的just方法,返回要写给前端的JSON数据
return response.writeWith(Mono.just(buffer));
}
/**
* 过滤器的优先级 0是最高级
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
启动类的配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class GateWayApp { public static void main(String[] args) { SpringApplication.run(GateWayApp.class,args); } }
2.3 系统业务模块的修改
引入鉴权工具模块
<dependency> <groupId>com.xin</groupId> <artifactId>xin-common-security</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
在主启动类上添加扫描aop注解的 注解
@ComponentScan(basePackages = {"com.xin.system","com.rcg.security"})
修改返回前端的api
query和insert为当前用户具有的权限 update不是该用户的权限
测试
先登录上获取token 然后通过网关来访问微服务模块测试
成功实现鉴权的功能。
总结
无