Spring Cloud Gateway + Spring Oauth 2.0 整合(服務端與資源端分離)


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-authyoci-gateyoci-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

1593327117473

校驗jwt token :http://localhost:8081/oauth/check_token

1593327301948

五、資源端(資源服務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打頭路由沒有配置安全路由)

1593329760740

不配置請求頭中的token,訪問/hello報401錯誤

1593329706170

請求頭中配置申請到的token,格式 Bearer (申請到的token),訪問/hello測試調用資源服務api成功

1593329508452

相關學習資料

demo源碼獲取

Spring Cloud Gateway 基於 OAuth2.0 的身份認證

Spring Boot Oauth2.0 服務端與資源端分離

Spring Security Oauth2.0學習視頻


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM