如何从 JWT 身份验证过滤器中抛出自定义异常

Posted

技术标签:

【中文标题】如何从 JWT 身份验证过滤器中抛出自定义异常【英文标题】:How to throw Custom Exception from JWT Authentication Filter 【发布时间】:2021-03-12 04:15:45 【问题描述】:

我在 Project 中使用带有 JWT 和 Spring Security 的 Spring Boot。 春季启动:2.3.5.RELEASE JJWT 版本:0.11.2

我已经创建了用于中断请求的 JWT 身份验证过滤器类。如果请求在标头中包含 JWT 令牌,则解析令牌、获取角色并在 spring 安全上下文中设置身份验证对象。我的身份验证过滤器类如下

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import io.jsonwebtoken.Claims;

public class JwtAuthenticationFilter extends OncePerRequestFilter 

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException 

        String header = request.getHeader(JwtConstant.AUTHORIZATION);

        if (StringUtils.isNotBlank(header) && header.startsWith(JwtConstant.BEARER)) 

            String authToken = header.replace(JwtConstant.BEARER, "");
            Claims claims = jwtTokenUtil.getJwtClaims(authToken);

            String username = claims.getSubject();

            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,
                    "", getAuthoritiesFromString(claims));

            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

            logger.info("authenticated user " + username + ", setting security context");
            SecurityContextHolder.getContext().setAuthentication(authentication);

        

        filterChain.doFilter(request, response);
    

    private Collection<? extends GrantedAuthority> getAuthoritiesFromString(Claims claims) 

        return Arrays.stream(claims.get(JwtConstant.AUTHORITIES).toString().split(",")).map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    


令牌解析方法如下:

public Claims getJwtClaims(String token) 

        Claims claims = null;

        try 
            claims = Jwts.parserBuilder().setSigningKey(getPublicKey()).build().parseClaimsJws(token).getBody();
         catch (ExpiredJwtException e) 
            throw new CustomException(env.getProperty(ExceptionMessage.TOKEN_EXPIRED),e, ErrorCode.TOKEN_EXPIRE);
         catch (SignatureException | MalformedJwtException e) 
            throw new CustomException(env.getProperty(ExceptionMessage.TOKEN_INVALID),e, ErrorCode.TOKEN_INVALID);
         catch (Exception e) 
            throw new CustomException(env.getProperty(ExceptionMessage.TOKEN_PARSING),e, ErrorCode.INTERNAL_SERVER_ERROR);
        

        return claims;
     

如果 oken 无效或过期,则在解析令牌时,我将抛出具有 Integer errorCode 的自定义异常。

当请求包含过期或无效令牌时,首先它会进入 jwt 身份验证过滤器类。在解析令牌时,我得到以下响应:


    "timestamp": "2020-11-30T08:28:40.319+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "message": "Token Expired",
    "path": "/api/users/profile"

因为这个异常是从 servlet 过滤器抛出的,它不会去 @RestControllerAdvise 和它抛出的内置异常响应。在这种情况下,我想用错误代码抛出我的自定义异常类。我尝试了不同的解决方案,但无法解决我的问题。有没有办法从这种情况下抛出自定义错误代码。

我想要下面的异常响应,这是我的自定义响应类:


    "message": "Token Expired",
    "code": 101,
    "error": true,
    "timestamp": "2020-11-30T08:31:46.911+00:00"

【问题讨论】:

【参考方案1】:

"/error" 创建一个 customerErrorController,例如:

@RestController
public class CustomErrorController implements ErrorController 
    
    private static final String PATH = "/error";
    
    @RequestMapping(PATH)
    public ResponseEntity<ErrorDetails> handleError(final HttpServletRequest request,
            final HttpServletResponse response) throws Throwable 
        throw (Throwable) request.getAttribute("javax.servlet.error.exception");
    
    
    @Override
    public String getErrorPath() 
        return PATH;
    

然后@RestControllerAdvise 将能够选择异常。

【讨论】:

它的工作。但是在这种情况下如何处理身份验证和 InsufficientAuthenticationException 异常?我正在为此使用身份验证入口点。 过滤器中抛出的异常将转到“/error”端点。如果在@RestControllerAdvise 中处理了该异常,并且像上面的示例一样覆盖了“/error” [可能需要从 HttpServletRequest 获取一些其他属性],那么将自动处理自定义异常。

以上是关于如何从 JWT 身份验证过滤器中抛出自定义异常的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET Core 2.1 Jwt 设置自定义声明

无法为 JWT CustomClaimVerifier 抛出自定义异常消息

如何在没有身份验证的情况下创建 JWT 令牌

在 Spring Boot 2 上实现基于过滤器的 JWT 身份验证与 OAuth2 JWT 身份验证

在多层spring项目中抛出自定义异常

捕获 AuthenticationProvider 中抛出的异常