Appearance
接口请求流程
每个接口请求时,请求头中都要携带封装了用户身份的token请求头,请求通过网关路由到后端具体的服务,并在网关的系统会将请求头中的token信息解析成用户信息(userId, orgId, name, account 等信息),再次封装到请求头中。
- 大致流程:
shell
"acuity-web-pro"->"acuity-gateway-server": 1 使用账号密码登录
"acuity-gateway-server"->"acuity-oauth-server": 2 路由请求到认证服务,验证账号密码
"acuity-oauth-server" --> "acuity-web-pro": 3 返回Token、用户基本信息
"acuity-web-pro"->"acuity-gateway-server": 4 请求头中携带Token、TenantId、ApplicationId、Authorization发起任意请求
Note Right of "acuity-gateway-server": 5 gateway将请求头中参数封装到请求头
"acuity-gateway-server"-->"acuity-web-pro": 5.1 验证失败,直接返回无权限
"acuity-gateway-server"->"acuity-base-server": 5.2 验证通过,路由请求到后端服务
Note Right of "acuity-base-server": 后端拦截器解析请求头中的用户信息,封装到ThreadLocal
"acuity-base-server"-->"acuity-web-pro": 6 返回数据
"acuity-web-pro"->"acuity-gateway-server": 1 使用账号密码登录
"acuity-gateway-server"->"acuity-oauth-server": 2 路由请求到认证服务,验证账号密码
"acuity-oauth-server" --> "acuity-web-pro": 3 返回Token、用户基本信息
"acuity-web-pro"->"acuity-gateway-server": 4 请求头中携带Token、TenantId、ApplicationId、Authorization发起任意请求
Note Right of "acuity-gateway-server": 5 gateway将请求头中参数封装到请求头
"acuity-gateway-server"-->"acuity-web-pro": 5.1 验证失败,直接返回无权限
"acuity-gateway-server"->"acuity-base-server": 5.2 验证通过,路由请求到后端服务
Note Right of "acuity-base-server": 后端拦截器解析请求头中的用户信息,封装到ThreadLocal
"acuity-base-server"-->"acuity-web-pro": 6 返回数据
- 详细流程:
解释
通过上图可以知道,正常流程中,调用后端请求时,需要在请求头中携带Token、TenantId、ApplicationId、Authorization等参数,在网关解析并验证Token、TenantId后,将UserId、TenantId、EmployeeId、ApplicationId等基础信息封装到请求头中,转发请求到具体的 业务服务中,每个业务服务都有一个上下文拦截器HeaderThreadLocalInterceptor
, 用于将请求头中的UserId、TenantId、EmployeeId、ApplicationId等信息封装到ContextUtil中。 这样,当请求到达Controller->Service->Mapper 层时,程序就能通过ContextUtil获取当前登录人的信息和租户编码用于切换数据源和业务处理了。
由上可知,默认情况下,每个请求默认都会携带Token、TenantId、ApplicationId、Authorization4个请求头,他们的用途如下:
Token:当前请求来自那个用户(userId)、员工(employeeId)
主要用途:封装用户信息,鉴别请求是谁发起的,请求头中携带了Token参数,才能进行URI鉴权。
Token设置了8小时的有效期,有效期结束表示用户登录失效。
TenantId:当前请求来自那个租户ID
主要用途:用于切换数据源。请求头中携带了TenantId参数,才能操作租户库和URI鉴权
ApplicationId:当前请求来自那个应用ID
主要用途:请求头中携带了ApplicationId参数,才能进行应用鉴权和URI鉴权
Authorization:当前请求来自那个客户端
如何让请求不携带租户ID(TanantId) ?
注意
配置acuity.ignore.anyTenant后,该请求就不需要携带 TanantId 和 Token,后台接口不能操作 租户数据库,不能获取租户信息,用户信息,不能校验URI权限
- 在 acuity-gateway-server.yml 中配置:
yaml
acuity:
ignore:
anyTenant:
ALL: # ALL 表示任意的请求类型
- /**/anyTenant/**
POST: # post 类型的请求
- /xxx/save
GET:
- /xxx/getById
PUT:
- /xxx/update
DELETE:
- /xxx/delete
acuity:
ignore:
anyTenant:
ALL: # ALL 表示任意的请求类型
- /**/anyTenant/**
POST: # post 类型的请求
- /xxx/save
GET:
- /xxx/getById
PUT:
- /xxx/update
DELETE:
- /xxx/delete
路径匹配规则是依照 AntPathMatcher 的来的
(1)? 匹配一个字符 (2)* 匹配0个或多个字符 (3)**匹配0个或多个目录
- 调试 TokenContextFilter 过滤器, 配置了的地址只要使得 isIgnoreTenant 方法放回true, 请求就可以不携带租户信息。
java
private void parseTenant(ServerHttpRequest request, ServerHttpRequest.Builder mutate) {
// 使请求忽略验证请求中的 租户ID (TenantId) 参数
if (isIgnoreTenant(request)) {
return;
}
String tenant = getHeader(TENANT_ID_KEY, request);
if (StrUtil.isNotEmpty(tenant)) {
ContextUtil.setTenantId(tenant);
addHeader(mutate, TENANT_ID_HEADER, ContextUtil.getTenantId());
MDC.put(TENANT_ID_HEADER, tenant);
}
}
private void parseTenant(ServerHttpRequest request, ServerHttpRequest.Builder mutate) {
// 使请求忽略验证请求中的 租户ID (TenantId) 参数
if (isIgnoreTenant(request)) {
return;
}
String tenant = getHeader(TENANT_ID_KEY, request);
if (StrUtil.isNotEmpty(tenant)) {
ContextUtil.setTenantId(tenant);
addHeader(mutate, TENANT_ID_HEADER, ContextUtil.getTenantId());
MDC.put(TENANT_ID_HEADER, tenant);
}
}
如何让请求不携带用户凭据(Token) ?
注意
配置后,需要携带租户ID,但不需要携带Token(不需要登录) 且不需要校验权限。 即: 请求头中携带 TenantId, 但不携带 Token
- 在 acuity-gateway-server.yml 中配置:
yaml
acuity:
ignore:
anyUser:
ALL: # ALL 表示任意的请求类型
- /**/anyUser/**
POST: # post 类型的请求
- /xxx/save
GET:
- /xxx/getById
PUT:
- /xxx/update
DELETE:
- /xxx/delete
acuity:
ignore:
anyUser:
ALL: # ALL 表示任意的请求类型
- /**/anyUser/**
POST: # post 类型的请求
- /xxx/save
GET:
- /xxx/getById
PUT:
- /xxx/update
DELETE:
- /xxx/delete
- 实现方式:
java
public class TokenContextFilter implements WebFilter, Ordered {
private Mono<Void> parseToken(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
ServerHttpRequest.Builder mutate = request.mutate();
// 判断接口是否需要忽略token验证
if (isIgnoreToken(request.getPath().toString())) {
log.debug("当前接口:{}, 不解析用户token", request.getPath().toString());
return chain.filter(exchange);
}
// 后面的代码省略
}
}
public class TokenContextFilter implements WebFilter, Ordered {
private Mono<Void> parseToken(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
ServerHttpRequest.Builder mutate = request.mutate();
// 判断接口是否需要忽略token验证
if (isIgnoreToken(request.getPath().toString())) {
log.debug("当前接口:{}, 不解析用户token", request.getPath().toString());
return chain.filter(exchange);
}
// 后面的代码省略
}
}
如何让请求不验证 URI权限 和 应用访问权限
- 全局禁用, 修改 acuity-gateway-server.yml : (禁用后,整个系统所有方法,都不需要校验URI权限)
yaml
acuity:
ignore:
# 是否启用网关的 uri权限鉴权 和 前端按钮权限 (设置为false,则不校验访问权限)
authEnabled: false
acuity:
ignore:
# 是否启用网关的 uri权限鉴权 和 前端按钮权限 (设置为false,则不校验访问权限)
authEnabled: false
- 禁用指定URI接口地址: (禁用后,能匹配的URI,都不需要校验URI权限)
yaml
acuity:
ignore:
anyone: # 需要携带TenantId和Token,但无需验证是否拥有 uri 权限的接口。 即: 请求头中携带 tenant,也携带 token, 但不对uri权限验证
ALL:
- /**/anyone/**
POST: # post 类型的请求
- /xxx/save
GET:
- /xxx/getById
PUT:
- /xxx/update
DELETE:
- /xxx/delete
acuity:
ignore:
anyone: # 需要携带TenantId和Token,但无需验证是否拥有 uri 权限的接口。 即: 请求头中携带 tenant,也携带 token, 但不对uri权限验证
ALL:
- /**/anyone/**
POST: # post 类型的请求
- /xxx/save
GET:
- /xxx/getById
PUT:
- /xxx/update
DELETE:
- /xxx/delete
如何禁用前端的按钮权限
前端的按钮权限数据是由后端返回的,只要后端接口返回false,前端就不在校验按钮权限。
java
@GetMapping("/visible/resource")
public R<VisibleResourceVO> visible(@ApiIgnore @LoginUser SysUser sysUser,
@RequestParam(value = "employeeId", required = false) Long employeeId,
@RequestParam Long applicationId) {
if (employeeId == null || employeeId <= 0) {
employeeId = sysUser.getEmployeeId();
}
return R.success(VisibleResourceVO.builder()
.roleList(Arrays.asList("PT_ADMIN"))
// 查询员工拥有的所有资源编码
.resourceList(oauthResourceBiz.findVisibleResource(employeeId, applicationId))
// 资源编码是否区分大小写
.caseSensitive(ignoreProperties.getCaseSensitive())
// 是否启用按钮权限
.enabled(ignoreProperties.getAuthEnabled())
.build());
}
@GetMapping("/visible/resource")
public R<VisibleResourceVO> visible(@ApiIgnore @LoginUser SysUser sysUser,
@RequestParam(value = "employeeId", required = false) Long employeeId,
@RequestParam Long applicationId) {
if (employeeId == null || employeeId <= 0) {
employeeId = sysUser.getEmployeeId();
}
return R.success(VisibleResourceVO.builder()
.roleList(Arrays.asList("PT_ADMIN"))
// 查询员工拥有的所有资源编码
.resourceList(oauthResourceBiz.findVisibleResource(employeeId, applicationId))
// 资源编码是否区分大小写
.caseSensitive(ignoreProperties.getCaseSensitive())
// 是否启用按钮权限
.enabled(ignoreProperties.getAuthEnabled())
.build());
}
- 修改配置
yaml
acuity:
ignore:
# 是否启用网关的 uri权限鉴权 和 前端按钮权限 (设置为false,则不校验访问权限)
authEnabled: false
acuity:
ignore:
# 是否启用网关的 uri权限鉴权 和 前端按钮权限 (设置为false,则不校验访问权限)
authEnabled: false