springboot 2.7 oauth server配置源码走读一

springboot 2.7 oauth server配置源码走读

入口:
springboot 2.7 oauth server配置源码走读一_第1张图片
上述截图中的方法签名和OAuth2AuthorizationServerConfiguration类中的一个方法一样,只不过我们自己的配置类优先级比spring中的配置类低,算是配置覆盖,看下图所示:
springboot 2.7 oauth server配置源码走读一_第2张图片
它们都提到了 OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
所以我们分析它:
springboot 2.7 oauth server配置源码走读一_第3张图片
一. new出来的OAuth2AuthorizationServerConfigurer:
官方声称它是:An AbstractHttpConfigurer for OAuth 2.0 Authorization Server support.(一个用于OAuth 2.0 授权服务器的抽象配置类)。
从源码中可以看到它“管理”着以下具体配置类:
springboot 2.7 oauth server配置源码走读一_第4张图片
先给出结论:这些配置类endpoint访问默认值对应如下(如何推导请耐心往下看):
springboot 2.7 oauth server配置源码走读一_第5张图片
我们具体看下上述第77行:getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) 的实现:
我们可以看到第一条绿色下划线:tokenEndpoint的来源,等下我们再去探究它:
springboot 2.7 oauth server配置源码走读一_第6张图片
所以我们去AntPathRequestMatcher类 查看实现:
可以看到用到了策略模式:根据pattern (/**表示MATCH_ALL)不同,初始化的matcher也不同:
springboot 2.7 oauth server配置源码走读一_第7张图片
接下来我们可以看上述第一条绿色下划线:tokenEndpoint的来源,现在去探究它:

ProviderSettings:它是一个配置类,继承于AbstractSettings(接下来我们自己配置中用到的TokenSettings+ClientSettings类也继承于它),如下所求,我们可以指定token endpoint(当然还有授权,introspection等等),
springboot 2.7 oauth server配置源码走读一_第8张图片

如果不指定,则默认配置如下:注意 /oauth2/jwks, 后面resource-server就可以指定它来验证token的有效性。
springboot 2.7 oauth server配置源码走读一_第9张图片

那ProvideSettings如何初始化呢?又是和配置类关联起来?
1.构造方法中传递:
springboot 2.7 oauth server配置源码走读一_第10张图片
2.在我们的配置类中流入bean(官方示例也是这么做的):
springboot 2.7 oauth server配置源码走读一_第11张图片

然后看源码:
springboot 2.7 oauth server配置源码走读一_第12张图片

其中providerSettings.getJwkSetEndpoint()我们上面看过,如果没有指定配置,则默认值为:
/oauth2/jwks
springboot 2.7 oauth server配置源码走读一_第13张图片
二. 我们配置类中的其它配置:
springboot 2.7 oauth server配置源码走读一_第14张图片
上述2个配置方法在OAuth2AuthorizationServerConfiguration类中也可以找到类似的方法,如下所示:
springboot 2.7 oauth server配置源码走读一_第15张图片
三.最后把完整的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:off
        http
                .authorizeHttpRequests(authorize -> authorize
                        .anyRequest().authenticated())
                .formLogin(Customizer.withDefaults());
        // @formatter:on

        return http.build();
    }

    @Bean
    public 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);
    }

    @Bean
    public JWKSource 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);
    }

    @Bean
    public JwtDecoder jwtDecoder(KeyPair keyPair) {
        return NimbusJwtDecoder.withPublicKey((RSAPublicKey) keyPair.getPublic()).build();
    }

    @Bean
    public ProviderSettings providerSettings() {
        return ProviderSettings.builder().issuer("http://127.0.0.1:9000").build();
    }

    @Bean
    public 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 step
        System.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);
        }
    }
}

你可能感兴趣的:(springboot,spring,boot,后端,java)