diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
index 11b82a12b..8ccbb8fe6 100644
--- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
+++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
@@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.framework.security.core.LoginUser;
@@ -41,28 +42,34 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
@SuppressWarnings("NullableProblems")
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
- String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
- if (StrUtil.isNotEmpty(token)) {
- Integer userType = WebFrameworkUtils.getLoginUserType(request);
- try {
- // 1.1 基于 token 构建登录用户
- LoginUser loginUser = buildLoginUserByToken(token, userType);
- // 1.2 模拟 Login 功能,方便日常开发调试
- if (loginUser == null) {
- loginUser = mockLoginUser(request, token, userType);
- }
+ // 情况一,基于 header[login-user] 获得用户,例如说来自 Gateway 或者其它服务透传
+ LoginUser loginUser = buildLoginUserByHeader(request);
- // 2. 设置当前用户
- if (loginUser != null) {
- SecurityFrameworkUtils.setLoginUser(loginUser, request);
+ // 情况二,基于 Token 获得用户
+ // 注意,这里主要满足直接使用 Nginx 直接转发到 Spring Cloud 服务的场景。
+ if (loginUser == null) {
+ String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
+ if (StrUtil.isNotEmpty(token)) {
+ Integer userType = WebFrameworkUtils.getLoginUserType(request);
+ try {
+ // 1.1 基于 token 构建登录用户
+ loginUser = buildLoginUserByToken(token, userType);
+ // 1.2 模拟 Login 功能,方便日常开发调试
+ if (loginUser == null) {
+ loginUser = mockLoginUser(request, token, userType);
+ }
+ } catch (Throwable ex) {
+ CommonResult> result = globalExceptionHandler.allExceptionHandler(request, ex);
+ ServletUtils.writeJSON(response, result);
+ return;
}
- } catch (Throwable ex) {
- CommonResult> result = globalExceptionHandler.allExceptionHandler(request, ex);
- ServletUtils.writeJSON(response, result);
- return;
}
}
+ // 设置当前用户
+ if (loginUser != null) {
+ SecurityFrameworkUtils.setLoginUser(loginUser, request);
+ }
// 继续过滤链
chain.doFilter(request, response);
}
@@ -113,4 +120,9 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
.setTenantId(WebFrameworkUtils.getTenantId(request));
}
+ private LoginUser buildLoginUserByHeader(HttpServletRequest request) {
+ String loginUserStr = request.getHeader(SecurityFrameworkUtils.LOGIN_USER_HEADER);
+ return StrUtil.isNotEmpty(loginUserStr) ? JsonUtils.parseObject(loginUserStr, LoginUser.class) : null;
+ }
+
}
diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java
index 5dc17b626..3fa9c0004 100644
--- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java
+++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java
@@ -22,6 +22,8 @@ public class SecurityFrameworkUtils {
public static final String AUTHORIZATION_BEARER = "Bearer";
+ public static final String LOGIN_USER_HEADER = "login-user";
+
private SecurityFrameworkUtils() {}
/**
diff --git a/yudao-gateway/pom.xml b/yudao-gateway/pom.xml
index 8d2386954..fccace9c8 100644
--- a/yudao-gateway/pom.xml
+++ b/yudao-gateway/pom.xml
@@ -35,10 +35,10 @@
spring-cloud-starter-loadbalancer
-
- org.springframework.cloud
- spring-cloud-starter-openfeign
-
+
+
+
+
@@ -48,4 +48,27 @@
+
+
+ ${project.artifactId}
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 2.6.7
+
+ true
+
+
+
+
+ repackage
+
+
+
+
+
+
+
diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/GatewayServerApplication.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/GatewayServerApplication.java
index 5ad844753..d5904db49 100644
--- a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/GatewayServerApplication.java
+++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/GatewayServerApplication.java
@@ -1,14 +1,9 @@
package cn.iocoder.yudao.gateway;
-import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
-@EnableFeignClients(clients = {
- OAuth2TokenApi.class
-}) // TODO 芋艿:需要改下
public class GatewayServerApplication {
public static void main(String[] args) {
diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/config/TmpConfiguration.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/config/TmpConfiguration.java
deleted file mode 100644
index 0d00d3288..000000000
--- a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/config/TmpConfiguration.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package cn.iocoder.yudao.gateway.config;
-
-import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.reactive.function.client.WebClient;
-
-@Configuration
-public class TmpConfiguration {
-
- @Bean
- public WebClient webClient(ReactorLoadBalancerExchangeFilterFunction lbFunction) {
- return WebClient.builder().filter(lbFunction).build();
- }
-
-}
diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/TokenAuthenticationFilter.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/TokenAuthenticationFilter.java
index b7ad27f2e..5a456f0dd 100644
--- a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/TokenAuthenticationFilter.java
+++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/TokenAuthenticationFilter.java
@@ -4,11 +4,11 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.gateway.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.gateway.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
-import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
import com.fasterxml.jackson.core.type.TypeReference;
-import com.google.common.net.HttpHeaders;
+import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
@@ -29,14 +29,21 @@ import java.util.function.Function;
*
* @author 芋道源码
*/
-@Component // TODO 芋艿:要改成 configuration
+@Component
public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
-// @Resource
-// private OAuth2TokenApi oauth2TokenApi;
+ private static final TypeReference> CHECK_RESULT_TYPE_REFERENCE
+ = new TypeReference>() {};
- @Resource
- private WebClient webClient;
+ private final WebClient webClient;
+
+ public TokenAuthenticationFilter(ReactorLoadBalancerExchangeFilterFunction lbFunction) {
+ // Q:为什么不使用 OAuth2TokenApi 进行调用?
+ // A1:Spring Cloud OpenFeign 官方未内置 Reactive 的支持 https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#reactive-support
+ // A2:校验 Token 的 API 需要使用到 header[tenant-id] 传递租户编号,暂时不想编写 RequestInterceptor 实现
+ // 因此,这里采用 WebClient,通过 lbFunction 实现负载均衡
+ this.webClient = WebClient.builder().filter(lbFunction).build();
+ }
@Override
public Mono filter(final ServerWebExchange exchange, GatewayFilterChain chain) {
@@ -46,46 +53,23 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
return chain.filter(exchange);
}
-// exchange = exchange.mutate().request(r -> r.headers(new Consumer() {
-// @Override
-// public void accept(HttpHeaders headers) {
-// headers.set("user-id", "1");
-// }
-// })).build();
-
-
-// return Mono.fromCallable(new Callable>() {
-// @Override
-// public CommonResult call() throws Exception {
-//// return oauth2TokenApi.checkAccessToken("1234");
-// return CommonResult.success(new OAuth2AccessTokenCheckRespDTO().setUserId(1L));
-// }
-// }).subscribeOn(Schedulers.boundedElastic()).flatMap(new Function, Mono>() {
-// @Override
-// public Mono apply(CommonResult oAuth2AccessTokenCheckRespDTOCommonResult) {
-// return chain.filter(exchange);
-// }
-// });
-
// 情况二,如果有 Token 令牌,则解析对应 userId、userType、tenantId 等字段,并通过 通过 Header 转发给服务
- // TODO 芋艿:tenant-id
- String tenantId = exchange.getRequest().getHeaders().getFirst("tenant-id");
return webClient.get()
.uri(OAuth2TokenApi.URL_CHECK, uriBuilder -> uriBuilder.queryParam("accessToken", token).build())
- .header("tenant-id", tenantId)
+ .headers(httpHeaders -> WebFrameworkUtils.setTenantIdHeader(exchange, httpHeaders)) // 设置租户的 Header
.retrieve().bodyToMono(String.class) // 发起请求,设置 body 为 String 结果
- // 处理请求的结果
- .flatMap((Function>) body -> chain.filter(buildNewServerWebExchange(exchange, body)));
+ .flatMap((Function>) body -> chain.filter(buildNewServerWebExchange(exchange, body))); // 处理请求的结果
}
private ServerWebExchange buildNewServerWebExchange(ServerWebExchange exchange, String body) {
// 校验 Token 令牌失败,则直接返回
- CommonResult> result = JsonUtils.parseObject(body, CommonResult.class);
+ CommonResult result = JsonUtils.parseObject(body, CHECK_RESULT_TYPE_REFERENCE);
if (result == null || result.isError()) {
return exchange;
}
- // 创建新的 exchange 对象
- return exchange.mutate().request(builder -> builder.header("login-user", result.getData().toString())).build();
+
+ // 将访问令牌封装成 LoginUser,并设置到 login-user 的请求头,使用 json 存储值
+ return exchange.mutate().request(builder -> SecurityFrameworkUtils.setLoginUserHeader(builder, result.getData())).build();
}
@Override
diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/SecurityFrameworkUtils.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/SecurityFrameworkUtils.java
index 1ce8af64d..ea107fd32 100644
--- a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/SecurityFrameworkUtils.java
+++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/SecurityFrameworkUtils.java
@@ -1,8 +1,14 @@
package cn.iocoder.yudao.gateway.util;
+import cn.hutool.core.map.MapUtil;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
+import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
+import java.util.Map;
+
/**
* 安全服务工具类
*
@@ -12,9 +18,11 @@ import org.springframework.web.server.ServerWebExchange;
*/
public class SecurityFrameworkUtils {
- public static final String AUTHORIZATION_HEADER = "Authorization";
+ private static final String AUTHORIZATION_HEADER = "Authorization";
- public static final String AUTHORIZATION_BEARER = "Bearer";
+ private static final String AUTHORIZATION_BEARER = "Bearer";
+
+ private static final String LOGIN_USER_HEADER = "login-user";
private SecurityFrameworkUtils() {}
@@ -36,4 +44,21 @@ public class SecurityFrameworkUtils {
return authorization.substring(index + 7).trim();
}
+ /**
+ * 将访问令牌封装成 LoginUser,并设置到 login-user 的请求头,使用 json 存储值
+ *
+ * @param builder 请求
+ * @param token 访问令牌
+ */
+ public static void setLoginUserHeader(ServerHttpRequest.Builder builder, OAuth2AccessTokenCheckRespDTO token) {
+ // 构建 LoginUser 对象。由于 Gateway 没有 loginUser 类,所以使用 Map
+ Map loginUser = MapUtil.newHashMap(4);
+ loginUser.put("id", token.getUserId());
+ loginUser.put("userType", token.getUserType());
+ loginUser.put("tenantId", token.getTenantId());
+ loginUser.put("scopes", token.getScopes());
+ // 设置到 Header 中
+ builder.header(LOGIN_USER_HEADER, JsonUtils.toJsonString(loginUser));
+ }
+
}
diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/WebFrameworkUtils.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/WebFrameworkUtils.java
new file mode 100644
index 000000000..e696ae710
--- /dev/null
+++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/WebFrameworkUtils.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.gateway.util;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.server.ServerWebExchange;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Web 工具类
+ *
+ * copy from yudao-spring-boot-starter-web 的 WebFrameworkUtils 类
+ *
+ * @author 芋道源码
+ */
+public class WebFrameworkUtils {
+
+ @SuppressWarnings("UastIncorrectHttpHeaderInspection")
+ private static final String HEADER_TENANT_ID = "tenant-id";
+
+ private WebFrameworkUtils() {}
+
+ /**
+ * 将 Gateway 请求中的 header,设置到 HttpHeaders 中
+ *
+ * @param exchange Gateway 请求
+ * @param httpHeaders WebClient 的请求
+ */
+ public static void setTenantIdHeader(ServerWebExchange exchange, HttpHeaders httpHeaders) {
+ String tenantId = exchange.getRequest().getHeaders().getFirst(HEADER_TENANT_ID);
+ if (StrUtil.isNotEmpty(tenantId)) {
+ return;
+ }
+ httpHeaders.set(HEADER_TENANT_ID, tenantId);
+ }
+
+}