The latest version of Spring Boot 3.x integrates OAuth 2.0 to implement authentication and authorization services, third-party application clients and resource services

foreword

Spring Boot 3 has been released for a while, and there are not many materials about Spring Boot 3 on the Internet . In line with the enthusiasm for new technologies, I have learned and researched a lot of new functions and features of Spring Boot 3. Interested students can refer to the official spring data for a comprehensive Detailed introduction of new features/improvements

  • Spring version upgrade to 6.x
  • JDK version at least 17+

There are many new features. This article mainly focuses on the integration of OAuth 2.0. If you quickly develop your own authentication and authorization services, OAuth clients, and resource services

Introduction to the development environment of this article

development dependencies Version
Spring Boot 3.0.2

Development environment port description

Create three new services, corresponding to authentication and authorization service, OAuth client and resource service

Serve port
Authentication and Authorization Service 8080
OAuth client service 8081
resource service 8082

Authentication and Authorization Service

pom.xml dependencies

Spring released the spring-security-oauth2-authorization-server project, the latest version is version 1.0, pom.xml depends on the following

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-authorization-server</artifactId>
        <version>${spring-security-oauth2-authorization-server.version}</version>
    </dependency>
</dependencies>

Create a new Oauth2ServerAutoConfiguration class

package com.wen3.oauth.ss.authserver.authconfigure;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;

@Configuration
public class Oauth2ServerAutoConfiguration {
    
    

    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
    
    
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                .oidc(Customizer.withDefaults());	// Enable OpenID Connect 1.0
        http
                // Redirect to the login page when not authenticated from the
                // authorization endpoint
                .exceptionHandling((exceptions) -> exceptions
                        .authenticationEntryPoint(
                                new LoginUrlAuthenticationEntryPoint("/login"))
                )
                // Accept access tokens for User Info and/or Client Registration
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);

        return http.build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    
    
        http
                .authorizeHttpRequests((authorize) -> authorize
                        .requestMatchers(new AntPathRequestMatcher("/actuator/**"),
                                new AntPathRequestMatcher("/oauth2/**"),
                                new AntPathRequestMatcher("/**/*.json"),
                                new AntPathRequestMatcher("/**/*.html")).permitAll()
                        .anyRequest().authenticated()
                )
                // Form login handles the redirect to the login page from the
                // authorization server filter chain
                .formLogin(Customizer.withDefaults());

        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
    
    
        UserDetails userDetails = User.withDefaultPasswordEncoder()
                .username("test")
                .password("test")
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(userDetails);
    }

    @Bean
    public RegisteredClientRepository registeredClientRepository() {
    
    
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("demo-client-id")
                .clientSecret("{noop}demo-client-secret")
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
//                .tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE).build())
                .redirectUri("http://127.0.0.1:8081/login/oauth2/code/client-id-1")
                .redirectUri("http://127.0.0.1:8081/login/oauth2/code/client-id-2")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .scope("message.read")
                .scope("message.write")
                .scope("user_info")
                .scope("pull_requests")
                // 登录成功后对scope进行确认授权
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                .build();

        return new InMemoryRegisteredClientRepository(registeredClient);
    }

    @Bean
    public JWKSource<SecurityContext> jwkSource() {
    
    
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }

    private static KeyPair generateRsaKey() {
    
    
        KeyPair keyPair;
        try {
    
    
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        }
        catch (Exception ex) {
    
    
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
    
    
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
    
    
        return AuthorizationServerSettings.builder().build();
    }
}

main function

@SpringBootApplication
public class OauthServerApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(OauthServerApplication.class, args);
    }
}

yml configuration

server:
  port: 8080

Third-party application OAuth client

pom.xml dependencies

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>
</dependencies>

Create a new Oauth2ClientAutoConfiguration class

package com.wen3.oauth.ss.authclient.autoconfigure;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class Oauth2ClientAutoConfiguration {
    
    

    @Bean
    public SecurityFilterChain authorizationClientSecurityFilterChain(HttpSecurity http) throws Exception {
    
    
        http
                .authorizeHttpRequests()
                .anyRequest().authenticated()
                .and().logout()
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
                .and().oauth2Client()
                .and().oauth2Login();

        return http.build();
    }
}

Create a new OauthClientDemoController class

package com.wen3.oauth.ss.authclient.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class OauthClientDemoController {
    
    

    @RequestMapping(path = "/hello")
    public String demo() {
    
    
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        log.info("authentication: {}", authentication);

        return "hello";
    }
}

main function

@SpringBootApplication
public class OauthServerApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(OauthServerApplication.class, args);
    }
}

yml configuration

server:
  port: 8081
  servlet:
    session:
      cookie:
        # 需要更换存放sessionId的cookie名字,否则认证服务和客户端的sessionId会相互覆盖
        name: JSESSIONID-2

spring:
  security:
    oauth2:
      client:
        registration:
          client-id-1:
            provider: demo-client-id
            client-id: demo-client-id
            client-secret: demo-client-secret
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/{action}/oauth2/code/{registrationId}'
            #            client-authentication-method: POST
            scope: user_info, pull_requests
            client-name: demo-client-id
          client-id-2:
            provider: demo-client-id2
            client-id: demo-client-id
            client-secret: demo-client-secret
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/{action}/oauth2/code/{registrationId}'
            #            client-authentication-method: POST
            scope: user_info, pull_requests
            client-name: demo-client-id2
        provider:
          demo-client-id:
            authorization-uri: http://127.0.0.1:8080/oauth2/authorize
            token-uri: http://127.0.0.1:8080/oauth2/token
            user-info-uri: http://127.0.0.1:8082/user/info
            user-name-attribute: name
            jwk-set-uri: http://127.0.0.1:8080/oauth2/jwks
          demo-client-id2:
            authorization-uri: http://127.0.0.1:8080/oauth2/authorize
            token-uri: http://127.0.0.1:8080/oauth2/token
            user-info-uri: http://127.0.0.1:8082/user/info
            user-name-attribute: name
            jwk-set-uri: http://127.0.0.1:8080/oauth2/jwks

resource service

pom.xml dependencies

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
</dependencies>

Create a new ResourceServerAutoConfiguration class

package com.wen3.oauth.ss.resourceserver.autoconfigure;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ResourceServerAutoConfiguration {
    
    

    @Bean
    public SecurityFilterChain resourceServerSecurityFilterChain(HttpSecurity http) throws Exception {
    
    
        http.authorizeHttpRequests().anyRequest().authenticated().and()
                .oauth2ResourceServer().jwt();

        return http.build();
    }
}

Create a new UserController class

package com.wen3.oauth.ss.resourceserver.controller;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class UserController {
    
    

    @RequestMapping(path = "/user/info", method = {
    
    RequestMethod.GET,RequestMethod.POST}, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public Map<String, Object> getUser(HttpServletRequest request, HttpServletResponse response) {
    
    
        Map<String, Object> map = new HashMap<>();
        map.put("name", "xxx");
        return map;
    }
}

main function

package com.wen3.oauth.ss.resourceserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ResourceServerApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(ResourceServerApplication.class, args);
    }
}

yml configuration

server:
  port: 8082

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: http://127.0.0.1:8080/oauth2/jwks

demo

  • Enter in the browser address barhttp://127.0.0.1:8081/hello
    insert image description here

    Because multiple clients are configured, the user will choose which client to use for OAuth login

  • Select the first client-id-1
  • Jump to the authentication and authorization service to log in
  • Enter username test, password test
    insert image description here
  • Jump to the authorization page after successful login
    insert image description here
  • Check the scope to confirm the authorization
  • redirect request
    insert image description here

All of the above pages are Spring’s default, real business development will customize these pages

OAuth client openid demo

  • Type in the browserhttp://127.0.0.1:8080/.well-known/openid-configuration
    {
          
          "issuer":"http://127.0.0.1:8080","authorization_endpoint":"http://127.0.0.1:8080/oauth2/authorize","token_endpoint":"http://127.0.0.1:8080/oauth2/token","token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","client_secret_jwt","private_key_jwt"],"jwks_uri":"http://127.0.0.1:8080/oauth2/jwks","userinfo_endpoint":"http://127.0.0.1:8080/userinfo","response_types_supported":["code"],"grant_types_supported":["authorization_code","client_credentials","refresh_token"],"revocation_endpoint":"http://127.0.0.1:8080/oauth2/revoke","revocation_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","client_secret_jwt","private_key_jwt"],"introspection_endpoint":"http://127.0.0.1:8080/oauth2/introspect","introspection_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","client_secret_jwt","private_key_jwt"],"subject_types_supported":["public"],"id_token_signing_alg_values_supported":["RS256"],"scopes_supported":["openid"]}
    
  • If it is configured , it will be called to get the configuration information issuer-uriat startup , and if it is configured, it will be replaced with${issuer-uri}/.well-known/openid-configurationproviderissuer-uripath/.well-known/openid-configuration
  • /.well-known/openid-configurationThe address obtained from this interface user-info-uriis http://127.0.0.1:8080/userinfoso the user information will be obtained from the authorization service
  • In order to allow /userinfothe interface of the authorization service to return normally, you need to registrationadd it to the scope during configuration openid, and at the same time, you need to add at least one of profile, email, address, phoneto the scope. The modified yml configuration is as follows
server:
  port: 8081
  servlet:
    session:
      cookie:
        # 需要更换存放sessionId的cookie名字,否则认证服务和客户端的sessionId会相互覆盖
        name: JSESSIONID-2

spring:
  security:
    oauth2:
      client:
        registration:
          client-id-1:
            provider: demo-client-id
            client-id: demo-client-id
            client-secret: demo-client-secret
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/{action}/oauth2/code/{registrationId}'
#            client-authentication-method: POST
            scope: openid, profile, user_info, pull_requests
            client-name: demo-client-id
          client-id-2:
            provider: demo-client-id2
            client-id: demo-client-id
            client-secret: demo-client-secret
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/{action}/oauth2/code/{registrationId}'
#            client-authentication-method: POST
            scope: openid, profile, user_info, pull_requests
            client-name: demo-client-id2
        provider:
          demo-client-id:
            issuer-uri: http://127.0.0.1:8080
          demo-client-id2:
            issuer-uri: http://127.0.0.1:8080
  • Authorization page will change
    insert image description here

Finish

After studying the relevant source code of SpringBoot3, I basically experienced all the functions. This article is mainly to demonstrate the latest version of SpringBoot integrated OAuth function. The principle behind it, if you have any questions, you can leave a message to communicate.

Guess you like

Origin blog.csdn.net/friendlytkyj/article/details/128889875