springboot 2.7 oauth server配置源码走读
入口:
上述截图中的方法签名和OAuth2AuthorizationServerConfiguration类中的一个方法一样,只不过我们自己的配置类优先级比spring中的配置类低,算是配置覆盖,看下图所示:
它们都提到了 OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
所以我们分析它:
一. new出来的OAuth2AuthorizationServerConfigurer:
官方声称它是:An AbstractHttpConfigurer for OAuth 2.0 Authorization Server support.(一个用于OAuth 2.0 授权服务器的抽象配置类)。
从源码中可以看到它“管理”着以下具体配置类:
先给出结论:这些配置类endpoint访问默认值对应如下(如何推导请耐心往下看):
我们具体看下上述第77行:getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) 的实现:
我们可以看到第一条绿色下划线:tokenEndpoint的来源,等下我们再去探究它:
所以我们去AntPathRequestMatcher类 查看实现:
可以看到用到了策略模式:根据pattern (/**表示MATCH_ALL)不同,初始化的matcher也不同:
接下来我们可以看上述第一条绿色下划线:tokenEndpoint的来源,现在去探究它:
ProviderSettings:它是一个配置类,继承于AbstractSettings(接下来我们自己配置中用到的TokenSettings+ClientSettings类也继承于它),如下所求,我们可以指定token endpoint(当然还有授权,introspection等等),
如果不指定,则默认配置如下:注意 /oauth2/jwks, 后面resource-server就可以指定它来验证token的有效性。
那ProvideSettings如何初始化呢?又是和配置类关联起来?
1.构造方法中传递:
2.在我们的配置类中流入bean(官方示例也是这么做的):
然后看源码:
其中providerSettings.getJwkSetEndpoint()我们上面看过,如果没有指定配置,则默认值为:
/oauth2/jwks
二. 我们配置类中的其它配置:
上述2个配置方法在OAuth2AuthorizationServerConfiguration类中也可以找到类似的方法,如下所示:
三.最后把完整的oauth2 server配置如下:
package com.jel.tech.auth.config;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.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
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.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.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
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.jwt.NimbusJwtDecoder;
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.oauth2.server.authorization.config.TokenSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.UUID;/*** @author: jelex.xu* @Date: 2024/1/2 18:31* @desc:**/
@Configuration
public class OAuth2AuthorizeSecurityConfig {/*** 重写:org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration* #authorizationServerSecurityFilterChain(HttpSecurity)* @param http* @return* @throws Exception*/@Bean@Order(1)public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);return http.formLogin(Customizer.withDefaults()).build();}@Bean@Order(2)public SecurityFilterChain standardSecurityFilterChain(HttpSecurity http) throws Exception {// @formatter:offhttp.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()).formLogin(Customizer.withDefaults());// @formatter:onreturn http.build();}@Beanpublic RegisteredClientRepository registeredClientRepository() {RegisteredClient loginClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("login-client").clientSecret("{noop}openid-connect").clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC).authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).redirectUri("http://127.0.0.1:8080/login/oauth2/code/login-client").redirectUri("http://127.0.0.1:8080/authorized").scope(OidcScopes.OPENID).scope(OidcScopes.PROFILE).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("messaging-client").clientSecret("{noop}secret").clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC).authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).scope("message:read").scope("message:write")// 指定token有效期:token:30分(默认5分钟),refresh_token:1天.tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofMinutes(30)).refreshTokenTimeToLive(Duration.ofDays(1)).build())
// .id("xxx").build();return new InMemoryRegisteredClientRepository(loginClient, registeredClient);}@Beanpublic JWKSource<SecurityContext> jwkSource(KeyPair keyPair) {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);}@Beanpublic JwtDecoder jwtDecoder(KeyPair keyPair) {return NimbusJwtDecoder.withPublicKey((RSAPublicKey) keyPair.getPublic()).build();}@Beanpublic ProviderSettings providerSettings() {return ProviderSettings.builder().issuer("http://127.0.0.1:9000").build();}@Beanpublic UserDetailsService userDetailsService() {PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();// outputs {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG// remember the password that is printed out and use in the next stepSystem.out.println(encoder.encode("password"));UserDetails userDetails = User.withUsername("user").username("user").password("{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG").roles("USER").build();return new InMemoryUserDetailsManager(userDetails);}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)KeyPair generateRsaKey() {KeyPair keyPair;try {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);keyPair = keyPairGenerator.generateKeyPair();return keyPair;} catch (NoSuchAlgorithmException e) {throw new IllegalArgumentException(e);}}
}