title: How to return HTTP 403 after successful authentication, but unsuccessful authorization?
body: <p>I have a Spring Boot application which also uses Spring Security. I want to check if user has access to login to the application, but it must be after authentication. The point is that, at login, the user selects the project to which they must connect. A user can be allowed to connect to one project, but can not be allowed to connect to another project. However, if user enters invalid credentials, message about invalid credentials must be shown first even if user has no right to login to the selected project. For this reason, checking for the rights to the project must be after authentication.</p>
  <p>This my SecurityConfig class:</p>
  <pre><code>package org.aze.accountingprogram.config;
  
  import org.aze.accountingprogram.models.CurrentUser;
  import org.aze.accountingprogram.models.PermissionAliasConstants;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.http.HttpStatus;
  import org.springframework.security.access.AccessDeniedException;
  import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
  import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  import org.springframework.security.core.Authentication;
  import org.springframework.security.core.userdetails.UserDetailsService;
  import org.springframework.security.web.access.AccessDeniedHandler;
  import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
  
  import javax.servlet.ServletException;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import java.io.IOException;
  
  @Configuration
  @EnableWebSecurity
  @EnableGlobalMethodSecurity(prePostEnabled = true)
  public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
      private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
  
      @Autowired
      private UserDetailsService userDetailsService;
  
      @Override
      protected void configure(HttpSecurity http) throws Exception {
          http
                  .authorizeRequests().antMatchers(&quot;/lib/**&quot;).permitAll().anyRequest().fullyAuthenticated()
                  .and()
                  .formLogin().successHandler(successHandler()).loginPage(&quot;/login&quot;).permitAll()
                  .and().exceptionHandling().accessDeniedHandler(accessDeniedHandler())
                  .and()
                  .logout().logoutUrl(&quot;/logout&quot;).logoutSuccessUrl(&quot;/login&quot;).permitAll();
  
          http.csrf().disable();
      }
  
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
          auth.userDetailsService(userDetailsService).passwordEncoder(new Md5PasswordEncoder());
      }
  
      private AccessDeniedHandler accessDeniedHandler() {
          return (request, response, e) -&gt; {
              logger.debug(&quot;Returning HTTP 403 FORBIDDEN with message: \&quot;{}\&quot;&quot;, e.getMessage());
              response.sendError(HttpStatus.FORBIDDEN.value(), e.getMessage());
          };
      }
  
      private AuthenticationSuccessHandler successHandler() {
          return new AuthenticationSuccessHandler() {
              @Override
              public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                  CurrentUser user = (CurrentUser) authentication.getPrincipal();
                  if (!user.hasAccessRight(PermissionAliasConstants.LOGIN)) {
                      throw new AccessDeniedException(String.format(&quot;User \&quot;%s\&quot; is not authorized to login \&quot;%s\&quot; project&quot;, user.getUsername(), user.getProject().getName()));
                  }
              }
          };
      }
  
  }
  </code></pre>
  <p>implementation of UserDetailsService:</p>
  <pre><code>package org.aze.accountingprogram.serviceimpl;
  
  import org.aze.accountingprogram.common.Constants;
  import org.aze.accountingprogram.exceptions.DataNotFoundException;
  import org.aze.accountingprogram.models.AccessRightsPermission;
  import org.aze.accountingprogram.models.CurrentUser;
  import org.aze.accountingprogram.models.Project;
  import org.aze.accountingprogram.models.User;
  import org.aze.accountingprogram.service.AccessRightsService;
  import org.aze.accountingprogram.service.ProjectService;
  import org.aze.accountingprogram.service.UserService;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.security.core.GrantedAuthority;
  import org.springframework.security.core.authority.SimpleGrantedAuthority;
  import org.springframework.security.core.userdetails.UserDetails;
  import org.springframework.security.core.userdetails.UserDetailsService;
  import org.springframework.security.core.userdetails.UsernameNotFoundException;
  import org.springframework.stereotype.Service;
  
  import javax.servlet.http.HttpServletRequest;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Set;
  import java.util.stream.Collectors;
  
  @Service
  public class UserDetailsServiceImpl implements UserDetailsService {
  
      @Autowired
      private UserService userService;
  
      @Autowired
      private ProjectService projectService;
  
      @Autowired
      private AccessRightsService accessRightsService;
      
      @Autowired
      private HttpServletRequest request;
  
      private static final Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
  
      @Override
      public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
          User user;
          Project project;
          final String projectId = request.getParameter(Constants.SESSION_PROJECT_ID);
          logger.debug(&quot;Username: {}, projectId: {}&quot;, username, projectId);
  
          try {
              user = userService.getUserByUsername(username);
          } catch (DataNotFoundException e) {
              throw new UsernameNotFoundException(e.getMessage(), e);
          }
  
          // Value of projectId is from combo box which is filled from table of projects
          // That is why there is no probability that the project will not be found
          project = projectService.getProjectById(Integer.valueOf(projectId));
  
          // User can have different rights for different projects
          List&lt;AccessRightsPermission&gt; accessRights = accessRightsService.getAccessRightsByProject(user.getId(), project.getId());
          Set&lt;GrantedAuthority&gt; authorities = new HashSet&lt;&gt;(accessRights.size());
          authorities.addAll(accessRights.stream().map(right -&gt; new SimpleGrantedAuthority(right.getAlias())).collect(Collectors.toList()));
  
          final CurrentUser currentUser = new CurrentUser(user, project, authorities);
  
          // If to check LOGIN access right to project here, and user entered right credentials
          // then user will see message about invalid credentials.
          // .and().exceptionHandling().accessDeniedHandler(accessDeniedHandler()) at SecurityConfig is not working in this case
  //        if (!currentUser.hasAccessRight(PermissionAliasConstants.LOGIN)) {
  //            throw new AccessDeniedException(String.format(&quot;User \&quot;%s\&quot; is not authorized to login \&quot;%s\&quot; project&quot;, user.getUsername(), project.getName()));
  //        }
  
          logger.info(&quot;Logged in user: {}&quot;, currentUser);
          return currentUser;
      }
  }
  </code></pre>
  <p>and LoginController</p>
  <pre><code>package org.aze.accountingprogram.controllers;
  
  import org.springframework.stereotype.Controller;
  import org.springframework.web.bind.annotation.RequestMapping;
  import org.springframework.web.bind.annotation.RequestMethod;
  import org.springframework.web.bind.annotation.RequestParam;
  import org.springframework.web.servlet.ModelAndView;
  
  import java.util.Optional;
  
  @Controller
  public class LoginController {
  
      @RequestMapping(value = &quot;/login&quot;, method = RequestMethod.GET)
      public ModelAndView getLoginPage(@RequestParam Optional&lt;String&gt; error) {
          return new ModelAndView(&quot;login&quot;, &quot;error&quot;, error);
      }
  
  }
  </code></pre>
  <p><code>successHandler()</code> works and application throws <code>AccessDeniedException</code> if user has not access right to login to the project. But <code>accessDeniedHandler()</code> doesn't work and doesn't send HTTP 403. Instead I receive HTTP 500.</p>
  <p>How to return response with HTTP 403 and message of exception (e.g. &quot;User &quot;tural&quot; is not authorized to login &quot;AZB&quot; project&quot;) and to handle it in <code>LoginController</code> (using <code>@ResponseStatus(HttpStatus.FORBIDDEN)</code> or <code>@ExceptionHandler</code>)?</p>