在前面的篇章中,我们介绍了账户服务、商品服务、订单服务、网关服务的搭建,这些服务统称为资源服务。目前这些资源服务都是未受保护的,访问他们是不需要进行认证授权的,这样毫无安全性可言。这一篇,我们将介绍如何对我们的服务资源进行保护,以及如何使用 Spring Authorization Server 进行认证授权。

1. 关于 Spring Authorization Server

Spring Authorization Server 是一个框架,它提供了 OAuth 2.1 和 OpenID Connect 1.0 规范以及其他相关规范的实现。它建立在 Spring Security 之上,为构建 OpenID Connect 1.0 身份提供者和 OAuth2 授权服务器产品提供了一个安全、轻量级和可定制的基础。关于 Spring Authorization Server 更多的介绍,可以阅读本人的写的 Spring Authorization Server 系列文章(文章地址)或参考 Spring Authorization Server 官网(官网地址)。

2. 前后端交互流程

本篇涉及到前端应用、认证服务器、资源服务器的交互,时序图如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Cloud

3. 认证服务器搭建

由于认证服务器只负责认证授权,不承担过多的业务处理,因此认证服务器的工程架构,我们采用基于 Spring Boot 的 MVC 架构即可。本篇认证服务器的代码,直接取自本人《Spring Authorization Server (十一)单点登录-SSO》这一篇中的 spring-oauth-sso-server 代码进行修改(主要修改 pom.xml、application.yml 两个文件),建议读者先去阅读该篇文章。

认证服务器工程名称取名为:mall-oauth-server。

3.1. pom.xml

pom.xml 文件修改后,内容如下。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.example</groupId>
        <artifactId>mall-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <artifactId>mall-oauth-server</artifactId>
    <name>mall-oauth-server</name>
    <description>认证服务器</description>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <springframework.version>4.0.0</springframework.version>
        <spring-boot.version>3.0.2</spring-boot.version>
        <oauth2-server.version>1.1.1</oauth2-server.version>
        <spring-cloud.version>2022.0.0</spring-cloud.version>
        <spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
    </properties>

    <dependencies>
        <!--spring-boot-starter-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>
        <!--nacos注册中心客户端-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
        </dependency>
        <!-- nacos配置中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
        </dependency>
        <!-- 2020.X.X版本官方重构了bootstrap引导配置的加载方式,需要添加以下依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <!--2020版本以后就已经移除了对ribbon的支持,而是使用LoadBalancer-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <!--Spring Authorization Server-->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-authorization-server</artifactId>
            <version>${oauth2-server.version}</version>
        </dependency>
        <!--添加spring security cas支持-->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-cas</artifactId>
            <version>6.1.1</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.0.33</version>
        </dependency>
        <!--阿里数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.16</version>
        </dependency>
        <!-- mybatis-plus 3.5.3及以上版本 才支持 spring boot 3-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>
        <!-- spring-boot-starter-thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>3.1.1</version>
        </dependency>
        <!-- webjars-locator-core -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator-core</artifactId>
            <version>0.53</version>
        </dependency>
        <!-- bootstrap:5.2.3 -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>5.2.3</version>
        </dependency>
        <!-- jquery:3.6.4 -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.6.4</version>
        </dependency>
    </dependencies>
  

</project>

上面 pom.xml 文件主要增加引入 Spring Cloud Alibaba 相关依赖。

3.2. application.yml

将 application.yml 改名为 bootstrap.yml,内容如下。

server:
  port: 7010
spring:
  application:
    name: mall-oauth-server
  cloud:
    nacos:
      discovery:
        server-addr: ${NACOS_ADDR}
        namespace: ${NACOS_NAMESPACE}
        group: mall
      config:
        server-addr: ${NACOS_ADDR}
        namespace: ${NACOS_NAMESPACE}
        file-extension: yaml
        group: mall
        extension-configs:
          - data-id: datasource.yaml
            group: mall
            refresh: true

这里给认证服务器分配 7010 端口,bootstrap.yml、datasource.yaml 文件中的参数配置在 bootstrap.properties 文件中,内容如下。

#nacos服务地址
NACOS_ADDR=127.0.0.1:8848
#nacos名称空间
NACOS_NAMESPACE=dev
#数据库名称
DB_NAME=mall-oauth

其中,mall-oauth 数据库,为《Spring Authorization Server (十一)单点登录-SSO》一篇中的数据库:oauth-server。

3.3. 工程结构

mall-oauth-server 项目结构如下所示。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_COLA_02

3.4. 网关配置

mall-web-gateway.yaml 添加认证服务器配置信息,内容如下。

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: mall-account-service
          uri: lb://mall-account-service
          predicates:
            - Path=/web/v1/account/**
        - id: mall-product-service
          uri: lb://mall-product-service
          predicates:
            - Path=/web/v1/product/**
        - id:  mall-order-service
          uri: lb://mall-order-service
          predicates:
            - Path=/web/v1/order/**
        - id:  mall-oauth-server-browser
          uri: http://spring-oauth-server:7010
          predicates:
            - Path=/oauth2/authorize,/activate
        - id:  mall-oauth-server
          uri: lb://mall-oauth-server
          predicates:
            - Path=/oauth2/**,/userinfo

这里添加 http://spring-oauth-server:7010 和 lb://mall-oauth-server 两个 uri,主要用以区别浏览器和 restful 接口的请求。对于 OAuth 2.1 中的授权码模式、设备授权码模式,都涉及到浏览器登录交互,这是基于 session 的操作,如果配置成 lb://mall-oauth-server,将会导致 lb://mall-oauth-server 获取的 ip 地址与认证服务器实际的 ip 地址不一致,从而造成登录失败。

4. 认证服务器测试

搭建好认证服务器后,我们先对认证服务器做一下测试。

给 mall-oauth 数据库中的 oauth2_registered_client 客户端表添加一条客户端信息如下。

INSERT INTO `mall-oauth`.`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`, `post_logout_redirect_uris`, `scopes`, `client_settings`, `token_settings`) VALUES ('d84e9e7c-abb1-46f7-bb0f-4511af362cb1', 'gateway-client-id', '2023-07-12 07:33:42', '$2a$10$.J0Rfg7y2Mu8AN8Dk2vL.eBFa9NGbOYCPOAFEw.QhgGLVXjO7eFDC', NULL, '客户端-网关服务', 'none,client_secret_basic', 'refresh_token,authorization_password,authorization_mobile,authorization_code,client_credentials,urn:ietf:params:oauth:grant-type:device_code', 'http://www.baidu.com', 'http://spring-oauth-server:7010', 'openid,profile', '{\"@class\":\"java.util.Collections$UnmodifiableMap\",\"settings.client.require-proof-key\":false,\"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\",1800.000000000],\"settings.token.access-token-format\":{\"@class\":\"org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat\",\"value\":\"self-contained\"},\"settings.token.refresh-token-time-to-live\":[\"java.time.Duration\",3600.000000000],\"settings.token.authorization-code-time-to-live\":[\"java.time.Duration\",300.000000000],\"settings.token.device-code-time-to-live\":[\"java.time.Duration\",300.000000000]}');

启动 mall-oauth-server 服务,看到 Nacos 已经注册上来了。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Cloud_03

以下测试均采用网关端口 7020 发起,通过网关将请求转发到认证服务器。

4.1. 客户端模式

在 postman 中,输入如下客户端信息。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_OAuth2.1_04

向认证服务器发起获取 token 请求(grant_type=client_credentials),结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_COLA_05

4.2. 授权码模式

浏览器输入地址:http://localhost:7020/oauth2/authorize?response_type=code&client_id=gateway-client-id&scope=profile openid&redirect_uri=http://www.baidu.com,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Boot3_06

输入用户名:user,密码:123456。提交后,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Cloud_07

勾选授权,提交后,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Cloud Alibaba_08


将获取到的 code,使用 postman 向地址:http://spring-oauth-server:7020/oauth2/token,发起获取 token 请求,参数如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Cloud_09

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_COLA_10

认证服务器返回结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Boot3_11

4.3. 设备授权码模式

使用 postman 认证服务器向地址:http://spring-oauth-server:7020/oauth2/device_authorization?client_id=gateway-client-id&scope=openid profile ,发起获取设备授权码请求,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_OAuth2.1_12

在浏览器中打开认证服务器返回的地址:http://spring-oauth-server:7010/activate,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Cloud Alibaba_13

输入用户名:user,密码:123456。提交后,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Cloud_14

输入认证服务器返回的用户码:VMRD-ZLSK。提交后,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Cloud_15

勾选授权,提交后,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_COLA_16

使用认证服务器返回的 device_code 向认证服务器发起获取 token 请求,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Cloud_17

4.4. 自定义密码模式

在 postman 中,输入如下信息。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Boot3_18

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_OAuth2.1_19

向认证服务器,发起获取 token 请求,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_OAuth2.1_20

4.5. refresh_token 模式

在 postman 中,输入如下客户端信息。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Cloud Alibaba_21

使用上面密码模式中获取到的 refresh_token 向认证服务器发起获取 token 请求(grant_type=refresh_token,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Cloud Alibaba_22

5. 资源服务器改造

这里资源服务器包括网关服务、账户服务、商品服务、订单服务。其中,账户服务、商品服务、订单服务,我们称为应用服务。

5.1. 网关服务

pom.xml 文件,添加如下依赖。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
  <version>${spring-boot.version}</version>
</dependency>

mall-web-gateway.yaml 文件添加如下依赖。

spring:
  security:
    oauth2:
      resource-server:
        jwt:
          issuer-uri: http://spring-oauth-server:7010
logging:
  level:
    org.springframework.security: trace

创建 org.example.gateway.config 包目录,在该目录下添加 AuthorizationClientConfig 文件,内容如下。

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class AuthorizationClientConfig {

    @Bean
    public SecurityWebFilterChain authorizationClientSecurityFilterChain(ServerHttpSecurity http) throws Exception {

        //uri放行
        String[] ignoreUrls = new String[]{"/oauth2/**","/*.html","/favicon.ico","/webjars/**","/v3/api-docs/swagger-config","/*/v3/api-docs**"};
        //禁用csrf与cors
        http.csrf().disable();
        http.cors().disable();
        //客户端设置
        http
                .authorizeExchange(authorize ->
                        authorize.pathMatchers(ignoreUrls).permitAll()
                                //其他请求,需要认证
                                .anyExchange().authenticated()
                );
        http
                //资源服务器会向 http://spring-oauth-server:7010 发起 token 校验
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(Customizer.withDefaults())

                );
        return http.build();
    }
}

5.2. 应用服务

如果已经在网关服务做了 token 校验,且应用服务器仅在内网之间进行通信,不对外暴露的话,可以不用添加 spring-boot-starter-oauth2-resource-server 再进行一次远程认证,这样可以提高接口处理效率。如果网关服务不做 token 校验,那么每个应用服务就必须在各自的服务上对 token 进行认证。由于网关服务是基于 WebFlux 的,而应用服务是基于 WebMvc 的,因此,在应用服务中集成 spring-boot-starter-oauth2-resource-server 时,添加 AuthorizationClientConfig 的内容与网关服务有细微的差别。应用服务的 AuthorizationClientConfig 内容如下。

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true)
public class AuthorizationClientConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http)
			throws Exception {

		//uri放行
		String[] ignoreUrls = new String[]{"/*.html","/favicon.ico","/webjars/**","/*/v3/api-docs**","/v3/api-docs/**"};
		http.authorizeHttpRequests(authorize ->
						authorize.requestMatchers(ignoreUrls).permitAll()
								 //其他请求,需要认证
								.anyRequest().authenticated()
				);

		http
                 //资源服务器会向 http://spring-oauth-server:7010 发起 token 校验
				.oauth2ResourceServer(oauth2 -> oauth2
								.jwt(Customizer.withDefaults())

				);
		return http.build();

	}
}

6. 交互流程测试

启动认证服务、网关服务、账户服务、商品服务、订单服务,对前后端交互流程进行测试。以账户服务为例,我们来测试一下”根据 id 查询账户信息“接口。

6.1. 不传 access_token

6.1.1. 通过网关访问

在 postman 中输入地址:http://localhost:7020/web/v1/account/queryById/1693333896005545986,不传 token,向网关服务发起接口请求,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_OAuth2.1_23

可以看到,返回 401 状态。

6.1.2. 直接访问应用

在 postman 中输入地址:http://localhost:7030/web/v1/account/queryById/1693333896005545986,不传 token,向账户服务发起接口请求,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_OAuth2.1_24

可以看到,同样也是返回 401 状态。

6.2. 传入 access_token

6.2.1. 通过网关访问

在 postman 中输入地址:http://localhost:7020/web/v1/account/queryById/1693333896005545986,传入 token,向网关服务发起接口请求,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_Spring Boot3_25

可以看到,正常返回接口数据。

6.2.2. 直接访问应用

在 postman 中输入地址:http://localhost:7030/web/v1/account/queryById/1693333896005545986,传入 token,向账户服务发起接口请求,结果如下。

基于 COLA 架构的 Spring Cloud Alibaba(八) Spring Authorization Server_COLA_26

可以看到,同样正常返回接口数据。

7. 总结

本篇先用时序图介绍了前端应用、认证服务器、资源服务器的交互流程。接着,介绍了认证服务器的搭建和测试。然后,介绍了资源服务器的改造,以达到对资源服务器的访问进行资源保护效果。最后对前端应用、认证服务器、资源服务器的交互流程进行了测试验证。


基础篇项目代码:链接地址