Custom authentication/authorization/OAuth2 responses - Spring Security - java

So, I have created my own authorization server for my application. It also plays the role of a Resource Server, just for testing at the moment.
For consistency I would like to customize the responses for both OAuth2 endpoints (token request fails - authentication error for example), and resource server endpoints (authorization errors). I've seen various resources online which mentioned creating a custom AuthenticationEntryPoint or an ExceptionTranslator but none worked for me.
This is the ApiError model:
public class ApiError {
private List<String> errors = new ArrayList<>();
private LocalDateTime timestamp;
private Cause cause;
private HttpResponseStatus httpResponseStatus;
//constructors, getters, setters
private class HttpResponseStatus {
private int value;
private String message;
public HttpResponseStatus(HttpStatus httpStatus) {
this.value = httpStatus.value();
this.message = httpStatus.getReasonPhrase();
}
public int getValue() {
return value;
}
public String getMessage() {
return message;
}
}
private class Cause {
private String exception;
private String message;
public Cause(Class<?> exception, String message) {
this.exception = exception.getSimpleName();
this.message = message;
}
public String getException() {
return exception;
}
public String getMessage() {
return message;
}
}
}
Here's my AuthenticationEntryPoint. I've tried placing it in WebSecurityConfigurerAdapter configuration below but it did not work:
#Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
ObjectMapper objectMapper = new ObjectMapper();
HttpStatus httpStatus = HttpStatus.UNAUTHORIZED;
ApiError apiError = new ApiError(httpStatus, e, "Authentication error");
String jsonResponse = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(apiError);
httpServletResponse.setStatus(httpStatus.value());
httpServletResponse.getOutputStream().print(jsonResponse);
}
Here's the type of default Spring Security responses that I would like to change:
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
{
"error": "invalid_token",
"error_description": "Invalid access token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleAiOjE1ODY4NjkyOTYsInVzZXJfbmFtZSI6ImFkaXBvcEBnbWFpbC5jb20iLCJhdXRob3JpdGllcyI6WyJST0xFX01BTkFHRVIiXSwianRpIjoiNGYxZmQ1NTItYmQxNC00NDcyLWJhOTctNjIxY2FlZmYzNGQxIiwiY2xpZW50X2lkIjoic2FmZWRyaXZlLXdlYmFwcCIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdfQ.IMCogr_M8Skw0R0g0pqshROh8-nms8U3zt5i1G7CXO48OcJ76V1WXTGizn5anzFFIRHk0xGhw-r46lgHhLoWB89pjCC04PAIjFwl31flKWVSW6js9QfMt4O8CL6TAXnyHShUyJxbLZnnavTL3b40iLOHNJSeIf7Ed6goqOZMwZUBDB2KKCY_rmu80Ntj69uzVBrVfXCdDW7SRy-05uTqIGlBTWf3v4NZ4lV7EYzTJOcjavkBcSJeLcNi0DpYu1enF4rLPP9MeIUdiWT9sD6FOlLjs_vXQ_rZnxj-TVVkGSYRNn4u3-Znx4WZ3QBbcDU_O_w8qrp5_JnQbXy27-fmpg"
}
{
"error": "invalid_grant",
"error_description": "Bad credentials"
}
Here's how my WebSecurityConfigurerAdapter (the generic security configuration - order 1, the AuthenticationManager is configured in another WebSecurityConfigurerAdapter with order 100) looks like:
#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.headers().frameOptions().disable()
.and()
.antMatcher("/console/**")
.authorizeRequests()
.antMatchers("/console/**").hasRole("ADMIN")
.and()
.httpBasic();
}
The ResourceServerConfigurerAdapter:
#Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/actuator/**", "/api-docs/**").permitAll()
.antMatchers("/protected/**").hasAnyRole("ADMIN", "MANAGER")
.anyRequest().authenticated();
}
AuthorizationServerConfigurerAdapter:
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
//enables chaining multiple types of claims containing different information
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenConverter));
endpoints.tokenStore(tokenStore)
.accessTokenConverter(tokenConverter)
.authenticationManager(authenticationManager)
.tokenEnhancer(tokenEnhancerChain);
}
How should I set this up? I've looked over countless resources. Which HttpSecurity takes priority? I am a bit confused.

Related

Spring Security Authentication Provider exception handling

I have an authentication provider, that throwing my custom exception.
This provider validating token on every request to controllers. Exceptions in controllers handling by controller advice, but provider works before controller, so controller advice cant handle exceptions that provider throws.
How can i handle exception from provider?
Provider
#Component
#RequiredArgsConstructor
public class BearerTokenAuthenticationProvider implements AuthenticationProvider {
private final Wso2TokenVerificationClient client;
#Override
public Authentication authenticate( Authentication authentication ) {
BearerTokenAuthenticationToken token = (BearerTokenAuthenticationToken) authentication;
Map<String, String> requestBody = new HashMap<>();
requestBody.put( "token", token.getToken() );
Wso2TokenValidationResponse tokenValidationResponse = client.introspectToken( requestBody );
if( !Boolean.parseBoolean( tokenValidationResponse.getActive() ) ) {
throw new AuthenticationException(
"Token not valid", HttpStatus.UNAUTHORIZED
);
}
DecodedJWT jwt = JWT.decode(token.getToken());
UserDetails details = new UserDetails();
details.setId( Long.parseLong(jwt.getClaim( OidcUserClaims.USER_ID ).asString()) );
details.setEmail( jwt.getClaim( OidcUserClaims.EMAIL ).asString() );
token.setDetails( details );
return token;
}
#Override
public boolean supports( Class<?> aClass ) {
return BearerTokenAuthenticationToken.class.equals( aClass );
}
Security Config
#Configuration
#RequiredArgsConstructor
public class CommonWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
private final BearerTokenAuthenticationProvider bearerTokenProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().contentSecurityPolicy("script-src 'self'");
http
.csrf().disable()
.authorizeRequests(auth -> auth
.antMatchers("/public/**").not().hasAuthority("ROLE_ANONYMOUS")
)
.and()
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
}
#Override
protected void configure( AuthenticationManagerBuilder auth ) throws Exception {
auth.authenticationProvider( bearerTokenProvider );
}
}
You can add an authenticationEntryPoint to handle custom exception.
#Configuration
#RequiredArgsConstructor
static class CommonWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
private final BearerTokenAuthenticationProvider bearerTokenProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.contentSecurityPolicy("script-src 'self'");
http
.csrf().disable()
.authorizeRequests(auth -> auth
.antMatchers("/public/**").not().hasAuthority("ROLE_ANONYMOUS")
)
.oauth2ResourceServer(c -> c.jwt()
.and()
.authenticationEntryPoint((request, response, authException) -> {
//handle CustomAuthenticationException
}
)
);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(bearerTokenProvider);
}
}
public class CustomAuthenticationException extends AuthenticationException {
HttpStatus status;
public CustomAuthenticationException(String message, HttpStatus status) {
super(message);
this.status = status;
}
}
I support the accepted answer here but want to highlight key tricky part of it:
I had same structure but instead of throwing my custom exception, I used AuthenticationServiceException and that was simply not working.
To be able to handle exception in your custom AuthenticationEntryPoint you MUST extend AuthenticationException with your own implementation as it is done in accepted answer.

How to remove a variable from Unauthorized response in springboot

I have this response when it comes to check the user Unauthorized.
i there any possibility to remove the Path from the Unauthorized response ? since it does not gives valuable information for the user
{
"timestamp": "2021-03-18T09:16:09.699+0000",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/test/v1/api/test.com/config/settings"
}
this is how my config looks like
public class ResourceConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.cors();
httpSecurity
.anonymous().disable()
.requestMatchers().antMatchers("/api/**")
.and()
.authorizeRequests()
.antMatchers("/api/**")
.authenticated()
.and()
.exceptionHandling()
.accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
Adding on #linhx idea of using custom AuthenricationEntryPoint, you can use HandlerExceptionResolver which resolves to a page.
You can get a detailed comparison of different approaches here.
#Component
public class ABAuthenticationEntryPoint implements AuthenticationEntryPoint {
protected final Logger logger = LoggerFactory.getLogger(ABAuthenticationEntryPoint.class);
private final String realmName = "CustomRealm";
#Autowired
#Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
resolver.resolveException(request, response, null, authException);
}
}
The HandlerExceptionResolver uses the handler (HandlerMethod) to obtain the Controller class and scan it for methods annotated with #ExceptionHandler. If one of this methods matches the exception (ex) then this methods get invoked in order to handle the exception. (else null get returned signaling that this exception resolver feels no responsible).
So, add a class with #ControllerAdvice:
#ExceptionHandler(value = InsufficientAuthenticationException.class)
public ResponseEntity<Object> handleInsufficientAuthenticationException(InsufficientAuthenticationException ex) {
String methodName = "handleInsufficientAuthenticationException()";
return buildResponseEntity(HttpStatus.UNAUTHORIZED, null, null, ex.getMessage(), null);
}
private ResponseEntity<Object> buildResponseEntity(HttpStatus status, HttpHeaders headers, Integer internalCode, String message, List<Object> errors) {
ResponseBase response = new ResponseBase()
.success(false)
.message(message)
.resultCode(internalCode != null ? internalCode : status.value())
.errors(errors != null
? errors.stream().filter(Objects::nonNull).map(Objects::toString).collect(Collectors.toList())
: null);
return new ResponseEntity<>((Object) response, headers, status);
}
SecurityConfig class:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
#Autowired
private ABAuthenticationEntryPoint authenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
.....
.and()
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint); //AuthenticationEntryPoint has to be the last
}
}
Finally you will get something like the following, based on how you buildResponseEntity
{
"success": false,
"resultCode": 401,
"message": "Full authentication is required to access this resource"
}

Is it possible to override the spring security authentication error message

We are using spring boot 2.2.2.
I want to return the custom error message in the json response along with 401 status code when user enters the wrong credentials for that I did following steps.
WebSecurityConfig.java:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
public WebSecurityConfig(RepositoryUserDetailsService userDetailsService RestAuthenticationEntryPoint restAuthenticationEntryPoint) {
this.userDetailsService = userDetailsService;
this.restAuthenticationEntryPoint = restAuthenticationEntryPoint;
}
#Override
public void configure(final WebSecurity web) {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/proj-name/**/*.{js,html}")
.antMatchers("/test/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/login").permitAll()
.antMatchers("/error").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
#Bean
RestAuthenticationEntryPoint authenticationEntryPoint() {
return new RestAuthenticationEntryPoint();
}
#Bean
AuthenticationEntryPoint forbiddenEntryPoint() {
return new HttpStatusEntryPoint(FORBIDDEN);
}
#Bean
public CustomAuthenticationProvider customDaoAuthenticationProvider() throws Exception {
CustomAuthenticationProvider customDaoAuthenticationProvider = new CustomAuthenticationProvider();
customDaoAuthenticationProvider.setPasswordEncoder(new PasswordEncoderConfig().encoder());
customDaoAuthenticationProvider.setUserDetailsService(userDetailsService);
return customDaoAuthenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customDaoAuthenticationProvider());
}
}
RestAuthenticationEntryPoint.java
#Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.addHeader("WWW-Authenticate", "Basic realm=" + "");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = httpServletResponse.getWriter();
writer.println("HTTP Status 401 - " + e.getMessage());
}
}
CustomAuthenticationProvider.java
public class CustomAuthenticationProvider extends DaoAuthenticationProvider {
#Autowired
private PasswordEncoder passwordEncoder;
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "test bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
throw new BadCredentialsException(
messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Invalid credentials"));
}
}
}
When I hit the /login api is returning the following response: Full authentication is required to access this resource
If I add .antMatchers("/error").permitAll() is returning the following response.
{
"timestamp": 1594970514264,
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/login"
}
I tried adding #Component annotation on the CustomAuthenticationProvider then it throws the following error on server startup: java.lang.IllegalArgumentException: A UserDetailsService must be set
I tried adding the CustomAuthenticationFailureHandler by following the url, but didn't work. I've seen couple of articles, but that examples also didn't work. I've also tried creating an exception handler class with #ControllerAdvice annotation still didn't work. Is there any other way to override the default error message or am I missing any annotations or configuration? Thanks in advance for the help!
You could use a controller advice to handle the error thrown by your service :
#ControllerAdvice
#Order(HIGHEST_PRECEDENCE)
public class AppointmentErrorController {
#ExceptionHandler(BadCredentialsException.class)
public ResponseEntity<ApiError> handleError(HttpServletRequest request, BadCredentialsException ex) {
ApiError apiError = new ApiError(...); //retrieve info from BadCredentialsException
return ResponseEntity.status(HttpStatus.UNAUTHORIZED, apiError);
}

Spring Security java configuration confusion

Why the login isn't prompted with following configuration? When I try to access /public/user, I get error 403 (access denied). However, if I uncomment those commented lines at WebServiceSecurityConfiguration.configure, I got redirected to login page, as desired. Why those lines are needed for from-login being properly configured, as the antMatcher matches different path in the first place. I guess there is some conflict, which misconfigures the AuthenticationEntryPoint, but I don't really have idea how that happens. What I'm trying to achieve is configuring two security chains, one for login path to obtain the JWT token, and another for web services to authenticate against the token. Everything works perfectly with those lines uncommented, but I noticed by accident form-login stopped working without them, and am super confused why is that.
#Configuration
#Profile("javasecurity")
#Order(11)
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private TokenHandler tokenHandler;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").authorities(new SimpleGrantedAuthority("ROLE_USER")).and()
.withUser("admin").password("password").authorities(
new SimpleGrantedAuthority("ROLE_USER"),
new SimpleGrantedAuthority("ROLE_ADMIN")).and()
.withUser("guest").password("guest").authorities(new SimpleGrantedAuthority("ROLE_GUEST"));
}
#Override
#Bean
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**")
.permitAll()
.and()
.formLogin()
.successHandler(authenticationSuccessHandler())
.and()
.logout();
}
#Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
return new AuthenticationSuccessHandler() {
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
tokenHandler.setToken(response, authentication.getName());
response.getWriter().println("User authenticated and cookie sent");
response.flushBuffer();
}
};
}
#Configuration
#Profile("javasecurity")
#Order(10)
public static class WebServiceSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private TestAuthenticationFilter testAuthenticationFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secured/**")
.authenticated();
// .and()
// .antMatcher("/secured/**")
// .securityContext().securityContextRepository(new NullSecurityContextRepository())
// .and()
// .addFilterAt(testAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
--
#Component("TestAuthenticationFilter")
public class TestAuthenticationFilter extends GenericFilterBean {
#Autowired
private TokenHandler tokenHandler;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("TestAuthenticationFilter doFitler");
attemptAuthentication((HttpServletRequest) request);
chain.doFilter(request, response);
clearAuthentication();
System.out.println("doFitler end");
}
public void attemptAuthentication(HttpServletRequest request) {
try {
UserDetails user = tokenHandler.loadUserFromToken(request);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, user.getPassword());
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (Exception e) {
// Do nothing
}
}
public void clearAuthentication() {
SecurityContextHolder.getContext().setAuthentication(null);
}
#Configuration
public static class DisableFilterRegistration {
#Autowired
private TestAuthenticationFilter filter;
#Bean
public FilterRegistrationBean disablerBean() {
FilterRegistrationBean bean = new FilterRegistrationBean(filter);
bean.setEnabled(false);
return bean;
}
}
}
--
#Component("TokenHandler")
public class TokenHandler {
#Autowired(required = false)
private UserDetailsService userDetailsService;
public void setToken(HttpServletResponse response, String username) {
response.addCookie(new Cookie("user", username));
}
public UserDetails loadUserFromToken(HttpServletRequest request) throws BadCredentialsException {
Cookie[] cookies = request.getCookies();
Cookie token = null;
for (Cookie c : cookies) {
if (c.getName().equals("user")) {
token = c;
break;
}
}
if (token == null)
return null;
else
return userDetailsService.loadUserByUsername(token.getValue());
}
}
--
#RestController
#RequestMapping("/public")
public class PublicController {
#GetMapping("/norole")
public String noRole() {
return "no role";
}
#GetMapping("/user")
#PreAuthorize("hasRole('ROLE_USER')")
public String roleUser() {
return "role_user";
}
}
--
#RestController
#RequestMapping("/secured")
public class SecuredController {
#GetMapping("/user")
#PreAuthorize("hasRole('ROLE_USER')")
public String roleUser() {
return "role_user";
}
#GetMapping("/admin")
#PreAuthorize("hasRole('ROLE_ADMIN')")
public String roleAdmin() {
return "role_admin";
}
#GetMapping("/norole")
public String noRole() {
return "no role";
}
}
Login-from got functional again after declaring adding
http.antMatcher("/secured/**")
As the first call in the WebServiceSecurityConfiguration.configure. Does that mean that without it the configuration negates the form-login, which is configured after this particular configuration? Also, it seems like the position of antMatcher can be arbitrary, is this the case? Could someone explain what is actually happening there?

Spring Boot Security: Exception handling with custom authentication filters

I'm using Spring Boot + Spring Security (java config).
My question is the old one, but all info which I've found is partially outdated and mostly contains xml-config (which difficult or even impossible to adapt some time)
I'm trying to do stateless authentication with a token (which doesn't stored on server side). Long story short - it is a simple analogue for JSON Web Tokens authentication format.
I'm using two custom filters before default one:
TokenizedUsernamePasswordAuthenticationFilter which creates token after
successful authentication on entry point ("/myApp/login")
TokenAuthenticationFilter which tries to authenticate the user using token (if provided) for all restricted URLs.
I do not understand how properly handle custom exceptions(with custom message or redirect) if I want some...
Exceptions in filters are not relevant to exceptions in controllers, so they will not be handled by same handlers...
If I've understood right, I can not use
.formLogin()
.defaultSuccessUrl("...")
.failureUrl("...")
.successHandler(myAuthenticationSuccessHandler)
.failureHandler(myAthenticationFailureHandler)
to customize exceptions, because I use custom filters...
So what the way to do it?
My config:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and() .anonymous()
.and() .authorizeRequests()
.antMatchers("/").permitAll()
...
.antMatchers(HttpMethod.POST, "/login").permitAll()
.and()
.addFilterBefore(new TokenizedUsernamePasswordAuthenticationFilter("/login",...), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new TokenAuthenticationFilter(...), UsernamePasswordAuthenticationFilter.class)
}
We can set AuthenticationSuccessHandler and AuthenticationFailureHandler in your custom filter as well.
Well in your case,
// Constructor of TokenizedUsernamePasswordAuthenticationFilter class
public TokenizedUsernamePasswordAuthenticationFilter(String path, AuthenticationSuccessHandler successHandler, AuthenticationFailureHandler failureHandler) {
setAuthenticationSuccessHandler(successHandler);
setAuthenticationFailureHandler(failureHandler);
}
Now to use these handlers just invoke onAuthenticationSuccess() or onAuthenticationFailure() methods.
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authentication) throws IOException, ServletException {
getSuccessHandler().onAuthenticationSuccess(request, response, authentication);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException failed)
throws IOException, ServletException {
getFailureHandler().onAuthenticationFailure(request, response, failed);
}
You can create your custom authentication handler classes to handle the success or failure cases. For example,
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication)
throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authentication);
// Do your stuff, eg. Set token in response header, etc.
}
}
Now for handling the exception,
public class LoginFailureHandler implements AuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e)
throws IOException, ServletException {
String errorMessage = ExceptionUtils.getMessage(e);
sendError(httpServletResponse, HttpServletResponse.SC_UNAUTHORIZED, errorMessage, e);
}
private void sendError(HttpServletResponse response, int code, String message, Exception e) throws IOException {
SecurityContextHolder.clearContext();
Response<String> exceptionResponse =
new Response<>(Response.STATUES_FAILURE, message, ExceptionUtils.getStackTrace(e));
exceptionResponse.send(response, code);
}
}
My custom response class to generate desired JSON response,
public class Response<T> {
public static final String STATUES_SUCCESS = "success";
public static final String STATUES_FAILURE = "failure";
private String status;
private String message;
private T data;
private static final Logger logger = Logger.getLogger(Response.class);
public Response(String status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
public String getStatus() {
return status;
}
public String getMessage() {
return message;
}
public T getData() {
return data;
}
public String toJson() throws JsonProcessingException {
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
try {
return ow.writeValueAsString(this);
} catch (JsonProcessingException e) {
logger.error(e.getLocalizedMessage());
throw e;
}
}
public void send(HttpServletResponse response, int code) throws IOException {
response.setStatus(code);
response.setContentType("application/json");
String errorMessage;
errorMessage = toJson();
response.getWriter().println(errorMessage);
response.getWriter().flush();
}
}
I hope this helps.

Categories