微服务认证与授权 Spring Cloud Security:ExceptionTranslationFilter详解
ExceptionTranslationFilter。
ExceptionTranslationFilter
位于安全过滤器调用链的后端,它本身不执行任何实际的安全性处理,但是处理由其他的安全拦截器抛出的异常以及提供适当的HTTP响应。它属于异常处理和转化过滤器。
具体的处理方法如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
chain.doFilter(request, response); }catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
// 封装认证失败异常
RuntimeException ase = (AuthenticationException) throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase == null) {
// 封装鉴权失败异常
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
AccessDeniedException.class, causeChain);
}
if (ase != null) {
// 处理异常
handleSpringSecurityException(request, response, chain, ase);
}else {
// Rethrow ServletExceptions and RuntimeExceptions as-is
if (ex instanceof ServletException) {
throw (ServletException) ex;
}else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
throw new RuntimeException(ex);
}
}
}
ExceptionTranslationFilter
过滤器主要处理AuthenticationException
认证异常以及AccessDeniedException
鉴权访问异常。在对上述两种异常完成类型转换和封装后,将委托handleSpringSecurityException()
对异常进行具体的处理。
handleSpringSecurityException()
的方法实现如下:
private void handleSpringSecurityException(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, RuntimeException exception)
throws IOException, ServletException {
// 处理认证失败异常
if (exception instanceof AuthenticationException) {
// 由AuthenticationEntryPoint处理
sendStartAuthentication(request, response, chain,
(AuthenticationException) exception);
}
// 处理鉴权失败异常
else if (exception instanceof AccessDeniedException) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
// 如果用户未完全登录,由AuthenticationEntryPoint处理
sendStartAuthentication(
request,
response,
chain,
new InsufficientAuthenticationException(
"Full authentication is required to access this resource"));
} else {
// 传递给AccessDeniedHandler进行处理
accessDeniedHandler.handle(request, response,
(AccessDeniedException) exception);
}
}
}
ExceptionTranslationFilter
将AuthenticationException
和AccessDeniedException
分别委托给AuthenticationEntryPoint
和AccessDeniedHandler
进行处理。
AuthenticationEntryPoint
是一个提供认证方案的接口,将未认证的请求重定向不同的认证端点进行认证或者展示异常,仅提供一个接口:
void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException;
AuthenticationEntryPoint
诸多默认实现方式,类图如下,展示了部分的实现类,在实际使用时可以根据自己的需要自由选择实现类:
平常我们如果没有登录访问资源时看到的空荡荡的401错误页面就是来源其中之一的BasicAuthenticationEntryPoint
public class BasicAuthenticationEntryPoint implements AuthenticationEntryPoint,
InitializingBean {
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\"");
// 设置401错误页面
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
authException.getMessage());
}
AccessDeniedHandler
是设计来处理访问被拒绝的异常,提供了唯一的接口:
void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException,
ServletException;
默认实现为AccessDeniedHandlerImpl
,将根据errorPage的配置状态以及状态码决定响应结果
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException,
ServletException {
if (!response.isCommitted()) {
// 如果存在errorPage,重定向到errorPage
if (errorPage != null) {
// Put exception into request scope (perhaps of use to a view)
request.setAttribute(WebAttributes.ACCESS_DENIED_403,
accessDeniedException);
// 设置状态码为403
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
// 重定向到错误页面
RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
dispatcher.forward(request, response);
}else {
// 直接返回403状态码
response.sendError(HttpServletResponse.SC_FORBIDDEN,
accessDeniedException.getMessage());
}
}
}
在错误页面存在的情况下,将会在重定向到错误页面中,否则只会把响应的状态码设置为403。
- 点赞
- 收藏
- 关注作者
评论(0)