当HttpServletResponse已提交时,将调用Spring Security Access Denied Handler

时间:2017-09-28 12:54:23

标签: spring-security

我有一个HTTP API,受Spring Security和JWT保护 当我试图访问受保护资源时,我得到401 如果我经过身份验证(JWT有效)并且我有正确的角色,我会获得资源。资源受@PreAuthorize("hasRole('USER')")保护。

我遇到的问题是,当我没有正确的角色时,我想返回403(在下面的代码中,为了测试,它是401)。 但是知道我得到500是因为当角色不正确时抛出了AccessDeniedException 奇怪的是,它转到我的JwtAccessDeniedHandler自定义代码但响应已经提交(isCommitted()== true)所以每当我尝试设置状态等时它什么也不做。
您对可能配置错误或丢失的内容有什么想法吗?

配置:

@Slf4j
@EnableWebSecurity
@EnableGlobalMethodSecurity(
        prePostEnabled = true,
        securedEnabled = true,
        jsr250Enabled = true
)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(
                        jwtAuthenticationFilter(joseHelper(jsonWebKey())),
                        UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                .antMatchers("/auth/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .csrf().disable()
                .exceptionHandling()
                .authenticationEntryPoint(new JwtAuthenticationEntryPoint())
                .accessDeniedHandler(new JwtAccessDeniedHandler());
    }

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter(JoseHelper joseHelper) {
        return new JwtAuthenticationFilter(joseHelper);
    }

    @Bean
    public JoseHelper joseHelper(PublicJsonWebKey key) {
        return new JoseHelper(key);
    }

    @Bean
    public PublicJsonWebKey jsonWebKey() throws IOException, JoseException {
        return RsaJwkGenerator.generateJwk(2048);
    }

    private void sendUnauthorized(HttpServletResponse httpServletResponse) throws IOException {
        httpServletResponse.setContentType("application/json");
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        ApiError apiError = ApiError.builder()
                .code(HttpStatus.UNAUTHORIZED.name())
                .message(HttpStatus.UNAUTHORIZED.getReasonPhrase())
                .httpStatus(HttpStatus.UNAUTHORIZED)
                .build();
        httpServletResponse.getWriter().print(objectMapper.writeValueAsString(apiError));
    }

    private class JwtAccessDeniedHandler implements AccessDeniedHandler {
        @Override
        public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
            log.info("accessDeniedHandler", e);
            sendUnauthorized(httpServletResponse);
        }
    }

    private class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest httpServletRequest,
                             HttpServletResponse httpServletResponse,
                             AuthenticationException e) throws IOException, ServletException {
            sendUnauthorized(httpServletResponse);
        }
    }
}

过滤器:

@Slf4j
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private static final String BEARER = "Bearer ";

    private JoseHelper joseHelper;

    @Autowired
    public JwtAuthenticationFilter(JoseHelper joseHelper) {
        this.joseHelper = joseHelper;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        String header = httpServletRequest.getHeader("Authorization");

        if (header == null || !header.startsWith(BEARER)) {
            log.error("JWT token is not valid");
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        final String encryptedToken = header.substring(BEARER.length());

        try {
            final String decryptedJwt = joseHelper.decryptJwt(encryptedToken);
            final String verifiedJwt = joseHelper.verifyJwt(decryptedJwt);
            final JwtClaims jwtClaims = joseHelper.parse(verifiedJwt);

            List<SimpleGrantedAuthority> authorities = jwtClaims.getStringListClaimValue("userRoles")
                    .stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());

            JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwtClaims, null, authorities);
            SecurityContextHolder.getContext().setAuthentication(jwtAuthenticationToken);
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        } catch (JoseException | InvalidJwtException | MalformedClaimException e) {
            log.error("JWT token is not valid", e);
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        }
    }

}

1 个答案:

答案 0 :(得分:2)

问题是因为我显然使用了泽西岛。我现在没有时间调查原因 一旦我在JerseyConfig中注册了异常映射器,我就能够正确捕获和处理AccessDeniedException。
从那时起,访问被拒绝处理程序不再被调用,变得无用 有点奇怪,但可能有一个很好的理由。