【Spring Security】 Oauth2 资源服务器异常处理
【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. 测试
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 蝶梦庄生!
评论