Skip to content

Spring Auth Server

Implementation of the OAuth2.1 and OpenID Connect 1.0 standards. The Authorization server maintains User accounts, and it issues Access Tokens for Applications which need to access resources (API) maintained by the Resource server.

For convenience, add following record into OS hosts file:

127.0.0.1 auth-server
  1. Spring Security framework includes support for standard OAuth2 Authorization server. You can take advantage of Spring Boot autoconfiguration, which defines and initializes required beans. You can still fine tune several aspects of Auth Server in Configuration class of your application. Let's begin and add required dependency into build.gradle file.

     implementation 'org.springframework.boot:spring-boot-starter-oauth2-authorization-server'
    
  2. Next you will provide basic configuration of the ServletFilterChain. Spring provides required filters and related components, so you do not need to implement them. But usually you need to configure some details. Open the OAuthServerSecurityConfig class. Add @EnableWebSecurity class level annotation. You will define two beans of type ServletFilterChain. One for endpoints provided by Auth Server itself, and another one for application which handles User Authentication (Form Login). The OAuth Server specific ServletFilterChain Bean should be initialized before the application one. You use @Order annotation.

  3. The ServletFilterChain for Auth Server endpoints. There is also example how to customize default server endpoints, the Userinfo endpoint in this case. There is userInfoMapper function, which maps User details attributes into Userinfo response. Notice the @Order annotation.

        // Security filter chain configuration for Auth Server endpoints
        // http://auth-server:9000/oauth2/jwks
        // http://auth-server:9000/oauth2/authorize
        // http://auth-server:9000/oauth2/authorize
        // etc.
        @Bean
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public SecurityFilterChain authServerSecurityFilterChain(final HttpSecurity http) throws Exception {
           OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
           RequestMatcher endpointsMatcher = http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).getEndpointsMatcher();
    
            // This function code gives idea how to encode claims info UserInfo provided by /userinfo endpoint
            // In production Users are stored in database and User Account usually consists of
            // properties as user first name, last name, ande-mail address.
           Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper = (context) -> {
                OidcUserInfoAuthenticationToken authentication = context.getAuthentication();
                Map<String,Object> newClaims = new HashMap<>();
                UserDetails userDetails =  userDetailsService().loadUserByUsername(authentication.getName());
                newClaims.put("name", userDetails.getUsername());
                newClaims.put("user_name", userDetails.getUsername());
                newClaims.put("email", userDetails.getUsername() + "@email.net");
                newClaims.put("roles", userDetails.getAuthorities().stream().map(r -> "ROLE_" + r.getAuthority()).toList());
                List<String> audience = new ArrayList<>();
                audience.add("library-api");
                newClaims.put("aud", audience);
                return new OidcUserInfo(newClaims);
            };
    
           // Example of Auth Server endpoints customization -> Userinfo.
           http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc((oidc) -> oidc
                    .providerConfigurationEndpoint(Customizer.withDefaults())
                    .clientRegistrationEndpoint(Customizer.withDefaults())
                    .userInfoEndpoint((userInfo) -> userInfo
                            .userInfoMapper(userInfoMapper)
                    ));    // Enable OpenID Connect 1.0
    
           http
             .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()))
             .exceptionHandling((exceptions) -> exceptions
             .defaultAuthenticationEntryPointFor(
                new LoginUrlAuthenticationEntryPoint("/login"),
                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
             ));
           return http.build();
      }
    
  4. Now add second _SecurityFilterChain_bean definition for applications.

      // Security Filter Chain configuration for end user login
      // Authorization Server provides login page, an application is redirected to
      // upon User login via OAuth Login.
      @Bean
      @Order(2)
      SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest()
                       .authenticated())
                       .formLogin(withDefaults());
        return http.build();
      }
    
  5. The Auth Server issues access token upon successful Authentication of Client (user, machine, device). You can customize, what should be encoded into token. If you use RBAC access control, an access token should include User's roles. Example how you can achieve this:

       // Encode GrantedAuthorities as Roles into OAuth2 access token
       // OAuth2 by default does not recognize Roles, but defines Scopes
       @Bean
       public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
           return context -> {
               if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
                       Authentication authentication = context.getPrincipal();
                       if(authentication instanceof UsernamePasswordAuthenticationToken) {
                           context.getClaims().claim("roles",
                                   ((UserDetails) authentication.getPrincipal()).getAuthorities().stream()
                                           .map(GrantedAuthority::getAuthority).collect(Collectors.toList()));
                       // Hardcode ROLE_ADMIN into access token issued as part of client credentials flow
                       // Just for this example project
                       } else if(authentication instanceof OAuth2ClientAuthenticationToken) {
                           context.getClaims().claim("roles", new String[] {"ROLE_ADMIN"});
                       }
               }
           };
       }
    
  6. Authorization Server maintains also OAuth2 clients. Clients are stored in database for production system. Spring enables programmatic client definition, or you can use application.yaml file. Auth Server maintains clients in client registry. Let's define at least one client in application.yaml.

       spring:    
         security:    
           oauth2:
             authorizationserver:
               issuer: "http://auth-server:9000"
               client:
                 app-client:
                   registration:
                     client-id: "app-client"
                     client-secret: "{noop}secret"
                     client-name: "Application Client"
                     client-authentication-methods:
                       - none
                       - client_secret_post
                       - client_secret_basic
                     authorization-grant-types:
                       - authorization_code
                       - refresh_token
                       - client_credentials
                     redirect-uris:
                       - http://localhost:8080/login/oauth2/code/app-client
                       - http://127.0.0.1:8080/authorized
                       - https://oidcdebugger.com/debug
                     scopes:
                       - "openid"
                       - "hello.get"
                       - "hello.post"
                       - "hello.admin.get"
                   require-authorization-consent: false
    

    The client id is app-client and it is configured to support three most common authorization grants authorization_code, refresh_token, and client_credentials. There are also supported OAuth2 scopes defined. The authorization consent is switched off for now.

  7. Everything should be setup. Start the application. You can test Auth Server's endpoint with postman for example. You have probably noticed, there are two Users defined in config class: user/password and admin/password. You can use jwt.io to decode tokens.

Standard endpoints

Client authentication

Supported methods:

Default filters

  • org.springframework.security.web.session.DisableEncodeUrlFilter,
  • org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
  • org.springframework.security.web.context.SecurityContextHolderFilter
  • org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.AuthorizationServerContextFilter
  • org.springframework.security.web.header.HeaderWriterFilter
  • org.springframework.web.filter.CorsFilter
  • org.springframework.security.web.csrf.CsrfFilter
  • org.springframework.security.oauth2.server.authorization.oidc.web.OidcLogoutEndpointFilter
  • org.springframework.security.web.authentication.logout.LogoutFilter
  • org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter
  • org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter
  • org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter
  • org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter
  • org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter
  • org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter
  • org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
  • org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
  • org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
  • org.springframework.security.web.savedrequest.RequestCacheAwareFilter
  • org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
  • org.springframework.security.web.authentication.AnonymousAuthenticationFilter
  • org.springframework.security.web.access.ExceptionTranslationFilter
  • org.springframework.security.web.access.intercept.AuthorizationFilter
  • org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter
  • org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter
  • org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter
  • org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceAuthorizationEndpointFilter
  • org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter

Tools