Currently, whenever a user fails authentication, spring security responds with:
{"error": "invalid_grant","error_description": "Bad credentials"}
And I would like to enhance this response with a response code like:
{"responsecode": "XYZ","error": "invalid_grant","error_description": "Bad credentials"}
After some poking around, it looks like what I need to do this is implement an AuthenticationFailureHandler, which I have begun to do. However, the onAuthenticationFailure method never seems to be reached whenever I submit invalid login credentials. I have stepped through the code, and placed logging in the onAuthenticationFailure method to confirm it is not being reached.
My failure handler is:
#Component
public class SSOAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler{
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
super.onAuthenticationFailure(request, response, exception);
response.addHeader("responsecode", "XYZ");
}
}
And my WebSecurityConfigurerAdapter contains:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired SSOAuthenticationFailureHandler authenticationFailureHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.formLogin().failureHandler(authenticationFailureHandler);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(service).passwordEncoder(passwordEncoder());
auth.authenticationEventPublisher(defaultAuthenticationEventPublisher());
}
#Bean
public DefaultAuthenticationEventPublisher defaultAuthenticationEventPublisher(){
return new DefaultAuthenticationEventPublisher();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public SSOAuthenticationFailureHandler authenticationHandlerBean() {
return new SSOAuthenticationFailureHandler();
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
}
My questions are:
Is this the right way to achieve the result I want? (Customizing the spring security authentication response)
If so, did I do something wrong trying to set up my authentication failure handler (since a bad login doesn't seem to be reaching the onAuthenticationFailure method?
Thank you!
You can add exception handling to you Spring Security by calling .exceptionHandling() on your HttpSecurity object in your configure method.
If you only want to handle just bad credentials you can ignore the .accessDeniedHandler(accessDeniedHandler()).
The access denied handler handles situations where you hav secured you app at method level such as using the #PreAuthorized, #PostAuthorized, & #Secured.
An example of your security config could be like this
SecurityConfig.java
/*
The following two are the classes we're going to create later on.
You can autowire them into your Security Configuration class.
*/
#Autowired
private CustomAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private CustomAccessDeniedHandler accessDeniedHandler;
/*
Adds exception handling to you HttpSecurity config object.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.exceptionHandling()
.authencationEntryPoint(unauthorizedHandler) // handles bad credentials
.accessDeniedHandler(accessDeniedHandler); // You're using the autowired members above.
http.formLogin().failureHandler(authenticationFailureHandler);
}
/*
This will be used to create the json we'll send back to the client from
the CustomAuthenticationEntryPoint class.
*/
#Bean
public Jackson2JsonObjectMapper jackson2JsonObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
return new Jackson2JsonObjectMapper(mapper);
}
CustomAuthenticationEntryPoint.java
You can create this in its own separate file. This is Entry point handles the invalid credentials.
Inside the method we'll have to create and write our own JSON to the HttpServletResponse object. We'll
use the Jackson object mapper bean we created in the Security Config.
#Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
#Autowired // the Jackson object mapper bean we created in the config
private Jackson2JsonObjectMapper jackson2JsonObjectMapper;
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e) throws IOException {
/*
This is a pojo you can create to hold the repsonse code, error, and description.
You can create a POJO to hold whatever information you want to send back.
*/
CustomError error = new CustomError(HttpStatus.FORBIDDEN, error, description);
/*
Here we're going to creat a json strong from the CustomError object we just created.
We set the media type, encoding, and then get the write from the response object and write
our json string to the response.
*/
try {
String json = jackson2JsonObjectMapper.toJson(error);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
response.getWriter().write(json);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
CustomAccessDeniedHandler.java
This handles authorization errors such as trying to access method without the
appropriate priviledges. You can implement it in the same way we did above with the bad credentials exception.
#Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException e) throws IOException, ServletException {
// You can create your own repsonse here to handle method level access denied reponses..
// Follow similar method to the bad credentials handler above.
}
}
Hopefully this is somewhat helpful.
Related
I have a simple Spring Boot 2.1 application with a Spring Interceptor and #RestControllerAdvice.
My requirement is to have the Spring Interceptor be called for all situations, including when an exception occurs.
For custom exceptions, the Interceptor handler methods do get called, e.g. preHandle() and afterCompletion(). However, for exceptions handled by ResponseEntityExceptionHandler, the Spring Interceptor does not get called (I need ResponseEntityExceptionHandler's methods to create a custom ResponseBody to send back, however, I also need to trigger Interceptor's afterCompletion() for auditing purposes).
For instance, if a REST request is made with PATCH HTTP method, it only executes PersonControllerExceptionHandler.handleHttpRequestMethodNotSupported() and no PersonInterceptor is invoked.
Exception Handler:
#RestControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class PersonControllerExceptionHandler extends ResponseEntityExceptionHandler {
private final static Logger LOGGER = LoggerFactory.getLogger(PersonControllerExceptionHandler.class);
#ExceptionHandler(value = {PersonException.class })
public ResponseEntity<Object> handlePersonException(PersonException exception) {
LOGGER.info("Person exception occurred");
return new ResponseEntity<Object>(new Person("Bad Age", -1),
HttpStatus.BAD_REQUEST);
}
#ExceptionHandler(value = {Exception.class })
public ResponseEntity<Object> handleException(Exception exception) {
LOGGER.info("Exception occurred");
return new ResponseEntity<Object>(new Person("Unknown Age", -100),
HttpStatus.INTERNAL_SERVER_ERROR);
}
#Override
public ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
LOGGER.info("handleHttpRequestMethodNotSupported()...");
return new ResponseEntity<>(new Person("Argh!", 900), HttpStatus.METHOD_NOT_ALLOWED);
}
}
The Interceptor:
#Order(Ordered.HIGHEST_PRECEDENCE)
public class PersonInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(PersonInterceptor.class);
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
LOGGER.info("PersonInterceptor#preHandler()...");
return true;
}
#Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
LOGGER.info("PersonInterceptor#postHandler()...");
}
#Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
LOGGER.info("PersonInterceptor#afterCompletion()...");
if (ex != null) {
LOGGER.error("afterCompletion(): An exception occurred", ex);
}
}
}
Registering the Interceptor:
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new PersonInterceptor()).addPathPatterns("/person/*");
}
}
Controller:
#RestController
#RequestMapping("/")
public class PersonController {
private final static Logger LOGGER = LoggerFactory.getLogger(PersonController.class);
#Autowired
private PersonService personService;
#GetMapping(path = "/person/{age}", produces = MediaType.APPLICATION_JSON_VALUE)
public Person getPerson(#PathVariable("age") Integer age) throws PersonException {
LOGGER.info("Age: {}", age);
return personService.getPerson(age);
}
}
Initially I thought it has something to do with #Ordered but trying various scenarios where I give PersonInterceptor a higher precedence than #RestControllerAdvice yields the same undesirable outcome (or vice versa).
After digging into Spring framework, it seems like if a handler is not found, an exception is thrown back to DispacherServlet#doDispatch() which goes into a catch block, and therefore, it skips interceptor mapping process, including the afterCompletion() (I'm using Spring 5.1. as an example to trace the execution path):
DispacherServlet#doDispatch() is called and attempts is made to get the HandlerExecutionChain
I can see there are several HandlerMapping's; the one that fails is RequestMappingHandlerMapping
In AbstractHandlerMapping#getHandler(), it tries to get the handler via AbstractHandlerMethodMapping#getHandlerInternal()
Eventually, AbstractHandlerMethodMapping#lookupHandlerMethod() is called which fails to find a matching pattern due to the fact that there is no PATCH getPerson(), but rather GET getPerson()
At this point, RequestMappingInfoHandlerMapping#handleNoMatch() throws HttpRequestMethodNotSupportedException
This exception bubbles up to DispatcherServlet#doDispatch() exception clause which then processes by the exception resolver that it finds in
DispatcherServlet#processHandlerException() (of course, this finds an exception resolver and doesn't throw an exception which might trigger DispatcherServlet#triggerAfterCompletion() when an exception is caught in DispacherServlet#doDispatch() exception clause
Is there something I am missing to trigger the interceptor's afterCompletion() in cases when there is no handler match?
After struggling with this for a few days (searching SO for similar questions, doing trial & error), I am tempted to give up...
So the problem is I have a REST service based on Spring Boot using Spring Security and JWT for authentication. Now I want to secure some of the methods to be only called by authorized people using the #PreAuthorize-annotation.
This seems to work partly because instead of calling the method Spring returns 404. I would have expected 403.
I have read this SO-question and tried the answers given there, but it did not help. I have moved the #EnableGlobalMethodSecurity(prePostEnabled = true)-Annotation from my SecurityConfiguration to the Application class as suggested elsewhere, still it does not work.
My security configuration looks like this:
#Configuration
#Profile("production")
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Value("${adDomain}")
private String adDomain;
#Value("${adUrl}")
private String adUrl;
#Value("${rootDn}")
private String rootDn;
#Value("${searchFilter}")
private String searchFilter;
private final AuthenticationManagerBuilder auth;
private final SessionRepository sessionRepository;
#Autowired
public SecurityConfiguration(AuthenticationManagerBuilder auth, SessionRepository sessionRepository) {
this.auth = auth;
this.sessionRepository = sessionRepository;
}
#Override
public void configure(WebSecurity webSecurity) throws Exception
{
webSecurity
.ignoring()
// All of Spring Security will ignore the requests
.antMatchers("/static/**", "/api/web/logout")
.antMatchers(HttpMethod.POST, "/api/web/login");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() // Using JWT there is no need for CSRF-protection!
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthorizationFilter(authenticationManagerBean(), sessionRepository));
}
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
ActiveDirectoryLdapAuthenticationProvider adProvider =
new ActiveDirectoryLdapAuthenticationProvider(adDomain, adUrl, rootDn);
adProvider.setConvertSubErrorCodesToExceptions(true);
adProvider.setUseAuthenticationRequestCredentials(true);
adProvider.setSearchFilter(searchFilter);
adProvider.setUserDetailsContextMapper(new InetOrgPersonContextMapper());
auth.authenticationProvider(adProvider);
return super.authenticationManagerBean();
}
}
The controller method looks like this
#RequestMapping(path = "/licenses", method = RequestMethod.GET)
#PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> getAllLicenses(#RequestParam("after") int pagenumber, #RequestParam("size") int pagesize
, #RequestParam("searchText") String searchText) {
List<LicenseDTO> result = ...
return new ResponseEntity<Object>(result, HttpStatus.OK);
}
I am quite sure I am missing something very simple, but I just cannot figure out what.
By the way: if the user requesting the licenses has the ADMIN role everything works as expected, so the problem is not a real 404.
You need to define the exceptionHandling at security configuration as follows,
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() // Using JWT there is no need for CSRF-protection!
.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(new AccessDeniedExceptionHandler())
.and()
.addFilter(new JwtAuthorizationFilter(authenticationManagerBean(), sessionRepository));
}
You can define AccessDeniedExceptionHandler class as follows,
public class AccessDeniedExceptionHandler implements AccessDeniedHandler
{
#Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException ex) throws IOException, ServletException {
response.setStatus(HttpStatus.FORBIDDEN);
}
}
I finally found a solution fitting my purposes. I do not know if this is the best way to deal with this, but just adding an ExceptionHandler did the trick. Somewhere deep inside the filterchain the 403 mutates to 404 when there is no such handler in place.
Perhaps I am to dump to read and understand the documentation, but I did not find anything that suggest you have to do this. So maybe I am wrong solving the problem like this, but here is the code that did the trick (it is a really basic implementation that should be improved over time):
#ControllerAdvice
public class MyExceptionHandler {
#ExceptionHandler(Throwable.class)
#ResponseBody
public ResponseEntity<String> handleControllerException(Throwable ex) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
if(ex instanceof AccessDeniedException) {
status = HttpStatus.FORBIDDEN;
}
return new ResponseEntity<>(ex.getMessage(), status);
}
}
Global method security can be enabled with the help of annotation #EnableGlobalMethodSecurity(prePostEnabled=true) . The combination of this and #Preauthorize will create a new proxy for your controller and it will loose the Request mapping which will result in 404 Exception.
To handle this you can use the annotation #EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true) which is there in your SecurityConfiguration class.
Provided the details in another answer as well.
Currently, whenever a user fails authentication, spring security responds with:
{"error": "invalid_grant","error_description": "Bad credentials"}
And I would like to enhance this response with a response code like:
{"responsecode": "XYZ","error": "invalid_grant","error_description": "Bad credentials"}
After some poking around, it looks like what I need to do this is implement an AuthenticationFailureHandler, which I have begun to do. However, the onAuthenticationFailure method never seems to be reached whenever I submit invalid login credentials. I have stepped through the code, and placed logging in the onAuthenticationFailure method to confirm it is not being reached.
My failure handler is:
#Component
public class SSOAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler{
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
super.onAuthenticationFailure(request, response, exception);
response.addHeader("responsecode", "XYZ");
}
}
And my WebSecurityConfigurerAdapter contains:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired SSOAuthenticationFailureHandler authenticationFailureHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.formLogin().failureHandler(authenticationFailureHandler);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(service).passwordEncoder(passwordEncoder());
auth.authenticationEventPublisher(defaultAuthenticationEventPublisher());
}
#Bean
public DefaultAuthenticationEventPublisher defaultAuthenticationEventPublisher(){
return new DefaultAuthenticationEventPublisher();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public SSOAuthenticationFailureHandler authenticationHandlerBean() {
return new SSOAuthenticationFailureHandler();
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
}
My questions are:
Is this the right way to achieve the result I want? (Customizing the spring security authentication response)
If so, did I do something wrong trying to set up my authentication failure handler (since a bad login doesn't seem to be reaching the onAuthenticationFailure method?
Thank you!
You can add exception handling to you Spring Security by calling .exceptionHandling() on your HttpSecurity object in your configure method.
If you only want to handle just bad credentials you can ignore the .accessDeniedHandler(accessDeniedHandler()).
The access denied handler handles situations where you hav secured you app at method level such as using the #PreAuthorized, #PostAuthorized, & #Secured.
An example of your security config could be like this
SecurityConfig.java
/*
The following two are the classes we're going to create later on.
You can autowire them into your Security Configuration class.
*/
#Autowired
private CustomAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private CustomAccessDeniedHandler accessDeniedHandler;
/*
Adds exception handling to you HttpSecurity config object.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.exceptionHandling()
.authencationEntryPoint(unauthorizedHandler) // handles bad credentials
.accessDeniedHandler(accessDeniedHandler); // You're using the autowired members above.
http.formLogin().failureHandler(authenticationFailureHandler);
}
/*
This will be used to create the json we'll send back to the client from
the CustomAuthenticationEntryPoint class.
*/
#Bean
public Jackson2JsonObjectMapper jackson2JsonObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
return new Jackson2JsonObjectMapper(mapper);
}
CustomAuthenticationEntryPoint.java
You can create this in its own separate file. This is Entry point handles the invalid credentials.
Inside the method we'll have to create and write our own JSON to the HttpServletResponse object. We'll
use the Jackson object mapper bean we created in the Security Config.
#Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
#Autowired // the Jackson object mapper bean we created in the config
private Jackson2JsonObjectMapper jackson2JsonObjectMapper;
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e) throws IOException {
/*
This is a pojo you can create to hold the repsonse code, error, and description.
You can create a POJO to hold whatever information you want to send back.
*/
CustomError error = new CustomError(HttpStatus.FORBIDDEN, error, description);
/*
Here we're going to creat a json strong from the CustomError object we just created.
We set the media type, encoding, and then get the write from the response object and write
our json string to the response.
*/
try {
String json = jackson2JsonObjectMapper.toJson(error);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
response.getWriter().write(json);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
CustomAccessDeniedHandler.java
This handles authorization errors such as trying to access method without the
appropriate priviledges. You can implement it in the same way we did above with the bad credentials exception.
#Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException e) throws IOException, ServletException {
// You can create your own repsonse here to handle method level access denied reponses..
// Follow similar method to the bad credentials handler above.
}
}
Hopefully this is somewhat helpful.
We have a requirement to verify whether a username exists in database and then authenticate against AD. If username doesn’t exist application will return error instead of trying to authenticate against AD. I have authenticated against multiple AD’s and/or database but I have trouble getting this to work. Any hints would be helpful. Thank you
In my class that extends WebSecurityConfigurerAdapter I tried to play with authenticationProvider where I could verify the existence in DB. But not sure of what to return so that the authentication could be proceed to LDAP.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(authenticationProvider)
.authenticationEventPublisher(authenticationEventPublisher)
.ldapAuthentication()
.....;
}
I also tried adding a before/after filter but not successful in there either
#Override
protected void configure(HttpSecurity http) throws Exception {
http
....
.and()
.addFilterBefore(preAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
;
In the filter preAuthenticationFilter the instance of request passed in doFilter() method FirewalledRequest. From this instance I am unable to get the username; looks like this is by design. If anyone has any advice on how we could retrieve username from the instance of FirewalledRequest please share it here. I will give it a try.
So instead of using the filter I decided to play with the custom AuthenticationProvider. In the AuthenticationProvider implementation under the method authenticate() I return null (and log, notify, etc.) when user exist. If user doesn’t exits I return the same instance of authentication passed. This breaks the chain and stops proceeding to authenticating against AD. Throwing any instance of AuthenticationException doesn’t work as Spring security captures this exception and proceeds further (per docs).
Here is how the code snippet looks like
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
Optional<User> user = service.findUserByUsername((String) authentication.getPrincipal());
if (user.isPresent())
return null;
return authentication;
}
Please share any better ideas.
#gandr solution got me thinking, and my final solution was:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(PreLdapAuthenticationProvider)
// authenticationEventPublisher not used anymore
.ldapAuthentication()
.....;
private static class PreLdapAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
this.userService.checkUserEnabled((String) authentication.getPrincipal());
return null;
}
}
public class UserService {
public void checkUserEnabled(String username) throws AuthenticationException {
UserEntity entity = userRepository.findByLogin(username);
if (entity == null) {
throw new PreLdapUsernameNotFoundException();
// normal UsernameNotFoundException extends AuthenticationException
// and will be caught and ignore, but not if my custom class
// extends AccountStatusException
}
if (!entity.isEnabled()) {
throw new DisabledException("DisabledException");
}
}
}
public class PreLdapUsernameNotFoundException extends AccountStatusException {
public PreLdapUsernameNotFoundException() {
super("PreLdapUsernameNotFoundException");
}
}
Then you can catch PreLdapUsernameNotFoundException and InternalAuthenticationServiceException in
private AuthenticationFailureHandler authenticationFailureHandler() {
return (request, response, authenticationException) -> {
if (authenticationException instanceof PreLdapUsernameNotFoundException){
...
} else ...
}
}
I have a CustomLoginSucessHandler in my Spring MVC 4 project to manage an action when the user Logs In.
This is working properly. In the same class I have the method determineTargetUrl to redirect the user according to his ROLE.
Here is the code:
#Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response){
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
final String userName = authentication.getName();
log.debug("TARGET URL METHOD!");
List<Authority> authorityList = authorityService.getAllAuthoritiesByUserName(userName);
for(Authority authority: authorityList){
switch (authority.getAuthority()){
case "ROLE_ADMIN":
return "processFile";
case "ROLE_USER":
return "userPortal";
case "ROLE_DEMO1":
return "processFile";
case "ROLE_DEMO2":
return "processFile";
}
}
return "403";
}
See that I have a log.debug("TARGET URL METHOD")
This log is never called and of course the page is not being redirected, it's going to the default landing page that is processFile.html.
I am puzzled why the second method is not being called while my onAuthenticationSuccess works perfectly. They are in the same Class.
Here is the code how I create the instance of my CustomLoginSucessHandler:
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private CustomLoginSucessHandler customLoginSucessHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin().loginPage("/login.html")
.loginProcessingUrl("/login").permitAll().and().logout().logoutSuccessUrl("/")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll().and().exceptionHandling()
.accessDeniedPage("/403.html");
http.csrf().requireCsrfProtectionMatcher(new CsrfRequestMatcher());
http.formLogin().successHandler(customLoginSucessHandler);
}
Thank you.
You are trying to ovverride the wrong function, that is the root cause of your issue. In the excerpt you provided you have a function that seems to be overriding another:
#Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response){
but in fact it is not overriding anything. If you check the javadoc of AuthenticationSuccessHandler, you will see that it provides only one function: onAuthenticationSuccess which you reported as "working". It works, but it is an overriden function and it does get called as part of the standard login procedure. If you follow closely this example:
CustomLoginSuccessHandler example (probably you followed this already)
you will see that the determineTargetUrl function is not overriden, but explicitly called by the implementation:
protected void handle(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException {
String targetUrl = determineTargetUrl(authentication);
which handle method in turn is also being called from:
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException {
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}