目录
背景
版本
Spring Boot 3.1
Spring Authorization Server 1.1.0官方文档
基础
spring security
OAuth2.0
模块构成
授权方式
集成过程
官方demo
代码集成
依赖
授权服务AuthorizationServerConfig配置
重要组件
测试
查看授权服务配置
访问授权服务
授权
回调
获取 access_token
获取用户信息
个性化改造
集成GateWay
代办事项
sql脚本
背景
基于 Spring Cloud Alibaba 架构下,需要一个统一授权中心,与 gateway 配合使用实现微服务的授权与认证,下面主要介绍整个集成过程,基于springboot3.1最新版
版本
Spring Boot 3.1
最新发布的springboot3.1版本对 oauth2 提供了默认的支持,可以引用下面的依赖来快速构建,为了体验新版本特性,我这边切换到了 3.1版本

Spring Boot 3.1 提供了一个 spring-boot-starter-oauth2-authorization-server 启动器,可以支持 Spring Authorization Server 的自动配置,轻松配置基于 Servlet 的 OAuth2 授权服务器,同时@EnableAuthorizationServer这些注解也早已废弃
Spring Authorization Server 1.1.0官方文档
Spring Authorization Server
基础
spring security
关于springsecurity的基础知识,之前写过一篇 springboot 与 Spring Security 集成的基于 jwt的授权的,可以看下面的
(296条消息) springboot 2.7整合spring security 5.7整合jwt实现用户登录注册与鉴权全记录_ricardo.M.Yu的博客-CSDN博客
OAuth2.0
OAuth2.0可以提供一个统一的认证服务。主要模块如下:
模块构成
- Resource owner(资源拥有者):拥有该资源的服务或用户,如我们自己或者资源网站
- Authorization server(认证服务器):即用来认证与颁发令牌(如token)的服务
- Resource server(资源服务器):拥有资源的服务,如我们要访问的网站
- Client(客户端):即访问的客户端,如我们自己用的访问网站
授权方式
- 授权码模式(authorization_code):最正规的模式,客户端先将用户导向认证服务器,登录后获取授权码,然后进行授权,最后根据授权码获取访问令牌
- 刷新模式(refresh_token):用刷新码获取
- 客户端模式(client_credentials):第三方应用自己本身需要获取资源
详见 AuthorizationGrantType 这个类

下面的密码模式已经被废弃
- 密码模式(resource owner password credentials):直接带用户名和密码去向认证服务器申请令牌
集成过程
我下面会分为三个阶段逐次递进改造,
- 第一阶段:官方demo演示与组件讲解测试
- 第二阶段:个性化改造
- 第三阶段:集成 springcloud gateway 完成分布式授权改造
官方demo
代码集成
依赖
只需要下面的这一个依赖,前提springboot 版本为 3.1
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.1.0</version>
    <relativePath/>
</parent>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>授权服务AuthorizationServerConfig配置
spring 官方在快速开始里面给出了下面的默认最小配置,
Getting Started (spring.io)
我先粘下来再介绍,代码结构大概这样,一共两个配置类

AuthorizationServerConfig
@Configuration
public class AuthorizationServerConfig {
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
            throws Exception {
        //针对 Spring Authorization Server 最佳实践配置
        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
                        .defaultAuthenticationEntryPointFor(
                                new LoginUrlAuthenticationEntryPoint("/login"),
                                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                        )
                )
                // Accept access tokens for User Info and/or Client Registration
                .oauth2ResourceServer((resourceServer) -> resourceServer
                        .jwt(Customizer.withDefaults()));
        return http.build();
    }
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("oidc-client")
                .clientSecret("{noop}secret")
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .redirectUri("http://www.baidu.com")
                .redirectUri("http://127.0.0.1:8080/login/oauth2/code/oidc-client")
                .postLogoutRedirectUri("http://127.0.0.1:8080/")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .scope("message.read")
                .scope("message.write")
                .scope("all")
                // 设置 Client 需要页面审核授权
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                .build();
        return new InMemoryRegisteredClientRepository(oidcClient);
    }
    /**
     * 默认发放令牌
     * @return
     */
    @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();
    }
}DefaultSecurityConfig
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class DefaultSecurityConfig {
    @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()
                )
                .cors(Customizer.withDefaults())
                .csrf((csrf) -> csrf.disable())
//                .httpBasic(Customizer.withDefaults())
//				// 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("user")
                .password("password")
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(userDetails);
    }
}分别介绍下这几个@Bean配置,也是 AuthorizationServer 的几个重要概念。
重要组件
SecurityFilterChain -> authorizationServerSecurityFilterChain: Spring Security的过滤器链,用于协议端点的。
SecurityFilterChain -> defaultSecurityFilterChain: Spring Security的过滤器链,用于Spring Security的身份认证
UserDetailsService :主要进行用户身份验证
RegisteredClientRepository:主要用于管理客户端
JWKSource:用于签名访问令牌
KeyPair: 启动时生成的带有密钥的KeyPair实例,用于创建上面的JWKSource
JwtDecoder:JwtDecoder的一个实例,用于解码已签名的访问令牌
AuthorizationServerSettings:用于配置Spring Authorization Server的AuthorizationServerSettings实例。
测试
为了方便测试,上面的配置中,客户端的回调地址我已经改成了 百度的,授权方式用授权码模式,认证方式用client_secret_basic
服务启动,端口为9000

查看授权服务配置
地址:
调用 http://127.0.0.1:9000/.well-known/openid-configuration
后,查看地址配置如下:其实就是每个请求的url

详细的是下面
{
"issuer": "http://127.0.0.1:9000",
"authorization_endpoint": "http://127.0.0.1:9000/oauth2/authorize",
"device_authorization_endpoint": "http://127.0.0.1:9000/oauth2/device_authorization",
"token_endpoint": "http://127.0.0.1:9000/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:9000/oauth2/jwks",
"userinfo_endpoint": "http://127.0.0.1:9000/userinfo",
"end_session_endpoint": "http://127.0.0.1:9000/connect/logout",
"response_types_supported": [
"code"
],
"grant_types_supported": [
"authorization_code",
"client_credentials",
"refresh_token",
"urn:ietf:params:oauth:grant-type:device_code"
],
"revocation_endpoint": "http://127.0.0.1:9000/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:9000/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"
]
}
访问授权服务
浏览器地址栏输入
http://localhost:9000/oauth2/authorize?response_type=code&client_id=oidc-client&scope=message.read openid&redirect_uri=http://www.baidu.com
用这个请求来模拟客户端,实际开发中,其实是先访问资源服务,由资源服务来拼接这几个参数来重定向到授权服务的,参数意义如下,这些参数都是需要再上面RegisteredClientRepository配置过的
- response_type:这个意思是相应的方式为code码
- client_id:即客户端的id,即上面配置中在 RegisteredClientRepository 配置的
- scope:请求授权范围,也需要在上面的配置中
- redirect_uri:授权通过后,重定向回来的地址
输入完上面的地址后,会重定向到下面这个登录页面,
我们输入上面配置好的用户名密码:
user
password
点击登录

授权
登录过后,会到下面这个授权页面,点击授权范围,然后点击 submit

回调
授权通过后,授权服务回调到了百度的地址,然后附带这我们的授权码,如下图

获取 access_token
拿到授权码之后,可以用postman测试来获取 access_token
测试接口参数
Header

请求体
http://localhost:9000/oauth2/token?grant_type=authorization_code&code=ajdNNIj8EiLjgw3OS8yu2q8n3XXCAb6cPY5LRsOHyRlAAB1ENKdmy8M4JBkJ8PrU-3K9QdpAZtyKg8QP5q0EHN2mR1k532FQUKz1ObSuH3EuSFy5LVzut9z1QVPuefoA&redirect_uri=http://www.baidu.com
curl命令如下
curl --location --request POST 'http://localhost:9000/oauth2/token?grant_type=authorization_code&code=a_lOQegEwElR09Sj6auVpBdYGgnhhK0uz1Uks286ei_zkbyDFKII2uf7gMIF7CU4cLN8ZEY3EsSq9jMAZ-Rmtmlq5pI6KPB95LMQg9fFirFg2wWjdd5PEwQLMEogY9B6&redirect_uri=http%3A%2F%2Fwww.baidu.com' \
--header 'Authorization: Basic b2lkYy1jbGllbnQ6c2VjcmV0'
参数说明:
- grant_type:即授权方式,authorization_code即授权码模式
- code:即授权码,上面重定向到百度给我们的授权码
- redirect_uri:重定向的url
- header中的 Authorization参数:因为我们用的客户端认证方式 为 client_secret_basic ,这个需要传参,还有一些其他的认证方式,具体参数说明如下
- client_secret_basic: 将 clientId 和 clientSecret 通过 ‘:’ 号拼接,( clientId 和 clientSecret 都在上面配置中,)并使用 Base64 进行编码得到一串字符,再在前面加个 注意有个 Basic 前缀(Basic后有一个空格), 即得到上面参数中的 Basic b2lkYy1jbGllbnQ6c2VjcmV0
- client_secret_post :clientId 和 clientSecret 放到表单去发送请求。如下图:
使用我们的 client_secret_basic 方式传参,接口调用结果:

已经正常拿到了 access_token。
完整的过滤器执行顺序,控制台输出

获取用户信息
获取用户信息接口为 /userinfo,注意需要有 opid 的授权范围,需要传参的值为 上面获取到的access_token,并在前面拼上 Bearer
参数说明
Authorization:值格式为 Bearer + ${access_token}, 注意 Bearer 后面附带空格
curl命令
curl --location --request POST 'http://127.0.0.1:9000/userinfo' \
--header 'Authorization: Bearer eyJraWQiOiI4ZDc5YTIwNi1kOWZhLTQ5NWQtODJkMi1iMzk2MjQwNGQ4YmIiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoib2lkYy1jbGllbnQiLCJuYmYiOjE2ODY3MzM4MTYsInNjb3BlIjpbIm9wZW5pZCIsIm1lc3NhZ2UucmVhZCJdLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJleHAiOjE2ODY3MzQxMTYsImlhdCI6MTY4NjczMzgxNn0.AiGV5LIl8a4_7a7L2gbR61sjvHVLW4dZ6cElAwsWZnp-P7ocQT119KIASTPv138MU6ZK2_aF_-ER5FKaFQVSOj10Fy_Gv9PXa2ExrzTajfkPtA_t63jCcazzllaVWY4QIVD4fU8hPe6zDwjNOOX8R7hJFu2qtZ8V3bhzTlC0M4XWDAQ0goymYrAnVq8BR6hRm5-pY4nMCUZPFCeEFqGnl68EGRzosdSQeuRd-PtzB837i-C7lxqIjs4Y5hZ9mQw3R1zfa0WoP2KeN8K3WjyTIYd9PvrLIFCB5Zhj54sdNpZTy7wwC-oCVzwFFCEkgY-vprfgk4e4sZ10Lx60j--fHA' \
--header 'Cookie: JSESSIONID=7B10DA37A285902E4AEE4586AC181343'
效果如下:
默认返回的只有用户名,其他的数据,需要我们来重写一些东西获取

 过滤器执行链
BearerTokenAuthenticationFilter: 检验token
AuthenticationEntryPointFailureHandler
AuthenticationFailureHandler
OidcUserInfoEndpointFilter
个性化改造
正在改造中。。。
集成GateWay
正在集成中。。。
关于 Spring Cloud Alibaba 的基础环境搭建,可以看下面的文章,很详细,本文主要介绍集成OAuth2的过程
Spring Cloud Alibaba 最新版本整合完整使用及与各中间件集成(基于Spring Boot 3.0.x)_ricardo.M.Yu的博客-CSDN博客
代办事项
现在需要做的改造如下:
1、新建授权服务,集成 oauth2-authorization-server ,即auth模块做授权中心
2、修改业务模块,集成 oauth2-client,即做资源中心与客户端
3、修改网关模块,即 gateway 做相关修改

Oauth2主要结构
OAuth2AuthorizationEndpointFilter: 针对 /login 或自行请求 授权码的处理器
OAuth2TokenEndpointFilter:针对获取 token 时的处理器
ProviderManager:
OAuth2ClientAuthenticationFilter
OAuth2TokenEndpointFilter
ClientSecretAuthenticationProvider
DelegatingAuthenticationConverter
OAuth2AuthorizationCodeAuthenticationProvider
OAuth2AuthorizationEndpointFilter
UsernamePasswordAuthenticationFilter
sql脚本
我直接整理好了
/*
IMPORTANT:
    If using PostgreSQL, update ALL columns defined with 'blob' to 'text',
    as PostgreSQL does not support the 'blob' data type.
*/
CREATE TABLE oauth2_authorization (
    id varchar(100) NOT NULL,
    registered_client_id varchar(100) NOT NULL,
    principal_name varchar(200) NOT NULL,
    authorization_grant_type varchar(100) NOT NULL,
    authorized_scopes varchar(1000) DEFAULT NULL,
    attributes blob DEFAULT NULL,
    state varchar(500) DEFAULT NULL,
    authorization_code_value blob DEFAULT NULL,
    authorization_code_issued_at timestamp DEFAULT NULL,
    authorization_code_expires_at timestamp DEFAULT NULL,
    authorization_code_metadata blob DEFAULT NULL,
    access_token_value blob DEFAULT NULL,
    access_token_issued_at timestamp DEFAULT NULL,
    access_token_expires_at timestamp DEFAULT NULL,
    access_token_metadata blob DEFAULT NULL,
    access_token_type varchar(100) DEFAULT NULL,
    access_token_scopes varchar(1000) DEFAULT NULL,
    oidc_id_token_value blob DEFAULT NULL,
    oidc_id_token_issued_at timestamp DEFAULT NULL,
    oidc_id_token_expires_at timestamp DEFAULT NULL,
    oidc_id_token_metadata blob DEFAULT NULL,
    refresh_token_value blob DEFAULT NULL,
    refresh_token_issued_at timestamp DEFAULT NULL,
    refresh_token_expires_at timestamp DEFAULT NULL,
    refresh_token_metadata blob DEFAULT NULL,
    user_code_value blob DEFAULT NULL,
    user_code_issued_at timestamp DEFAULT NULL,
    user_code_expires_at timestamp DEFAULT NULL,
    user_code_metadata blob DEFAULT NULL,
    device_code_value blob DEFAULT NULL,
    device_code_issued_at timestamp DEFAULT NULL,
    device_code_expires_at timestamp DEFAULT NULL,
    device_code_metadata blob DEFAULT NULL,
    PRIMARY KEY (id)
);
CREATE TABLE oauth2_authorization_consent (
    registered_client_id varchar(100) NOT NULL,
    principal_name varchar(200) NOT NULL,
    authorities varchar(1000) NOT NULL,
    PRIMARY KEY (registered_client_id, principal_name)
);
CREATE TABLE oauth2_registered_client (
    id varchar(100) NOT NULL,
    client_id varchar(100) NOT NULL,
    client_id_issued_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
    client_secret varchar(200) DEFAULT NULL,
    client_secret_expires_at timestamp DEFAULT NULL,
    client_name varchar(200) NOT NULL,
    client_authentication_methods varchar(1000) NOT NULL,
    authorization_grant_types varchar(1000) NOT NULL,
    redirect_uris varchar(1000) DEFAULT NULL,
    post_logout_redirect_uris varchar(1000) DEFAULT NULL,
    scopes varchar(1000) NOT NULL,
    client_settings varchar(2000) NOT NULL,
    token_settings varchar(2000) NOT NULL,
    PRIMARY KEY (id)
);




![[CKA]考试之网络策略NetworkPolicy](https://img-blog.csdnimg.cn/b9a8666f0046416b91e8a71a6eb9d114.png)















