Spring Boot 3.x の最新バージョンは、OAuth 2.0 を統合して、認証および認可サービス、サードパーティのアプリケーション クライアント、およびリソース サービスを実装します。

序文

Spring Boot 3 がリリースされてからしばらく経ちますが、インターネット上にはSpring Boot 3に関する資料があまりありません。新しいテクノロジーに対する熱意に合わせて、私はSpring Boot 3 の多くの新機能や特徴を学び、研究してきました。興味のある学生は、新機能/改善点の包括的な詳細な紹介について公式のデータを参照できます。

  • 春のバージョン 6.x へのアップグレード
  • JDK バージョン 17 以降

多くの新機能があります。この記事では主に OAuth 2.0 の統合に焦点を当てています。独自の認証および認可サービス、OAuth クライアント、およびリソース サービスを迅速に開発する場合は、

この記事の開発環境の紹介

開発の依存関係 バージョン
スプリングブーツ 3.0.2

開発環境ポートの説明

認証および認可サービス、OAuth クライアント、およびリソース サービスに対応する 3 つの新しいサービスを作成します

仕える ポート
認証・認可サービス 8080
OAuthクライアントサービス 8081
リソースサービス 8082

認証・認可サービス

pom.xml の依存関係

Spring はspring-security-oauth2-authorization-server プロジェクトをリリースしました。最新バージョンはバージョン 1.0 です。pom.xml は以下に依存します。

<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>

新しい Oauth2ServerAutoConfiguration クラスを作成する

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();
    }
}

メイン機能

@SpringBootApplication
public class OauthServerApplication {
    
    

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

yml設定

server:
  port: 8080

サードパーティアプリケーションのOAuthクライアント

pom.xml の依存関係

<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>

新しい Oauth2ClientAutoConfiguration クラスを作成する

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();
    }
}

新しい OauthClientDemoController クラスを作成する

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";
    }
}

メイン機能

@SpringBootApplication
public class OauthServerApplication {
    
    

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

yml設定

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

リソースサービス

pom.xml の依存関係

<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>

新しい ResourceServerAutoConfiguration クラスを作成する

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();
    }
}

新しいUserControllerクラスを作成する

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;
    }
}

メイン機能

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設定

server:
  port: 8082

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

デモ

  • ブラウザのアドレスバーに入力しますhttp://127.0.0.1:8081/hello
    ここに画像の説明を挿入

    複数のクライアントが構成されているため、ユーザーは OAuth ログインに使用するクライアントを選択します。

  • 最初の client-id-1 を選択します
  • 認証・認可サービスにジャンプしてログインします
  • ユーザー名テスト、パスワードテストを入力してください
    ここに画像の説明を挿入
  • ログイン成功後、認証ページにジャンプします
    ここに画像の説明を挿入
  • スコープをチェックして権限を確認します
  • リダイレクトリクエスト
    ここに画像の説明を挿入

上記のページはすべて Spring のデフォルトです。実際のビジネス開発ではこれらのページをカスタマイズします。

OAuth クライアントの openid デモ

  • ブラウザに入力してくださいhttp://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"]}
    
  • 構成されている場合は、issuer-uri起動時に構成情報を${issuer-uri}/.well-known/openid-configuration取得するために呼び出されます。構成されている場合は、次のように置き換えられます。providerissuer-uripath/.well-known/openid-configuration
  • このインターフェースから/.well-known/openid-configuration取得したアドレスは、認可サービスからユーザー情報を取得するためのものuser-info-uriです。http://127.0.0.1:8080/userinfo
  • 認可サービスのインターフェースが正常に戻るようにするには、構成中にそれをスコープに追加する/userinfo必要があり、同時に、 、 、 の少なくとも 1 つをスコープに追加する必要があります。以下のとおりでありますregistrationopenidprofileemailaddressphone
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
  • 認証ページが変わります
    ここに画像の説明を挿入

終了

SpringBoot3 の関連ソース コードを学習した後、基本的にすべての機能を体験しました。この記事は主に SpringBoot 統合 OAuth 機能の最新バージョンをデモすることを目的としています。その背後にある原理について、ご質問がある場合は、メッセージを残して連絡してください。 。

おすすめ

転載: blog.csdn.net/friendlytkyj/article/details/128889875