spring-securityspring-sessionspring-oauth2spring-authorization-server

Spring authorization server authenticate for each client


I'm trying to build an Identity Provider using Spring authorization-server that third party applications are going to use for FIM (federated identity management).

We want each OAuth client to require authentication (if a user tries to login with a different client they would need to authenticate for each client).

Out of the box the flow looks like this:

enter image description here

So there's 2 issues.

  1. The /oauth2/authorize endpoint just checks whether or not the sessions principal is authenticated, it doesn't care or know which client the principal was meant for.
  2. There's just a single /login endpoint, so during authentication it doesn't know which client is used.

My best bet here is that I should:

  1. Make the oauth2/authorize endpoint redirection to /login include the query parameter client_id
  2. Create a custom AuthenticationFilter that also adds the client_id to the User principal
  3. Override the authorizationRequestConverter for the oauth2/authorize endpoint and validate that the client in the request is the same as the client stored on the authenticated principal

Am I missing anything or do anyone know of a simpler way of doing this?


Solution

  • Based on your last comment, it seems one possibility is to simply require authentication every time, or at least every time an authorization is requested. In that case, you could clear out the authentication after the authorization code is issued to the client, using a Filter. This doesn't seem ideal and will result in a poor user experience, but may achieve your requirement.

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
    
        @Bean
        @Order(1)
        public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
                throws Exception {
            OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
            // ...
    
            // Add filter to remove the SecurityContext after successful authorization
            http.addFilterAfter(new RemoveSecurityContextOnAuthorizationFilter(), LogoutFilter.class);
    
            return http.build();
        }
    
        private static final class RemoveSecurityContextOnAuthorizationFilter extends OncePerRequestFilter {
    
            private SecurityContextHolderStrategy securityContextHolderStrategy =
                    SecurityContextHolder.getContextHolderStrategy();
    
            private final LogoutHandler logoutHandler = new CompositeLogoutHandler(
                    new CookieClearingLogoutHandler("JSESSIONID"),
                    new SecurityContextLogoutHandler()
            );
    
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
                try {
                    filterChain.doFilter(request, response);
                } finally {
                    String locationHeader = response.getHeader(HttpHeaders.LOCATION);
                    if (locationHeader != null) {
                        UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build();
                        if (uriComponents.getQueryParams().containsKey("code")) {
                            Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
                            this.logoutHandler.logout(request, response, authentication);
                        }
                    }
    
                }
            }
    
        }
    
        // ...
    
    }