SpringCloud 大型系列課程正在制作中,歡迎大家關(guān)注與提意見。
程序員每天的CV 與 板磚,也要知其所以然,本系列課程可以幫助初學(xué)者學(xué)習(xí) SpringBooot 項(xiàng)目開發(fā) 與 SpringCloud 微服務(wù)系列項(xiàng)目開發(fā)
本文章是系列文章中的一篇
- 1、SpringCloud 項(xiàng)目基礎(chǔ)工程搭建 【SpringCloud系列1】
- 2、SpringCloud 集成Nacos注冊(cè)中心 【SpringCloud系列2】
- 3、SpringCloud Feign遠(yuǎn)程調(diào)用 【SpringCloud系列3】
- 4、SpringCloud Feign遠(yuǎn)程調(diào)用公共類抽取 【SpringCloud系列4】
- 5、SpringCloud 整合Gateway服務(wù)網(wǎng)關(guān) 【SpringCloud系列5】
- 6、SpringCloud 整合 Spring Security 認(rèn)證鑒權(quán)【SpringCloud系列6】
本文章實(shí)現(xiàn)的是 Gateway 網(wǎng)關(guān)中的令牌校驗(yàn)功能 ,上圖中所示用戶所有的訪問全部走網(wǎng)關(guān),然后在網(wǎng)關(guān)每次都調(diào)用 auth-api 鑒權(quán),當(dāng)訪問量足夠大的時(shí)候,還是會(huì)有訪問性能問題,所以優(yōu)化如下:
1 網(wǎng)關(guān)Gateway 添加 security 與 oauth2 相關(guān)配置
這里添加的 security 與 oauth2 相關(guān)配置 ,是為了解密 auth-api 中生成的 access_token 令牌信息
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
然后添加安全攔截配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
/**
* @description 安全配置類
* @author 早起的年輕人
*/
@EnableWebFluxSecurity
@Configuration
public class SecurityConfig {
//安全攔截配置
@Bean
public SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/**").permitAll()
.anyExchange().authenticated()
.and().csrf().disable().build();
}
}
配置 JwtAccessTokenConverter 所使用的密鑰信息
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* @author 早起的年輕人
* @version 1.0
**/
@Configuration
public class TokenConfig {
String SIGNING_KEY = "test_key";
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
然后 網(wǎng)關(guān)配置文件中添加 auth-api 相關(guān)的路由
server:
port: 10001
spring:
application:
name: '@project.name@'
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 網(wǎng)關(guān)路由配置
- id: rewritepath_route
uri: https://www.baidu.com/
predicates:
- Path=/search/**
filters:
- RewritePath=/search/(?<segment>.*), /$\{segment}
- id: user-service # 路由id,自定義,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目標(biāo)地址 http就是固定地址
uri: lb://user-service # 路由的目標(biāo)地址 lb就是負(fù)載均衡,后面跟服務(wù)名稱
predicates: # 路由斷言,也就是判斷請(qǐng)求是否符合路由規(guī)則的條件
- Path=/user/** # 這個(gè)是按照路徑匹配,只要以/user/開頭就符合要求
- id: order-service # 路由id,自定義,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目標(biāo)地址 http就是固定地址
uri: lb://order-service
predicates:
- Path=/order/**
- id: auth-api # 路由id,自定義,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目標(biāo)地址 http就是固定地址
uri: lb://auth-api
predicates:
- Path=/oauth/**
2 網(wǎng)關(guān)認(rèn)證過慮器
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* @author 早起的年輕輕人
* @version 1.0
* @description 網(wǎng)關(guān)認(rèn)證過慮器
*/
@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {
//白名單
private static List<String> whitelist = null;
static {
//加載白名單
try (
InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties");
) {
Properties properties = new Properties();
properties.load(resourceAsStream);
Set<String> strings = properties.stringPropertyNames();
whitelist= new ArrayList<>(strings);
} catch (Exception e) {
whitelist = new ArrayList<>();
log.error("加載/security-whitelist.properties出錯(cuò):{}",e.getMessage());
e.printStackTrace();
}
}
@Autowired
private TokenStore tokenStore;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//請(qǐng)求的url
String requestUrl = exchange.getRequest().getPath().value();
AntPathMatcher pathMatcher = new AntPathMatcher();
//白名單放行
for (String url : whitelist) {
if (pathMatcher.match(url, requestUrl)) {
return chain.filter(exchange);
}
}
//檢查token是否存在
String token = getToken(exchange);
if (StringUtils.isBlank(token)) {
return buildReturnMono("沒有認(rèn)證",exchange);
}
//判斷是否是有效的token
OAuth2AccessToken oAuth2AccessToken;
try {
oAuth2AccessToken = tokenStore.readAccessToken(token);
boolean expired = oAuth2AccessToken.isExpired();
if (expired) {
return buildReturnMono("認(rèn)證令牌已過期",exchange);
}
return chain.filter(exchange);
} catch (InvalidTokenException e) {
log.info("認(rèn)證令牌無效: {}", token);
return buildReturnMono("認(rèn)證令牌無效",exchange);
}
}
/**
* 獲取token
*/
private String getToken(ServerWebExchange exchange) {
String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
if (StringUtils.isBlank(tokenStr)) {
return null;
}
return tokenStr;
}
private Mono<Void> buildReturnMono(String error, ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
Map<String,Object> map = new HashMap<>();
map.put("code",403);
map.put("message",error);
String jsonString = JSON.toJSONString(map);
byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return 0;
}
}
Spring Cloud Gateway 根據(jù)作用范圍劃分為 GatewayFilter 和 GlobalFilter
- GatewayFilter : 需要通過spring.cloud.routes.filters 配置在具體路由下,只作用在當(dāng)前路由上或通過spring.cloud.default-filters配置在全局,作用在所有路由上。
- GlobalFilter : 不需要在配置文件中配置,作用在所有的路由上,最終通過GatewayFilterAdapter包裝成GatewayFilterChain可識(shí)別的過濾器
3 啟動(dòng)服務(wù) 測(cè)試
首先通過網(wǎng)關(guān)訪問訂單詳情
http://localhost:10001/order/109
使用很久之前的一個(gè)token
然后再通過網(wǎng)關(guān)獲取令牌
然后使用新獲取到的令牌來訪問訂單詳情
4 獲取token中的用戶信息
在網(wǎng)關(guān)中將token解析,獲取登錄 token 中對(duì)應(yīng)的用戶 userId , 在網(wǎng)關(guān)中的令牌校驗(yàn)過濾器 GatewayAuthFilter 中添加內(nèi)容:
public class GatewayAuthFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
... ...
//----------獲取token中的用戶令牌--------------------------------------------------------------------
OAuth2Authentication authentication = tokenStore.readAuthentication(token);
User authUser = (User) authentication.getPrincipal();
//獲取保存的用戶令牌 我這里是一個(gè)JSON
String username = authUser.getUsername();
//使用Fastjson 將json字符串轉(zhuǎn)為map
Map<String, Object> parse = JSON.parseObject(username, Map.class);
//獲取其中的 userId
String userId = parse.get("userId").toString();
ServerHttpRequest req = exchange.getRequest();
HttpHeaders httpHeaders = req.getHeaders();
ServerHttpRequest.Builder requestBuilder = req.mutate();
// 先刪除,后新增
//requestBuilder.headers(k -> k.remove("要修改的header的key"));
// requestBuilder.header("要修改的header的key", 處理完之后的header的值);
// 或者直接修改,要求修改的變量為final
requestBuilder.headers(k -> k.set("userId", userId));
log.info("令牌解析成功:userId is {}",userId);
ServerHttpRequest request = requestBuilder.build();
exchange.mutate().request(request).build();
return chain.filter(exchange);
} catch (InvalidTokenException e) {
log.info("認(rèn)證令牌無效: {}", token);
return buildReturnMono("認(rèn)證令牌無效", exchange);
}
}
}
這里是獲取了用戶的 userId ,然后將userId添加到請(qǐng)求頭中,比如在后續(xù)的 admin 管理后臺(tái)的服務(wù)中,可以直接通過 @RequestHeader 獲取
到此 網(wǎng)關(guān)中的鑒權(quán)功能開發(fā)完成。文章來源:http://www.zghlxwxcb.cn/news/detail-483242.html
本項(xiàng)目源碼 https://gitee.com/android.long/spring-cloud-biglead/tree/master/biglead-api-07-auth
如果有興趣可以關(guān)注一下公眾號(hào) biglead ,每周都會(huì)有 java、Flutter、小程序、js 、英語相關(guān)的內(nèi)容分享文章來源地址http://www.zghlxwxcb.cn/news/detail-483242.html
到了這里,關(guān)于SpringCloud網(wǎng)關(guān)Gateway認(rèn)證鑒權(quán)【SpringCloud系列7】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!