提交
This commit is contained in:
1 parent
66a7fd7068
commit
df640dffad
2 files changed
+324
-10
No files matched your search
@@ -0,0 +1,187 @@
|
||||
package org.dromara.web.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.domain.model.LoginUser;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.web.domain.vo.LoginVo;
|
||||
import org.dromara.system.domain.vo.SysClientVo;
|
||||
import org.dromara.system.domain.vo.SysUserVo;
|
||||
import org.dromara.system.mapper.SysUserMapper;
|
||||
import org.dromara.web.service.SysLoginService;
|
||||
import org.dromara.system.service.ISysClientService;
|
||||
import org.dromara.common.tenant.helper.TenantHelper;
|
||||
|
||||
import org.dromara.common.core.exception.user.UserException;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.dromara.system.domain.SysUser;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* CAS 代理接口,用于前端通过后端获取 CAS accessToken(避免浏览器 CORS 限制)
|
||||
*/
|
||||
@Slf4j
|
||||
@SaIgnore
|
||||
@RestController
|
||||
@RequestMapping("/cas")
|
||||
@RequiredArgsConstructor
|
||||
public class CasController {
|
||||
|
||||
// 固定 CAS 地址与 client 配置(按需求写死)
|
||||
private static final String CAS_BASE = "http://172.16.0.82";
|
||||
private static final String CLIENT_ID = "0i7tcT5tULqvmmbbV8";
|
||||
private static final String CLIENT_SECRET = "68c4f7641523452aaf5ef6d0432869f7";
|
||||
private static final String REDIRECT_URI_ENCODED = "http%3A%2F%2F172.16.0.67%3A8882";
|
||||
|
||||
private final SysUserMapper userMapper;
|
||||
private final SysLoginService loginService;
|
||||
private final ISysClientService clientService;
|
||||
|
||||
/**
|
||||
* 代理获取 accessToken(由后端向 CAS 发起请求,返回 CAS 原始响应)
|
||||
*
|
||||
* 示例:GET /cas/accessToken?code=ST-xxx
|
||||
*/
|
||||
@GetMapping("/accessToken")
|
||||
public R<LoginVo> accessToken(@RequestParam("code") String code) {
|
||||
if (StrUtil.isBlank(code)) {
|
||||
return R.fail("code 不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
String url = String.format(
|
||||
"%s/cas/oauth2.0/accessToken?client_id=%s&client_secret=%s&code=%s&redirect_uri=%s",
|
||||
CAS_BASE, CLIENT_ID, CLIENT_SECRET, URLEncoder.encode(code, StandardCharsets.UTF_8.toString()),
|
||||
REDIRECT_URI_ENCODED);
|
||||
|
||||
log.info("请求 CAS accessToken: {}", url);
|
||||
String resp = HttpUtil.get(url);
|
||||
log.info("CAS accessToken 响应: {}", resp);
|
||||
|
||||
// 尝试解析 access_token(CAS 可能返回 JSON 或 query-string 风格)
|
||||
String accessToken = null;
|
||||
try {
|
||||
// 优先尝试作为 JSON 解析
|
||||
try {
|
||||
accessToken = JSONUtil.parseObj(resp).getStr("access_token");
|
||||
} catch (Exception ignore) {
|
||||
// 作为 query-string 风格解析,例如: access_token=xxx&expires_in=...
|
||||
for (String pair : resp.split("&")) {
|
||||
if (pair.startsWith("access_token=")) {
|
||||
accessToken = java.net.URLDecoder.decode(pair.split("=", 2)[1],
|
||||
StandardCharsets.UTF_8.toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("解析 CAS accessToken 响应出错", e);
|
||||
}
|
||||
|
||||
if (StrUtil.isNotBlank(accessToken)) {
|
||||
// 使用 access_token 请求 profile 接口
|
||||
String profileUrl = String.format("%s/cas/oauth2.0/profile?access_token=%s", CAS_BASE,
|
||||
URLEncoder.encode(accessToken, StandardCharsets.UTF_8.toString()));
|
||||
log.info("请求 CAS profile: {}", profileUrl);
|
||||
String profileResp = HttpUtil.get(profileUrl);
|
||||
log.info("CAS profile 响应: {}", profileResp);
|
||||
|
||||
// 解析 profile,尝试从 attributes 中找到 CODE(用户名) 和 TENANTID
|
||||
try {
|
||||
var profileObj = JSONUtil.parseObj(profileResp);
|
||||
var attrs = profileObj.getJSONArray("attributes");
|
||||
String username = null;
|
||||
String tenantId = null;
|
||||
if (attrs != null) {
|
||||
for (Object o : attrs) {
|
||||
try {
|
||||
var obj = JSONUtil.parseObj(o);
|
||||
if (obj.containsKey("CODE")) {
|
||||
username = obj.getStr("CODE");
|
||||
}
|
||||
if (obj.containsKey("TENANTID")) {
|
||||
tenantId = obj.getStr("TENANTID");
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(username)) {
|
||||
return R.fail("CAS profile 未包含用户信息");
|
||||
}
|
||||
// 选定客户端(使用前端默认 clientId,如需可改为配置)
|
||||
final String defaultClientId = "e5cd7e4891bf95d1d19206ce24a7b32e";
|
||||
SysClientVo client = clientService.queryByClientId(defaultClientId);
|
||||
|
||||
// 把外层变量复制为 final 以便在 lambda 中使用
|
||||
final String finalUsername = username;
|
||||
// 强制使用默认租户 000000(忽略 CAS 返回的 TENANTID)
|
||||
final String finalTenantId = "000000";
|
||||
|
||||
// 在对应租户上下文中查找用户并构建 LoginUser 并登录
|
||||
log.info("CAS auto-login attempt, username={}, tenantId={}", finalUsername, finalTenantId);
|
||||
LoginUser loginUser = TenantHelper.dynamic(finalTenantId, () -> {
|
||||
SysUserVo user = userMapper
|
||||
.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, finalUsername));
|
||||
if (user == null) {
|
||||
log.warn("CAS auto-login: user not found, username={}, tenantId={}", finalUsername, finalTenantId);
|
||||
throw new UserException("user.not.exists", finalUsername);
|
||||
} else {
|
||||
log.info("CAS auto-login: found user: userId={}, userName={}, status={}, tenantId={}",
|
||||
user.getUserId(), user.getUserName(), user.getStatus(), user.getTenantId());
|
||||
}
|
||||
if (SystemConstants.DISABLE.equals(user.getStatus())) {
|
||||
log.warn("CAS auto-login: user disabled, username={}", finalUsername);
|
||||
throw new UserException("user.blocked", finalUsername);
|
||||
}
|
||||
return loginService.buildLoginUser(user);
|
||||
});
|
||||
|
||||
// 填充 client 信息并生成 token
|
||||
if (client != null) {
|
||||
loginUser.setClientKey(client.getClientKey());
|
||||
}
|
||||
SaLoginParameter model = new SaLoginParameter();
|
||||
if (client != null) {
|
||||
model.setTimeout(client.getTimeout());
|
||||
model.setActiveTimeout(client.getActiveTimeout());
|
||||
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
|
||||
}
|
||||
LoginHelper.login(loginUser, model);
|
||||
|
||||
LoginVo loginVo = new LoginVo();
|
||||
loginVo.setAccessToken(StpUtil.getTokenValue());
|
||||
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
||||
loginVo.setClientId(client != null ? client.getClientId() : defaultClientId);
|
||||
return R.ok(loginVo);
|
||||
} catch (UserException ue) {
|
||||
log.warn("CAS 登录用户验证失败: {}", ue.getMessage());
|
||||
return R.fail(ue.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("处理 CAS profile 失败", e);
|
||||
return R.fail("处理 CAS profile 失败: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
return R.fail("无法解析 CAS access_token,原始响应: " + resp);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("请求 CAS accessToken 失败", e);
|
||||
return R.fail("请求 CAS accessToken 失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user