微服务认证与授权 Spring Cloud Security:ExceptionTranslationFilter详解

举报
chongz-z 发表于 2021/01/16 17:40:38 2021/01/16
【摘要】 在前面一篇文章介绍了Spring Cloud Security 中的重要过滤器FilterSecurityInterceptor,今天接着介绍spring-security中的核心过滤器:ExceptionTranslationFilter。ExceptionTranslationFilter位于安全过滤器调用链的后端,它本身不执行任何实际的安全性处理,但是处理由其他的安全拦截器抛出的异常以...

在前面一篇文章介绍了Spring Cloud Security 中的重要过滤器FilterSecurityInterceptor,今天接着介绍spring-security中的核心过滤器: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.classcauseChain);
  if (ase == null) {
   // 封装鉴权失败异常
   ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
     AccessDeniedException.classcauseChain);
  }
  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);
  }
 }
}

ExceptionTranslationFilterAuthenticationExceptionAccessDeniedException分别委托给AuthenticationEntryPointAccessDeniedHandler进行处理。

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。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。