基于过滤器的JWT Spring Security实现中的Spring Boot 2 - 403而不是401

时间:2018-03-26 17:24:43

标签: spring spring-boot spring-security

我试图使我的Spring Boot 1.5.x REST API项目2.x.x兼容,而不会破坏大量代码。我坚持使用过去基于过滤器的JWT Spring Security实现:

通过" / login"验证凭据。端点,我曾经扩展UsernamePasswordAuthenticationFilter如下:

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    private JWTUtil jwtUtil;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil) {
        this.authenticationManager = authenticationManager;
        this.jwtUtil = jwtUtil;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req,
                                                HttpServletResponse res) throws AuthenticationException {

        try {
            CredenciaisDTO creds = new ObjectMapper().readValue(req.getInputStream(), CredenciaisDTO.class);
            UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(creds.getEmail(), creds.getSenha(), new ArrayList<>());
            Authentication auth = authenticationManager.authenticate(authToken);
            return auth;
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest req,
                                            HttpServletResponse res,
                                            FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {
        String username = ((UserSS) auth.getPrincipal()).getUsername();
        String token = jwtUtil.generateToken(username);
        res.addHeader("Authorization", "Bearer " + token);
        res.addHeader("access-control-expose-headers", "Authorization");
    }
}

另外,为了授权beared-token请求,我曾经扩展BasicAuthenticationFilter如下:

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    private JWTUtil jwtUtil;

    private UserDetailsService userDetailsService;

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil, UserDetailsService userDetailsService) {
        super(authenticationManager);
        this.jwtUtil = jwtUtil;
        this.userDetailsService = userDetailsService;
    }

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

        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            UsernamePasswordAuthenticationToken auth = getAuthentication(header.substring(7));
            if (auth != null) {
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }
        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(String token) {
        if (jwtUtil.isValid(token)) {
            String username = jwtUtil.getUsername(token);
            UserDetails user = userDetailsService.loadUserByUsername(username);
            return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
        }
        return null;
    }
}

一切都按预期工作:

  • 请求/ login使用错误的凭据用于返回带有a的401 standand&#34;未授权/身份验证失败:凭据错误&#34;对象

  • 未经BasicAuthenticationFilter授权的Bearing-token请求 用于返回403标准&#34;禁止/拒绝访问&#34;对象

但是,如果我在Spring Boot 2.0.0项目中使用该代码,则请求/ login返回403并返回空体响应。

This发帖建议在安全配置类的http.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));方法中加入configure。这样我可以获得401 for / login请求,但它仍然返回一个空体响应(而不是那个标准&#34; Unauthorized&#34;错误对象)。此外,现在所有未经BasicAuthenticationFilter授权的请求也返回401,其中包含空体响应(正确的应该返回403,使用该标准&#34;禁止&#34;错误对象在体内)。

我怎样才能恢复以前的理想行为?

1 个答案:

答案 0 :(得分:3)

知道了。 here的答案没有必要。为了解决我的问题,我实现了AuthenticationFailureHandler

public class JWTAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
            throws IOException, ServletException {
        response.setStatus(401);
        response.setContentType("application/json"); 
        response.getWriter().append(json());
    }

    private String json() {
        long date = new Date().getTime();
        return "{\"timestamp\": " + date + ", "
            + "\"status\": 401, "
            + "\"error\": \"Unauthorized\", "
            + "\"message\": \"Authentication failed: bad credentials\", "
            + "\"path\": \"/login\"}";
    }
}

然后将其注入UsernamePasswordAuthenticationFilter

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    (...)

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil) {
        super.setAuthenticationFailureHandler(new JWTAuthenticationFailureHandler());
        (...)