前言
????????之前雖然單獨(dú)講過Security Client和Resource Server的對接,但是都是基于Spring webmvc的,Gateway這種非阻塞式的網(wǎng)關(guān)是基于webflux的,對于集成Security相關(guān)內(nèi)容略有不同,且涉及到代理其它微服務(wù),所以會稍微比較麻煩些,今天就帶大家來實(shí)現(xiàn)Gateway網(wǎng)關(guān)對接OAuth2認(rèn)證服務(wù)。
Gateway對接說明
身份問題
????????在本次示例中網(wǎng)關(guān)既是客戶端(OAuth2 Client Server)又是資源服務(wù)(OAuth2 Resource Server),Client服務(wù)負(fù)責(zé)認(rèn)證,Resource負(fù)責(zé)鑒權(quán),這樣如果有在瀏覽器直接訪問網(wǎng)關(guān)的需要可以直接在瀏覽器由框架引導(dǎo)完成OAuth2認(rèn)證過程。
框架版本與架構(gòu)說明
架構(gòu)圖
Spring Cloud依賴版本
框架 | 版本號 |
---|---|
Spring Boot | 3.1.0 |
Nacos Server | 2.2.1 |
Spring Cloud | 2022.0.4 |
Spring Cloud Alibaba | 2022.0.0.0 |
Spring Security | 6.1.0 |
Spring OAuth2 Client | 6.1.0 |
Spring OAuth2 Resource Server | 6.1.0 |
讀者可以自選版本使用,作為對接方版本問題不大;不確定Spring Cloud Alibaba 在部署時會不會有Spring Boot的版本限制,如果3.1.x無法使用請降級至3.0.10版本,開發(fā)時測試都是沒問題的。
網(wǎng)關(guān)集成認(rèn)證服務(wù)請求流程圖說明
- 用戶請求受限資源
- 網(wǎng)關(guān)檢測沒有認(rèn)證信息,通過
RedirectServerAuthenticationEntryPoint
處理并發(fā)起OAuth2登錄授權(quán)申請 - 授權(quán)申請到達(dá)認(rèn)證服務(wù),認(rèn)證服務(wù)檢測到未登錄重定向至登錄頁面并展示給用戶
- 用戶登錄成功后請求重定向至授權(quán)申請接口,通過校驗(yàn)后攜帶Token重定向至回調(diào)地址(redirect_uri),注意:這里回調(diào)地址要設(shè)置為網(wǎng)關(guān)的地址,htttp://{網(wǎng)關(guān)ip}:{網(wǎng)關(guān)port}/login/oauth2/code/{registrationId},后邊的
/login/oauth2/code/{registrationId}
路徑是固定的,這是框架(Security OAuth2 Client)自帶的端點(diǎn) - 請求到達(dá)網(wǎng)關(guān),由
OAuth2LoginAuthenticationWebFilter
攔截并調(diào)用父類AuthenticationWebFilter
的filter
方法進(jìn)行處理 -
AuthenticationWebFilter
調(diào)用OidcAuthorizationCodeReactiveAuthenticationManager
或OAuth2LoginReactiveAuthenticationManager
類處理(由授權(quán)申請的scope
決定,包含openid
就走OidcAuthorizationCodeReactiveAuthenticationManager
,否則走另一個) - 在獲取AccessToken成功以后調(diào)用
ReactiveOAuth2UserService
獲取用戶信息 - 獲取到用戶信息后會解析并將認(rèn)證信息保存至
ReactiveSecurityContextHolder
中 - 完成這一系列的認(rèn)證之后會重定向至最一開始請求的受限資源,這時候就能獲取到認(rèn)證信息了
- 如果訪問的是被網(wǎng)關(guān)代理的服務(wù)則會通過令牌中繼(TokenRelay)攜帶token訪問
這就是網(wǎng)關(guān)通過認(rèn)證服務(wù)獲取認(rèn)證信息的一個流程,基本上只需要添加配置文件即可由框架引導(dǎo)進(jìn)行OAuth2認(rèn)證流程。
開始編碼
前置條件
- 搭建好標(biāo)準(zhǔn)OAuth2認(rèn)證服務(wù)
- 搭建nacos服務(wù)
項(xiàng)目結(jié)構(gòu)
gateway-example # 父模塊
│
├─gateway-client-example # 網(wǎng)關(guān)
│
├─normal-resource-example # webmvc資源服務(wù)
│
├─webflux-resource-example # webflux資源服務(wù)
│
└─pom.xml # 公共依賴,依賴管理
創(chuàng)建一個空的maven項(xiàng)目
引入Spring Boot、Spring Cloud、Spring Cloud Alibaba,如下
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>gateway-example</artifactId>
<version>0.0.1</version>
<packaging>pom</packaging>
<name>gateway-example</name>
<description>gateway-example</description>
<modules>
<module>gateway-client-example</module>
<module>normal-resource-example</module>
<module>webflux-resource-example</module>
</modules>
<properties>
<java.version>17</java.version>
<!-- 修復(fù)漏洞 -->
<snakeyaml.version>2.0</snakeyaml.version>
<!-- Spring Cloud版本號 -->
<spring-cloud.version>2022.0.4</spring-cloud.version>
<!-- Spring Cloud Alibaba版本號 -->
<spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
</properties>
<dependencies>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot 測試依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 服務(wù)注冊與發(fā)現(xiàn) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 資源服務(wù)器starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
里邊的modules
標(biāo)簽是在新建module時自動添加的
創(chuàng)建網(wǎng)關(guān)gateway-client-example模塊
Spring Cloud 相關(guān)依賴已經(jīng)在parent模塊中引入,所以該模塊只需要引入Gateway、Client依賴,pom如下
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>gateway-example</artifactId>
<version>0.0.1</version>
</parent>
<artifactId>gateway-client-example</artifactId>
<name>gateway-client-example</name>
<description>gateway-client-example</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 負(fù)載均衡依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
編寫客戶端配置
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* 客戶端配置
*
* @author vains
*/
@Configuration
public class ClientServerConfig {
/**
* 解析用戶權(quán)限信息(當(dāng)在瀏覽器中直接訪問接口,框架自動調(diào)用OIDC流程登錄時會用到該配置)
*
* @return GrantedAuthoritiesMapper
*/
@Bean
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (authority instanceof OAuth2UserAuthority oAuth2UserAuthority) {
// 從認(rèn)證服務(wù)獲取的用戶信息中提取權(quán)限信息
Object userAuthorities = oAuth2UserAuthority.getAttributes().get("authorities");
if (userAuthorities instanceof Collection<?> collection) {
// 轉(zhuǎn)為SimpleGrantedAuthority的實(shí)例并插入mappedAuthorities中
collection.stream().filter(a -> a instanceof String)
.map(String::valueOf)
.map(SimpleGrantedAuthority::new)
.forEach(mappedAuthorities::add);
}
}
});
return mappedAuthorities;
};
}
}
該配置會在獲取到用戶信息后解析用戶的權(quán)限信息,詳見文檔
編寫網(wǎng)關(guān)資源服務(wù)配置
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import reactor.core.publisher.Mono;
/**
* 資源服務(wù)器配置
*
* @author vains
*/
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class ResourceServerConfig {
/**
* 配置認(rèn)證相關(guān)的過濾器鏈
*
* @param http Spring Security的核心配置類
* @return 過濾器鏈
*/
@Bean
public SecurityWebFilterChain defaultSecurityFilterChain(ServerHttpSecurity http) {
// 禁用csrf與cors
http.csrf(ServerHttpSecurity.CsrfSpec::disable);
http.cors(ServerHttpSecurity.CorsSpec::disable);
// 開啟全局驗(yàn)證
http.authorizeExchange((authorize) -> authorize
//全部需要認(rèn)證
.anyExchange().authenticated()
);
// 開啟OAuth2登錄
http.oauth2Login(Customizer.withDefaults());
// 設(shè)置當(dāng)前服務(wù)為資源服務(wù),解析請求頭中的token
http.oauth2ResourceServer((resourceServer) -> resourceServer
// 使用jwt
.jwt(jwt -> jwt
// 請求中攜帶token訪問時會觸發(fā)該解析器適配器
.jwtAuthenticationConverter(grantedAuthoritiesExtractor())
)
/*
// xhr請求未攜帶Token處理
.authenticationEntryPoint(this::authenticationEntryPoint)
// 權(quán)限不足處理
.accessDeniedHandler(this::accessDeniedHandler)
// Token解析失敗處理
.authenticationFailureHandler(this::failureHandler)
*/
);
return http.build();
}
/**
* 自定義jwt解析器,設(shè)置解析出來的權(quán)限信息的前綴與在jwt中的key
*
* @return jwt解析器適配器 ReactiveJwtAuthenticationConverterAdapter
*/
public Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
// 設(shè)置解析權(quán)限信息的前綴,設(shè)置為空是去掉前綴
grantedAuthoritiesConverter.setAuthorityPrefix("");
// 設(shè)置權(quán)限信息在jwt claims中的key
grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
}
需要注意的是開啟方法級別鑒權(quán)的注解變了,webflux的注解和webmvc的注解不一樣,并且過濾器鏈也換成SecurityWebFilterChain
了;jwt自定義解析器的方式也不一致,實(shí)現(xiàn)方式見上方代碼
說明文檔如下
EnableReactiveMethodSecurity注解文檔
Jwt解析器適配器詳見文檔
編寫application.yml
,添加nacos配置
spring:
cloud:
nacos:
serverAddr: 127.0.0.1:8848
config:
import:
- nacos:gateway.yml?refresh=true
application:
name: gateway
在nacos中創(chuàng)建gateway.yml配置文件
添加客戶端與資源服務(wù)配置,并添加其它資源服務(wù)的代理配置
server:
port: 7000
spring:
security:
oauth2:
# 資源服務(wù)器配置
resourceserver:
jwt:
# Jwt中claims的iss屬性,也就是jwt的簽發(fā)地址,即認(rèn)證服務(wù)器的根路徑
# 資源服務(wù)器會進(jìn)一步的配置,通過該地址獲取公鑰以解析jwt
issuer-uri: http://192.168.119.1:8080
client:
provider:
# 認(rèn)證提供者,自定義名稱
custom-issuer:
# Token簽發(fā)地址(認(rèn)證服務(wù)地址)
issuer-uri: http://192.168.119.1:8080
# 獲取用戶信息的地址,默認(rèn)的/userinfo端點(diǎn)需要IdToken獲取,為避免麻煩自定一個用戶信息接口
user-info-uri: ${spring.security.oauth2.client.provider.custom-issuer.issuer-uri}/user
user-name-attribute: name
registration:
messaging-client-oidc:
# oauth認(rèn)證提供者配置,和上邊配置的認(rèn)證提供者關(guān)聯(lián)起來
provider: custom-issuer
# 客戶端名稱,自定義
client-name: gateway
# 客戶端id,從認(rèn)證服務(wù)申請的客戶端id
client-id: messaging-client
# 客戶端秘鑰
client-secret: 123456
# 客戶端認(rèn)證方式
client-authentication-method: client_secret_basic
# 獲取Token使用的授權(quán)流程
authorization-grant-type: authorization_code
# 回調(diào)地址,這里設(shè)置為Spring Security Client默認(rèn)實(shí)現(xiàn)使用code換取token的接口,當(dāng)前服務(wù)(gateway網(wǎng)關(guān))的地址
redirect-uri: http://127.0.0.1:7000/login/oauth2/code/messaging-client-oidc
scope:
- message.read
- message.write
- openid
- profile
cloud:
gateway:
default-filters:
# 令牌中繼
- TokenRelay=
# 代理路徑,代理至服務(wù)后會去除第一個路徑的內(nèi)容
- StripPrefix=1
routes:
# 資源服務(wù)代理配置
- id: resource
uri: lb://resource
predicates:
- Path=/resource/**
# 資源服務(wù)代理配置
- id: webflux
uri: lb://webflux-resource
predicates:
- Path=/webflux/**
注意:配置文件中令牌中繼(TokenRelay)
的配置就是添加一個filter
:TokenRelay=
; 當(dāng)網(wǎng)關(guān)引入spring-boot-starter-oauth2-client
依賴并設(shè)置spring.security.oauth2.client.*
屬性時,會自動創(chuàng)建一個TokenRelayGatewayFilterFactory
過濾器,它會從認(rèn)證信息中獲取access token,并放入下游請求的請求頭中。 詳見Gateway關(guān)于TokenRelay的文檔
項(xiàng)目結(jié)構(gòu)
創(chuàng)建webmvc資源服務(wù)模塊normal-resource-example
在pom.xml中添加web依賴,如下
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>gateway-example</artifactId>
<version>0.0.1</version>
</parent>
<artifactId>normal-resource-example</artifactId>
<name>normal-resource-example</name>
<description>normal-resource-example</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
創(chuàng)建資源服務(wù)器配置,添加自定義jwt解析器
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
/**
* 資源服務(wù)器配置
*
* @author vains
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true)
public class ResourceServerConfig {
/**
* 自定義jwt解析器,設(shè)置解析出來的權(quán)限信息的前綴與在jwt中的key
*
* @return jwt解析器 JwtAuthenticationConverter
*/
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
// 設(shè)置解析權(quán)限信息的前綴,設(shè)置為空是去掉前綴
grantedAuthoritiesConverter.setAuthorityPrefix("");
// 設(shè)置權(quán)限信息在jwt claims中的key
grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
}
編寫application.yml
添加nacos配置
spring:
cloud:
nacos:
serverAddr: 127.0.0.1:8848
config:
import:
- nacos:resource.yml?refresh=true
application:
name: resource
在nacos中創(chuàng)建resource.yml
配置文件,添加資源服務(wù)配置
server:
port: 7100
spring:
security:
oauth2:
# 資源服務(wù)器配置
resourceserver:
jwt:
# Jwt中claims的iss屬性,也就是jwt的簽發(fā)地址,即認(rèn)證服務(wù)器的根路徑
# 資源服務(wù)器會進(jìn)一步的配置,通過該地址獲取公鑰以解析jwt
issuer-uri: http://192.168.119.1:8080
注意端口,不能與網(wǎng)關(guān)和認(rèn)證服務(wù)重復(fù)
模塊結(jié)構(gòu)
創(chuàng)建webflux資源服務(wù)模塊
pom.xml添加webflux依賴,如下
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>gateway-example</artifactId>
<version>0.0.1</version>
</parent>
<artifactId>webflux-resource-example</artifactId>
<name>webflux-resource-example</name>
<description>webflux-resource-example</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
創(chuàng)建資源服務(wù)配置并且添加jwt解析器適配器
跟網(wǎng)關(guān)的資源服務(wù)配置差不多,如下
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import reactor.core.publisher.Mono;
/**
* 資源服務(wù)器配置
*
* @author vains
*/
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class ResourceServerConfig {
/**
* 配置認(rèn)證相關(guān)的過濾器鏈
*
* @param http Spring Security的核心配置類
* @return 過濾器鏈
*/
@Bean
public SecurityWebFilterChain defaultSecurityFilterChain(ServerHttpSecurity http) {
// 禁用csrf與cors
http.csrf(ServerHttpSecurity.CsrfSpec::disable);
http.cors(ServerHttpSecurity.CorsSpec::disable);
// 開啟全局驗(yàn)證
http.authorizeExchange((authorize) -> authorize
//全部需要認(rèn)證
.anyExchange().authenticated()
);
// 設(shè)置當(dāng)前服務(wù)為資源服務(wù),解析請求頭中的token
http.oauth2ResourceServer((resourceServer) -> resourceServer
// 使用jwt
.jwt(jwtSpec -> jwtSpec
// 設(shè)置jwt解析器適配器
.jwtAuthenticationConverter(grantedAuthoritiesExtractor())
)
);
return http.build();
}
/**
* 自定義jwt解析器,設(shè)置解析出來的權(quán)限信息的前綴與在jwt中的key
*
* @return jwt解析器適配器 ReactiveJwtAuthenticationConverterAdapter
*/
public Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
// 設(shè)置解析權(quán)限信息的前綴,設(shè)置為空是去掉前綴
grantedAuthoritiesConverter.setAuthorityPrefix("");
// 設(shè)置權(quán)限信息在jwt claims中的key
grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
}
編寫application.yml
添加nacos配置
spring:
cloud:
nacos:
serverAddr: 127.0.0.1:8848
config:
import:
- nacos:webflux.yml?refresh=true
application:
name: webflux-resource
nacos中添加webflux.yml配置文件并添加資源服務(wù)配置
server:
port: 7200
spring:
security:
oauth2:
# 資源服務(wù)器配置
resourceserver:
jwt:
# Jwt中claims的iss屬性,也就是jwt的簽發(fā)地址,即認(rèn)證服務(wù)器的根路徑
# 資源服務(wù)器會進(jìn)一步的配置,通過該地址獲取公鑰以解析jwt
issuer-uri: http://192.168.119.1:8080
與webmvc的資源服務(wù)的配置是一樣的,注意端口不能與其它服務(wù)端口沖突
模塊結(jié)構(gòu)
在三個模塊中添加測試類,一式三份
webmvc測試接口
package com.example.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 測試接口
*
* @author vains
*/
@RestController
public class TestController {
@GetMapping("/test01")
@PreAuthorize("hasAnyAuthority('message.write')")
public String test01() {
return "test01";
}
@GetMapping("/test02")
@PreAuthorize("hasAnyAuthority('test02')")
public String test02() {
return "test02";
}
@GetMapping("/app")
@PreAuthorize("hasAnyAuthority('app')")
public String app() {
return "app";
}
}
webflux測試接口
package com.example.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
/**
* 測試接口
*
* @author vains
*/
@RestController
public class TestController {
@GetMapping("/test01")
@PreAuthorize("hasAnyAuthority('message.write')")
public Mono<String> test01() {
return Mono.just("test01");
}
@GetMapping("/test02")
@PreAuthorize("hasAnyAuthority('test02')")
public Mono<String> test02() {
return Mono.just("test02");
}
@GetMapping("/app")
@PreAuthorize("hasAnyAuthority('app')")
public Mono<String> app() {
return Mono.just("app");
}
}
測試
前置條件
- 啟動認(rèn)證服務(wù)
- 啟動nacos
- nacos中都有相關(guān)配置
啟動項(xiàng)目
依次啟動三個服務(wù),順序無所謂
在postman中直接訪問網(wǎng)關(guān)接口或代理的服務(wù)接口
被重定向至登錄了,攜帶X-Requested-With
請求頭訪問,代表當(dāng)前是xhr請求
響應(yīng)401,框架有區(qū)分是瀏覽器請求還是xhr請求,對于瀏覽器請求會重定向到頁面,對于xhr請求默認(rèn)會響應(yīng)401狀態(tài)碼,可自己實(shí)現(xiàn)異常處理,這里錯誤信息在請求頭中是因?yàn)闆]有重寫異常處理,網(wǎng)關(guān)資源服務(wù)配置代碼中有注釋。
在瀏覽器中訪問網(wǎng)關(guān)接口或代理的服務(wù)接口
訪問
瀏覽器打開地址:http://127.0.0.1:7000/resource/app
請求到達(dá)網(wǎng)關(guān)后檢測到未登錄會引導(dǎo)用戶進(jìn)行OAuth2認(rèn)證流程
登錄后提交
登錄提交后認(rèn)證服務(wù)重定向授權(quán)申請接口,校驗(yàn)通過后會生成code并攜帶code重定向至回調(diào)地址,注意,這里的回調(diào)地址是網(wǎng)關(guān)的服務(wù)地址,由網(wǎng)關(guān)中的OAuth2 Client處理,如圖
網(wǎng)關(guān)會根據(jù)code換取token,獲取token后根據(jù)token獲取用戶信息,并調(diào)用網(wǎng)關(guān)客戶端配置中自定義的userAuthoritiesMapper
解析權(quán)限信息。
訪問權(quán)限不足的接口
響應(yīng)403,并將錯誤信息放入響應(yīng)頭中
使用token訪問網(wǎng)關(guān)
過期token
響應(yīng)401并在響應(yīng)頭中提示token已過期
錯誤token
響應(yīng)401并在響應(yīng)頭中提示token無法解析
權(quán)限不足token
響應(yīng)403并提示權(quán)限不足
正常請求
響應(yīng)200并正確響應(yīng)接口信息
寫在最后
本文帶大家簡單實(shí)現(xiàn)了Spring Cloud Gateway對接認(rèn)證服務(wù),Gateway中添加客戶端主要是為了如果代理服務(wù)有靜態(tài)資源(html、css、image)時可以直接發(fā)起OAuth2授權(quán)流程,在瀏覽器登錄后直接訪問,同時也是開啟令牌中繼的必要依賴;引入Resource Server依賴是當(dāng)需要對網(wǎng)關(guān)的接口鑒權(quán)時可以直接使用,如果網(wǎng)關(guān)只負(fù)責(zé)轉(zhuǎn)發(fā)應(yīng)該是可以去掉資源服務(wù)相關(guān)依賴和配置的,由各個被代理的微服務(wù)對自己的接口進(jìn)行鑒權(quán)。這些東西在之前基本都是講過的內(nèi)容,所以本文很多地方都是一筆帶過的,如果某些地方不清楚可以針對性的翻翻之前的文章,也可以在評論區(qū)中提出。
如果有什么問題或者需要補(bǔ)充的請在評論區(qū)指出,謝謝。文章來源:http://www.zghlxwxcb.cn/news/detail-683272.html
附錄
Gitee倉庫地址
Gateway令牌中繼文檔
OAuth2登錄后用戶權(quán)限解析文檔
webflux開啟方法鑒權(quán)EnableReactiveMethodSecurity注解說明文檔
webflux的Jwt解析器適配器說明文檔
webflux對接OAuth2 Client文檔
webflux對接OAuth2 Resource Server文檔
文章來源地址http://www.zghlxwxcb.cn/news/detail-683272.html
到了這里,關(guān)于Spring Authorization Server入門 (十六) Spring Cloud Gateway對接認(rèn)證服務(wù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!