【Spring Security】 Oauth2 资源服务器异常处理

Metadata

title: 【Spring Security】 Oauth2 资源服务器异常处理
date: 2023-02-05 10:07
tags:
  - 行动阶段/完成
  - 主题场景/组件
  - 笔记空间/KnowladgeSpace/ProgramSpace/ModuleSpace
  - 细化主题/Module/SpringSecurity
categories:
  - SpringSecurity
keywords:
  - SpringSecurity
description: 【Spring Security】 Oauth2 资源服务器异常处理

【Spring Security】 Oauth2 资源服务器异常处理

表现

首先我们看下可能会发生的几个异常。

未传认证消息头:

传个错误令牌:

源码分析

接下传入一个错误 Token 分析下源码,康康这个异常是怎么处理的。

在之前的文档中,我们分析过,资源服务器是由 OAuth2AuthenticationProcessingFilter 过滤器来完成的,如果发生 OAuth2Exception 异常,也自行进行了处理。

1. 抛出异常

我们知道资源服务器,获取到 Token 以后,会去授权服务器校验令牌,获取信息。调用的是 RemoteTokenServices 的 loadAuthentication 方法。

可以看到这个令牌,调用远程校验的结果失败了,远程校验结果不是 active 时,就会抛出 InvalidTokenException(不可用令牌)异常。

2. 异常捕获

InvalidTokenException(不可用令牌)异常,是在 OAuth2AuthenticationProcessingFilter 过滤器中抛出的,所以会被它 catch 到。

catch (OAuth2Exception failed) {
            SecurityContextHolder.clearContext();

            if (debug) {
                logger.debug("Authentication request failed: " + failed);
            }
            // 发布认证失败事件
            eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
                    new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
            // 调用AuthenticationEntryPoint 处理异常
            authenticationEntryPoint.commence(request, response,
                    new InsufficientAuthenticationException(failed.getMessage(), failed));

            return;
        }

3. AuthenticationEntryPoint

在 catch 中,异常交给了 AuthenticationEntryPoint 处理,这里默认使用的是 OAuth2AuthenticationEntryPoint 对象。

AuthenticationEntryPoint 的 commence 调用的是其抽象类的 doHandle 方法。也会调用翻译器进行处理。然后调用 handleHttpEntityResponse 进行响应,经过序列化,最终返回错误信息给前端。

protected final void doHandle(HttpServletRequest request, HttpServletResponse response, Exception authException)
            throws IOException, ServletException {
        try {
            // 调用异常翻译器
            ResponseEntity<?> result = exceptionTranslator.translate(authException);
            result = enhanceResponse(result, authException);
            // 处理ResponseEntity
            exceptionRenderer.handleHttpEntityResponse(result, new ServletWebRequest(request, response));
            response.flushBuffer();
        }
        catch (ServletException e) {
            if (handlerExceptionResolver.resolveException(request, response, this, e) == null) {
                throw e;
            }
        }
        catch (IOException e) {
            throw e;
        }
        catch (RuntimeException e) {
            throw e;
        }
        catch (Exception e) {
            // Wrap other Exceptions. These are not expected to happen
            throw new RuntimeException(e);
        }
    }

自定义异常

我们知道最终是通过 AuthenticationEntryPoint 处理异常的,那么自定义处理类实现 AuthenticationEntryPoint 接口就行了。

1. 自定义 AuthenticationEntryPoint

@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {


    private ObjectMapper objectMapper = new ObjectMapper();


    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws ServletException {
        try {
            // 1. 检查看服务端是否已将数据输出到客户端,如果已返回,则不处理
            if (response.isCommitted()) {
                return;
            }
            // 2. 封装结果信息,应该使用统一结果集封装,这里使用Map
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json;charset=utf-8");
            Map<String, Object> result = new HashMap<>();
            result.put("code", 401);
            result.put("msg", authException.getMessage());
            result.put("result", "认证失败");
            objectMapper.writeValue(response.getOutputStream(), result);
        } catch (Exception e) {
            throw new ServletException(e);
        }
    }
}

2. ResourceServer 添加配置

@Configuration
@EnableResourceServer//标识为资源服务
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class MyResourceServerConfig extends ResourceServerConfigurerAdapter {
    public MyResourceServerConfig() {
        super();
    }


    @Autowired
    MyAuthenticationEntryPoint myAuthenticationEntryPoint;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.authenticationEntryPoint(myAuthenticationEntryPoint);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        super.configure(http);
    }



    // 密码解析器
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

3. 测试