|
|
@@ -1,31 +1,45 @@
|
|
|
package cn.start.tz.module.system.controller.admin.cas;
|
|
|
|
|
|
+import cn.hutool.core.util.IdUtil;
|
|
|
import cn.start.tz.framework.common.pojo.CommonResult;
|
|
|
+import cn.start.tz.framework.security.config.SecurityProperties;
|
|
|
+import cn.start.tz.framework.security.core.util.SecurityFrameworkUtils;
|
|
|
import cn.start.tz.module.system.controller.admin.auth.vo.AuthLoginRespVO;
|
|
|
import cn.start.tz.module.system.dal.dataobject.user.AdminUserDO;
|
|
|
+import cn.start.tz.module.system.enums.logger.LoginLogTypeEnum;
|
|
|
import cn.start.tz.module.system.framework.cas.config.CasProperties;
|
|
|
import cn.start.tz.module.system.service.auth.AdminAuthService;
|
|
|
import cn.start.tz.module.system.service.user.AdminUserService;
|
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|
|
+import io.swagger.v3.oas.annotations.Parameter;
|
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
|
import jakarta.annotation.Resource;
|
|
|
import jakarta.annotation.security.PermitAll;
|
|
|
import jakarta.servlet.http.HttpServletRequest;
|
|
|
+import jakarta.servlet.http.HttpSession;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.catalina.session.StandardSessionFacade;
|
|
|
import org.apereo.cas.client.validation.Assertion;
|
|
|
-import org.springframework.web.bind.annotation.GetMapping;
|
|
|
-import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
-import org.springframework.web.bind.annotation.RestController;
|
|
|
+import org.springframework.data.redis.core.StringRedisTemplate;
|
|
|
+import org.springframework.web.bind.annotation.*;
|
|
|
import org.springframework.web.util.UriComponentsBuilder;
|
|
|
|
|
|
import java.io.IOException;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
import static cn.start.tz.framework.common.pojo.CommonResult.success;
|
|
|
|
|
|
/**
|
|
|
* CAS 单点登录控制器
|
|
|
* 处理 CAS 登录回调、用户信息获取等操作
|
|
|
+ * <p>
|
|
|
+ * 安全流程(Authorization Code 模式):
|
|
|
+ * 1. CAS 认证成功 → 回调 /caslogin
|
|
|
+ * 2. /caslogin 验证用户 → 创建 Token → 生成一次性授权码存 Redis → 重定向前端?code=xxx
|
|
|
+ * 3. 前端拿 code → 调 /mineInfo?code=xxx → 后端用 code 从 Redis 取出 Token → 删除 code → 返回 Token
|
|
|
+ * <p>
|
|
|
+ * 优势:code 一次性使用、短时效(30秒),即使被截获也无法复用
|
|
|
*
|
|
|
* @author tz
|
|
|
*/
|
|
|
@@ -35,6 +49,11 @@ import static cn.start.tz.framework.common.pojo.CommonResult.success;
|
|
|
@RequestMapping("/system/cas")
|
|
|
public class CasController {
|
|
|
|
|
|
+ /** Redis key 前缀,用于存储 CAS 一次性授权码 */
|
|
|
+ private static final String CAS_CODE_KEY_PREFIX = "cas:auth:code:";
|
|
|
+ /** 授权码有效期(秒) */
|
|
|
+ private static final long CODE_EXPIRE_SECONDS = 30;
|
|
|
+
|
|
|
@Resource
|
|
|
private CasProperties casProperties;
|
|
|
|
|
|
@@ -44,16 +63,21 @@ public class CasController {
|
|
|
@Resource
|
|
|
private AdminUserService adminUserService;
|
|
|
|
|
|
+ @Resource
|
|
|
+ private SecurityProperties securityProperties;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private StringRedisTemplate stringRedisTemplate;
|
|
|
+
|
|
|
/**
|
|
|
* CAS 登录回调接口
|
|
|
- * CAS 服务器认证成功后会回调此接口
|
|
|
- * <p>
|
|
|
- * 注意:CAS 回调地址应该是: https://cloud-admin-uat.gzsei.com/admin-api/system/cas/caslogin
|
|
|
+ * CAS 服务器认证成功后会回调此接口。
|
|
|
* <p>
|
|
|
* 此接口的职责:
|
|
|
- * 1. CAS ValidationFilter 会自动验证 ticket 参数
|
|
|
- * 2. 验证成功后,Assertion 会自动存入 Session
|
|
|
- * 3. 此接口只需简单重定向到前端,前端会调用 /mineInfo 获取 token
|
|
|
+ * 1. CAS ValidationFilter 自动验证 ticket 参数,验证成功后 Assertion 存入 Session
|
|
|
+ * 2. 查询本地用户、创建 Token
|
|
|
+ * 3. 生成一次性授权码(code)存入 Redis,将 Token 绑定到 code
|
|
|
+ * 4. 重定向到前端页面,URL 中携带 code(不携带 token)
|
|
|
*
|
|
|
* @param request HTTP 请求对象
|
|
|
* @param response HTTP 响应对象
|
|
|
@@ -64,36 +88,21 @@ public class CasController {
|
|
|
public void casCallback(HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response) throws IOException {
|
|
|
try {
|
|
|
log.info("[casCallback] CAS 登录回调开始");
|
|
|
- log.info("[casCallback] 请求URI: {}", request.getRequestURI());
|
|
|
- log.info("[casCallback] 请求URL: {}", request.getRequestURL());
|
|
|
- log.info("[casCallback] Session ID: {}", request.getSession().getId());
|
|
|
|
|
|
// 1. 从 Session 中获取 CAS Assertion(由 CAS ValidationFilter 验证后存储)
|
|
|
Assertion assertion = (Assertion) request.getSession().getAttribute("_const_cas_assertion_");
|
|
|
|
|
|
if (assertion == null || assertion.getPrincipal() == null) {
|
|
|
- log.error("[casCallback] ❌ CAS Assertion 为空,ticket 未通过验证");
|
|
|
- log.error("[casCallback] Session 属性列表:");
|
|
|
- java.util.Enumeration<String> attrNames = request.getSession().getAttributeNames();
|
|
|
- while (attrNames.hasMoreElements()) {
|
|
|
- String attrName = attrNames.nextElement();
|
|
|
- log.error("[casCallback] - {}: {}", attrName, request.getSession().getAttribute(attrName));
|
|
|
- }
|
|
|
- log.error("[casCallback] 这通常意味着 ValidationFilter 未能成功验证 ticket");
|
|
|
- log.error("[casCallback] 可能的原因:");
|
|
|
- log.error("[casCallback] 1. CAS 服务器返回验证失败(ticket 无效或已过期)");
|
|
|
- log.error("[casCallback] 2. service 参数与 CAS 服务器注册的不一致");
|
|
|
- log.error("[casCallback] 3. CAS 服务器地址配置错误");
|
|
|
- log.error("[casCallback] 4. 网络连接问题,无法访问 CAS 服务器");
|
|
|
+ log.error("[casCallback] CAS Assertion 为空,ticket 未通过验证");
|
|
|
response.sendError(401, "CAS ticket验证失败,请检查CAS服务器配置和网络连接");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 2. 获取用户名
|
|
|
String username = assertion.getPrincipal().getName();
|
|
|
- log.info("[casCallback] ✅ CAS认证成功,用户名: {}", username);
|
|
|
+ log.info("[casCallback] CAS认证成功,用户名: {}", username);
|
|
|
|
|
|
- // 3. 查询本地用户(仅验证用户是否存在)
|
|
|
+ // 3. 查询本地用户
|
|
|
AdminUserDO user = adminUserService.getUserByUsername(username);
|
|
|
if (user == null) {
|
|
|
log.warn("[casCallback] 用户不存在: {}", username);
|
|
|
@@ -108,22 +117,37 @@ public class CasController {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 5. 手动设置 Session Cookie(解决跨域问题)
|
|
|
- String sessionId = request.getSession().getId();
|
|
|
- String cookieValue = String.format("JSESSIONID=%s; Path=/; HttpOnly; SameSite=None; Max-Age=3600", sessionId);
|
|
|
- response.setHeader("Set-Cookie", cookieValue);
|
|
|
- log.info("[casCallback] 手动设置 Cookie: {}", cookieValue);
|
|
|
+ // 5. 创建本地 Token
|
|
|
+ AuthLoginRespVO loginResp;
|
|
|
+ try {
|
|
|
+ cn.start.tz.module.system.service.oauth2.OAuth2TokenServiceImpl.setLoginTypeContext(
|
|
|
+ cn.start.tz.framework.security.core.LoginUser.LOGIN_TYPE_CAS);
|
|
|
+ loginResp = adminAuthService.createTokenAfterLoginSuccess(
|
|
|
+ user.getId(), username,
|
|
|
+ cn.start.tz.module.system.enums.logger.LoginLogTypeEnum.LOGIN_SOCIAL);
|
|
|
+ log.info("[casCallback] Token 创建成功, userId={}", user.getId());
|
|
|
+ } finally {
|
|
|
+ cn.start.tz.module.system.service.oauth2.OAuth2TokenServiceImpl.clearLoginTypeContext();
|
|
|
+ }
|
|
|
|
|
|
- // 6. 重定向到前端页面,携带 CAS 登录标识(前端根据此参数调用 /mineInfo 获取 token)
|
|
|
+ // 6. 生成一次性授权码,将 Token 信息绑定到 code 存入 Redis
|
|
|
+ String code = IdUtil.fastSimpleUUID();
|
|
|
+ String redisKey = CAS_CODE_KEY_PREFIX + code;
|
|
|
+ // 存储格式:accessToken,refreshToken,expiresTime(ISO格式)
|
|
|
+ String redisValue = loginResp.getAccessToken() + "," + loginResp.getRefreshToken()
|
|
|
+ + "," + loginResp.getExpiresTime().toString() + "," + loginResp.getUserId();
|
|
|
+ stringRedisTemplate.opsForValue().set(redisKey, redisValue, CODE_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
|
|
+ log.info("[casCallback] 授权码已生成, code={}, 有效期={}秒", code, CODE_EXPIRE_SECONDS);
|
|
|
+
|
|
|
+ // 7. 重定向到前端页面,只携带一次性 code(不暴露 token)
|
|
|
String frontUrl = casProperties.getFrontUrl();
|
|
|
String redirectUrl = UriComponentsBuilder.fromHttpUrl(frontUrl)
|
|
|
.queryParam("loginType", "cas")
|
|
|
+ .queryParam("code", code)
|
|
|
.build()
|
|
|
.toUriString();
|
|
|
|
|
|
- log.info("[casCallback] Session ID: {}", sessionId);
|
|
|
- log.info("[casCallback] Session 属性 - _const_cas_assertion_: {}", request.getSession().getAttribute("_const_cas_assertion_"));
|
|
|
- log.info("[casCallback] 重定向到前端页面: {}", redirectUrl);
|
|
|
+ log.info("[casCallback] 重定向到前端页面(携带授权码)");
|
|
|
response.sendRedirect(redirectUrl);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
@@ -133,80 +157,157 @@ public class CasController {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取当前用户信息并创建 Token
|
|
|
- * 前端用于检测用户登录状态,如果已通过 CAS 认证则返回 token
|
|
|
+ * 通过一次性授权码换取 Token
|
|
|
+ * 前端在 CAS 回调后,用 URL 中的 code 参数调用此接口换取真正的 Token。
|
|
|
+ * <p>
|
|
|
+ * 安全特性:
|
|
|
+ * - code 一次性使用,读取后立即从 Redis 删除
|
|
|
+ * - code 有效期仅 30 秒,超时自动失效
|
|
|
+ * - code 无法反推出 token 内容
|
|
|
*
|
|
|
- * @param request HTTP 请求对象
|
|
|
- * @return 登录结果(包含 token 和用户信息)或 401 未认证
|
|
|
+ * @param code 一次性授权码(由 /casCallback 生成)
|
|
|
*/
|
|
|
@GetMapping("/mineInfo")
|
|
|
@PermitAll
|
|
|
- @Operation(summary = "获取当前 CAS 用户信息并创建 Token")
|
|
|
- public CommonResult<Object> mineInfo(HttpServletRequest request) {
|
|
|
+ @Operation(summary = "通过授权码换取 CAS 登录 Token")
|
|
|
+ @Parameter(name = "code", description = "一次性授权码", required = true)
|
|
|
+ public CommonResult<Object> mineInfo(@RequestParam("code") String code) {
|
|
|
try {
|
|
|
- log.info("[mineInfo] ========== 开始获取当前用户信息 ==========");
|
|
|
- log.info("[mineInfo] 请求URI: {}", request.getRequestURI());
|
|
|
- log.info("[mineInfo] Session ID: {}", request.getSession().getId());
|
|
|
- log.info("[mineInfo] Session 是否 isNew: {}", request.getSession().isNew());
|
|
|
- log.info("[mineInfo] Session 属性列表:");
|
|
|
- java.util.Enumeration<String> attrNames = request.getSession().getAttributeNames();
|
|
|
- while (attrNames.hasMoreElements()) {
|
|
|
- String attrName = attrNames.nextElement();
|
|
|
- log.info("[mineInfo] - {}: {}", attrName, request.getSession().getAttribute(attrName));
|
|
|
+ if (code == null || code.isBlank()) {
|
|
|
+ return CommonResult.error(701, "授权码不能为空",
|
|
|
+ casProperties.getServerUrlPrefix() + "/login?service=" + casProperties.getClientHostUrl() + "&secret=" + casProperties.getSecret());
|
|
|
}
|
|
|
|
|
|
- // 1. 首先检查 CAS Session
|
|
|
- Assertion assertion = (Assertion) request.getSession().getAttribute("_const_cas_assertion_");
|
|
|
- if (assertion == null || assertion.getPrincipal() == null) {
|
|
|
- log.error("[mineInfo] ❌ 未获取到 CAS 认证信息");
|
|
|
- log.error("[mineInfo] assertion = {}, principal = {}", assertion, assertion != null ? assertion.getPrincipal() : null);
|
|
|
- return CommonResult.error(701, "未登录,请先登录",
|
|
|
- casProperties.getServerUrlPrefix() + "/login?service=" + casProperties.getClientHostUrl());
|
|
|
- }
|
|
|
+ // 1. 从 Redis 中用 code 取出 Token 信息,并立即删除(一次性使用)
|
|
|
+ String redisKey = CAS_CODE_KEY_PREFIX + code;
|
|
|
+ String redisValue = stringRedisTemplate.opsForValue().getAndDelete(redisKey);
|
|
|
|
|
|
- // 2. 获取 CAS 认证的用户名
|
|
|
- String username = assertion.getPrincipal().getName();
|
|
|
- log.info("[mineInfo] 获取到 CAS 认证用户名: {}", username);
|
|
|
-
|
|
|
- // 3. 查询本地用户信息
|
|
|
- AdminUserDO user = adminUserService.getUserByUsername(username);
|
|
|
- if (user == null) {
|
|
|
- log.warn("[mineInfo] 本地用户不存在: {}", username);
|
|
|
- return CommonResult.error(704, "用户不存在,请联系管理员创建账号");
|
|
|
+ if (redisValue == null) {
|
|
|
+ log.warn("[mineInfo] 授权码无效或已过期, code={}", code);
|
|
|
+ return CommonResult.error(701, "授权码无效或已过期,请重新登录",
|
|
|
+ casProperties.getServerUrlPrefix() + "/login?service=" + casProperties.getClientHostUrl() + "&secret=" + casProperties.getSecret());
|
|
|
}
|
|
|
|
|
|
- // 4. 检查用户状态
|
|
|
- if (user.getStatus() == null || user.getStatus() != 0) {
|
|
|
- log.warn("[mineInfo] 用户已被禁用: {}", username);
|
|
|
- return CommonResult.error(703, "用户已被禁用");
|
|
|
+ // 2. 解析 Token 信息
|
|
|
+ String[] parts = redisValue.split(",", 4);
|
|
|
+ if (parts.length != 4) {
|
|
|
+ log.error("[mineInfo] 授权码数据格式异常, code={}", code);
|
|
|
+ return CommonResult.error(700, "授权码数据格式异常");
|
|
|
}
|
|
|
|
|
|
- // 5. 创建本地 Token(如果 Session 中还没有 Token)
|
|
|
- AuthLoginRespVO loginResp;
|
|
|
- try {
|
|
|
- // 设置登录类型为 CAS
|
|
|
- cn.start.tz.module.system.service.oauth2.OAuth2TokenServiceImpl.setLoginTypeContext(
|
|
|
- cn.start.tz.framework.security.core.LoginUser.LOGIN_TYPE_CAS);
|
|
|
-
|
|
|
- loginResp = adminAuthService.createTokenAfterLoginSuccess(
|
|
|
- user.getId(),
|
|
|
- username,
|
|
|
- cn.start.tz.module.system.enums.logger.LoginLogTypeEnum.LOGIN_SOCIAL
|
|
|
- );
|
|
|
-
|
|
|
- log.info("[mineInfo] 成功获取用户信息并创建 Token: username={}, userId={}, token={}",
|
|
|
- user.getUsername(), user.getId(), loginResp.getAccessToken());
|
|
|
- } finally {
|
|
|
- // 清除 ThreadLocal,避免内存泄漏
|
|
|
- cn.start.tz.module.system.service.oauth2.OAuth2TokenServiceImpl.clearLoginTypeContext();
|
|
|
- }
|
|
|
+ // 3. 构建返回结果
|
|
|
+ AuthLoginRespVO loginResp = new AuthLoginRespVO();
|
|
|
+ loginResp.setAccessToken(parts[0]);
|
|
|
+ loginResp.setRefreshToken(parts[1]);
|
|
|
+ loginResp.setExpiresTime(LocalDateTime.parse(parts[2]));
|
|
|
+ loginResp.setUserId(parts[3]);
|
|
|
+ loginResp.setLoginType("cas");
|
|
|
|
|
|
- // 6. 返回登录结果(包含 token 和用户信息)
|
|
|
+ log.info("[mineInfo] 授权码换 Token 成功");
|
|
|
return success(loginResp);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
- log.error("[mineInfo] 获取用户信息异常", e);
|
|
|
+ log.error("[mineInfo] 换取 Token 异常", e);
|
|
|
return CommonResult.error(700, "获取用户信息异常: " + e.getMessage());
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * CAS 退出登录
|
|
|
+ * 同时清除:1. CAS Session 中的 Assertion 2. 本地系统的 Token(支持请求头带 token 和不带 token 两种场景)
|
|
|
+ *
|
|
|
+ * @param request HTTP 请求对象
|
|
|
+ * @return CAS 服务器登出 URL,前端需重定向到该地址完成 CAS 全局登出
|
|
|
+ */
|
|
|
+ @PostMapping("/logout")
|
|
|
+ @PermitAll
|
|
|
+ @Operation(summary = "CAS 退出登录")
|
|
|
+ public CommonResult<String> logout(HttpServletRequest request) {
|
|
|
+ log.info("[casLogout] 开始 CAS 退出登录");
|
|
|
+
|
|
|
+ // 0. 前置校验:必须存在 token 或 CAS Assertion,否则拒绝
|
|
|
+ String token = SecurityFrameworkUtils.obtainAuthorization(request,
|
|
|
+ securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
|
|
|
+ HttpSession session = request.getSession(false);
|
|
|
+ Assertion assertion = session != null
|
|
|
+ ? (Assertion) session.getAttribute("_const_cas_assertion_") : null;
|
|
|
+ if (token == null && assertion == null) {
|
|
|
+ log.warn("[casLogout] 未登录,无需退出");
|
|
|
+ return CommonResult.error(401, "未登录,无需退出");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 清除 CAS Session 中的 Assertion
|
|
|
+ String username = null;
|
|
|
+ try {
|
|
|
+ if (session != null) {
|
|
|
+ StandardSessionFacade ssf = (StandardSessionFacade) session;
|
|
|
+ if (assertion != null && assertion.getPrincipal() != null) {
|
|
|
+ username = assertion.getPrincipal().getName();
|
|
|
+ }
|
|
|
+ ssf.removeAttribute("_const_cas_assertion_");
|
|
|
+ log.info("[casLogout] CAS Assertion 已清除, username={}", username);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("[casLogout] 清除 CAS Session 异常: {}", e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 清除本地系统 Token(两种策略,复用步骤0中已获取的 token)
|
|
|
+ if (token != null) {
|
|
|
+ adminAuthService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType(), request);
|
|
|
+ log.info("[casLogout] 通过请求头 token 清除本地登录信息");
|
|
|
+ } else if (username != null) {
|
|
|
+ // 2.2 统一平台请求没有携带 token,通过 CAS 用户名查到 userId 后清除所有有效 token
|
|
|
+ AdminUserDO user = adminUserService.getUserByUsername(username);
|
|
|
+ if (user != null) {
|
|
|
+ adminAuthService.logoutByUserId(user.getId(), LoginLogTypeEnum.LOGOUT_SELF.getType());
|
|
|
+ log.info("[casLogout] 通过 CAS 用户名({})/userId({}) 清除本地登录信息", username, user.getId());
|
|
|
+ } else {
|
|
|
+ log.warn("[casLogout] CAS 用户名({})在本地系统中不存在", username);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ log.warn("[casLogout] 无法获取 token 或 CAS 用户名,跳过本地 Token 清除");
|
|
|
+ }
|
|
|
+ log.info("[casLogout] 退出登录完成");
|
|
|
+ return success("注销成功");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 测试接口(上线前删除) ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【测试接口】模拟生成授权码,用于调试 /mineInfo 接口
|
|
|
+ * 调用方式:GET /admin-api/system/cas/test-generate-code?username=xxx
|
|
|
+ * 返回 code 后,30秒内调用:GET /admin-api/system/cas/mineInfo?code=返回的code
|
|
|
+ */
|
|
|
+ @GetMapping("/test-generate-code")
|
|
|
+ @Operation(summary = "【测试】模拟生成授权码,用于调试 mineInfo 接口")
|
|
|
+ public CommonResult<String> testGenerateCode(@RequestParam("username") String username) {
|
|
|
+ // 1. 查询本地用户
|
|
|
+ AdminUserDO user = adminUserService.getUserByUsername(username);
|
|
|
+ if (user == null) {
|
|
|
+ return CommonResult.error(404, "用户不存在: " + username);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 创建 Token
|
|
|
+ AuthLoginRespVO loginResp;
|
|
|
+ try {
|
|
|
+ cn.start.tz.module.system.service.oauth2.OAuth2TokenServiceImpl.setLoginTypeContext(
|
|
|
+ cn.start.tz.framework.security.core.LoginUser.LOGIN_TYPE_CAS);
|
|
|
+ loginResp = adminAuthService.createTokenAfterLoginSuccess(
|
|
|
+ user.getId(), username,
|
|
|
+ cn.start.tz.module.system.enums.logger.LoginLogTypeEnum.LOGIN_SOCIAL);
|
|
|
+ } finally {
|
|
|
+ cn.start.tz.module.system.service.oauth2.OAuth2TokenServiceImpl.clearLoginTypeContext();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 生成授权码存入 Redis(调试用,有效期 120 秒)
|
|
|
+ String code = IdUtil.fastSimpleUUID();
|
|
|
+ String redisKey = CAS_CODE_KEY_PREFIX + code;
|
|
|
+ String redisValue = loginResp.getAccessToken() + "," + loginResp.getRefreshToken()
|
|
|
+ + "," + loginResp.getExpiresTime().toString() + "," + loginResp.getUserId();
|
|
|
+ stringRedisTemplate.opsForValue().set(redisKey, redisValue, 120, TimeUnit.SECONDS);
|
|
|
+
|
|
|
+ log.info("[testGenerateCode] 测试授权码已生成, username={}, code={}, 有效期=120秒", username, code);
|
|
|
+ return success("code=" + code + ",有效期120秒,请尽快调用 /mineInfo?code=" + code);
|
|
|
+ }
|
|
|
+
|
|
|
}
|