【问题标题】:Spring security - creating 403 Access denied custom responseSpring security - 创建 403 Access denied 自定义响应
【发布时间】:2018-06-26 15:22:28
【问题描述】:

我有一个带有 jwt 身份验证的 spring boot rest api。问题是我无法摆脱默认的 403 Access Denied 休息响应,如下所示:

{
    "timestamp": 1516206966541,
    "status": 403,
    "error": "Forbidden",
    "message": "Access Denied",
    "path": "/api/items/2"
}

我创建了自定义 AccessDeniedHandler:

public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest req,
                       HttpServletResponse res,
                       AccessDeniedException accessDeniedException) throws IOException, ServletException {



        ObjectMapper mapper = new ObjectMapper();
        res.setContentType("application/json;charset=UTF-8");
        res.setStatus(403);
        res.getWriter().write(mapper.writeValueAsString(new JsonResponse()
                .add("timestamp", System.currentTimeMillis())
                .add("status", 403)
                .add("message", "Access denied")));
    }
}

并将其添加到 WebConfig 类中

@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    private UserDetailsService userDetailsService;
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    public WebSecurity(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.NEVER)
                .and()
                    .csrf().disable()
                    .authorizeRequests()
                    .antMatchers(HttpMethod.POST, REGISTER_URL).permitAll()
                    .anyRequest().authenticated()
                .and()
                    .exceptionHandling().accessDeniedHandler(accessDeniedHandler())
                .and()
                    .addFilter(new JWTAuthenticationFilter(authenticationManager(), tokenProvider()))
                    .addFilter(new JWTAuthorizationFilter(authenticationManager(), tokenProvider()));

    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
    }

    @Bean
    public TokenProvider tokenProvider(){
        return new TokenProvider();
    }

    @Bean
    public AccessDeniedHandler accessDeniedHandler(){
        return new CustomAccessDeniedHandler();
    }
}

尽管如此,我仍然收到默认的拒绝访问响应。调试时我意识到自定义处理程序中的handle 方法甚至没有被调用。这是什么情况?

【问题讨论】:

  • 我已经解决了这个问题。看答案。不过感谢您的回复。
  • Reactive 堆栈遇到了同样的问题,对我来说,引入 AccessDeniedHandler 解决了。谢谢。

标签: java spring rest spring-security httpresponse


【解决方案1】:

试试这个

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.NEVER)
                .and()
                    .csrf().disable()
                    .authorizeRequests()
                    .antMatchers(HttpMethod.POST, REGISTER_URL).permitAll()
                    .anyRequest().authenticated()

                 .and().exceptionHandling().accessDeniedPage("/view/notAuth")
                .and()
                    .addFilter(new JWTAuthenticationFilter(authenticationManager(), tokenProvider()))
                    .addFilter(new JWTAuthorizationFilter(authenticationManager(), tokenProvider()));

    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
    }

    @Bean
    public TokenProvider tokenProvider(){
        return new TokenProvider();
    }

并为视图页面制作这个配置类

import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;


@Configuration
public class ViewRegistryConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/view/notAuth").setViewName("notAuth");
    }

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        // TODO Auto-generated method stub

    }

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        // TODO Auto-generated method stub

    }

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        // TODO Auto-generated method stub

    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        // TODO Auto-generated method stub

    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // TODO Auto-generated method stub

    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // TODO Auto-generated method stub

    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // TODO Auto-generated method stub

    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // TODO Auto-generated method stub

    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // TODO Auto-generated method stub

    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        // TODO Auto-generated method stub

    }

    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
        // TODO Auto-generated method stub

    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // TODO Auto-generated method stub

    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        // TODO Auto-generated method stub

    }

    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        // TODO Auto-generated method stub

    }

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        // TODO Auto-generated method stub

    }

    @Override
    public Validator getValidator() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public MessageCodesResolver getMessageCodesResolver() {
        // TODO Auto-generated method stub
        return null;
    }



}

【讨论】:

    【解决方案2】:

    这是一个最低限度的安全配置,演示了自定义 AccessDeniedHandler 在访问被拒绝 (403) 情况下被调用:

    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .antMatchers("/css/**", "/index").permitAll()
                    .antMatchers("/admin/**").hasRole("ADMIN")
                    .antMatchers("/user/**").hasRole("USER")
                    .and()
                .formLogin()
                    .and()
                .exceptionHandling()
                    .accessDeniedHandler((request, response, accessDeniedException) -> {
                        AccessDeniedHandler defaultAccessDeniedHandler = new AccessDeniedHandlerImpl();
                        defaultAccessDeniedHandler.handle(request, response, accessDeniedException);
                    });
        }
    
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth
                .inMemoryAuthentication()
                    .withUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER"))
                    .withUser(User.withDefaultPasswordEncoder().username("admin").password("password").roles("ADMIN"));
        }
    }
    

    重现步骤:

    1. user/password登录
    2. 尝试访问http://localhost:8080/user/index - 已授予访问权限
    3. 尝试访问http://localhost:8080/admin/index - 访问被拒绝并且自定义AccessDeniedHandler 被调用

    【讨论】:

    • 认证什么时候结束?我的意思是,是否有任何默认时间来保存身份验证凭据?
    【解决方案3】:

    我有同样的问题并尝试按照正确答案解决,但它并没有解决问题。 处理此问题的最佳方法是实现自定义访问拒绝处理程序。 AuthenticationEntryPoint 实现最好处理 401,UNAUTHORIZED 访问,而 AccessDeniedHandler 实现是针对 403,FOBIDDEN 访问。

    在你的实现类中重写 AccessDeniedHandler 的方法:

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, 
    AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.getWriter().write("Access Denied... Forbidden");
    }
    

    并在您的安全配置中添加此自定义拒绝访问处理程序,如下所示:

    .exceptionHandling()     
    .authenticationEntryPoint(authenticationEntryPoint())
    .accessDeniedHandler(accessDeniedHandler())
    

    【讨论】:

      【解决方案4】:

      我想我解决了这个问题。 我不得不创建一个自定义的 AuthenticationEntryPoint 并将其设置为异常处理,而不是创建 AccessDeniedHandler 的实现。

      WebConfig 现在看起来像这样:

      @EnableWebSecurity
      public class WebSecurity extends WebSecurityConfigurerAdapter {
      
          private UserDetailsService userDetailsService;
          private BCryptPasswordEncoder bCryptPasswordEncoder;
      
          @Autowired
          public WebSecurity(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
              this.userDetailsService = userDetailsService;
              this.bCryptPasswordEncoder = bCryptPasswordEncoder;
          }
      
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http
                      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                      .and()
                          .csrf().disable()
                          .authorizeRequests()
                          .antMatchers(HttpMethod.POST, REGISTER_URL).permitAll()
                          .anyRequest().authenticated()
                      .and()
                          .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
                      .and()
                          .addFilter(new JWTAuthenticationFilter(authenticationManager(), tokenProvider()))
                          .addFilter(new JWTAuthorizationFilter(authenticationManager(), tokenProvider()));
      
          }
      
          @Override
          public void configure(AuthenticationManagerBuilder auth) throws Exception {
              auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
          }
      
          @Bean
          public TokenProvider tokenProvider(){
              return new TokenProvider();
          }
      
          @Bean
          public AuthenticationEntryPoint authenticationEntryPoint(){
              return new CustomAuthenticationEntryPoint();
          }
      }
      

      和 CustomAuthenticationEntryPoint:

      public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
      
          @Override
          public void commence(HttpServletRequest req, HttpServletResponse res, AuthenticationException authException) throws IOException, ServletException {
              res.setContentType("application/json;charset=UTF-8");
              res.setStatus(403);
              res.getWriter().write(JsonBuilder //my util class for creating json strings
                      .put("timestamp", DateGenerator.getDate())
                      .put("status", 403)
                      .put("message", "Access denied")
                      .build());
          }
      }
      

      现在一切都如我所愿。

      【讨论】:

      • 感谢分享 CustomAuthenticationEntryPoint :)
      【解决方案5】:

      据此:

      http://www.baeldung.com/spring-security-custom-access-denied-page

      您还需要添加:

      .exceptionHandling().accessDeniedHandler(accessDeniedHandler());

      猜测进入“配置”。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-09-05
        • 2017-04-27
        • 2016-09-24
        • 2015-07-03
        • 2018-07-05
        • 2015-01-01
        • 1970-01-01
        • 2017-01-06
        相关资源
        最近更新 更多