个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview


Sa-Token的v1.39.0自定义鉴权注解怎么玩_鉴权

简介

Sa-Token最新的v1.39.0版本的更新日志中有这么一句话

核心:

  • 升级:重构注解鉴权底层,支持自定义鉴权注解了。 [重要]

正巧最近有看一个关于鉴权的东西,顺便看一下吧!

常见的自定义注解鉴权

目标:对于后端开放的api进行鉴权。

1、自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ApiAccess {

    /**
     * 交易码
     *
     * @return 交易码
     */
    String transCode();
}

2、定义拦截器

@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;
    }
}

下面是辅助验证的方法,真实生产上应该是查数据库或其他。

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、配置拦截器

@Slf4j
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

    private final ApiAccessInterceptor apiAccessInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(apiAccessInterceptor);
    }
}

4、验证

@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";
    }
}

测试可知,只有HeadertransCodetoken都不为空,并且HeadertransCode与接口配置的唯一transCode一致前提下,transCodetoken都能通过验证才能通过拦截器。

Sa-Token的v1.39.0自定义鉴权注解怎么玩_鉴权_02

使用新版Sa-Token完成

自定义注解

1、自定义注解

与前面一致就行

2、创建注解处理器

实现SaAnnotationHandlerInterface接口的两个抽象方法就好,checkMethod放鉴权逻辑,与前面的拦截器一致就好,将拦截器里使用的request替换为SaHolder.getRequest()(关于这个自己可以看官网文档或者源码都可,我之前介绍Sa-Token组件时也有提过)。

注意使用@Component将类注册为IOCBean就省去了手动注册了。

SaAnnotationStrategy.instance.registerAnnotationHandler(new CheckAccountHandler());
@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就好

@Slf4j
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

    private final ApiAccessInterceptor apiAccessInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 删除 registry.addInterceptor(apiAccessInterceptor);
        registry.addInterceptor(new SaInterceptor());
    }
}

4、验证

试过你就话发现,前后效果一样。

怎么做到的?

对比差异

通过Idea选择不同版本的提交记录对比,因为我使用的是Jdk17+boot3所以看的是图下的差异,其他类同的。

Sa-Token的v1.39.0自定义鉴权注解怎么玩_鉴权_03

可以看到,SaInterceptor鉴权逻辑从左边一坨变为了右边一行。

Sa-Token的v1.39.0自定义鉴权注解怎么玩_鉴权_04

sa-token-core的对比来看,原来的SaStrategy删除了所有相关于鉴权的方法,与此对应新增了SaAnnotationStrategy类。

Sa-Token的v1.39.0自定义鉴权注解怎么玩_鉴权_05

SaAnnotationStrategy默认注册了原来的所有鉴权处理器。

Sa-Token的v1.39.0自定义鉴权注解怎么玩_鉴权_06

SaInterceptor鉴权使用的就是SaAnnotationStrategycheckMethodAnnotation方法,其就是将所有注册的注解处理器跑一遍。

/**
 * 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
 */
@SuppressWarnings("unchecked")
public SaCheckMethodAnnotationFunction checkMethodAnnotation = (method) -> {
    // 遍历所有的注解处理器,检查此 method 是否具有这些指定的注解
    for (Map.Entry<Class<?>, SaAnnotationHandlerInterface<?>> entry: annotationHandlerMap.entrySet()) {

        // 先校验 Method 所属 Class 上的注解
        Annotation classTakeAnnotation = instance.getAnnotation.apply(method.getDeclaringClass(), (Class<Annotation>)entry.getKey());
        if(classTakeAnnotation != null) {
            entry.getValue().check(classTakeAnnotation, method);
        }

        // 再校验 Method 上的注解
        Annotation methodTakeAnnotation = instance.getAnnotation.apply(method, (Class<Annotation>)entry.getKey());
        if(methodTakeAnnotation != null) {
            entry.getValue().check(methodTakeAnnotation, method);
        }
    }
};

默认的所有注解处理器可以看sa-token-corecn.dev33.satoken.annotation.handler包。

可以知道其实就是将原来的SaStrategy的鉴权方法抽象到SaAnnotationHandlerInterface,并提供鉴权策略类和注解处理器集合。

Sa-Token的v1.39.0自定义鉴权注解怎么玩_拦截器_07

另外记得前面说的如果自定义实现了SaAnnotationHandlerInterface并且加入了Spring Ioc容器,就不用手动注册了吗?其实这个并不必多说,想想就知道是怎么实现的。

还是废话一下吧。

core里的默认注解处理器并不需要加入Spring Ioc,因为默认的初始化方法已经讲这些注册了,而用户如果自己扩展了注解处理器,就会被autoconfigSaBeaInject找到并完成注册。

小结

作为源码的主导者其实应该更加清楚,项目的不足,主导项目未来的规划发展,不仅仅是对于使用者提出的issues,更加应该是项目开发者的初心。

最后一句话:停在港口的船最安全,但这不是造船的意义!

写在最后

拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。


个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview

Sa-Token的v1.39.0自定义鉴权注解怎么玩_API_08