在spring安全过滤器中返回自定义错误

时间:2017-06-05 15:41:02

标签: java spring spring-security

我正在开发一个Spring Boot&使用JSON Web令牌的Spring Security应用程序。

我有一个spring安全过滤器,用于检查是否存在现有JWT,如果存在,则注入UsernamePasswordAuthenticationToken:

public class AuthenticationTokenFilter extends UsernamePasswordAuthenticationFilter {

    @Value("${api.token.header}")
    String tokenHeader;

    @Autowired
    TokenUtility tokenUtility;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;

        String incomingToken = httpRequest.getHeader(tokenHeader);

        if (SecurityContextHolder.getContext().getAuthentication() == null && incomingToken != null) {

            UserDetails userDetails = null;

            try {

                userDetails = tokenUtility.validateToken(incomingToken);

            } catch (TokenExpiredException e) {

                throw new ServletException("Token has expired", e);
            }

            if (userDetails != null) {

                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

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

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }
}

此过滤器注入如下:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    EntryPointUnauthorizedHandler unauthorizedHandler;

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {

        authenticationManagerBuilder
                            .userDetailsService(userDetailsService)
                            .passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManager() throws Exception {

        return super.authenticationManager();
    }

    @Bean
    public AuthenticationTokenFilter authenticationTokenFilter() throws Exception {

        AuthenticationTokenFilter authenticationTokenFilter = new AuthenticationTokenFilter();
        authenticationTokenFilter.setAuthenticationManager(authenticationManager());

        return authenticationTokenFilter;
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
            .csrf()
                .disable()
            .exceptionHandling()
                .authenticationEntryPoint(unauthorizedHandler)
                .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
            .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .antMatchers("/auth/**").permitAll()
                .anyRequest().authenticated();

        // filter injected here
        httpSecurity.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

如果用户传入已过期的令牌,则会收到以下错误:

{
    "timestamp":1496424964894,
    "status":500,
    "error":"Internal Server Error",
    "exception":"com.app.exceptions.TokenExpiredException",
    "message":"javax.servlet.ServletException: Token has expired",
    "path":"/orders"
}

我知道Spring安全性会在请求进入控制器层之前拦截请求,因此我无法使用现有的@ControllerAdvice来处理这些异常。

我的问题是,如何自定义此处返回的错误消息/对象?在其他地方,我使用JSON序列化的POJO来返回错误消息,我希望保持一致。我也不希望用户看到javax.servlet.ServletException

2 个答案:

答案 0 :(得分:1)

当你使用.exceptionHandling()时,我相信你可以配置一个新的ExceptionHandler;

另一种方法是覆盖您希望与之不同的邮件,例如post

答案 1 :(得分:-1)

首先,修改JWTTokenProvider类,以使用setAttribute()方法将自定义标头添加到Http Servlet请求。

   public boolean validateToken(String token,HttpServletRequest httpServletRequest){
    try {
        Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
        return true;
    }catch (SignatureException ex){
        System.out.println("Invalid JWT Signature");
    }catch (MalformedJwtException ex){
        System.out.println("Invalid JWT token");
    }catch (ExpiredJwtException ex){
        System.out.println("Expired JWT token");
        httpServletRequest.setAttribute("expired",ex.getMessage());
    }catch (UnsupportedJwtException ex){
        System.out.println("Unsupported JWT exception");
    }catch (IllegalArgumentException ex){
        System.out.println("Jwt claims string is empty");
    }
    return false;

}

然后修改JwtAuthenticationEntryPoint类中的begin方法,以检查上面添加的http servlet请求标头中的过期标头。

@Override
public void commence(HttpServletRequest httpServletRequest,
                     HttpServletResponse httpServletResponse,
                     AuthenticationException e) throws IOException, ServletException {

    final String expired = (String) httpServletRequest.getAttribute("expired");
    System.out.println(expired);
    if (expired!=null){
        httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,expired);
    }else{
        httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Invalid Login details");
    }

}

有关更多详细信息,请参见此Post。一个很好的简单解决方案。