个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang -
Overview
简介
在Sa-Token
最新的v1.39.0
版本的更新日志中有这么一句话
核心:
- 升级:重构注解鉴权底层,支持自定义鉴权注解了。
[重要]
正巧最近有看一个关于鉴权的东西,顺便看一下吧!
常见的自定义注解鉴权
目标:对于后端开放的api
进行鉴权。
1、自定义注解
1 2 3 4 5 6 7 8 9 10 11
| @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ApiAccess {
String transCode(); }
|
2、定义拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Slf4j @Component public class ApiAccessInterceptor implements HandlerInterceptor {
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("ApiAccessInterceptor preHandle"); if (handler instanceof HandlerMethod) { Method method = ((HandlerMethod) handler).getMethod(); if (method.isAnnotationPresent(ApiAccess.class)) { ApiAccess apiAccess = method.getAnnotation(ApiAccess.class); String transCode = apiAccess.transCode(); String transCodeHeader = request.getHeader("transCode"); String token = request.getHeader("token"); if (!StringUtils.hasText(transCodeHeader) || !StringUtils.hasText(token)) { throw new RuntimeException("transCode or token is empty"); } log.info("transCode: {}, transCodeHeader:{}, token:{}", transCode, transCodeHeader, token); if (!transCode.equals(transCodeHeader)) { throw new RuntimeException("transCode not match"); }
ApiAccessUtil.valid(transCode, token); } } return true; } }
|
下面是辅助验证的方法,真实生产上应该是查数据库或其他。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class ApiAccessUtil {
public static final List<ApiAccessPO> API_ACCESS_LIST = new ArrayList<>();
static { API_ACCESS_LIST.add(new ApiAccessPO("wnhyang01", "123456")); API_ACCESS_LIST.add(new ApiAccessPO("wnhyang02", "234567")); API_ACCESS_LIST.add(new ApiAccessPO("wnhyang03", "888888")); API_ACCESS_LIST.add(new ApiAccessPO("wnhyang04", "666666")); }
public static void valid(String transCode, String token) { for (ApiAccessPO apiAccessPO : API_ACCESS_LIST) { if (apiAccessPO.getTransCode().equals(transCode) && apiAccessPO.getToken().equals(token)) { return; } } throw new RuntimeException("invalid access"); }
@Data @NoArgsConstructor @AllArgsConstructor private static class ApiAccessPO {
private String transCode;
private String token; } }
|
3、配置拦截器
1 2 3 4 5 6 7 8 9 10 11 12
| @Slf4j @Configuration @RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer {
private final ApiAccessInterceptor apiAccessInterceptor;
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(apiAccessInterceptor); } }
|
4、验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Slf4j @RestController @RequestMapping("/api") public class ApiAccessController {
@GetMapping("/wnhyang01") @ApiAccess(transCode = "wnhyang01") public String wnhyang01() { return "wnhyang01"; }
@GetMapping("/wnhyang02") @ApiAccess(transCode = "wnhyang02") public String wnhyang02() { return "wnhyang02"; }
@GetMapping("/wnhyang03") @ApiAccess(transCode = "wnhyang03") public String wnhyang03() { return "wnhyang03"; }
@GetMapping("/wnhyang04") @ApiAccess(transCode = "wnhyang04") public String wnhyang04() { return "wnhyang04"; } }
|
测试可知,只有Header
的transCode
和token
都不为空,并且Header
的transCode
与接口配置的唯一transCode
一致前提下,transCode
和token
都能通过验证才能通过拦截器。
使用新版Sa-Token完成
自定义注解
1、自定义注解
与前面一致就行
2、创建注解处理器
实现SaAnnotationHandlerInterface
接口的两个抽象方法就好,checkMethod
放鉴权逻辑,与前面的拦截器方法一致就好,将拦截器里使用的request
替换为SaHolder.getRequest()
(关于这个自己可以看官网文档或者源码都可,我之前介绍Sa-Token
组件时也有提过)。
注意使用@Component
将类注册为IOCBean
就省去了手动注册了。
1
| SaAnnotationStrategy.instance.registerAnnotationHandler(new CheckAccountHandler());
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Slf4j @Component public class ApiAccessHandler implements SaAnnotationHandlerInterface<ApiAccess> {
@Override public Class<ApiAccess> getHandlerAnnotationClass() { return ApiAccess.class; }
@Override public void checkMethod(ApiAccess apiAccess, Method method) { log.info("checkMethod"); String transCode = apiAccess.transCode(); String transCodeHeader = SaHolder.getRequest().getHeader("transCode"); String token = SaHolder.getRequest().getHeader("token"); if (!StringUtils.hasText(transCodeHeader) || !StringUtils.hasText(token)) { throw new RuntimeException("transCode or token is empty"); } log.info("transCode: {}, transCodeHeader:{}, token:{}", transCode, transCodeHeader, token); if (!transCode.equals(transCodeHeader)) { throw new RuntimeException("transCode not match"); }
ApiAccessUtil.valid(transCode, token); } }
|
3、配置拦截器
与前面一样,删除自己的拦截器配置,加上SaInterceptor
就好
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Slf4j @Configuration @RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer {
private final ApiAccessInterceptor apiAccessInterceptor;
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SaInterceptor()); } }
|
4、验证
试过你就话发现,前后效果一样。
怎么做到的?
对比差异
通过Idea
选择不同版本的提交记录对比,因为我使用的是Jdk17
+boot3
所以看的是图下的差异,其他类同的。
可以看到,SaInterceptor
鉴权逻辑从左边一坨变为了右边一行。
从sa-token-core
的对比来看,原来的SaStrategy
删除了所有相关于鉴权的方法,与此对应新增了SaAnnotationStrategy
类。
SaAnnotationStrategy
默认注册了原来的所有鉴权处理器。
SaInterceptor
鉴权使用的就是SaAnnotationStrategy
的checkMethodAnnotation
方法,其就是将所有注册的注解处理器跑一遍。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
@SuppressWarnings("unchecked") public SaCheckMethodAnnotationFunction checkMethodAnnotation = (method) -> { for (Map.Entry<Class<?>, SaAnnotationHandlerInterface<?>> entry: annotationHandlerMap.entrySet()) {
Annotation classTakeAnnotation = instance.getAnnotation.apply(method.getDeclaringClass(), (Class<Annotation>)entry.getKey()); if(classTakeAnnotation != null) { entry.getValue().check(classTakeAnnotation, method); }
Annotation methodTakeAnnotation = instance.getAnnotation.apply(method, (Class<Annotation>)entry.getKey()); if(methodTakeAnnotation != null) { entry.getValue().check(methodTakeAnnotation, method); } } };
|
默认的所有注解处理器可以看sa-token-core
的cn.dev33.satoken.annotation.handler
包。
可以知道其实就是将原来的SaStrategy
的鉴权方法抽象到SaAnnotationHandlerInterface
,并提供鉴权策略类和注解处理器集合。
另外记得前面说的如果自定义实现了SaAnnotationHandlerInterface
并且加入了Spring Ioc
容器,就不用手动注册了吗?其实这个并不必多说,想想就知道是怎么实现的。
还是废话一下吧。
core
里的默认注解处理器并不需要加入Spring Ioc
,因为默认的初始化方法已经讲这些注册了,而用户如果自己扩展了注解处理器,就会被autoconfig
的SaBeaInject
找到并完成注册。
小结
作为源码的主导者其实应该更加清楚,项目的不足,主导项目未来的规划发展,不仅仅是对于使用者提出的issues
,更加应该是项目开发者的初心。
最后一句话:停在港口的船最安全,但这不是造船的意义!
写在最后
拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。
个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang -
Overview