辽宁CAS统一认证兼容

This commit is contained in:
cdf
2026-06-17 14:19:52 +08:00
parent d36c30973d
commit 507a7f7a09
9 changed files with 238 additions and 299 deletions

View File

@@ -66,6 +66,13 @@
<artifactId>common-oss</artifactId>
<version>${project.version}</version>
</dependency>
<!--辽宁调度现场单点登录-->
<dependency>
<groupId>com.sgcc.epri.auth</groupId>
<artifactId>sso-client-base</artifactId>
<version>2.1.1 </version>
</dependency>
</dependencies>
<build>
@@ -138,4 +145,4 @@
</project>
</project>

View File

@@ -0,0 +1,26 @@
package com.njcn.auth.config;
/**
* pqs
*
* @author cdf
* @date 2026/6/8
*/
import com.sgcc.epri.auth.config.EnableSSOClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
/**
* 仅控制 SSO 客户端开关,不影响任何其他功能
*/
@Configuration
@ConditionalOnProperty(
prefix = "cas.client", // 配置前缀
name = "enabled", // 配置项名称
havingValue = "true", // 值为true才生效
matchIfMissing = false // 不配置默认关闭
)
@EnableSSOClient
public class LnSsoClientConfig {
}

View File

@@ -37,7 +37,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/oauth/getPublicKey","/oauth/logout","/auth/getImgCode","/judgeToken/guangZhou","/judgeToken/heBei","/oauth/autoLogin").permitAll()
.antMatchers("/oauth/getPublicKey","/oauth/logout","/auth/getImgCode","/judgeToken/guangZhou","/judgeToken/heBei","/oauth/autoLogin","/oauth/lnLogin","/oauth/lnCheck","/oauth/lnRefreshToken").permitAll()
// @link https://gitee.com/xiaoym/knife4j/issues/I1Q5X6 (接口文档knife4j需要放行的规则)
.antMatchers("/webjars/**","/doc.html","/swagger-resources/**","/v2/api-docs").permitAll()
.anyRequest().authenticated()

View File

@@ -25,13 +25,17 @@ import com.njcn.user.pojo.po.UserStrategy;
import com.njcn.web.controller.BaseController;
import com.njcn.web.utils.RequestUtil;
import com.njcn.web.utils.RestTemplateUtil;
import com.sgcc.epri.auth.session.HttpSessionManager;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -39,6 +43,10 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponentsBuilder;
import springfox.documentation.annotations.ApiIgnore;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.security.KeyPair;
import java.security.Principal;
@@ -55,7 +63,7 @@ import java.util.stream.Collectors;
@Slf4j
@RestController
@RequestMapping("/oauth")
@AllArgsConstructor
@RequiredArgsConstructor
public class AuthController extends BaseController {
@@ -71,6 +79,11 @@ public class AuthController extends BaseController {
private final UserTokenService userTokenService;
@Value("${cas.redirect-url:http://10.21.30.11:8088/#/login?flag=1}")
private String redirectUrl;
private String UsernamePrefix = "CAS_";
@ApiIgnore
@OperateInfo(info = LogEnum.SYSTEM_SERIOUS, operateType = OperateType.AUTHENTICATE)
@@ -91,7 +104,6 @@ public class AuthController extends BaseController {
String methodDescribe = getMethodDescribe("postAccessToken");
String username = parameters.get(SecurityConstants.USERNAME);
String grantType = parameters.get(SecurityConstants.GRANT_TYPE);
if (grantType.equalsIgnoreCase(SecurityConstants.GRANT_CAPTCHA) || grantType.equalsIgnoreCase(SecurityConstants.REFRESH_TOKEN_KEY)) {
username = DesUtils.aesDecrypt(username);
@@ -104,19 +116,19 @@ public class AuthController extends BaseController {
UserStrategy data = passWordRuleFeugnClient.getUserStrategy().getData();
String onlineUserKey = SecurityConstants.TOKEN_ONLINE_PREFIX;
List<UserTokenInfo> onLineUser = (List<UserTokenInfo>) redisUtil.getLikeListAllValues(onlineUserKey);
if(CollectionUtil.isNotEmpty(onLineUser)){
if (CollectionUtil.isNotEmpty(onLineUser)) {
String finalUsername = username;
onLineUser = onLineUser.stream().filter(item->{
onLineUser = onLineUser.stream().filter(item -> {
JSONObject jsonObject = AuthPubUtil.getLoginByToken(item.getRefreshToken());
String login = jsonObject.getStr(SecurityConstants.USER_NAME_KEY);
long exp = Long.parseLong(jsonObject.getStr(SecurityConstants.JWT_EXP));
long now = Calendar.getInstance().getTimeInMillis()/1000;
return (exp > now) && !login.equals(finalUsername);
long now = Calendar.getInstance().getTimeInMillis() / 1000;
return (exp > now) && !login.equals(finalUsername);
}).collect(Collectors.toList());
}
Integer maxNum = data.getMaxNum();
if((CollectionUtil.isNotEmpty(onLineUser)?onLineUser.size():0)>=maxNum){
if ((CollectionUtil.isNotEmpty(onLineUser) ? onLineUser.size() : 0) >= maxNum) {
throw new BusinessException(UserResponseEnum.LOGIN_USER_OVERLIMIT);
}
@@ -143,7 +155,7 @@ public class AuthController extends BaseController {
@OperateInfo(info = LogEnum.SYSTEM_SERIOUS, operateType = OperateType.LOGOUT)
@ApiOperation("用户登出系统")
@DeleteMapping("/logout")
public HttpResult<Object> logout() {
public HttpResult<Object> logout(HttpServletRequest request, HttpServletResponse response) {
String methodDescribe = getMethodDescribe("logout");
String userIndex = RequestUtil.getUserIndex();
String username = RequestUtil.getUsername();
@@ -165,6 +177,24 @@ public class AuthController extends BaseController {
long lifeTime = Math.abs(refreshTokenExpire.plusMinutes(5L).toEpochSecond(ZoneOffset.of("+8")) - LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")));
redisUtil.saveByKeyWithExpire(blackUserKey, blackUsers, lifeTime);
}
// 以下代码是辽宁登出代码,关键:使 Session 失效
request.getSession().invalidate();
// 清除 JSESSIONID
Cookie jsessionidCookie = new Cookie("JSESSIONID", null);
jsessionidCookie.setMaxAge(0);
jsessionidCookie.setPath("/");
response.addCookie(jsessionidCookie);
// 清除 loginUser Cookie关键
Cookie loginUserCookie = new Cookie("loginUser", null);
loginUserCookie.setMaxAge(0);
loginUserCookie.setPath("/");
response.addCookie(loginUserCookie);
log.info("登出成功。。。。。。。。。。。。。。。。");
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
@@ -186,20 +216,152 @@ public class AuthController extends BaseController {
*/
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.AUTHENTICATE)
@ApiOperation("自动登录")
@PostMapping("/autoLogin")
@GetMapping("/autoLogin")
@ApiImplicitParam(name = "phone", value = "手机号", required = true, paramType = "query")
@ApiIgnore
public HttpResult<Object> autoLogin(@RequestParam String phone) {
String methodDescribe = getMethodDescribe("autoLogin");
String userUrl = "http://127.0.0.1:10214/oauth/token";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(userUrl)
.queryParam("grant_type", "sms_code")
.queryParam("client_id", "njcnapp")
.queryParam("grant_type", SecurityConstants.GRANT_AUTHORIZATION_CODE)
.queryParam("client_id", "njcn")
.queryParam("client_secret", "njcnpqs")
.queryParam("phone", phone)
.queryParam("smsCode", "123456789");
.queryParam("username", "%2FPY4%2FD07ExoKDUg6yCi2cA%3D%3D")
.queryParam("imageCode", "verifyCode")
.queryParam("verifyCode", "0");
URI uri = builder.build().encode().toUri();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, Objects.requireNonNull(RestTemplateUtil.post(uri, HttpResult.class).getBody()).getData(), methodDescribe);
}
/**
* 【电科院CAS调控云单点登录适配】
* 这个只用来匹配
*/
@ApiIgnore
@GetMapping("/lnLogin")
@ApiOperation("获取ln系统用户token")
public HttpResult<Object> lnLogin(@RequestParam String clientId, @RequestParam String clientSecret, HttpServletRequest request, HttpServletResponse response) throws HttpRequestMethodNotSupportedException {
log.info("进入lnLogin++++++++++++++++++");
String methodDescribe = getMethodDescribe("lnLogin");
// 读取CAS信息
String userName = String.valueOf(HttpSessionManager.getAttribute(request, HttpSessionManager.AUTH_USER_KEY));
String userId = String.valueOf(HttpSessionManager.getAttribute(request, HttpSessionManager.USER_ID_KEY));
String owner = String.valueOf(HttpSessionManager.getAttribute(request, HttpSessionManager.USER_OWNER));
String name = String.valueOf(HttpSessionManager.getAttribute(request, HttpSessionManager.USER_NAME_CHN));
String employeeId = String.valueOf(HttpSessionManager.getAttribute(request, HttpSessionManager.USER_EMPLOYEE_ID));
log.info("userName:{}", userName);
log.info("userId:{}", userId);
log.info("owner:{}", owner);
log.info("name:{}", name);
log.info("employeeId:{}", employeeId);
if ("null".equals(userName)) {
throw new BusinessException(UserResponseEnum.LN_AUTH_ERROR);
}
// 2. 【关键】用户名前面加上"CAS_"前缀让UserDetailsService识别
String casUsername = userName;
// 2. 直接构造 OAuth2 必要参数(跳过所有密码/加密校验)
Map<String, String> parameters = new HashMap<>();
parameters.put("grant_type", "password"); // 固定密码模式
parameters.put("client_id", clientId); // 你的客户端ID
parameters.put("client_secret", clientSecret); // 你的客户端秘钥
parameters.put("username", userName); // 统一认证传过来的用户名
parameters.put("password", "@#001njcnpqs");
// 3. 直接调用 OAuth2 生成 Token跳过所有登录校验
Authentication authentication = new UsernamePasswordAuthenticationToken(
clientId, clientSecret, Collections.emptyList()
);
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(authentication, parameters).getBody();
// 获取过期时间(秒数)
int expiresIn = oAuth2AccessToken.getExpiresIn();
log.info("token过期时间: {} 秒", expiresIn);
log.info("token过期时间: {} 分钟", expiresIn / 60);
log.info("token过期时间: {} 小时", expiresIn / 3600);
log.info("====== 免密登录成功返回token给前端 ======");
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, oAuth2AccessToken, methodDescribe);
}
/**
* 重点:
* 这个接口 不加 白名单
* 访问它 → 自动跳CAS → 登录成功 → 重定向到登录页
*/
@GetMapping("/lnCheck")
@ApiOperation("检查CAS是否登录")
public void lnToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
log.info("进入lnCheck。。。。");
response.sendRedirect(redirectUrl);
}
/**
*/
@GetMapping("/lnRefreshToken")
@ApiOperation("刷新token")
public HttpResult<Object> lnRefreshToken(
@RequestParam String refreshToken,
@RequestParam String clientId,
@RequestParam String clientSecret,
HttpServletRequest request,
HttpServletResponse response) throws HttpRequestMethodNotSupportedException {
log.info("进入lnRefreshToken开始刷新token");
String methodDescribe = getMethodDescribe("lnRefreshToken");
// ========== 【前置优先校验CAS会话是否过期】 ==========
String userName = String.valueOf(HttpSessionManager.getAttribute(request, HttpSessionManager.AUTH_USER_KEY));
if ("null".equals(userName)) {
log.error("CAS会话已过期跳转登录页");
throw new BusinessException(UserResponseEnum.LN_AUTH_ERROR);
}
// 1. 先尝试用refresh_token正常刷新
Map<String, String> parameters = new HashMap<>();
parameters.put("grant_type", "refresh_token");
parameters.put("refresh_token", refreshToken);
parameters.put("client_id", clientId);
parameters.put("client_secret", clientSecret);
Authentication authentication = new UsernamePasswordAuthenticationToken(
clientId, clientSecret, Collections.emptyList()
);
try {
OAuth2AccessToken newAccessToken = tokenEndpoint.postAccessToken(authentication, parameters).getBody();
log.info("refresh_token刷新成功");
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, newAccessToken, methodDescribe);
} catch (Exception e) {
log.warn("refresh_token刷新失败尝试回退到CAS会话重新签发token", e);
}
// 3. CAS会话有效重新签发token等同于重新登录
log.info("CAS会话有效为用户[{}]重新签发token", userName);
String casUsername = userName;
Map<String, String> reLoginParams = new HashMap<>();
reLoginParams.put("grant_type", "password");
reLoginParams.put("client_id", clientId);
reLoginParams.put("client_secret", clientSecret);
reLoginParams.put("username", userName);
reLoginParams.put("password", "@#001njcnpqs");
Authentication reAuth = new UsernamePasswordAuthenticationToken(
clientId, clientSecret, Collections.emptyList()
);
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(reAuth, reLoginParams).getBody();
log.info("CAS回退重签token成功userName:{}", userName);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, oAuth2AccessToken, methodDescribe);
}
}

View File

@@ -55,3 +55,22 @@ mybatis-plus:
mqtt:
client-id: @artifactId@${random.value}
cas:
client:
# true 开启 false 关闭
enabled: false
redirect-url: http://PQMonitoring.dcloud.ln.dc.sgcc.com.cn/#/login?flag=1
server-url-prefix: http://privilege-epri.dcloud.ln.dc.sgcc.com.cn/cas
server-login-url: http://privilege-epri.dcloud.ln.dc.sgcc.com.cn/cas/login
client-host-url: http://PQMonitoring.dcloud.ln.dc.sgcc.com.cn:80
validation-type: CAS
#白名单设置
# /oauth/lnLogin$|/pqs-auth/oauth/lnLogin|/oauth/lnRefreshToken$|/pqs-auth/oauth/lnRefreshToken
sso:
whiteList: