Spring Cloud Gateway + Spring Oauth 2.0 整合(服務端與資源端分離)
個人開發環境
java環境:Jdk1.8.0_60 (idea 需安裝lombok插件)
編譯器:IntelliJ IDEA 2019.1
框架:spirng cloud Hoxton + springboot 2.2 + spring oauth 2.0 + spring security 5
一、前言
服務名 | 注釋 | 描述 |
---|---|---|
yoci-auth | 鑒權服務 | 實現一個簡單的基本的 oauth2鑒權服務 使用 jwt token,使用自定義 JwtTokenStore |
yoci-api | 資源服務 | 實現簡單資源服務,提供簡單的 Restful API,通過 gateway調用 |
yoci-gate | 網關服務 | 使用 spring cloud gateway 實現簡單路由,實現統一路由轉發 |
依次運行 yoci-auth
,yoci-gate
,yoci-api
二、父工程構建
1.pom.xml
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
<spring-platform.version>Cairo-SR8</spring-platform.version>
<alibaba-cloud.version>2.1.1.RELEASE</alibaba-cloud.version>
<spring-boot.version>2.2.2.RELEASE</spring-boot.version>
</properties>
<modules>
<module>yoci-auth</module>
<module>yoci-api</module>
<module>yoci-gate</module>
</modules>
<!-- 依賴管理,用於管理spring-cloud的依賴 -->
<dependencyManagement>
<dependencies>
<!-- spring-boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring-io 版本兼容管理 -->
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>${spring-platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring-cloud & alibaba-cloud -->
<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>${alibaba-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- lombok 開發小插件 -->
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.14</version>
<scope>provided</scope>
</dependency>
</dependencies>
<!-- maven倉庫-依賴聲明 -->
<repositories>
<repository>
<id>oss</id>
<name>oss</name>
<url>https://oss.sonatype.org/content/groups/public</url>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>sonatype-nexus-snapshots</id>
<name>Sonatype Nexus Snapshots</name>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
</repositories>
三、網關
網關在demo中實現統一路由轉發作用,不做安全驗證,在實戰微服務環境中,也可在gateway網關處實現其提供的相應過濾器進行統一攔截,實現安全驗證,此demo為了學習演示方便,采用資源服務器通過遠程token校驗進行安全驗證
1.pom.xml
<artifactId>yoci-gate</artifactId>
<dependencies>
<!-- spring boot 相關依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- webflux 相關依賴 (spring gateway采用的webflux 無需添加spring-boot-web依賴(會和servlet沖突),) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- spring gateway 相關依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
2.application.yml
server:
port: 8082
## gateway
spring:
application:
name: yoci-gate
cloud:
gateway:
discovery:
locator:
enabled: true # 開啟從注冊中心動態創建路由的功能,利用微服務名稱進行路由
routes:
- id: yoci-api
# uri: lb://yoci-api # 動態路由方式需要配合eureka、nacos注冊中心使用
uri: http://localhost:8083
predicates:
- Path=/api/**
filters:
- StripPrefix=1
- id: yoci-auth
# uri: lb://yoci-auth # 動態路由方式需要配合eureka、nacos注冊中心使用
uri: http://localhost:8081
predicates:
- Path=/auth/**
filters:
- StripPrefix=1
3.啟動類
/**
* 網關主啟動類
*
* @author: YoCiyy
* @date: 2020/6/22
*/
@SpringBootApplication
public class GateBootstrap {
public static void main(String[] args) {
SpringApplication.run(GateBootstrap.class, args);
}
}
四、服務端(鑒權服務 yoci-auth)
1.pom.xml
<artifactId>yoci-auth</artifactId>
<dependencies>
<!-- spring oauth2.0 (包含了spring security無需單獨引入) 相關 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- spring boot 相關依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2.application.yml
# 服務端口號
server:
port: 8081
# 服務名
spring:
application:
name: yoci-auth
# actuator
management:
endpoints:
web:
exposure:
include: "*"
3.啟動類
/**
* auth主啟動類
*
* @author: YoCiyy
* @date: 2020/6/22
*/
@SpringBootApplication
public class AuthBootstrap {
public static void main(String[] args) {
SpringApplication.run(AuthBootstrap.class, args);
}
}
4.java配置類
創建oauth認證服務器配置AuthorizationServerConfig
配置類,繼承AuthorizationServerConfigurerAdapter
/**
* oauth認證服務器配置
*
* @author: YoCiyy
* @date: 2020/6/22
*/
@Configuration
@AllArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/** 令牌持久化配置 */
private final TokenStore tokenStore;
/** 客戶端詳情服務 */
private final ClientDetailsService clientDetailsService;
/** 認證管理器 */
private final AuthenticationManager authenticationManager;
/** 授權碼服務 */
private final AuthorizationCodeServices authorizationCodeServices;
/** jwtToken解析器 */
private final JwtAccessTokenConverter jwtAccessTokenConverter;
/**
* 客戶端詳情服務配置 (demo采用本地內存存儲)
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
// 使用本地內存存儲
.inMemory()
// 客戶端id
.withClient("client_1")
// 客戶端密碼
.secret(new BCryptPasswordEncoder().encode("123456"))
// 該客戶端允許授權的類型
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
// 該客戶端允許授權的范圍
.scopes("all")
// false跳轉到授權頁面,true不跳轉,直接發令牌
.autoApprove(false);
}
/**
* 配置訪問令牌端點
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
// 認證管理器
.authenticationManager(authenticationManager)
// 授權碼服務
.authorizationCodeServices(authorizationCodeServices)
// 令牌管理服務
.tokenServices(tokenServices())
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
/**
* 配置令牌端點安全約束
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
// oauth/check_token公開
.checkTokenAccess("permitAll()")
// oauth/token_key 公開密鑰
.tokenKeyAccess("permitAll()")
// 允許表單認證
.allowFormAuthenticationForClients();
}
/**
* 令牌服務配置
*
* @return 令牌服務對象
*/
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore);
tokenServices.setSupportRefreshToken(true);
// 令牌增強
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
tokenServices.setTokenEnhancer(tokenEnhancerChain);
// 令牌默認有效期2小時
tokenServices.setAccessTokenValiditySeconds(7200);
// 刷新令牌默認有效期3天
tokenServices.setRefreshTokenValiditySeconds(259200);
return tokenServices;
}
}
創建Security 安全配置類WebSecurityConfig
繼承WebSecurityConfigurerAdapter
/**
* security 安全相關配置類
*
* @author: YoCiyy
* @date: 2020/6/22
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 安全攔截機制
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// 放行
.antMatchers("/auth/**")
.permitAll()
// 其他請求必須認證通過
.anyRequest().authenticated()
.and()
.formLogin() // 允許表單登錄
// .successForwardUrl("/login-success") //自定義登錄成功跳轉頁
.and()
.csrf().disable();
}
/**
* token持久化配置
*/
@Bean
public TokenStore tokenStore() {
// 本地內存存儲令牌
return new InMemoryTokenStore();
}
/**
* 密碼加密器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 認證管理器配置
*/
@Bean
@Override
protected AuthenticationManager authenticationManager() {
return authentication -> daoAuthenticationProvider().authenticate(authentication);
}
/**
* 認證是由 AuthenticationManager 來管理的,但是真正進行認證的是 AuthenticationManager 中定義的 AuthenticationProvider,用於調用userDetailsService進行驗證
*/
@Bean
public AuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return daoAuthenticationProvider;
}
/**
* 用戶詳情服務
*/
@Bean
@Override
protected UserDetailsService userDetailsService() {
// 測試方便采用內存存取方式
InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
userDetailsService.createUser(User.withUsername("user_1").password(passwordEncoder().encode("123456")).authorities("ROLE_USER").build());
userDetailsService.createUser(User.withUsername("user_2").password(passwordEncoder().encode("1234567")).authorities("ROLE_USER").build());
return userDetailsService;
}
/**
* 設置授權碼模式的授權碼如何存取,暫時采用內存方式
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new InMemoryAuthorizationCodeServices();
}
/**
* jwt token解析器
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
// 對稱密鑰,資源服務器使用該密鑰來驗證
converter.setSigningKey("YoCiyy");
return converter;
}
}
5.postman測試
采用密碼模式訪問測試
獲取jwt token:http://localhost:8081/oauth/token
校驗jwt token :http://localhost:8081/oauth/check_token
五、資源端(資源服務yoci-api)
1.pom.xml
<artifactId>yoci-api</artifactId>
<dependencies>
<!-- spring oauth2.0 (包含了spring security無需單獨引入) 相關 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- spring boot 相關依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2.application.yml
server:
port: 8083
spring:
application:
name: yoci-api
3.啟動類
/**
* 模擬接口啟動類
*
* @author: YoCiyy
* @date: 2020/6/23
*/
@SpringBootApplication
public class ApiBootstrap {
public static void main(String[] args) {
SpringApplication.run(ApiBootstrap.class, args);
}
}
4.java配置類
創建資源服務配置ResourceServerConfig
繼承ResourceServerConfigurerAdapter
/**
* 資源服務配置
*
* @author: YoCiyy
* @date: 2020/6/19
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
/**
* token服務配置
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(tokenServices());
}
/**
* 路由安全認證配置
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 配置hello打頭的路由需要安全認證,order無配置無需認證
.antMatchers("/hello/**").authenticated()
.and().csrf().disable();
}
/**
* jwt token 校驗解析器
*/
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
/**
* Token轉換器必須與認證服務一致
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("YoCiyy");
return accessTokenConverter;
}
/**
* 資源服務令牌解析服務
*/
@Bean
@Primary
public ResourceServerTokenServices tokenServices() {
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:8081/oauth/check_token");
remoteTokenServices.setClientId("client_1");
remoteTokenServices.setClientSecret("123456");
return remoteTokenServices;
}
}
5.postman測試
通過網關路由轉發,請求資源服務器
不配置請求頭token,直接/order
可直接訪問成功(在資源服務配置類中,/order打頭路由沒有配置安全路由)
不配置請求頭中的token,訪問/hello
報401錯誤
請求頭中配置申請到的token,格式 Bearer (申請到的token),訪問/hello
測試調用資源服務api成功
相關學習資料
Spring Cloud Gateway 基於 OAuth2.0 的身份認證