Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
Y
yudao-cloud
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
hblj
yudao-cloud
Commits
ef1096f5
提交
ef1096f5
authored
6月 25, 2022
作者:
YunaiV
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
TokenAuthenticationFilter 增加本地缓存
上级
d79514d8
隐藏空白字符变更
内嵌
并排
正在显示
5 个修改的文件
包含
112 行增加
和
38 行删除
+112
-38
LoginUser.java
...a/cn/iocoder/yudao/gateway/filter/security/LoginUser.java
+34
-0
TokenAuthenticationFilter.java
...ao/gateway/filter/security/TokenAuthenticationFilter.java
+53
-11
SecurityFrameworkUtils.java
...cn/iocoder/yudao/gateway/util/SecurityFrameworkUtils.java
+15
-18
WebFrameworkUtils.java
...java/cn/iocoder/yudao/gateway/util/WebFrameworkUtils.java
+9
-8
AuthController.http
...o/module/system/controller/admin/auth/AuthController.http
+1
-1
没有找到文件。
yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/security/LoginUser.java
0 → 100644
浏览文件 @
ef1096f5
package
cn
.
iocoder
.
yudao
.
gateway
.
filter
.
security
;
import
lombok.Data
;
import
java.util.List
;
/**
* 登录用户信息
*
* copy from yudao-spring-boot-starter-security 的 LoginUser 类
*
* @author 芋道源码
*/
@Data
public
class
LoginUser
{
/**
* 用户编号
*/
private
Long
id
;
/**
* 用户类型
*/
private
Integer
userType
;
/**
* 租户编号
*/
private
Long
tenantId
;
/**
* 授权范围
*/
private
List
<
String
>
scopes
;
}
yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/security/TokenAuthenticationFilter.java
浏览文件 @
ef1096f5
package
cn
.
iocoder
.
yudao
.
gateway
.
filter
.
security
;
package
cn
.
iocoder
.
yudao
.
gateway
.
filter
.
security
;
import
cn.hutool.core.util.StrUtil
;
import
cn.hutool.core.util.StrUtil
;
import
cn.iocoder.yudao.framework.common.core.KeyValue
;
import
cn.iocoder.yudao.framework.common.pojo.CommonResult
;
import
cn.iocoder.yudao.framework.common.pojo.CommonResult
;
import
cn.iocoder.yudao.framework.common.util.cache.CacheUtils
;
import
cn.iocoder.yudao.framework.common.util.json.JsonUtils
;
import
cn.iocoder.yudao.framework.common.util.json.JsonUtils
;
import
cn.iocoder.yudao.gateway.util.SecurityFrameworkUtils
;
import
cn.iocoder.yudao.gateway.util.SecurityFrameworkUtils
;
import
cn.iocoder.yudao.gateway.util.WebFrameworkUtils
;
import
cn.iocoder.yudao.gateway.util.WebFrameworkUtils
;
import
cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi
;
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.OAuth2AccessTokenCheckRespDTO
;
import
com.fasterxml.jackson.core.type.TypeReference
;
import
com.fasterxml.jackson.core.type.TypeReference
;
import
com.google.common.cache.CacheLoader
;
import
com.google.common.cache.LoadingCache
;
import
org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction
;
import
org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction
;
import
org.springframework.cloud.gateway.filter.GatewayFilterChain
;
import
org.springframework.cloud.gateway.filter.GatewayFilterChain
;
import
org.springframework.cloud.gateway.filter.GlobalFilter
;
import
org.springframework.cloud.gateway.filter.GlobalFilter
;
...
@@ -17,6 +21,7 @@ import org.springframework.web.reactive.function.client.WebClient;
...
@@ -17,6 +21,7 @@ import org.springframework.web.reactive.function.client.WebClient;
import
org.springframework.web.server.ServerWebExchange
;
import
org.springframework.web.server.ServerWebExchange
;
import
reactor.core.publisher.Mono
;
import
reactor.core.publisher.Mono
;
import
java.time.Duration
;
import
java.util.function.Function
;
import
java.util.function.Function
;
/**
/**
...
@@ -34,6 +39,17 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
...
@@ -34,6 +39,17 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
private
final
WebClient
webClient
;
private
final
WebClient
webClient
;
private
final
LoadingCache
<
KeyValue
<
Long
,
String
>,
LoginUser
>
loginUserCache
=
CacheUtils
.
buildAsyncReloadingCache
(
Duration
.
ofMinutes
(
1
),
new
CacheLoader
<
KeyValue
<
Long
,
String
>,
LoginUser
>()
{
@Override
public
LoginUser
load
(
KeyValue
<
Long
,
String
>
keyValue
)
{
String
body
=
checkAccessToken
(
keyValue
.
getKey
(),
keyValue
.
getValue
()).
block
();
return
buildUser
(
body
);
}
});
public
TokenAuthenticationFilter
(
ReactorLoadBalancerExchangeFilterFunction
lbFunction
)
{
public
TokenAuthenticationFilter
(
ReactorLoadBalancerExchangeFilterFunction
lbFunction
)
{
// Q:为什么不使用 OAuth2TokenApi 进行调用?
// Q:为什么不使用 OAuth2TokenApi 进行调用?
// A1:Spring Cloud OpenFeign 官方未内置 Reactive 的支持 https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#reactive-support
// A1:Spring Cloud OpenFeign 官方未内置 Reactive 的支持 https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#reactive-support
...
@@ -54,24 +70,50 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
...
@@ -54,24 +70,50 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
}
}
// 情况二,如果有 Token 令牌,则解析对应 userId、userType、tenantId 等字段,并通过 通过 Header 转发给服务
// 情况二,如果有 Token 令牌,则解析对应 userId、userType、tenantId 等字段,并通过 通过 Header 转发给服务
Long
tenantId
=
WebFrameworkUtils
.
getTenantId
(
exchange
);
KeyValue
<
Long
,
String
>
cacheKey
=
new
KeyValue
<
Long
,
String
>().
setKey
(
tenantId
).
setValue
(
token
);
LoginUser
user
=
loginUserCache
.
getUnchecked
(
cacheKey
);
if
(
user
!=
null
)
{
SecurityFrameworkUtils
.
setLoginUser
(
exchange
,
user
);
return
chain
.
filter
(
exchange
.
mutate
().
request
(
builder
->
SecurityFrameworkUtils
.
setLoginUserHeader
(
builder
,
user
)).
build
());
}
return
checkAccessToken
(
cacheKey
.
getKey
(),
token
)
.
flatMap
((
Function
<
String
,
Mono
<
Void
>>)
body
->
chain
.
filter
(
buildNewServerWebExchange
(
exchange
,
cacheKey
,
body
)));
// 处理请求的结果
}
private
Mono
<
String
>
checkAccessToken
(
Long
tenantId
,
String
token
)
{
return
webClient
.
get
()
return
webClient
.
get
()
.
uri
(
OAuth2TokenApi
.
URL_CHECK
,
uriBuilder
->
uriBuilder
.
queryParam
(
"accessToken"
,
token
).
build
())
.
uri
(
OAuth2TokenApi
.
URL_CHECK
,
uriBuilder
->
uriBuilder
.
queryParam
(
"accessToken"
,
token
).
build
())
.
headers
(
httpHeaders
->
WebFrameworkUtils
.
setTenantIdHeader
(
exchange
,
httpHeaders
))
// 设置租户的 Header
.
headers
(
httpHeaders
->
WebFrameworkUtils
.
setTenantIdHeader
(
tenantId
,
httpHeaders
))
// 设置租户的 Header
.
retrieve
().
bodyToMono
(
String
.
class
)
// 发起请求,设置 body 为 String 结果
.
retrieve
().
bodyToMono
(
String
.
class
);
.
flatMap
((
Function
<
String
,
Mono
<
Void
>>)
body
->
chain
.
filter
(
buildNewServerWebExchange
(
exchange
,
body
)));
// 处理请求的结果
}
}
private
ServerWebExchange
buildNewServerWebExchange
(
ServerWebExchange
exchange
,
String
body
)
{
private
ServerWebExchange
buildNewServerWebExchange
(
ServerWebExchange
exchange
,
KeyValue
<
Long
,
String
>
cacheKey
,
String
body
)
{
// 校验 Token 令牌失败,则直接返回
// 1.1 解析 User
CommonResult
<
OAuth2AccessTokenCheckRespDTO
>
result
=
JsonUtils
.
parseObject
(
body
,
CHECK_RESULT_TYPE_REFERENCE
);
LoginUser
user
=
buildUser
(
body
);
if
(
result
==
null
||
result
.
isError
())
{
// 1.2 校验 Token 令牌失败,则直接返回
if
(
user
==
null
)
{
return
exchange
;
return
exchange
;
}
}
// 设置登录用户
// 2. 设置到缓存
SecurityFrameworkUtils
.
setLoginUser
(
exchange
,
result
.
getData
());
loginUserCache
.
put
(
cacheKey
,
user
);
// 将访问令牌封装成 LoginUser,并设置到 login-user 的请求头,使用 json 存储值
return
exchange
.
mutate
().
request
(
builder
->
SecurityFrameworkUtils
.
setLoginUserHeader
(
builder
,
result
.
getData
())).
build
();
// 3.1 设置登录用户
SecurityFrameworkUtils
.
setLoginUser
(
exchange
,
user
);
// 3.2 将 user 并设置到 login-user 的请求头,使用 json 存储值
return
exchange
.
mutate
().
request
(
builder
->
SecurityFrameworkUtils
.
setLoginUserHeader
(
builder
,
user
)).
build
();
}
private
LoginUser
buildUser
(
String
body
)
{
CommonResult
<
OAuth2AccessTokenCheckRespDTO
>
result
=
JsonUtils
.
parseObject
(
body
,
CHECK_RESULT_TYPE_REFERENCE
);
if
(
result
==
null
||
result
.
isError
())
{
return
null
;
}
// 创建登录用户
OAuth2AccessTokenCheckRespDTO
tokenInfo
=
result
.
getData
();
return
new
LoginUser
().
setId
(
tokenInfo
.
getUserId
()).
setUserType
(
tokenInfo
.
getUserType
())
.
setTenantId
(
tokenInfo
.
getTenantId
()).
setScopes
(
tokenInfo
.
getScopes
());
}
}
@Override
@Override
...
...
yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/SecurityFrameworkUtils.java
浏览文件 @
ef1096f5
...
@@ -2,13 +2,11 @@ package cn.iocoder.yudao.gateway.util;
...
@@ -2,13 +2,11 @@ package cn.iocoder.yudao.gateway.util;
import
cn.hutool.core.map.MapUtil
;
import
cn.hutool.core.map.MapUtil
;
import
cn.iocoder.yudao.framework.common.util.json.JsonUtils
;
import
cn.iocoder.yudao.framework.common.util.json.JsonUtils
;
import
cn.iocoder.yudao.
module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO
;
import
cn.iocoder.yudao.
gateway.filter.security.LoginUser
;
import
org.springframework.http.server.reactive.ServerHttpRequest
;
import
org.springframework.http.server.reactive.ServerHttpRequest
;
import
org.springframework.util.StringUtils
;
import
org.springframework.util.StringUtils
;
import
org.springframework.web.server.ServerWebExchange
;
import
org.springframework.web.server.ServerWebExchange
;
import
java.util.Map
;
/**
/**
* 安全服务工具类
* 安全服务工具类
*
*
...
@@ -51,13 +49,19 @@ public class SecurityFrameworkUtils {
...
@@ -51,13 +49,19 @@ public class SecurityFrameworkUtils {
* 设置登录用户
* 设置登录用户
*
*
* @param exchange 请求
* @param exchange 请求
* @param
token 访问令牌
* @param
user 用户
*/
*/
public
static
void
setLoginUser
(
ServerWebExchange
exchange
,
OAuth2AccessTokenCheckRespDTO
token
)
{
public
static
void
setLoginUser
(
ServerWebExchange
exchange
,
LoginUser
user
)
{
exchange
.
getAttributes
().
put
(
LOGIN_USER_ID_ATTR
,
token
.
getUser
Id
());
exchange
.
getAttributes
().
put
(
LOGIN_USER_ID_ATTR
,
user
.
get
Id
());
exchange
.
getAttributes
().
put
(
LOGIN_USER_TYPE_ATTR
,
token
.
getUserType
());
exchange
.
getAttributes
().
put
(
LOGIN_USER_TYPE_ATTR
,
user
.
getUserType
());
}
}
/**
* 移除请求头的用户
*
* @param exchange 请求
* @return 请求
*/
public
static
ServerWebExchange
removeLoginUser
(
ServerWebExchange
exchange
)
{
public
static
ServerWebExchange
removeLoginUser
(
ServerWebExchange
exchange
)
{
// 如果不包含,直接返回
// 如果不包含,直接返回
if
(!
exchange
.
getRequest
().
getHeaders
().
containsKey
(
LOGIN_USER_HEADER
))
{
if
(!
exchange
.
getRequest
().
getHeaders
().
containsKey
(
LOGIN_USER_HEADER
))
{
...
@@ -90,20 +94,13 @@ public class SecurityFrameworkUtils {
...
@@ -90,20 +94,13 @@ public class SecurityFrameworkUtils {
}
}
/**
/**
* 将
访问令牌封装成 LoginUser,
并设置到 login-user 的请求头,使用 json 存储值
* 将
user
并设置到 login-user 的请求头,使用 json 存储值
*
*
* @param builder 请求
* @param builder 请求
* @param
token 访问令牌
* @param
user 用户
*/
*/
public
static
void
setLoginUserHeader
(
ServerHttpRequest
.
Builder
builder
,
OAuth2AccessTokenCheckRespDTO
token
)
{
public
static
void
setLoginUserHeader
(
ServerHttpRequest
.
Builder
builder
,
LoginUser
user
)
{
// 构建 LoginUser 对象。由于 Gateway 没有 loginUser 类,所以使用 Map
builder
.
header
(
LOGIN_USER_HEADER
,
JsonUtils
.
toJsonString
(
user
));
Map
<
String
,
Object
>
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
));
}
}
}
}
yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/WebFrameworkUtils.java
浏览文件 @
ef1096f5
...
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.gateway.util;
...
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.gateway.util;
import
cn.hutool.core.net.NetUtil
;
import
cn.hutool.core.net.NetUtil
;
import
cn.hutool.core.util.ArrayUtil
;
import
cn.hutool.core.util.ArrayUtil
;
import
cn.hutool.core.util.StrUtil
;
import
cn.hutool.extra.servlet.ServletUtil
;
import
cn.hutool.extra.servlet.ServletUtil
;
import
cn.iocoder.yudao.framework.common.util.json.JsonUtils
;
import
cn.iocoder.yudao.framework.common.util.json.JsonUtils
;
import
lombok.extern.slf4j.Slf4j
;
import
lombok.extern.slf4j.Slf4j
;
...
@@ -28,22 +27,24 @@ public class WebFrameworkUtils {
...
@@ -28,22 +27,24 @@ public class WebFrameworkUtils {
private
static
final
String
HEADER_TENANT_ID
=
"tenant-id"
;
private
static
final
String
HEADER_TENANT_ID
=
"tenant-id"
;
private
static
final
String
HEADER_TAG
=
"tag"
;
private
WebFrameworkUtils
()
{}
private
WebFrameworkUtils
()
{}
/**
/**
* 将 Gateway 请求中的 header,设置到 HttpHeaders 中
* 将 Gateway 请求中的 header,设置到 HttpHeaders 中
*
*
* @param
exchange Gateway 请求
* @param
tenantId 租户编号
* @param httpHeaders WebClient 的请求
* @param httpHeaders WebClient 的请求
*/
*/
public
static
void
setTenantIdHeader
(
ServerWebExchange
exchange
,
HttpHeaders
httpHeaders
)
{
public
static
void
setTenantIdHeader
(
Long
tenantId
,
HttpHeaders
httpHeaders
)
{
String
tenantId
=
exchange
.
getRequest
().
getHeaders
().
getFirst
(
HEADER_TENANT_ID
);
if
(
tenantId
==
null
)
{
if
(
StrUtil
.
isNotEmpty
(
tenantId
))
{
return
;
return
;
}
}
httpHeaders
.
set
(
HEADER_TENANT_ID
,
tenantId
);
httpHeaders
.
set
(
HEADER_TENANT_ID
,
String
.
valueOf
(
tenantId
));
}
public
static
Long
getTenantId
(
ServerWebExchange
exchange
)
{
String
tenantId
=
exchange
.
getRequest
().
getHeaders
().
getFirst
(
HEADER_TENANT_ID
);
return
tenantId
!=
null
?
Long
.
parseLong
(
tenantId
)
:
null
;
}
}
/**
/**
...
...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http
浏览文件 @
ef1096f5
...
@@ -29,5 +29,5 @@ tenant-id: {{adminTenentId}}
...
@@ -29,5 +29,5 @@ tenant-id: {{adminTenentId}}
### 请求 /list-menus 接口 => 成功
### 请求 /list-menus 接口 => 成功
GET {{systemBaseUrl}}/system/auth/list-menus
GET {{systemBaseUrl}}/system/auth/list-menus
#Authorization: Bearer {{token}}
#Authorization: Bearer {{token}}
Authorization: Bearer
81e64ecd759a410ca54d3f00bdeb4574
Authorization: Bearer
c347026e805e4d99b0d116eae66eda8c
tenant-id: {{adminTenentId}}
tenant-id: {{adminTenentId}}
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论