Seguridad de Spring: creación de una respuesta personalizada 403 Acceso denegado
Tengo una Spring Boot
aplicación basada en REST
api con JWT
autenticación. El problema es que no puedo deshacerme de la respuesta de descanso predeterminada 403 Acceso denegado que se ve así:
{
"timestamp": 1516206966541,
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/api/items/2"
}
Creé personalizado 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")));
}
}
y lo agregué a la WebConfig
clase:
@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();
}
}
A pesar de esto, sigo recibiendo la respuesta predeterminada de Acceso denegado . Al depurar, me di cuenta de que handle
ni siquiera se llama al método del controlador personalizado. ¿Cuál es el caso aquí?
Creo que resolví el problema. En lugar de crear una implementación, AccessDeniedHandler
tuve que crear una personalizada AuthenticationEntryPoint
y configurarla en manejo de excepciones.
WebConfig
ahora se ve así:
@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();
}
}
y el 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());
}
}
Ahora todo funciona como quería.
Tengo el mismo problema y traté de resolverlo según la respuesta correcta, pero no resuelve el problema. La mejor manera de manejar esto es implementar un controlador de acceso denegado personalizado. La implementación de AuthenticationEntryPoint es mejor para manejar 401, acceso NO AUTORIZADO y la implementación de AccessDeniedHandler está ahí para 403, acceso PROHIBIDO.
Anule el método de AccessDeniedHandler en su clase de implementación como:
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.getWriter().write("Access Denied... Forbidden");
}
Y agregue este controlador de acceso denegado personalizado en su configuración de seguridad como este:
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint())
.accessDeniedHandler(accessDeniedHandler())
Prueba esto
@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();
}
Y cree esta clase de configuración para ver la página.
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;
}
}