统一登录平台(CAS)包含了统一登录与用户管理、应用授权管理两块的功能,本文也会从这两块分别介绍如何接入CAS。
注册APP
接入前需要在CAS注册APP, 补充应用编号(后文以APP_CODE作为缩写)、名称、产品号、应用URL、应用版本等信息。
注册APP配置示例
对于统一登录来说最重要的是APP_CODE 和 应用URL这两个参数:
- APP_CODE 是应用标识,不可更改,后续接入的API中会到这个参数;
- URL 对应系统访问地址,从控制台点击对应APP卡片或者登录后跳转的地方。
对于授权管理主要会用到APP_CODE、应用版本号这两个参数, 应用版本号对应应用的具体版本,不同版本所含的服务列表可能不一样。
统一登录与用户管理
登录基本流程及部署架构
CAS登陆流程说明
典型的两个登陆流程:
- 用户CAS登录之后,从CAS带Token跳转到APP系统就行登录
- 或者用户直接访问APP系统,但是未携带Token,此时重定向到CAS进行登录之后跳回应用。
总之如果没有有效的token,会登陆之后带Token访问APP系统的前端,如:http://10.10.32.32:9030/?castoken=eyJ0IjoiNGFlZmZhMGE0ZDI2NDcwOGE3NWEyYzcxYTAyYzU0ZmQiLCJ1IjoiMSJ9
带token到APP前端后,前端存储token并在后续请求中带token访问应用,后端收到请求之后会拦截并做校验。
Token校验流程:
- 判断token是否有效(reids)
- 按需延长token有效期 (http接口)
- 获取token对应用户信息(redis)
CAS的关键接口如Token校验、获取Token对应的用户信息是通过Redis获取的,其他非关键信息是直接通过HTTP接口提供的,目的是为了提高系统的可用性,即使CAS挂了,不影响已登陆的用户继续使用。
为了进一步提高系统可用性,cas的token校验、用户信息获取还增加了本地缓存,在redis挂的时候会fallback到本地缓存继续服务(这个功能默认没有开启,需要手工打开)
用户信息后续的传递流程
- cas默认的filter会把用户信息放入http header,然后继续向下透传
- 下层应用可以直接从filter获取用户信息 虽然也可以通过token调用cas的服务、redis获取用户信息,不过推荐从header直接获取,这种方式的性能和可用性都更好。
整体部署架构如下图所示:
CAS整体部署架构
基本的登录流程到这里就基本说清楚了,不过在应用接入的过程中遇到的一个问题是用户信息怎么同步。
对于简单的应用来说,可以不维护自己的用户表,直接走CAS的接口即可,不过对于MES、IOT这种复杂的应用来说这么做可能不够, 比如因为要维护用户组织、权限等功能,用户表和其他表之间需要做连接查询,这种情况冗余用户表实现成本会小一些。
因此会涉及用户的同步,目前覆盖以下两个“同步时机”就可以满足需要:
- 用户登录时,这个时候可以检查cas的用户是否在APP中存在及信息是否一致,不一致则同步。
- 查看用户列表时,主动或被动同,只在用户登陆的时候做同步可能不够,管理员把用户分配给APP之后,可能马上就进入APP内部做一些权限、工单分配之类的操作,此时用户尚未登陆app,管理员会发现再APP中无法看到这个用户。因此需要在APP中增加主动或被动的用户同步功能。
统一登录后端CAS Filter 接入方案
在应用中引入cas-sso-filter
<dependency>
<groupId>com.hvisions</groupId>
<artifactId>cas-sso-client-starter</artifactId>
<version>2.2.1-SNAPSHOT</version>
</dependency>
客户端引入后,会自动配置相关filter:
相关配置:
参数 | 含义 | 其他 |
h-visions.appCode | 接入cas后的appCode,sso接入和授权都会用到。 | 必需 |
h-visions.sso.exclusionPatterns | 在gateway校验时跳过的filter | 默认值:/actuator,/v2/api-docs,/swagger-ui |
h-visions.sso.authFilter.order | gateway中配置的filter的order | 默认值:10 |
h-visions.sso.userFilter.order | 除gateway之外应用中配置的filter的order | 默认值:100 |
h-visions.sso.redis.* | sso所需redis集群信息,支持和spring.reids.* 一样的参数 | 必需 |
h-visions.sso.enabled | 是否开启sso登录功能,true/false | 默认值:true |
注意调整下gateway中的cors filter的优先级,需要先于 sso.authFilter 执行,避免authFilter校验不通过导致的跨域错误, 同时Cors Filter需要设置下ExposedHeaders, 允许'HVISIONS-LICENSE-STATUS'授权校验状态透传(授权管理会用到,后续给出详细说明,先写在这里了)。
应用配置参考(以IOT为例)
h-visions:
appCode: hiper-matrix-xxx
sso:
exclusionPatterns: "/cas, /actuator,/v2/api-docs,/swagger-ui,/webjars,/swagger-ui.html,/swagger-resources"
redis:
host: 10.10.32.34
port: 6379
在非gateway应用获取当前用户使用
com.hvisions.cas.client.sso.SSOUtil#getCurrentUser
即可获取到当前用户信息,用户信息目前包括:// 用户id
private Long id;
// 创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date gmtCreate;
// 账号
private String userAccount;
// 昵称
private String userNick;
// 状态
private Byte status;
// 邮箱
private String email;
// 手机
private String mobilePhone;
//
private String dingTalkId;
// 钉钉unionId 钉钉三方登录使用
private String dingTalkUnionId;
// 最近一次登录时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date gmtLastLogin;
// 是否是appAdmin
private boolean appAdmin;
// 扩展字段
private Map<String, String> extAttrs;
主要就是登录相关字段,后续可能会扩展或者调整。
统一登录登录相关API
这一部分会展开介绍CAS Filter接入方案中的实现用到的类及相关API,如果Cas提供的Filter无法满足需求,APP可以基于此定制。
SSOAuthManager
SSO登录验证管理的核心类,封装了对Redis的访问和实现了本地缓存功能,对外提供根据token校验和根据token获取用户的API。
Token校验
从token解析用户id,然后通过authManager.getSessionInfo获取session,然后还要对sessionInfo.getExpiredTs做校验。
参考:
Long userId = LoginUtil.parseUserId(token);
if (userId == null) {
return buildErrResult(exchange, HttpStatus.UNAUTHORIZED, "Token错误");
}
// 检查登录状态
SessionInfo sessionInfo = authManager.getSessionInfo(userId, token);
if (sessionInfo == null || sessionInfo.getExpiredTs() < System.currentTimeMillis()) {
return buildErrResult(exchange, HttpStatus.UNAUTHORIZED, "Token错误或登录过期");
}
获取token对应用户
通过userId、appCode获取当前用户信息
AppUserDTO appUserDTO = authManager.getAppUserDTO(userId, appCode);
if (appUserDTO == null) {
return buildErrResult(exchange, HttpStatus.FORBIDDEN, "没有权限或无法找到用户");
}
缓存功能
缓存功能默认不开启,不过推荐开启,开启之后在redis挂的情况依然可以部分提供服务(看缓存大小和token有效期)。
h-visions:
appCode: hiper-matrix
sso:
exclusionPatterns: "/cas, /actuator,/v2/api-docs,/swagger-ui,/webjars,/swagger-ui.html,/swagger-resources"
redis:
host: 10.10.32.34
port: 6379
circuit-breaker:
enabled: true
sessionCacheSize: 500
userCacheSize: 200
userCacheExpiredTimeInMinutes: 720
failureRateThreshold: 10
增加circuit-breaker相关配置即可
配置项 | 含义 | 备注 |
h-visions.sso.circuit-breaker.enabled | 是否开启 | 默认值false |
h-visions.sso.circuit-breaker.sessionCacheSize | sessionInfo size大小 | 默认值500 |
h-visions.sso.circuit-breaker.userCacheSize | userCache大小 | 默认值200 |
h-visions.sso.circuit-breaker.userCacheExpiredTimeInMinutes | 用户缓存过期时间,单位分钟 | 默认值720分钟 |
h-visions.sso.circuit-breaker.failureRateThreshold | 短路保护器开关失败率阈值 | 失败率超过多少则进入断路保护器模式,具体参考https://resilience4j.readme.io/docs/circuitbreaker |
sessionCache的过期时间默认为sesssion的expiredTs,因为一个人可能同时登录多次,建议大小和userCacheSize大小成一定比例,比如2~3。
Token刷新
cas注册到了一个应用的nacos,提供了rpc接口可供调用,并提供了feignClient接口。
Webclient调用例子
private Mono<Void> tryRefreshToken(String token, Long expiredTs) {
// 为开启自动刷新
if (!refreshTokenConfig.getAutoRenewToken()) {
return Mono.empty();
}
// 可以延长的时间太少了,忽略这次调用
long now = System.currentTimeMillis();
long newExpiredTs = now + TimeUnit.MINUTES.toMillis(refreshTokenConfig.getRenewTime());
if (newExpiredTs - expiredTs < TimeUnit.MINUTES.toMillis(refreshTokenConfig.getRenewInterval())) {
return Mono.empty();
}
return webClient
.get()
.uri(uriBuilder ->
uriBuilder.path("/rpc/login/refreshToken")
.queryParam("appCode", appCode)
.queryParam("token", token)
.build())
.retrieve()
.toEntity(new ParameterizedTypeReference<ResultVO<Long>>() {
})
.flatMap(refreshResponseEntity -> {
if (refreshResponseEntity.getStatusCode() != HttpStatus.OK) {
// 只打warning错误日志, token刷新失败不影响后续执行,直到token真正的过期才会导致重新登录
log.warn("token refresh failed, token:{} expiredTs:{} statusCode:{}", token, expiredTs, refreshResponseEntity.getStatusCode());
}
return Mono.empty();
});
}
WebClient初始化参考
@Bean
@LoadBalanced
public WebClient.Builder casLoadBalancedWebClientBuilder() {
return WebClient.builder();
}
@Bean
CasSpringCloudGateWayAuthFilter casGateWayAuthFilter(WebClient.Builder casLoadBalancedWebClientBuilder,
CasRedisProperties casRedisProperties,
SSOCircuitBreakerConfig circuitBreakerConfig) {
SSOAuthManager ssoAuthManager = new SSOAuthManager(casRedisProperties, circuitBreakerConfig);
return new CasSpringCloudGateWayAuthFilter(appCode, exclusionPatterns, forbiddenPatterns, order,
casLoadBalancedWebClientBuilder.baseUrl(CasConstants.SSO_APP_BASE_URL).build(),
ssoAuthManager);
}
FeignClient接口参考:com.hvisions.cas.client.LoginClient#refreshToken
Logout
RPC接口登出例子
private Mono<Void> doLogout(String token) {
return webClient
.get()
.uri(uriBuilder ->
uriBuilder.path("/rpc/login/logout")
.queryParam("appCode", appCode)
.queryParam("token", token)
.build())
.retrieve()
.toEntity(Void.class)
.flatMap(
voidResponseEntity -> {
if (HttpStatus.OK == voidResponseEntity.getStatusCode()) {
return Mono.empty();
} else {
return Mono.error(BAD_REQUEST);
}
});
}
FeignClient接口参考:com.hvisions.cas.client.LoginClient#logout
注:因为cas会直接注册到应用nacos,前端也可直接调用http://gateway/cas/auth/logout 接口。
统一登录前端接入
如前文“典型登陆流程”部分所说,登录相关主要是处理登录token。
获取token
当用户访问APP前端页面时候,前端发现没有活着token失效,需要跳转到cas获取token,同时把当前页面的地址设置到redirect_url参数,从cas获取token后(登录或者cas前端缓存有有效token),会带token跳回APP。为了按校验跳转地址的合法性,同时需要带上appCode参数。
如访问IOT开发环境时,假如没有有效token,IOT前端会跳转到CAS开发环境:
http://10.10.32.34:8060/login?redirect_url=http://10.10.32.32:9030&appCode=hiper-matrix
获取有效token后,会带上casToken跳回IOT页面:
http://10.10.32.32:9030/?castoken=eyJ0IjoiZDY4MTM1ZmE4ZTg4NDNkZDhkZDE3MGVmY2U2YzUzMDkiLCJ1IjoiMSJ9
后续带Token访问后端
Token通过header “token”传递, 如:
curl 'http://10.10.32.32:9030/api/edge-management/connection/edge?timestamp=1703489412687' \
-H 'token: eyJ0IjoiZDY4MTM1ZmE4ZTg4NDNkZDhkZDE3MGVmY2U2YzUzMDkiLCJ1IjoiMSJ9'
后端校验到token失效或者无APP访问权限时候,会返回错误,并设置Htpp状态码401或403,如:
➜ ~ curl -v 'http://10.10.32.32:9030/api/auth/module/getModuleListWithModuleButtonListByUserId/1977' \
-H 'token: eyJ0IjoiZDY4MTM1ZmE4ZTg4NDNkZDhkZDE3MGVmY2U2YzUzMDkiLCJ1IjoiMSJ9'
* Trying 10.10.32.32:9030...
* Connected to 10.10.32.32 (10.10.32.32) port 9030 (#0)
> GET /api/auth/module/getModuleListWithModuleButtonListByUserId/1977 HTTP/1.1
> Host: 10.10.32.32:9030
> User-Agent: curl/7.87.0
> Accept: */*
> token: eyJ0IjoiZDY4MTM1ZmE4ZTg4NDNkZDhkZDE3MGVmY2U2YzUzMDkiLCJ1IjoiMSJ9
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 401
< Server: nginx/1.19.2
< Date: Mon, 25 Dec 2023 07:41:29 GMT
< Content-Type: application/json;charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
<
{"code":401,"message":"Token错误或登录过期"}
登出
以直接调用cas接口为例给出说明。
curl 'http://10.10.32.32:9030/api/cas/auth/logout?timestamp=1703490143509' \
-X 'POST' \
-H 'Accept: application/json, text/plain, */*' \
-H 'token: eyJ0IjoiM2IzNTAzMmY1Njc0NDEwMWFmMWE0ZThjYjZlMWU5YTciLCJ1IjoiMSJ9'
其他
获取APP配置
根据appCode获取应用配置 logo、主题、多语言开关配置。
curl 'http://10.10.32.32:9030/api/cas/sysConfig/getAppConfig?appCode=hiper-matrix' \
-H 'token: eyJ0IjoiNmI4MWQ4YzNmNGM4NDY2MmJhNjQzMzg0ZmVkMjc0MzUiLCJ1IjoiMSJ9' \
{
"data": {
"appCode": "hiper-matrix",
"logo": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzQiIGhlaWdodD0iNzQiIHZpZXdCb3g9IjAgMCA3NCA3NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzFfMTcyKSI+CjxwYXRoIGQ9Ik00Ny4zNCAxMS4wNkwzNi44NCAxNy4xMlY1TDkuMDMgMjEuMDVWNTMuMTZMMzYuODEgMzcuMTFMNjQuNjQgNTMuMTZWMjEuMDVMNDcuMzQgMTEuMDZaIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfMV8xNzIpIi8+CjxwYXRoIGQ9Ik0zNi44MiA1Ny4yMlY2OS4yMUw2NC42NCA1My4xNkwzNi44MSAzNy4xMUw5IDUzLjE2TDI2LjQyIDYzLjIyTDM2LjgyIDU3LjIyWiIgZmlsbD0idXJsKCNwYWludDFfbGluZWFyXzFfMTcyKSIvPgo8cGF0aCBkPSJNNTcgMTdIMTdWNTdINTdWMTdaIiBmaWxsPSIjMUYxRjFGIi8+CjxwYXRoIGQ9Ik00MS4xNTAzIDUxLjQ0ODZDNDEuMTUwMyA1MC42MTc0IDQwLjQ3NjUgNDkuOTQzNiAzOS42NDUzIDQ5Ljk0MzZDMzguODE0MSA0OS45NDM2IDM4LjE0MDMgNTAuNjE3NCAzOC4xNDAzIDUxLjQ0ODZDMzguMTQwMyA1Mi4yNzk4IDM4LjgxNDEgNTIuOTUzNiAzOS42NDUzIDUyLjk1MzZDNDAuNDc2NSA1Mi45NTM2IDQxLjE1MDMgNTIuMjc5OCA0MS4xNTAzIDUxLjQ0ODZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNDcuMTYwMyA1MS40NDg2QzQ3LjE2MDMgNTAuNjE3NCA0Ni40ODY1IDQ5Ljk0MzYgNDUuNjU1MyA0OS45NDM2QzQ0LjgyNDEgNDkuOTQzNiA0NC4xNTAzIDUwLjYxNzQgNDQuMTUwMyA1MS40NDg2QzQ0LjE1MDMgNTIuMjc5OCA0NC44MjQxIDUyLjk1MzYgNDUuNjU1MyA1Mi45NTM2QzQ2LjQ4NjUgNTIuOTUzNiA0Ny4xNjAzIDUyLjI3OTggNDcuMTYwMyA1MS40NDg2WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTUzLjE3MDMgNTEuNDQ4NkM1My4xNzAzIDUwLjYxNzQgNTIuNDk2NSA0OS45NDM2IDUxLjY2NTMgNDkuOTQzNkM1MC44MzQxIDQ5Ljk0MzYgNTAuMTYwMyA1MC42MTc0IDUwLjE2MDMgNTEuNDQ4NkM1MC4xNjAzIDUyLjI3OTggNTAuODM0MSA1Mi45NTM2IDUxLjY2NTMgNTIuOTUzNkM1Mi40OTY1IDUyLjk1MzYgNTMuMTcwMyA1Mi4yNzk4IDUzLjE3MDMgNTEuNDQ4NloiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik00My4wMSAyMS4wNkgzMy40NlYyMi45N0gzNy4yOFYzMy43OUgzOS4yVjIyLjk3SDQzLjAxVjIxLjA2WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTIyLjU3IDIxLjA1SDIwLjY2VjMzLjc5SDIyLjU3VjIxLjA1WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTI5LjAyIDIxLjA2SDI3Ljc1QzI1Ljk5IDIxLjA2IDI0LjU3IDIyLjQ4IDI0LjU3IDI0LjI0VjMwLjYxQzI0LjU3IDMyLjM3IDI1Ljk5IDMzLjc5IDI3Ljc1IDMzLjc5SDI5LjAyQzMwLjc4IDMzLjc5IDMyLjIgMzIuMzYgMzIuMiAzMC42MVYyNC4yNEMzMi4yIDIyLjQ4IDMwLjc4IDIxLjA2IDI5LjAyIDIxLjA2Wk0zMC4yOSAzMC42MUMzMC4yOSAzMS4zMSAyOS43MyAzMS44NyAyOS4wMyAzMS44N0gyNy43M0MyNy4wMyAzMS44NyAyNi40NyAzMS4zMSAyNi40NyAzMC42MVYyNC4yMUMyNi40NyAyMy41MSAyNy4wNSAyMi45NiAyNy43NCAyMi45N0gyOS4wMUMyOS43MSAyMi45NyAzMC4yOCAyMy41MiAzMC4yOCAyNC4yMVYzMC42MUgzMC4yOVoiIGZpbGw9IndoaXRlIi8+CjwvZz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl8xXzE3MiIgeDE9IjExLjMyIiB5MT0iNTQuMDMiIHgyPSI1Ny4yNyIgeTI9IjIzLjU2IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiM2RjQ3RkQiLz4KPHN0b3Agb2Zmc2V0PSIwLjE4IiBzdG9wLWNvbG9yPSIjODI0OUZCIi8+CjxzdG9wIG9mZnNldD0iMC41MSIgc3RvcC1jb2xvcj0iI0ExNENGQSIvPgo8c3RvcCBvZmZzZXQ9IjAuOCIgc3RvcC1jb2xvcj0iI0I0NEVGOSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNCQjRGRjkiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDFfbGluZWFyXzFfMTcyIiB4MT0iMzQuNTkiIHkxPSI0NC4wNiIgeDI9IjM5LjMxIiB5Mj0iNjMuMjkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzZGNDdGRCIvPgo8c3RvcCBvZmZzZXQ9IjAuMTgiIHN0b3AtY29sb3I9IiM4MjQ5RkIiLz4KPHN0b3Agb2Zmc2V0PSIwLjUxIiBzdG9wLWNvbG9yPSIjQTE0Q0ZBIi8+CjxzdG9wIG9mZnNldD0iMC44IiBzdG9wLWNvbG9yPSIjQjQ0RUY5Ii8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0JCNEZGOSIvPgo8L2xpbmVhckdyYWRpZW50Pgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzFfMTcyIj4KPHJlY3Qgd2lkdGg9IjU1LjY0IiBoZWlnaHQ9IjY0LjIxIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoOSA1KSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=",
"submenuLogo": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAT0AAAA3CAYAAAB+d51PAAAAAXNSR0IArs4c6QAAGPFJREFUeF7tXXmcHEW9//56NheCCAkQDi/0kQhB4XEJmR6z00s4VFAUPNBAILszizEKyMwGUNcjZHsSDKJkdyYc+hCVgCIq18v2JkxvwoOHwhMFlUNF5fJBQEKS3ez0z0/Nziy709XX7IyZDV1/zvzqV1Xfqv52ddXvoJ5cliEpQ4M7ZixevPhF2X9BfutZk70LjJNtdYg/m2xN/sBJV082uwqEL1b+T4RlidbE5UH6EMrKEUirfcsAvlT2Lys8P3Nvy7paY9eJTmWbqv4FwEES3c/opnbgeNvsiBofZcJPZXqIeHVXvuVzQdtIx4yfgvHRoPXGLU+4Tc9rZzjpSceME8DYWGU7LwN4hoFNEaLbpuTzd3ei0wqqK60agif2rqynMM9a3t/yR5m+y443Dhxqwh8AvEny/0rd1C4J2o+yfOe89VO3FfhRgN9p18F3UEh61UI78eu5kR4BN3aZ2oJajzKt9p0E8N0OemtCeinVWEPAIoc2ntZN7e1Bx7WLkl4lDA9ZoAtXmPF7g+BTDekJ/emYcTkY35C0NcgWvTezMS5IMXBJqX1fJvDXnfSGpBcY0l2nghvpAXhtWkSZ2bmheUstR9yh9v2AwWfXj/SY0rG+v4LhvGO0aI6+Mf67ION6g5CegKRAjMu6+jXdLz7Vkt7nT7lzym5bpvwWwLttbTHW6f3afL99KMul5q0/iArW7+U7SMroZjwdkl5QVHcheQ/SA5jO0fvj/1WrIX9p/j1vimxreg7A7vUivY7YuiOZlV+79ZlAqS4zviLIuN5ApFeEhYELM6Z2lR+MqiU9oTsV6zuDmH8ib4c+pJvxO/z0YYT01L4fE/gTEhJ9HlOtWXrvia+EpBcE0V1M1pP0QL26GT+xVsNOxXrPIabvuegb9+dtKmpcRoRvuveZNuhmvDnIuN5opAdgCJbVrG88sd8Lp/GQntCdVnvvBugkSTtPbt194LDv3HXqgFcfxP9L1b65FtgEQJXyTHxuJt/yffF7SHp+0NxFZbxJD1YTDb1jWf6kv9YCgrRq9ALQ6kl6adUQh/onePR3aCCi7HPVhmZxkO+rvAFJT+Dyv7oZPw4g6WVnGbjxkt5StfdQC/QwgEmVk+F3V168IItF7wfT0ZIJ/dU00zy2fEnTsKR3ww03vGUrb92jcgCFLYVXlixZ8k9fKzUUckXAB+mJ5d4R5HzHqcHSbZ24tY3Ui/QuPP7uvSc3TXrBo41i88Q4q6tfu8XvElmq9U5XdhR28ytfQKSDmS6wyTNd16Ts+JpfPdakyNblRoujFYXL7e2TxOxxJqbsxcSfBIpWEk2yPjGoJWPGDbf+jpf0hO60alwN4POSdl5lqzA7s3H+M2596FCNJAPdEhlWmI5f3h+/v/xfw5Ke30URylWPgB/SA/CobmqHVd/KcM10zOgAY7mHnnF93qZivWcTk6MZVEXb39NNbeF4x+VUPxUzuoiRtpMevq33azZTrGr74UJ6j+mmdqgfvSm1TyPwPbKXBQHf7TI1GRmNqK4F6XVEzb2YBoV5y4zKPhPo+11m/FynsbjVBWCb55D0/KyKXVTGJ+nBAo5aYWqulwNeEKWixiNEmFNP0ktHjZtA+LRXX0r/vzDNNPevxi7Nj/6JRHoeO62HdFP7z3rv9IT+ILu10f1x2yVGMGnWFWbs2dHyIen5WcG7qIxf0gNwlW5qF1YLQ0e07ygmftBH/ap3emeeuTZy8HPTnwcw3Uc7RRG2lGMyG5v99MuvyhG5CUd6MSMGht0+j/G83q/N/HeQXpBzuXJ/3M4DQbhEz2srbTvHRjVODrzKwgqBEQhAei9s3rrnQblfHb0jcCPi0zZqXAXCF3zUrZr0qvFMYEZnpl/zfb7mo/8TlvSWzls/2ypYj0nGuEM3tcn/DtITbbjdwBKwsMvUxtz+u9z8PrF194E5spvfcKcXZCXvYrIBSE8c/Qe2mRJwdc5b37RtyPobCPv5gK9q0uuIGd9kxmWSNoRb1dMA3iH57wHd1I7z0a/AIhNtp9copCeATvmwtSvKudj4KUQfXJ6P3ymbuJD0Ai/nXadCENJj0M0ZMy5u+gKVS6K9H1KIfuGzUtWkl471PgSmI2TERmCDQUtlhBgZ5AOuuL9FfBbXtISkB7j53rqB7eZVweAVGbMl5erNAb5DN1s+5NRGSHo1XeoTS1kQ0gOwfSCi7B/Etk2gkY4aN4Nwlk9kqiK9S9X8/gXs+LvMKJUIXysQG4pFeWkfaux1Um4jJL3qSa+4btTerwAkO3oo+uVShM+s1m83JD2fT+OuKBaQ9ISZe2uXqV3rF4vO4+5887bJU4Tb2TSfdaoivQ7VWMTAGumnjMLHPrnvS78++Lnpwn7PFgmk2h2s13gmGum5uO+9ppuak9tgEYZamKxU4nnh8ZumTW7a9qjsWIIYJhPEjbItQkt5J+g2P46kB3AfgKoOrsc2SEfJbG/gEVoquybbxsz2kDqEHyZbk47+oD09PSezQhd7LUrxPwkTVWbxafMsFN4wuG3QWLJkiS+XFy/9q1ev3ouamk4lWMcA2J9BexPjnyD6OzH/xrKsn7e3t4sHsWbluuuu22OwUDgFxEcS0wHEfFMikfhvpwaCkh6AvG5qH/Db4bRqtALI+ZUXYY6qCS3l4i3xj2mmOVOYpTieEwGvbN665z7VXtI4jW3CkZ6jcS/9STfjB7vNYT1IT7SXUns/TiDfBuRgjPjXVkl6AZZqNaJ1iqfXs6bnfDD53o1UdP1lgDJbt2y56qKLLtpWzbCuueaamZFJk74K8Pkyt5pROgtMuBlD1mXt7e1/rqatcp1cLneIxfx1ED4CYEr5d2K0JhIJRyxcSE+MXbY74wINvWtl/qQ/+elvWjWECURMIuukPzDpdR66dvK26dP/H4DNe2e0UWs62rcAxEXfS1shfEDPa/LPXz8DlchMJNIb9mRp+i1A+1cOhYBfdpnah3cG6Yk206ohjKZ9RVsZ7V8bkl7whfs4MU5LJBIiRI3vsnrNGpXY+gkB+/iuBGwHcatbQFUnXWvXro289PLLIhDoRVK/xWpJj7EWhDOljtugr2TMuCwG2phuXnyC8famCAQ52p2/QTdLI2FUsdPriPa2MJE02CkRf7Ir33Kz6NhSNb+PhR3iU1upxJMJeiavdQSYM0/RiUJ66Wjf+0ovg/c5DGqJbmrf2VmkV7LD+z8nN7lR/RrjXxuSnucSlb79X7SUwtwLzr/AVyDD1bncaQqKIXKkPoweXWAiJBOtCd+fguJTdkeh8GOAT3XSXf1OjzIAv99hl/aEbsYP8XJCdwnkyEz0EWK+XdLvwDu9tGp8C4DMcLowOLRj31X3nfxSuZ20agj/y2Ml7T6im9p7q1kmTnUagPReA+g+9zGxiDk4W/ZiKtXbQjz5bV396uadRXrF3V6077sgdot2bfOvDUmv+tX8x8HtA8d4BTjI5XKHWWCxwGyfWAGa3gGLW5LJpOdnVmdnpzLzgJm3C9s518mtdqcHyhD4cafLAUXhE5bf2+L6QKVVQ+ySZ1X2j4B7mazPg5Xf1Ij0pO3Izh87YkYnM74qw2yogHdcuUkTARFqUhqA9MY9DnHz3ZXXOr0U1etMr9yuh2+tEAvkR+1ykeE11HH+35hnepJB8fJkW1KaR6Is3LMm2wt2DZnkF6zfP/fMs4d3dnYOuVXoXpNdLqKfeCkdz05v2uD2ZS43r926qdkjiJQ6lJq7/v2kWFJSZOJFBH6gFqTXEe09mImelOJAWKrnta7R/6VU41gCRqJtjP6PgPYuU+vxwtTv/xOe9Igf3PzaW07wc8FTb9Ib3u25+VXTyboZF2d/vooj6RHjC6zwa760uAlZ9EXIHM3//aT3MJEk9IyF/XjYSV1s82Vl69Dgjrc5JUnK5XIfsMAbHCEgGGThLih4FRb2YIIIXnmK7GxJ6CDG2YlE4odO+oqXFmARYtsWe6xUZ4iBdQQyifk2t3NJ54uM4bDaLjeeL23dfeAAp+COHbHea6RhlUq2flOsobfWgvTSqiGif4iQRLKX1R1MELZ7rxcmhQBxwWQ7ZwThF3peO23c671M/Ds/ysp4hvJooTDUvHLTSb6sC+pNeq7+tcNRnu/LmPG5XkcuZUAa1k6v2mxozre3fFuyLSnNKnX11VdPmTRtyipitMtWCjHOTSQS0pu/7jXZ1Q71mBgLZfWy2ex8JvxMfkNKtyfb2sQtrLT05HpuBkhu7Et4sABlwedaW2U+lDZ9XqSXVvs+CPAvpZRC9LFMPm7LOFa6TRVkYwsRVLaJS8fWHV4b0nOMuFvNAy9ygszo3NC8vZrKlXUm8E7v1oGI0hrECL3epOfiXzsCOzMWZPq1G/3MXUh6JZRK52QbARIH+BWFb0q2JT8jAzSby4qzL1tiEwKuT7QlxK5CWrqz2QuJIA7hK8trida2PYjs0Wqz2eyeTBBvX4kDOD1AzM2JRGKrn4kXMl6kV/SbLVgiarIsysbPdFOzpUR0S79Y9t+tBemVjFdFcE2/hs8+YAn2meSmcIKR3hAY65lI9woYKhtzPUnPPYfGqN4wnp+2Y+CQzvtP9QwwHJLeKNyGDaKRlUxsf7ItodqokJmya3LC5mzENm5ExuIjksmkuGqXFhEZemDHoHhobSYUk5sm7Xveeef9o7Jidy73SQL/SKJwR0GJHPq5RYue8PFkj4h4kV6RGJ0jpAwORZQDr9zQLGzkXtfpnB92JFJLLUgvHTU+DMLPg4zXS5aIr+7Kt/iJBuOlCg1Aei8Qj3qpEqIMyC6+CkpEmbN8Q3Mg86wxc15F3ltPAEUYZbdsaTIFjC69X5P5WI+RDklvFBw9a3pOBJPMg+GPybaE7SaytPOS5llgwtXE5GHgzOIBm1o5f1akMFtmKpPNZb8lslTZ55tuTba1Cbu6QMUP6bnGwiNerOdbrik3WgrXLsJ6218Co2Ly1YL0UqrRTUAy0IC9hZ/STe1d3mLeEg1AemMiJ188b/2MpoL1lNTCgMcXzbleOz2XvLhOEzCoMB/ulGC8XCkkvVHwda9ZEye2ZPkAnky2JWyfsNdee+3eQ1bBMX+B96Mhl2hSItMXLVo0Yl9Wlsrmsj9iwBbphAiLE62JEfLx264f0hO6XKIe36+b2shxQCradwERS/sxOvpyLUivQzX+zEDgpN1e2LBFs6tNMj1ad6ORnuhbOmp8A4TLJRgMFmhotl9Pm8r69SC9Uk4VYSNr86/FcC4McRloCxdGwM+7TO10t3kOSa/xSO/hZFviSNmk9eR6DIDitv+Iz0y2Jm/1eqDti7VPeHNIzHGGb2/L8m75LZSI8p7yp1E61nsfWHYmijG7jvGS3qXz1s8pFKxHpOMl/IIB4ajuWBTGmxhYLBVgvljvb5GdtQaCtyFJr2XdnhhQhImPPbo08fV6vsXxDNpt8PUgvZRq/JCAT0nafQFTrEN4gE509Mu1cKq+UbvLqc8h6TUa6bmY8nTnshsIsDn8M+iM9ra22wI9lT4uMsr6XDOZMb6p92tf7pi3/t1csERiF5k5yBibufGSXkfUSDNhjA3eyKdLRPmPrg3NnmebadUQLnKSwKK1yfXbiKRX3O2pvSmAdMlaKTBZh2fyJ/q6+R9dv9akV4qCLfLt2tYSMZ3f1R+/fngsDn65jN9v3rbne51sDEPSayjSox8lWlvPlt3cim7uLNIrLTBpzloC/jLVNA/erkY7GfRlycNky507XtJzCWTwB93UnOwtx3QtrRqrAamJ0iBbU2ZkNkZfDfoSGS3fqKTXOW/91G0F63EAB1WOj4FbMqbmN/bhSPVakl4xT4aq/g8AEZ2osvx6mmkeU07m5OaXS6CLusz4KtkchqQ3CpVcLtdigWXO648n2xKHVAJYuoF19Uv0+eAMMmPl9L32+spZZ51VcKqzM0kvFes9h5jG5CcY2V2x1cykXAdAFoLI0E2tZczOYBx2eunhTzRxsy0xzqZv6WbcV1gxt4jOxDijq18LvHOeCKRXeoEJe1RB+jbeY+D9GVN7wOe6LYrVkvRcwpExLCumbzxR7ABfJ1xnv9x/RjBpdmUmNFExJL1RAHbncq0Eljn9BzdZAd1eitXnuH5YwXZi+gMXCre3t7eP9R6Q1NqZpNc5b/3u2wqWiFIiO1gW52uHS9+qsmQu4yC9jljvJ5jpx7K2/CSmLtcr2fkJcxtbAm8Grs2YmogFWHVp1J2eGFDbUQ9O2mu3V8RnrO2mmkF3Z8y4uCTwXWpFeqWgs+LywmYXSsCNXaa2oLJTrn65TNfp/fFFlXVC0ishUjROPnD/fjCOl7wAHY2Te3JZcX5kN3NgrEgmEinfK8eH4M4kvdIbXVi8S420Hbq/la0pMys/FcfzeZuO9X4fTLbFD2DL1t0HZji5xsn6l44Zd4KLt4AVhZ8dDmZqNxD3MU1FkUYmveG57P0MQFIPBlY4nrm3Zb3fsdaK9NKqIT5HZYnQt7BVmJXZOF+YQ9mKi9WAxcDxlTvXkPQAlOLSrQJYmsmdQQvb29qkn3Y9uVw3wDJ7sSFifDqRSPiP/OqxynY26XXEjPnM8O/YDbqpy4zbSLJa0iue90TVZ6SZ1Qi36XlN6mboBGtKNRYTII0VZxWUI1dsan7Y74NfKdfopFc6OxMJ3G1x9BjYmDG1qN+x14L0UrF17yFWhDG//dhCEjxidN9KOY9/5TAWm1/uG4j0YAs4YFkgIjoI4I+7BBzYRoy3JRKJMZ4HZdBdbPtKInwPEQnbv1e8FtHkpslrFy5cKDV2FnV3NumVHhQRfsl2CC4fm9ytq1rS84iSEih/h+jvl2L3vDPCTcJg177XY1ye6deWec2ZI6Hu/IADY8yEpDtdte90gIUPuKT4T/lZE9JT++4i8MmSjjw1LaIc5uUTnZ67LgpFEWHZ7NYD4M/qZssPyrrfSKRX1foVphHtrQlX1xZH+7mALRZIOdQtWMDOJj0xnLRqrBB84Tk04QvZpBzUuaHZFiarWtJziYfHTUN467L7NM9z0cp+p6PGYyBphJ1NuqnN9Ryng0Cj7/TK3U6pxiaC7EgHj0wzzSPKN6VuOIyX9FKxvtMcgsqCiU7P5OO+3A3TqiG+qsQGprI8N21wYFbZLzckPZfZJOAJMI5OJBKuu7RsNns4E0T8ONkhv+/nZkKQ3ty+w6AUQ1u5FmZcmenXpORYLemlVUPcKspMGR7STU1kxwpcUlFjJRFkN74FZTLvt9xoqcrjZqKQXjpmxMAQuUwkhT+lmy3SS6PRwuMhvVJUHnERZrOOAGC7+Xeb4Mti97x1iJuED7HtcorAy7vMlqIhfkh6zihuhsXRZDLpat1frt6dy32MwCIfQyTwk1eqMBFIr7jbc06s/frQmY7Q++PSgAvVkN6XTrhn30ik6VlZgAYiLOvKazL3Ks+p6IgZcWbIXA8BorP1fNwxtqGb8olCesX5VPvWATzGrKg0tsc3b93zMK9AouMhvZTat5TAV0iwHIpElCOv2NDs+YIdXdfla2DQosKcFfn5j4ekJ0Fb7PCGSDnNb1y6sopSnDxBfG/xfNokAhOG9KK9F4HoSpcx/k43tTlO/1dDeh2qcS4DN0h1EubqeW1TNZiXzDeE3d+elfXJ4SLGTzsTifRSc9cfTYoldtGS8zC06aYmzSlcxqFa0rtk3vqZSsESJipvlmC6Sjc1kfAqUHHLlwvw7brZ8pGQ9MZC+ipAK3ebOvXKBQsWVBU1uru7+0AlonydgXOC7vomCuldelzvfoXJ9DfHJEiES/S8trKWpJdSjbWEYoa2yvLSUzNf3PeWW5yNur2emnTU+AkIspvfqnVPJNIr7vacMXhmcGjau1fdd4JjxKBqSS+tGk4mUC8MRJRZQQKZjp7jdKzvLHDxq8tWiPkU6snlpPY4g9u3n+6VEMdrMYn/e3I54SN5XKWsAixra2sTrk3S0pPLiRwMtkXOwI3tbW1F3ztZccl7+wIDtmQ0RNhMjL8x8b1k0bogQTjdxp/NZmewwh8WZ4IEOsiSnDPYMGGcn0gknnbB5EoG27N2KVZn+6L2jX7mY7RMR7TvPCaZUzev9fF2XwWQdDfH1tA5TjZVov3hW9NJNiNwBl7MmHFbFJniQ1k8pCbbDpqIN/pJXuOGTbp4iykPQEARavfjy1upf9izgEQagrGFrFtHh+MKOmeV8sOuWMq3Je08HSSAgKOeYcUr3XJQpNXenwGKbafMEfpsZkOzeDnaStGfO4IbhP2EDSLm67v6NVncSN9wpaN914JkUXj4Mdl21rfiRhSsJlx8I44j7FOIQIhAfRAISa8+uIZaQwRCBBoUgZD0GnRiwm6FCIQI1AeBkPTqg2uoNUQgRKBBEQhJr0EnJuxWiECIQH0QCEmvPriGWkMEQgQaFIGQ9Bp0YsJuhQiECNQHgZD06oNrqDVEIESgQREISa9BJybsVohAiEB9EAhJrz64hlpDBEIEGhSBkPQadGLCboUIhAjUB4GQ9OqDa6g1RCBEoEERCEmvQScm7FaIQIhAfRAISa8+uIZaQwRCBBoUgX8BraEFrDkKXK0AAAAASUVORK5CYII=",
"localConfigs": {
"locale_switch": "true"
},
"themeConfigs": {
"@input-hover-border-color": "#24A1C8",
"@primary-color": "#24A1C8",
"@ne-layout-menu-logo-color": "#009bbf",
"@ne-primary-bg-color": "rgba(36,161,200,0.15)",
"@link-color": "#24A1C8",
"@ne-layout-secondary-menu-bg-color": "rgba(36,161,200,0.15)",
"@ne-layout-header-background": "#def1f7"
}
},
"code": 200,
"message": null
}
用户管理相关API
提供了获取APP用户列表和修改用户信息的API,不做展开描述,具体可以参考cas的swagger-ui。

其他
如何把CAS注册到应用的naocs
在cas的配置文件中增加如下配置,然后重启cas生效,可以按指定ip把cas注册到对应nacos的namespace。
h-visions:
sso:
discoveryNacos:
- server-addr: 10.10.32.32:8848
namespace: dev
ip: 10.10.32.34
port: 9015
- server-addr: 192.168.13.14:8849
namespace: dev
ip: 192.168.10.13
port: 9015
- server-addr: 192.168.13.14:8849
namespace: test
ip: 192.168.10.13
port: 9015
CasToken格式
base64之后的json字符串,字符串包含userId和uuid。
生成token的具体方法:com.hvisions.cas.client.utils.LoginUtil#generateToken
public static String generateToken(Long userId) {
TokenInfo tokenInfo = new TokenInfo();
tokenInfo.setT(UUID.randomUUID().toString().replace("-", ""));
tokenInfo.setU(String.valueOf(userId));
return new String(Base64.getUrlEncoder().withoutPadding().encode(Json.toBytes(tokenInfo)));
}
com.hvisions.cas.client.utils.LoginUtil 提供了从解析userId的工具方法,有需要的可以参考。
授权管理
授权管理基本流程
授权管理包括授权激活、迁出及验证等功能。
下图是一个部署结构图:

授权管理部署结构图
其中有两个关键流程:
- 用户(管理员)对APP进行激活、授权迁出等操作 通过授权码从license-remote换回激活码,对应用进行激活,涉及到以下几个步骤:根据授权码结合本地机器信息生成请求码把请求码发送到服务器,服务器对请求码中的授权码、产品版本信息进行校验,通过之后发回激活码从激活码解析出产品信息,加密存储在本地授权文件中 如果是在线激活,这个流程是通过后台API完成的,在用户看来就是根据授权码进行激活;若是走离线激活,操作的过程和上述的步骤就是一一对应的。
- 根据授权码结合本地机器信息生成请求码
- 把请求码发送到服务器,服务器对请求码中的授权码、产品版本信息进行校验,通过之后发回激活码
- 从激活码解析出产品信息,加密存储在本地授权文件中
- APP部署之后按APP_CODE对服务进行授权校验
这个是通过license-plugin完成的,会连接license-local进行校验,大致的过程:
- 使用应用appCode、服务名称和 一个uuid(当前应用的标识) 构造一个参数连接license-local,问它授权是不是有效;
- license-local收到请求之后会对appCode、serviceCode、授权数量、授权有效期 进行校验然后返回校验结果。
- license-plugin收到返回会针对校验结果做处理并返回Hvisions-License-Status状态码。
授权管理后端接入
License-local-plugin
java客户端:
<dependency>
<groupId>com.hvisions</groupId>
<artifactId>hvisions-license-plugin</artifactId>
<version>1.0.6-SNAPSHOT</version>
</dependency>
目前仅支持springboot应用,会基于springbootautoconfig做自动配置。相关参数
参数 | 含义 | 说明 |
h-visions.appCode | 应用code | 必须,在cas上的appCode |
h-visions.license.local.server | license-local server地址 | 必须,localserver地址 |
spring.application.name | service名称 | 必须,按service名称验证是否包含对服务的授权 |
h-visions.license.local.checkIntervals | 验证间隔 | 非必需,默认值60s,最大180s,间隔时间的长短影响license-local清理失效节点的时机, 间隔越大授权被占用导致的服务不可用时间越大 |
hvisions.license.local.server(旧) | license-local server地址 | 兼容性参数,和h-visions.license.local.server保留其一即可 |
hvisions.license.local.checkIntervals(旧) | 验证间隔 | 兼容性参数,和h-visions.license.local.checkIntervals保留其一即可 |
授权校验状态
license-plugin 会根据校验结果影响返回的HTTP数据,通过Http Header Hvisions-License-Status返回。
Hvisions-License-Status可能取值:
值 | 含义 | 备注 |
OK | 正常 | |
EXPIRING_SOON | 授权即将过期 | 15天之后即将过期,临时会遇到, 不影响请求正常返回数据 |
INVALID | 过期或没有授权 | http状态码为601, 并且无数据返回 |
FAILED_CONN_TO_LICENSE_LOCAL | 本地授权服务器连接失败 | 不影响请求正常返回数据 |
INVALID_FAILED_CONN_TO_LICENSE_LOCAL | 无效超过7天连不上LicenseLocal | http状态码为601, 并且无数据返回 |
状态例子:
授权状态透出例子
跨域透传HVISIONS-LICENSE-STATUS 配置
允许'HVISIONS-LICENSE-STATUS'授权校验状态跨域透传一般配置在Gateway中,比如(IOT中的配置):
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
// 允许cookies跨域
config.setAllowCredentials(gatewayConfiguration.isAllowCredentials());
// 允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.setAllowedOrigins(gatewayConfiguration.getAllowedOrigin());
// #允许访问的头信息,*表示全部
config.setAllowedHeaders(gatewayConfiguration.getAllowedHeader());
// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.setMaxAge(gatewayConfiguration.getMaxAge());
// 允许提交请求的方法,*表示全部允许
config.setAllowedMethods(gatewayConfiguration.getAllowedMethod());
// 允许透传HVISIONS-LICENSE-STATUS Header
config.setExposedHeaders(Collections.singletonList("HVISIONS-LICENSE-STATUS"));
source.registerCorsConfiguration(gatewayConfiguration.getCorsPath(), config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
//设置执行顺序,数字越小越先执行
bean.setOrder(0);
return bean;
授权管理前端接入
前端需要对返回授权状态做处理
值 | 含义 | 处理方式 |
OK | 正常 | 无需处理 |
EXPIRING_SOON | 授权即将过期 | 提示授权即将过期;每人每天提醒一次 |
INVALID | 过期或没有授权 | 提醒用户授权无效 |
FAILED_CONN_TO_LICENSE_LOCAL | 本地授权服务器连接失败 | 提示用户无法连接本地授权服务,请联系管理员处理;每人每天提醒一次。 |
INVALID_FAILED_CONN_TO_LICENSE_LOCAL | 无效超过7天连不上LicenseLocal | 提示用户长时间无法连接本地授权服务,请联系管理员处理;跳转到cas home页 |