Spring Authorization Server 授权服务器

说明

Spring Authorization Server 遵循Oauth2.1和OpenID Connect 1.0,它建立在Spring Security之上。

最小化项目

创建项目

要求JDK11以上

使用Idea创建一个Maven的Spring Boot(笔者使用的是spring boot 2.7)项目

pom需要引入Authorization Server的配置


    org.springframework.security
    spring-security-oauth2-authorization-server
    0.3.1

复制代码

完整的pom.xml文件如下:



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.7.0
         
    
    com.itlab1024
    Spring_Authorization_Server_0_3_x
    0.0.1-SNAPSHOT
    Spring-Authorization-Server-0.3.0
    Spring-Authorization-Server-0.3.0
    
        17
    
    
        
        
            org.springframework.security
            spring-security-oauth2-authorization-server
            0.3.1
        
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.springframework.boot
            spring-boot-starter-actuator
        
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

复制代码

配置

使用@Bean@Configuration创建配置,这是官方推荐的最小配置。

package com.itlab1024.base;

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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
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.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.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;

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

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    /**
     * 这是个Spring security 的过滤器链,默认会配置
     * 

* OAuth2 Authorization endpoint *

* OAuth2 Token endpoint *

* OAuth2 Token Introspection endpoint *

* OAuth2 Token Revocation endpoint *

* OAuth2 Authorization Server Metadata endpoint *

* JWK Set endpoint *

* OpenID Connect 1.0 Provider Configuration endpoint *

* OpenID Connect 1.0 UserInfo endpoint * 这些协议端点,只有配置了他才能够访问的到接口地址(类似mvc的controller)。 * * @param http * @return * @throws Exception */ @Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http // Redirect to the login page when not authenticated from the // authorization endpoint .exceptionHandling((exceptions) -> exceptions .authenticationEntryPoint( new LoginUrlAuthenticationEntryPoint("/login")) ); return http.build(); } /** * 这个也是个Spring Security的过滤器链,用于Spring Security的身份认证。 * @param http * @return * @throws Exception */ @Bean @Order(2) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) // Form login handles the redirect to the login page from the // authorization server filter chain .formLogin(Customizer.withDefaults()); return http.build(); } /** * 配置用户信息,或者配置用户数据来源,主要用于用户的检索。 * @return */ @Bean public UserDetailsService userDetailsService() { UserDetails userDetails = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build(); return new InMemoryUserDetailsManager(userDetails); } /** * oauth2 用于第三方认证,RegisteredClientRepository 主要用于管理第三方(每个第三方就是一个客户端) * @return */ @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("messaging-client") .clientSecret("{noop}secret") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc") .redirectUri("http://127.0.0.1:8080/authorized") .scope(OidcScopes.OPENID) .scope("message.read") .scope("message.write") .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) .build(); return new InMemoryRegisteredClientRepository(registeredClient); } /** * 用于给access_token签名使用。 * @return */ @Bean public JWKSource 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); } /** * 生成秘钥对,为jwkSource提供服务。 * @return */ 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; } /** * 配置Authorization Server实例 * @return */ @Bean public ProviderSettings providerSettings() { return ProviderSettings.builder().build(); } } 复制代码

至此最小化项目完成,这就能够完成oauth2的授权。

测试

授权码模式

浏览器访问 http://127.0.0.1:8080/oauth2/authorize?response_type=code&client_id=messaging-client&scope=message.read&redirect_uri=http://127.0.0.1:8080/authorized

需要注意的是redirect_uri必须是RegisteredClient实例设置的。

Spring Authorization Server 授权服务器_第1张图片

输入用户名(user)密码(password)后

Spring Authorization Server 授权服务器_第2张图片

提交后,会自动跳转到redirect_uri地址,并且地址会紧跟着code

Spring Authorization Server 授权服务器_第3张图片

返回的code是

axia5-kuIIzO1D1eu1V_02KawWIkRydiZrDEPAtLhNlYC7kLeUazD_bh5UXGQVJj7W2gxC1zpQJuQ2D9ZVrQyVfufxMYyv4fkjjMitiQ1gH-bGQ6KqGy5egeC15NfHBt
复制代码

接下来需要使用这个code获取token(我用postman请求)。

获取token

授权码获取token的请求地址是oauth2/token,post请求:

Spring Authorization Server 授权服务器_第4张图片

上线这个三个参数是必须的,并且要跟代码中设置完全一直,另外获取token要传递client_id和client_secret参数,默认不支持使用表单传递,要通过header传递。比如在postman中

Spring Authorization Server 授权服务器_第5张图片

其实上线的操作实际上就是在header中传递了一个header,key=Authorization, value是client_id:client_secret,然后使用base64加密的字符串,然后前面加上Basic (注意后面有空格)。对于我这个例子来说就是Basic bWVzc2FnaW5nLWNsaWVudDpzZWNyZXQ=

返回结果是:

{
    "access_token": "eyJraWQiOiIxNGMxOTM5Yy02YzcxLTQ1MGMtOTg4OS1jOTdiNjM5NTE3ZmEiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoibWVzc2FnaW5nLWNsaWVudCIsIm5iZiI6MTY1NDMyMzg0OSwic2NvcGUiOlsibWVzc2FnZS5yZWFkIl0sImlzcyI6Imh0dHA6XC9cLzEyNy4wLjAuMTo4MDgwIiwiZXhwIjoxNjU0MzI0MTQ5LCJpYXQiOjE2NTQzMjM4NDl9.r6KSDrVbd65n_KRC2SnOH93nGYnoP2uWZwyiamke5PGWa72OHPxgwktgAxK0gHIjQ_sgh5tD4R2swb9bARIn2ZvUb3DtIXpLzEoCGRu4DqJoaUFnj71oAvX1MSruHeLqQaCwL2nJ-C-TNwj_mFHzcZFdaFZRQIIIkaG46Zgj1G0BCxpKtJy3FVIcbGJK-HYHHdh2XOMAIyCA5MrDn2VtZmJDwSbhSSEdU8jY8n41LPUd79koozIH_6onrx-y9ly3-evV3cAGBvsWA26h6PAR0Nxv47LXaUM5Hn_6OA20noCi53CC0qdahRJSs9eHpXsLd0rpjPDrk4nK9S7G0wTIlw",
    "refresh_token": "2CvlhRXdg6EK0ZzS_3kI-AI-AeCXBFpvD1krSbu28sTundjXnwvZT4AuQ03rtUr5TD2VFUWyuAJ68fAmNIonUVSRaDKzdx-Z2Z61np_HlcBF2iUxLRyl4JW9jeBQ7CZG",
    "scope": "message.read",
    "token_type": "Bearer",
    "expires_in": 299
}
复制代码

刷新token

Spring Authorization Server 授权服务器_第6张图片

结果是:

{
    "access_token": "eyJraWQiOiIxNGMxOTM5Yy02YzcxLTQ1MGMtOTg4OS1jOTdiNjM5NTE3ZmEiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoibWVzc2FnaW5nLWNsaWVudCIsIm5iZiI6MTY1NDMyNDU1OCwic2NvcGUiOlsibWVzc2FnZS5yZWFkIl0sImlzcyI6Imh0dHA6XC9cLzEyNy4wLjAuMTo4MDgwIiwiZXhwIjoxNjU0MzI0ODU4LCJpYXQiOjE2NTQzMjQ1NTh9.pOsWaoBrNrJyGTYOyvlN1d4FrKjpo2PxRIi7SHLfYjQ0xuqnuYaqPVOhs8rw9VN1hhjpl1d59RixOXkOAIK6PUI_-y_6MTmXL71YZ1lmrifhZ24bYkqXQKMAsbFvj3bXn6RyVnTwFsiy9IzZBRK_-PTPWQd9DbaYkmpryeZtGBqUFYAyBDrgCTYgw0SEoDI2qEX_W3Bgxiz9yTDH5Gszdbe0CzxvHP7LOGDi7-q-WziGhQCoMfFMK0P2WvzeAagseUEUpoSJTk8IMh-_8EgatrwilSYjkKKwgf_-hd9UXDi4bsW9MNA9iIDCYqKJ5dflTutoUJX8oxpnYTwP8iGNDA",
    "refresh_token": "2CvlhRXdg6EK0ZzS_3kI-AI-AeCXBFpvD1krSbu28sTundjXnwvZT4AuQ03rtUr5TD2VFUWyuAJ68fAmNIonUVSRaDKzdx-Z2Z61np_HlcBF2iUxLRyl4JW9jeBQ7CZG",
    "scope": "message.read",
    "token_type": "Bearer",
    "expires_in": 299
}
复制代码

简化模式

在oauth2.1中被移除

客户端模式

获取token

Spring Authorization Server 授权服务器_第7张图片

结果是:

{
    "access_token": "eyJraWQiOiIxNGMxOTM5Yy02YzcxLTQ1MGMtOTg4OS1jOTdiNjM5NTE3ZmEiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJtZXNzYWdpbmctY2xpZW50IiwiYXVkIjoibWVzc2FnaW5nLWNsaWVudCIsIm5iZiI6MTY1NDMyNDc5Nywic2NvcGUiOlsib3BlbmlkIiwibWVzc2FnZS5yZWFkIiwibWVzc2FnZS53cml0ZSJdLCJpc3MiOiJodHRwOlwvXC8xMjcuMC4wLjE6ODA4MCIsImV4cCI6MTY1NDMyNTA5NywiaWF0IjoxNjU0MzI0Nzk3fQ.CMWqUxhjOlYzg6SY5uKkWIQDy96XV559TmG2YHZYlwe08a6u7xrwEm_b9m3rd9-QqkQpuxbFBD_o4dk3wl7PKVlZuWNCVrcvEXMFREexU6wwKtzTWKTBWYtDOAvKJN81iJ34UqsXRQ_M3xvUlpVXMjFKY9c3hsP9te8FpfcMi4IZfnHS79CunTh7tgovEo53nu9UNQ2qKy_MR9a13cXpe_AepOP_68gaLO-SAdRI-H9L4e57Y3w7Lq-UWUxywtnAtEcnm_PTGaA-gIEvCiN0rx6pZFBOxv-58OhNfp79oTN33yBDN-E3dSWgioQDp-Sc7kIb8z-rzXa1ZQgx19xTGg",
    "scope": "openid message.read message.write",
    "token_type": "Bearer",
    "expires_in": 299
}
复制代码

客户端模式没有刷新token模式。

密码模式

在oauth2.1中被移除

以上是最小化示例,我上传到了github,地址是:github.com/ITLab1024/S… 标签是:v1.0.0

配置

默认配置

之前已经通过最小配置,完成了一个Spring Authorization Server项目,本章学习下关于配置的内容。

Spring Authorization Server还提供了一种实现最小配置的默认配置形式。就是通过OAuth2AuthorizationServerConfiguration这个类。

看下这个类的源码:

/*
 * Copyright 2020-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.security.config.annotation.web.configuration;

import java.util.HashSet;
import java.util.Set;

import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RequestMatcher;

/**
 * {@link Configuration} for OAuth 2.0 Authorization Server support.
 *
 * @author Joe Grandja
 * @since 0.0.1
 * @see OAuth2AuthorizationServerConfigurer
 */
@Configuration(proxyBeanMethods = false)
public class OAuth2AuthorizationServerConfiguration {

   @Bean
   @Order(Ordered.HIGHEST_PRECEDENCE)
   public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
      applyDefaultSecurity(http);
      return http.build();
   }

   // @formatter:off
   public static void applyDefaultSecurity(HttpSecurity http) throws Exception {
      OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
            new OAuth2AuthorizationServerConfigurer<>();
      RequestMatcher endpointsMatcher = authorizationServerConfigurer
            .getEndpointsMatcher();

      http
         .requestMatcher(endpointsMatcher)
         .authorizeRequests(authorizeRequests ->
            authorizeRequests.anyRequest().authenticated()
         )
         .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
         .apply(authorizationServerConfigurer);
   }
   // @formatter:on

   public static JwtDecoder jwtDecoder(JWKSource jwkSource) {
      Set jwsAlgs = new HashSet<>();
      jwsAlgs.addAll(JWSAlgorithm.Family.RSA);
      jwsAlgs.addAll(JWSAlgorithm.Family.EC);
      jwsAlgs.addAll(JWSAlgorithm.Family.HMAC_SHA);
      ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor<>();
      JWSKeySelector jwsKeySelector =
            new JWSVerificationKeySelector<>(jwsAlgs, jwkSource);
      jwtProcessor.setJWSKeySelector(jwsKeySelector);
      // Override the default Nimbus claims set verifier as NimbusJwtDecoder handles it instead
      jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
      });
      return new NimbusJwtDecoder(jwtProcessor);
   }

   @Bean
   RegisterMissingBeanPostProcessor registerMissingBeanPostProcessor() {
      RegisterMissingBeanPostProcessor postProcessor = new RegisterMissingBeanPostProcessor();
      postProcessor.addBeanDefinition(ProviderSettings.class, () -> ProviderSettings.builder().build());
      return postProcessor;
   }

}
复制代码

这里注入一个叫做authorizationServerSecurityFilterChain的bean,这跟我之前最小化项目时实现的基本是相同的。

有了这个bean,就会支持如下协议端点:

  • OAuth2 Authorization endpoint
  • OAuth2 Token endpoint
  • OAuth2 Token Introspection endpoint
  • OAuth2 Token Revocation endpoint
  • OAuth2 Authorization Server Metadata endpoint
  • JWK Set endpoint
  • OpenID Connect 1.0 Provider Configuration endpoint
  • OpenID Connect 1.0 UserInfo endpoint

接来我我尝试使用OAuth2AuthorizationServerConfiguration这个类来实现一个Authorization Server

本次我会将 Spring Security和Authorization Server的配置分开

Spring Security 使用 SecurityConfig 类,创建一个新的Authorization Server配置类 AuthorizationServerConfig

SecurityConfig类配置如下:

package com.itlab1024.base;

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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
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.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.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;

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

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    /**
     * 这个也是个Spring Security的过滤器链,用于Spring Security的身份认证。
     *
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
            throws Exception {
        http
                .authorizeHttpRequests((authorize) -> authorize
                        .anyRequest().authenticated()
                )
                // Form login handles the redirect to the login page from the
                // authorization server filter chain
                .formLogin(Customizer.withDefaults());

        return http.build();
    }

    /**
     * 配置用户信息,或者配置用户数据来源,主要用于用户的检索。
     *
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails userDetails = User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(userDetails);
    }
}
复制代码

代码如下:

package com.itlab1024.base;

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.context.annotation.Import;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
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.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.ClientSettings;

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

@Configuration
@Import(OAuth2AuthorizationServerConfiguration.class)
public class AuthorizationServerConfig {

    /**
     * oauth2 用于第三方认证,RegisteredClientRepository 主要用于管理第三方(每个第三方就是一个客户端)
     *
     * @return
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("messaging-client")
                .clientSecret("{noop}secret")
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
                .redirectUri("http://127.0.0.1:8080/authorized")
                .scope(OidcScopes.OPENID)
                .scope("message.read")
                .scope("message.write")
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                .build();

        return new InMemoryRegisteredClientRepository(registeredClient);
    }

    /**
     * 用于给access_token签名使用。
     *
     * @return
     */
    @Bean
    public JWKSource 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);
    }

    /**
     * 生成秘钥对,为jwkSource提供服务。
     *
     * @return
     */
    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;
    }
}
复制代码

至此可以实现了Authorization Server

测试客户端调用。

授权码模式测试

Spring Authorization Server 授权服务器_第8张图片

Spring Authorization Server 授权服务器_第9张图片


        "client_secret_post",
        "client_secret_jwt",
        "private_key_jwt"
    ],
    "jwks_uri": "http://localhost:8080/oauth2/jwks",
    "userinfo_endpoint": "http://localhost:8080/userinfo",
    "response_types_supported": [
        "code"
    ],
    "grant_types_supported": [
        "authorization_code",
        "client_credentials",
        "refresh_token"
    ],
    "subject_types_supported": [
        "public"
    ],
    "id_token_signing_alg_values_supported": [
        "RS256"
    ],
    "scopes_supported": [
        "openid"
    ]
}
复制代码

客户端注册端点

OpenID Connect 1.0客户端注册端点默认禁用,因为许多部署不需要动态客户端注册。

增加如下配置:

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
  OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
    new OAuth2AuthorizationServerConfigurer<>();
  RequestMatcher endpointsMatcher = authorizationServerConfigurer
    .getEndpointsMatcher();
  http
    .requestMatcher(endpointsMatcher)
    .authorizeRequests(authorizeRequests ->
                       authorizeRequests.anyRequest().authenticated()
                      )
    .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
    .apply(authorizationServerConfigurer);
  http
    // Redirect to the login page when not authenticated from the
    // authorization endpoint
    .exceptionHandling((exceptions) -> exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")));
  // 也可以使用如下代码,跳转到login page
  //        http.formLogin(Customizer.withDefaults());

  authorizationServerConfigurer
    .oidc(oidc -> {
      // 用户信息
      oidc.userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint.userInfoMapper(oidcUserInfoAuthenticationContext -> {
        OAuth2AccessToken accessToken = oidcUserInfoAuthenticationContext.getAccessToken();
        Map claims = new HashMap<>();
        claims.put("url", "https://github.com/ITLab1024");
        claims.put("accessToken", accessToken);
        claims.put("sub", oidcUserInfoAuthenticationContext.getAuthorization().getPrincipalName());
        return new OidcUserInfo(claims);

      }));
      // 客户端注册
      oidc.clientRegistrationEndpoint(Customizer.withDefaults());
    }
         );
  http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);


  return http.build();
}
复制代码

请注意:

Spring Authorization Server 授权服务器_第10张图片

测试:

请求:http://127.0.0.1:8080/oauth2/authorize?response_type=code&client_id=messaging-client&scope=client.create&redirect_uri=http://127.0.0.1:8080/authorized

输入用户名密码后:

Spring Authorization Server 授权服务器_第11张图片

勾选scope,并提交,从地址栏中获取到code。

qdd5jgQxyfAlZELZr8kclamAaqkXlWxoEPa5h_7E0BbNLDkgfjDLnpo5SmPrFbZxZJVwO3KCMpmQD5D_y-2e_in8UD50ZpDPNs5GXjvOs_vU-tjhf0V-A7C9H1Phf9gw
复制代码

获取accessToken

Spring Authorization Server 授权服务器_第12张图片

json结果是:

{
    "access_token": "eyJjdXN0b21lckhlYWRlciI6Iui_meaYr-S4gOS4quiHquWumuS5iWhlYWRlciIsImFsZyI6IlJTMjU2Iiwia2lkIjoiOWI1ZDkyZDAtZTY3ZC00ZDVjLTkyZmUtN2M2NDM3Y2Y4OGI4In0.eyJzdWIiOiJ1c2VyIiwiYXVkIjoibWVzc2FnaW5nLWNsaWVudCIsImN1c3RvbWVyQ2xhaW0iOiLov5nmmK_kuIDkuKroh6rlrprkuYlDbGFpbSIsIm5iZiI6MTY1Njg1NjAyMCwic2NvcGUiOlsiY2xpZW50LmNyZWF0ZSJdLCJpc3MiOiJodHRwOlwvXC8xMjcuMC4wLjE6ODA4MCIsImV4cCI6MTY1Njg1NjMyMCwiaWF0IjoxNjU2ODU2MDIwfQ.ODhSpJ73yrkHvKbcFDbjKozV2q1vGqim1kgKXLxgc402phmS0JvhvviXqbc_udeDSZEdozy9iNCoEENcLWRsBh_RPRiQ6gx-IVXZouTnQkvl_nJChSDRv6a75HqDnxW3Q_iFQlxJx9wbXUIJ1k4mt2q0VqfPS5tTKoblhuEdAKyL90RLAEP1BSzxFFEYeSKIl-nEwsUWsZY9mqnzw33Z1dRVgZLrhIdhpwg41sSFKqw6B5yRzOHrc_0_nAW10TJK9ONbxrJvTc2ZHI8yLBjRoqEdFPgcOMkBHAH8baGsoHNLGEbVK4U8fb_hvZ1F9Ara3Aw3_yga4kJciGi_yt6y-A",
    "refresh_token": "Hux9d04U-mi1GIFhy3t244xpS9wvrDaPSYXoR0gRP6vlQuKqAWlEER8RbdP7EAGOuFGoAyZ-nKAoxBL5JZxuwIyoGECrgHTEnof5eDaMVdrbNzwr8B8xxSZ0f-JicBys",
    "scope": "client.create",
    "token_type": "Bearer",
    "expires_in": 299
}
复制代码

通过获取的accessToken,POST请求/connect/register接口

Spring Authorization Server 授权服务器_第13张图片

返回结果:

{
    "client_id": "Gw-kfSnh0R948WSJnAdIQCbXtcfifFhDygB7vPOWieI",
    "client_id_issued_at": 1656856140,
    "client_name": "My Example",
    "client_secret": "lmngmSlQNBJdCpBO4Ha2gubD0jtK_3Px2khf-u_tpyFZAT6imLV5F_bcsyZCZXPd",
    "redirect_uris": [
        "https://client.example.org/callback",
        "https://client.example.org/callback2"
    ],
    "grant_types": [
        "authorization_code"
    ],
    "response_types": [
        "code"
    ],
    "token_endpoint_auth_method": "client_secret_basic",
    "id_token_signed_response_alg": "RS256",
    "registration_client_uri": "http://127.0.0.1:8080/connect/register?client_id=Gw-kfSnh0R948WSJnAdIQCbXtcfifFhDygB7vPOWieI",
    "registration_access_token": "eyJjdXN0b21lckhlYWRlciI6Iui_meaYr-S4gOS4quiHquWumuS5iWhlYWRlciIsImFsZyI6IlJTMjU2Iiwia2lkIjoiOWI1ZDkyZDAtZTY3ZC00ZDVjLTkyZmUtN2M2NDM3Y2Y4OGI4In0.eyJzdWIiOiJHdy1rZlNuaDBSOTQ4V1NKbkFkSVFDYlh0Y2ZpZkZoRHlnQjd2UE9XaWVJIiwiYXVkIjoiR3cta2ZTbmgwUjk0OFdTSm5BZElRQ2JYdGNmaWZGaER5Z0I3dlBPV2llSSIsImN1c3RvbWVyQ2xhaW0iOiLov5nmmK_kuIDkuKroh6rlrprkuYlDbGFpbSIsIm5iZiI6MTY1Njg1NjE0MCwic2NvcGUiOlsiY2xpZW50LnJlYWQiXSwiaXNzIjoiaHR0cDpcL1wvMTI3LjAuMC4xOjgwODAiLCJleHAiOjE2NTY4NTY0NDAsImlhdCI6MTY1Njg1NjE0MH0.iUPvyaCfpumCMesa-5JKsYdA9w-p6BZvh-7Fsn6vaysJKzCOHdr5QrFsvudnmyJYtv1rmrC8lbljycMXGcGRgBUSL1Zi5AFSoGCflYnVLvgHEQE70WLlcTFf4SW3JTQTBd2iTpTxAnNOQLG8UYgfDj4KQRAqkmdUUxNc-mPdpUNJQcDciNUgvuW5YhED3aPEVEp4tNfF47umyMUJ5kSGdFNOc3JDVI_nFE3TajxP1WYzvbF5SY8rq7TEtQPz1SB-YwSKZwusABjRQrmQAn0-oI-c-MvxjbyzMQPG6XXDArVv9rSfuouShU6VZYnPhL4t_D11RLPvL62AiLNya1VzIQ",
    "client_secret_expires_at": 0
}
复制代码

查看数据库表(oauth2_registered_client),可以看到多了一条记录

id client_id client_id_issued_at client_secret client_secret_expires_at client_name client_authentication_methods authorization_grant_types redirect_uris scopes client_settings token_settings
f60a7102-08f6-4217-99c8-6d73a5dba9ef Gw-kfSnh0R948WSJnAdIQCbXtcfifFhDygB7vPOWieI 2022-07-03 21:49:01 lmngmSlQNBJdCpBO4Ha2gubD0jtK_3Px2khf-u_tpyFZAT6imLV5F_bcsyZCZXPd NULL My Example client_secret_basic authorization_code client.example.org/callback,ht… {"@class":"java.util.Collections$UnmodifiableMap","settings.client.require-proof-key":true,"settings.client.require-authorization-consent":true} {"@class":"java.util.Collections$UnmodifiableMap","settings.token.reuse-refresh-tokens":true,"settings.token.id-token-signature-algorithm":["org.springframework.security.oauth2.jose.jws.SignatureAlgorithm","RS256"],"settings.token.access-token-time-to-live":["java.time.Duration",300.000000000],"settings.token.access-token-format":{"@class":"org.springframework.security.oauth2.core.OAuth2TokenFormat","value":"self-contained"},"settings.token.refresh-token-time-to-live":["java.time.Duration",3600.000000000]}

说明客户端已经注册成功。


Spring Authorization Server其实目前也在不断完善中,很多功能也是不全的。

如果有人对openid connect1.0协议不了解,建议查看openid.net/connect/。

本文Github地址:github.com/itlab1024/S…


理解有限,很多功能未能完全展现,会持续不断的进行更新,期待您的关注。

你可能感兴趣的:(spring,服务器,spring,boot)