Spring security - creating 403 Access denied custom response

25,118

Solution 1

I think i resolved the problem. Instead of creating an implementation of AccessDeniedHandler i had to create a custom AuthenticationEntryPoint and set it in exception handling.

WebConfig now looks like this:

@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();
    }
}

and the 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());
    }
}

Now everything works as i wanted.

Solution 2

I have the same problem & tried to resolve as per the right answer, but it doesn't solve the issue. The best way to handle this is to implement custom access denied handler. AuthenticationEntryPoint implementation is best to handle 401, UNAUTHORIZED access and AccessDeniedHandler implementation is there for 403, FORBIDDEN access.

Override AccessDeniedHandler's method in your implementation class as:

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

And add this custom access denied handler in your security config like this:

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

Solution 3

Try this

 @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();
    }

And make this configuration class for view page

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;
    }



}
Share:
25,118

Related videos on Youtube

kubi
Author by

kubi

Updated on January 28, 2021

Comments

  • kubi
    kubi over 3 years

    I have a spring boot rest api with jwt authentication. The problem is i cannot get rid of default 403 Access Denied rest response which looks like this:

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

    I created custom 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")));
        }
    }
    

    and added it to WebConfig class

    @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();
        }
    }
    

    Despite this i'm still getting the default Access Denied response. When debugging i realized that the handle method from custom handler isn't even called. What is the case here?

    • chaoluo
      chaoluo over 6 years
    • kubi
      kubi over 6 years
      I already resolved this problem. Look at answers. Thanks for response though.
    • Roman T
      Roman T over 3 years
      Had the same problem with the Reactive stack and for me introducing AccessDeniedHandler worked out. Thank you.
  • OO7
    OO7 about 3 years
    Thanks for sharing CustomAuthenticationEntryPoint :)
  • Burak
    Burak over 2 years
    When will the authentication end? I mean, is there any default time to hold the authentication credentials?