文章目錄
- 一、網關搭建
- 1. 引入依賴
- 2. 配置檔案
- 3. 增權重限管理器
- 4. 自定義認證接口管理類
- 5. 增加網關層的安全配置
- 6. 搭建授權認證中心
- 二、搭建産品服務
- 2.1. 建立boot項目
- 2.2. 引入依賴
- 2.3. controller
- 2.4. 啟動類
- 2.5. 配置
- 四、測試驗證
- 4.1. 啟動nacos
- 4.2. 啟動認證中心
- 4.3. 啟動産品服務
- 4.3. 請求認證授權中心
- 4.4. 網關請求産品子產品
- 4.5. 擷取token
- 4.6. 攜帶token請求産品服務
- 4.7. 直接請求産品服務
- 4.8. 請求結果比對
- 五、總結
一、網關搭建
1. 引入依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<spring.cloud-version>Hoxton.SR9</spring.cloud-version>
</properties>
<dependencies>
<!--安全認證架構-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--security-oauth2整合-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<!--oauth2-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--網關-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<!--https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2. 配置檔案
server:
port: 8081
spring:
cloud:
gateway:
routes:
- id: product
uri: http://localhost:9000
predicates:
- Host=product.gblfy.com**
- id: auth
uri: http://localhost:5000
predicates:
- Path=/oauth/token
- id: skill
uri: http://localhost:13000
predicates:
- Path=/skill
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/auth-serv?characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: 123456
3. 增權重限管理器
package com.gblfy.gatewayserv.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
@Slf4j
@Component
public class AccessManager implements ReactiveAuthorizationManager<AuthorizationContext> {
private Set<String> permitAll = new ConcurrentSkipListSet<>();
private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
public AccessManager() {
permitAll.add("/");
permitAll.add("/error");
permitAll.add("/favicon.ico");
permitAll.add("/**/v2/api-docs/**");
permitAll.add("/**/swagger-resources/**");
permitAll.add("/webjars/**");
permitAll.add("/doc.html");
permitAll.add("/swagger-ui.html");
permitAll.add("/**/oauth/**");
permitAll.add("/**/current/get");
}
/**
* 實作權限驗證判斷
*/
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authenticationMono, AuthorizationContext authorizationContext) {
ServerWebExchange exchange = authorizationContext.getExchange();
//請求資源
String requestPath = exchange.getRequest().getURI().getPath();
// 是否直接放行
if (permitAll(requestPath)) {
return Mono.just(new AuthorizationDecision(true));
}
return authenticationMono.map(auth -> {
return new AuthorizationDecision(checkAuthorities(exchange, auth, requestPath));
}).defaultIfEmpty(new AuthorizationDecision(false));
}
/**
* 校驗是否屬于靜态資源
*
* @param requestPath 請求路徑
* @return
*/
private boolean permitAll(String requestPath) {
return permitAll.stream()
.filter(r -> antPathMatcher.match(r, requestPath)).findFirst().isPresent();
}
//權限校驗
private boolean checkAuthorities(ServerWebExchange exchange, Authentication auth, String requestPath) {
if (auth instanceof OAuth2Authentication) {
OAuth2Authentication athentication = (OAuth2Authentication) auth;
String clientId = athentication.getOAuth2Request().getClientId();
log.info("clientId is {}", clientId);
}
Object principal = auth.getPrincipal();
log.info("使用者資訊:{}", principal.toString());
return true;
}
}
4. 自定義認證接口管理類
package com.gblfy.gatewayserv.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import reactor.core.publisher.Mono;
public class ReactiveJdbcAuthenticationManager implements ReactiveAuthenticationManager {
Logger logger= LoggerFactory.getLogger(ReactiveJdbcAuthenticationManager.class);
private TokenStore tokenStore;
public ReactiveJdbcAuthenticationManager(TokenStore tokenStore){
this.tokenStore = tokenStore;
}
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
return Mono.justOrEmpty(authentication)
.filter(a -> a instanceof BearerTokenAuthenticationToken)
.cast(BearerTokenAuthenticationToken.class)
.map(BearerTokenAuthenticationToken::getToken)
.flatMap((accessToken ->{
logger.info("accessToken is :{}",accessToken);
OAuth2AccessToken oAuth2AccessToken = this.tokenStore.readAccessToken(accessToken);
//根據access_token從資料庫擷取不到OAuth2AccessToken
if(oAuth2AccessToken == null){
return Mono.error(new InvalidTokenException("invalid access token,please check"));
}else if(oAuth2AccessToken.isExpired()){
return Mono.error(new InvalidTokenException("access token has expired,please reacquire token"));
}
OAuth2Authentication oAuth2Authentication =this.tokenStore.readAuthentication(accessToken);
if(oAuth2Authentication == null){
return Mono.error(new InvalidTokenException("Access Token 無效!"));
}else {
return Mono.just(oAuth2Authentication);
}
})).cast(Authentication.class);
}
}
5. 增加網關層的安全配置
package com.gblfy.gatewayserv.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import javax.sql.DataSource;
@Configuration
public class SecurityConfig {
private static final String MAX_AGE = "18000L";
@Autowired
private DataSource dataSource;
@Autowired
private AccessManager accessManager;
@Bean
SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception{
//token管理器
ReactiveAuthenticationManager tokenAuthenticationManager = new ReactiveJdbcAuthenticationManager(new JdbcTokenStore(dataSource));
//認證過濾器
AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(tokenAuthenticationManager);
authenticationWebFilter.setServerAuthenticationConverter(new ServerBearerTokenAuthenticationConverter());
http
.httpBasic().disable()
.csrf().disable()
.authorizeExchange()
.pathMatchers(HttpMethod.OPTIONS).permitAll()
.anyExchange().access(accessManager)
.and()
//oauth2認證過濾器
.addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION);
return http.build();
}
}
這個類是SpringCloug Gateway 與 Oauth2整合的關鍵,通過建構認證過濾器 AuthenticationWebFilter 完成Oauth2.0的token校驗。
AuthenticationWebFilter 通過我們自定義的 ReactiveJdbcAuthenticationManager 完成token校驗。
6. 搭建授權認證中心
SpringCloudAliaba 基于OAth2.0 搭建認證授權中心
二、搭建産品服務
2.1. 建立boot項目
子產品名稱product-serv

2.2. 引入依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gblfy</groupId>
<artifactId>product-serv</artifactId>
<version>1.0-SNAPSHOT</version>
<!--https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E-->
<properties>
<java.version>1.8</java.version>
<spring.cloud-version>Hoxton.SR9</spring.cloud-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--服務注冊發現-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!--spring-cloud 版本控制-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring-cloud-alibaba 版本控制-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.3. controller
package com.gblfy.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
//http://localhost:9000/product/" + productId
@GetMapping("/product/{productId}")
public String getProductName(@PathVariable Integer productId) {
return "IPhone 12";
}
}
2.4. 啟動類
package com.gblfy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProductAplication {
public static void main(String[] args) {
SpringApplication.run(ProductAplication.class);
}
}
2.5. 配置
server:
port: 9000
四、測試驗證
4.1. 啟動nacos
4.2. 啟動認證中心
4.3. 啟動産品服務
4.3. 請求認證授權中心
不攜帶token
4.4. 網關請求産品子產品
通過網關請求産品服務,提示需要認證
4.5. 擷取token
http://localhost:8081/oauth/token 通過認證授權中心擷取toekn
grant_type:password
client_id:app
client_secret:app
username:ziya
password:111111
發起請求,擷取token
4.6. 攜帶token請求産品服務
http://product.gblfy.com:8081/product/1
Authorization:Bearer d364c6cc-3c60-402f-b3d0-af69f6d6b73e
4.7. 直接請求産品服務
4.8. 請求結果比對
從4.6和4.7可以看出,當從授權中心擷取token,攜帶token通過網關服務請求産品服務和直接請求産品服務效果是一樣的。