Estou usando Spring MVC @ControllerAdvice
e @ExceptionHandler
para lidar com todas as exceções de um REST Api. Ele funciona bem para exceções lançadas por controladores web mvc, mas não funciona para exceções lançadas por filtros personalizados de segurança Spring porque eles são executados antes de os métodos do controlador serem chamados.
Eu tenho um filtro de segurança de mola personalizado que faz uma autenticação baseada em token:
public class AegisAuthenticationFilter extends GenericFilterBean {
...
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
try {
...
} catch(AuthenticationException authenticationException) {
SecurityContextHolder.clearContext();
authenticationEntryPoint.commence(request, response, authenticationException);
}
}
}
Com este ponto de entrada personalizado:
@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
}
}
E com esta classe para lidar com exceções globalmente:
@ControllerAdvice
public class RestEntityResponseExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({ InvalidTokenException.class, AuthenticationException.class })
@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
@ResponseBody
public RestError handleAuthenticationException(Exception ex) {
int errorCode = AegisErrorCode.GenericAuthenticationError;
if(ex instanceof AegisException) {
errorCode = ((AegisException)ex).getCode();
}
RestError re = new RestError(
HttpStatus.UNAUTHORIZED,
errorCode,
"...",
ex.getMessage());
return re;
}
}
O que eu preciso fazer é retornar um corpo JSON detalhado, mesmo para Spring security AuthenticationException. Existe uma maneira de fazer o spring security AuthenticationEntryPoint e spring mvc @ExceptionHandler trabalharem juntos?
Estou usando o spring security 3.1.4 e o spring mvc 3.2.4.
fonte
(@)ExceptionHandler
só funcionará se a solicitação for tratada peloDispatcherServlet
. No entanto, essa exceção ocorre antes disso, pois é lançada por aFilter
. Portanto, você nunca será capaz de lidar com essa exceção com um(@)ExceptionHandler
.EntryPoint
. Você pode querer construir o objeto lá e injetar umMappingJackson2HttpMessageConverter
ali.Respostas:
Ok, eu tentei, conforme sugerido, escrever o json eu mesmo a partir do AuthenticationEntryPoint e funciona.
Apenas para teste, alterei o AutenticationEntryPoint removendo response.sendError
Desta forma, você pode enviar dados json personalizados junto com o 401 não autorizado, mesmo se estiver usando Spring Security AuthenticationEntryPoint.
Obviamente, você não construiria o json como eu fiz para fins de teste, mas serializaria alguma instância de classe.
fonte
Este é um problema muito interessante, pois o Spring Security e o Spring Web Framework não são muito consistentes na maneira como lidam com a resposta. Acredito que ele deva oferecer suporte nativo ao manuseio de mensagens de erro de
MessageConverter
uma maneira prática.Tentei encontrar uma maneira elegante de injetar
MessageConverter
no Spring Security para que eles pudessem capturar a exceção e devolvê-la no formato correto de acordo com a negociação de conteúdo . Ainda assim, minha solução abaixo não é elegante, mas pelo menos faz uso do código Spring.Presumo que você saiba como incluir a biblioteca Jackson e JAXB, caso contrário, não há como continuar. Existem 3 etapas no total.
Etapa 1 - Criar uma classe autônoma, armazenando MessageConverters
Esta classe não joga mágica. Ele simplesmente armazena os conversores de mensagem e um processador
RequestResponseBodyMethodProcessor
. A mágica está dentro desse processador que fará todo o trabalho, incluindo negociação de conteúdo e conversão do corpo de resposta de acordo.Etapa 2 - Criar AuthenticationEntryPoint
Como em muitos tutoriais, esta classe é essencial para implementar o tratamento de erros personalizado.
Etapa 3 - Registrar o ponto de entrada
Como mencionado, faço isso com Java Config. Acabei de mostrar a configuração relevante aqui, deve haver outra configuração, como sessão sem estado , etc.
Tente com alguns casos de falha de autenticação, lembre-se de que o cabeçalho da solicitação deve incluir Aceitar: XXX e você deve obter a exceção em JSON, XML ou alguns outros formatos.
fonte
InvalidGrantException
mas minha versão de seuCustomEntryPoint
não está sendo invocada. Alguma ideia do que posso estar perdendo?AuthenticationEntryPoint
eAccessDeniedHandler
tais comoUsernameNotFoundException
eInvalidGrantException
pode ser tratado porAuthenticationFailureHandler
como explicado aqui .A melhor maneira que encontrei é delegar a exceção ao HandlerExceptionResolver
então você pode usar @ExceptionHandler para formatar a resposta da maneira que desejar.
fonte
@ControllerAdvice
não funcionará se tiver especificado basePackages na anotação. Tive que remover isso totalmente para permitir que o manipulador fosse chamado.@Component("restAuthenticationEntryPoint")
? Por que a necessidade de um nome como restAuthenticationEntryPoint? É para evitar algumas colisões de nomes do Spring?No caso de Spring Boot e
@EnableResourceServer
, é relativamente fácil e conveniente estender emResourceServerConfigurerAdapter
vez deWebSecurityConfigurerAdapter
na configuração Java e registrar um personalizadoAuthenticationEntryPoint
substituindoconfigure(ResourceServerSecurityConfigurer resources)
e usandoresources.authenticationEntryPoint(customAuthEntryPoint())
dentro do método.Algo assim:
Há também um bom
OAuth2AuthenticationEntryPoint
que pode ser estendido (já que não é final) e parcialmente reutilizado durante a implementação de um personalizadoAuthenticationEntryPoint
. Em particular, ele adiciona cabeçalhos "WWW-Authenticate" com detalhes relacionados ao erro.Espero que isso ajude alguém.
fonte
commence()
função do meuAuthenticationEntryPoint
não está sendo invocada - estou perdendo alguma coisa?Obtendo respostas de @Nicola e @Victor Wing e adicionando uma forma mais padronizada:
Agora, você pode injetar Jackson, Jaxb ou o que quer que você use para converter corpos de resposta em sua anotação MVC ou configuração baseada em XML com seus serializadores, desserializadores e assim por diante.
fonte
<property name="messageConverter" ref="myConverterBeanName"/>
tag. Quando você usa uma@Configuration
classe apenas use osetMessageConverter()
método.Precisamos usar
HandlerExceptionResolver
nesse caso.Além disso, você precisa adicionar a classe de manipulador de exceção para retornar seu objeto.
pode você receber um erro no momento da execução de um projeto por causa de várias implementações de
HandlerExceptionResolver
, Nesse caso, você tem que adicionar@Qualifier("handlerExceptionResolver")
emHandlerExceptionResolver
fonte
GenericResponseBean
é apenas java pojo, você pode criar o seu próprioConsegui lidar com isso simplesmente substituindo o método 'unsuccessfulAuthentication' em meu filtro. Lá, eu envio uma resposta de erro ao cliente com o código de status HTTP desejado.
fonte
Atualização: Se você gosta e prefere ver o código diretamente, eu tenho dois exemplos para você, um usando Spring Security padrão que é o que você está procurando, o outro usando o equivalente a Reactive Web e Reactive Security:
- Normal Segurança Web + Jwt
- Jwt reativo
O que sempre uso para meus endpoints baseados em JSON se parece com o seguinte:
O mapeador de objetos se torna um bean assim que você adiciona o Spring Web Starter, mas eu prefiro customizá-lo, então aqui está minha implementação para ObjectMapper:
O AuthenticationEntryPoint padrão que você definiu na classe WebSecurityConfigurerAdapter:
fonte
Personalize o filtro e determine que tipo de anormalidade deve haver um método melhor do que este
}
fonte
Estou usando o objectMapper. Cada serviço Rest está trabalhando principalmente com json e em uma de suas configurações você já configurou um mapeador de objetos.
O código é escrito em Kotlin, espero que esteja tudo bem.
fonte
Na
ResourceServerConfigurerAdapter
aula, o código abaixo cortado funcionou para mim.http.exceptionHandling().authenticationEntryPoint(new AuthFailureHandler()).and.csrf()..
não funcionou. É por isso que escrevi como chamada separada.Implementação de AuthenticationEntryPoint para capturar a expiração do token e cabeçalho de autorização ausente.
fonte