自定义 Spring Security 鉴权

3 minute read

背景

当我们使用 Spring 技术栈搭建单体的系统或服务时,若系统涉及到登录鉴权等功能,一般会使用 SpringSecurity 搭配一个 RBAC 的权限模型,很容易实现一套 OAuth2 鉴权、授权的流程。 随着业务扩展,单体的服务作为一个微服务并入一个大的系统之后。我们为保证其他业务能够调用该单体服务,但是又无法让其他系统来使用该服务已有鉴权,从而引入其他微服务都用基础的鉴权服务。根据业务场景的区分,当单体服务内部使用的时候走 SpringSecurity 的鉴权,单体服务外部调用的时候使用基础鉴权服务鉴权。

基础

要根据不同的场景启用或绕过 SpringSecurity。我们可以首先想到能否通过 URL 来区分不同的场景,使用 HttpSecuritypermitAll()authenticated() 来让不同路径的接口是否需要走鉴权。除此之外,可以去修改 SpringSecurity 的 Filter ,使用自定义 Filter 或者用动态代理对 Filter 进行增强。

实现

 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Aspect
@Component
@Slf4j
public class AuthorizationHeaderAspect {

    /**
     * 基础鉴权服务 Feign 调用业务
     */
    @Autowired
    private OauthFeignBiz oauthFeignBiz;

    @Pointcut("execution(* org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(..))")
    public void securityOauth2DoFilter() {
    }

    @Pointcut("execution(* org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(..))")
    public void securitySecurityInterceptor2DoFilter() {

    }

    @Around("securityOauth2DoFilter()")
    public void enhanceSecurityOauth2DoFilter(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length != 3 || !(args[0] instanceof HttpServletRequest && args[1] instanceof javax.servlet.ServletResponse && args[2] instanceof FilterChain)) {
            joinPoint.proceed();
            return;
        }

        HttpServletRequest request = (HttpServletRequest) args[0];
        String accessToken = request.getHeader("Authorization");

        if (StringUtils.isNotBlank(request.getParameter("sceneId")) && StringUtils.isNotBlank(request.getParameter("sceneType"))) {
            // 这里我们可以根据业务场景确定走哪种鉴权方式
            SecurityUtils.setBaseAuth(true);
        } else {
            SecurityUtils.setBaseAuth(false);
        }

        if (SecurityUtils.isBaseAuth()) {
            Response<CheckTokenResponse> checkTokenResponse = oauthFeignBiz.checkToken(accessToken);
            if (checkTokenResponse.getCode() == 0) {
                SecurityUtils.setBaseAuth((true));
                ((FilterChain) args[2]).doFilter((ServletRequest) args[0], (ServletResponse) args[1]);
            } else {
                throw new Exception("鉴权失败");
            }
        } else {
            // 让原 Filter 的逻辑继续执行
            joinPoint.proceed();
        }
    }

    @Around("securitySecurityInterceptor2DoFilter()")
    public void enhanceSecuritySecurityInterceptor2DoFilter(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length != 3 || !(args[0] instanceof HttpServletRequest && args[1] instanceof javax.servlet.ServletResponse && args[2] instanceof FilterChain)) {
            joinPoint.proceed();
            return;
        }

        if (!SecurityUtils.isBaseOauth()) {
            joinPoint.proceed();
            return;
        }
        ((FilterChain) args[2]).doFilter((ServletRequest) args[0], (ServletResponse) args[1]);
    }

}