修改经办段统一门户登录

This commit is contained in:
sh
2025-11-16 11:19:37 +08:00
parent 4716127fc2
commit 31662c3a11
5 changed files with 230 additions and 111 deletions

View File

@@ -1,7 +1,10 @@
package com.ruoyi.cms.util.oauth; package com.ruoyi.cms.util.oauth;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference; import com.alibaba.fastjson2.TypeReference;
import com.ruoyi.common.core.domain.entity.tymh.authority.QxUserRole;
import com.ruoyi.common.core.domain.entity.tymh.nwToken.NwTokenResult; import com.ruoyi.common.core.domain.entity.tymh.nwToken.NwTokenResult;
import com.ruoyi.common.core.domain.entity.tymh.nwToken.NwUserInfoResult; import com.ruoyi.common.core.domain.entity.tymh.nwToken.NwUserInfoResult;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
@@ -11,6 +14,7 @@ import org.springframework.stereotype.Component;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -24,53 +28,38 @@ public class OauthClient {
private static final Logger log = LoggerFactory.getLogger(OauthClient.class); private static final Logger log = LoggerFactory.getLogger(OauthClient.class);
/*====================== 内网 ======================*/ /*====================== 内网 ======================*/
// 网关请求-内网获取token
@Value("${oauth.usptnw.nwGatewayGetTokenUrl}") @Value("${oauth.usptnw.nwGatewayGetTokenUrl}")
private String nwGatewayGetTokenUrl; private String nwGatewayGetTokenUrl;
// 网关请求-内网获取用户信息
@Value("${oauth.usptnw.nwGatewayGetUserInfoUrl}") @Value("${oauth.usptnw.nwGatewayGetUserInfoUrl}")
private String nwGatewayGetUserInfoUrl; private String nwGatewayGetUserInfoUrl;
/*====================== 外网 ======================*/ /*====================== 外网 ======================*/
//post-外网注册地址
@Value("${oauth.usptww.wwRegisterPostUrl}") @Value("${oauth.usptww.wwRegisterPostUrl}")
private String wwRegisterPostUrl; private String wwRegisterPostUrl;
// post-外网获取token
@Value("${oauth.usptww.wwTokenPostUrl}") @Value("${oauth.usptww.wwTokenPostUrl}")
private String wwTokenPostUrl; private String wwTokenPostUrl;
//post-外网获取用户信息
@Value("${oauth.usptww.wwQueryWebPersonalInfoPostUrl}") @Value("${oauth.usptww.wwQueryWebPersonalInfoPostUrl}")
private String wwQueryWebPersonalInfoPostUrl; private String wwQueryWebPersonalInfoPostUrl;
//post-外网获取单位信息
@Value("${oauth.usptww.wwQueryWebEnterpriseInfoPostUrl}") @Value("${oauth.usptww.wwQueryWebEnterpriseInfoPostUrl}")
private String wwQueryWebEnterpriseInfoPostUrl; private String wwQueryWebEnterpriseInfoPostUrl;
/*====================== 统一门户 ======================*/ /*====================== 统一门户 ======================*/
//用户新增接口
@Value("${oauth.tyAddUserUrl}") @Value("${oauth.tyAddUserUrl}")
private String tyAddUserUrl; private String tyAddUserUrl;
//获取当前用户有权系统列表
@Value("${oauth.tyQueryUserSysListUrl}") @Value("${oauth.tyQueryUserSysListUrl}")
private String tyQueryUserSysListUrl; private String tyQueryUserSysListUrl;
//获取当前用户有权角色列表
@Value("${oauth.tyQueryUserRoleListUrl}") @Value("${oauth.tyQueryUserRoleListUrl}")
private String tyQueryUserRoleListUrl; private String tyQueryUserRoleListUrl;
//获取角色功能权限信息
@Value("${oauth.tyQueryRoleInfoUrl}") @Value("${oauth.tyQueryRoleInfoUrl}")
private String tyQueryRoleInfoUrl; private String tyQueryRoleInfoUrl;
//获取用户详细信息
@Value("${oauth.tyQueryUserInfo}") @Value("${oauth.tyQueryUserInfo}")
private String tyQueryUserInfo; private String tyQueryUserInfo;
//获取机构详细信息
@Value("${oauth.tyQueryUnitInfo}") @Value("${oauth.tyQueryUnitInfo}")
private String tyQueryUnitInfo; private String tyQueryUnitInfo;
//客户端id
@Value("${oauth.appid}") @Value("${oauth.appid}")
private String appid; private String appid;
//授权码
@Value("${oauth.clientsecretkey}") @Value("${oauth.clientsecretkey}")
private String clientsecretkey; private String clientsecretkey;
// 超时配置
@Value("${oauth.connect-timeout:10}") @Value("${oauth.connect-timeout:10}")
private int connectTimeout; private int connectTimeout;
@Value("${oauth.read-timeout:30}") @Value("${oauth.read-timeout:30}")
@@ -80,7 +69,6 @@ public class OauthClient {
/** /**
* 获取经办段token * 获取经办段token
* @return
*/ */
public NwTokenResult nwGetToken(String code) throws IOException, TimeoutException { public NwTokenResult nwGetToken(String code) throws IOException, TimeoutException {
if (StringUtils.isEmpty(code)) { if (StringUtils.isEmpty(code)) {
@@ -119,7 +107,6 @@ public class OauthClient {
/** /**
* 获取经办段用户id * 获取经办段用户id
* @return
*/ */
public NwUserInfoResult nwGetUserInfo(String accessToken) throws IOException, TimeoutException { public NwUserInfoResult nwGetUserInfo(String accessToken) throws IOException, TimeoutException {
Map<String, Object> params = new HashMap<>(2); Map<String, Object> params = new HashMap<>(2);
@@ -134,120 +121,177 @@ public class OauthClient {
); );
} }
/**
* 根据用户id查询用户角色列表
* @param userid
* @return
* @throws IOException
* @throws TimeoutException
*/
public List<QxUserRole> getUserRoleList(Long userid) throws IOException, TimeoutException {
Map<String, Object> params = new HashMap<>(2);
params.put("appid", appid);
params.put("userid", String.valueOf(userid));
return executePostRequestResultList(
tyQueryUserRoleListUrl,
params,
QxUserRole.class,
"根据用户id获取用户角色列表"
);
}
/** /**
* 公共POST请求执行方法 * 公共POST请求执行方法
* @param url 请求URL
* @param params 请求参数
* @param typeReference 响应类型引用
* @param operationName 操作名称(用于日志和异常信息)
* @return 响应数据对象
*/ */
private <T> T executePostRequest(String url, Map<String, Object> params, private <T> T executePostRequest(String url, Map<String, Object> params,
TypeReference<Response<T>> typeReference, String operationName) TypeReference<Response<T>> typeReference, String operationName)
throws IOException, TimeoutException { throws IOException, TimeoutException {
String responseJson = null; String responseJson = HttpUtils.doPostJson(
try { url,
responseJson = HttpUtils.doPostJson( params,
url, connectTimeout,
params, readTimeout,
connectTimeout, writeTimeout
readTimeout, );
writeTimeout
);
}catch (Exception e){
e.printStackTrace();
}
return validateResponse(responseJson, operationName, typeReference); return validateResponse(responseJson, operationName, typeReference);
} }
/** /**
* 公共响应校验工具方法(网关响应 + 业务 errflag 校验) * 公共POST请求执行方法处理List
*/
private <T> List<T> executePostRequestResultList(String url, Map<String, Object> params,
Class<T> clazz, String operationName)
throws IOException, TimeoutException {
String responseJson = HttpUtils.doPostJson(
url,
params,
connectTimeout,
readTimeout,
writeTimeout
);
return parseMapOrDataArray(responseJson, operationName, clazz);
}
/**
* 简化后的响应校验方法(已移除网关返回码判断)
*/ */
private <T> T validateResponse(String responseJson, String operationName, TypeReference<Response<T>> typeReference) { private <T> T validateResponse(String responseJson, String operationName, TypeReference<Response<T>> typeReference) {
// 1. 校验响应是否为 // 1. 校验响应空
if (StringUtils.isEmpty(responseJson)) { if (StringUtils.isEmpty(responseJson)) {
String errorMsg = operationName + "失败:接口返回空响应"; String errorMsg = operationName + "失败:接口返回空";
log.error(errorMsg); log.error(errorMsg);
throw new BusinessException("EMPTY_RESPONSE", errorMsg); throw new RuntimeException(errorMsg);
} }
log.debug("{}接口返回原始数据: {}", operationName, responseJson); log.debug("{}返回数据: {}", operationName, responseJson);
// 2. 解析网关响应(外层 appcode 校验) // 2. 解析响应并校验基础格式
Response<T> gatewayResponse = JSON.parseObject(responseJson, typeReference); Response<T> gatewayResponse = JSON.parseObject(responseJson, typeReference);
if (gatewayResponse == null) { if (gatewayResponse == null) {
String errorMsg = operationName + "失败:网关响应格式异常(无法解析)"; String errorMsg = operationName + "失败:响应格式错误";
log.error("{} | 原始响应: {}", errorMsg, responseJson); log.error("{} | 原始响应: {}", errorMsg, responseJson);
throw new BusinessException("GATEWAY_PARSE_ERROR", errorMsg); throw new RuntimeException(errorMsg);
}
if (!"0".equals(gatewayResponse.getAppcode())) {
String errorMsg = String.format("%s失败网关响应失败 | appcode: %s, msg: %s",
operationName, gatewayResponse.getAppcode(), gatewayResponse.getMsg());
log.error("{} | 原始响应: {}", errorMsg, responseJson);
throw new BusinessException(gatewayResponse.getAppcode(), errorMsg);
} }
// 3. 校验业务数据移除了网关appcode的判断逻辑
T data = gatewayResponse.getData(); T data = gatewayResponse.getData();
if (data == null) { if (data == null) {
String errorMsg = operationName + "失败:响应data字段为空"; String errorMsg = operationName + "失败:业务数据为空";
log.error("{} | 原始响应: {}", errorMsg, responseJson); log.error("{} | 原始响应: {}", errorMsg, responseJson);
throw new BusinessException("DATA_EMPTY", errorMsg); throw new RuntimeException(errorMsg);
} }
if (data instanceof ErrFlagResponse) {
ErrFlagResponse errFlagResponse = (ErrFlagResponse) data;
if (!errFlagResponse.isSuccess()) {
String errorMsg = String.format("%s失败%s错误标识%s",
operationName, errFlagResponse.getErrtext(), errFlagResponse.getErrflag());
log.error("{} | 原始响应: {}", errorMsg, responseJson);
throw new BusinessException(errFlagResponse.getErrflag(), errorMsg);
}
}
// 6. 所有校验通过,返回业务数据
return data; return data;
} }
/**
* 解析List
* @param jsonStr
* @param clazz
* @return
*/
public static <T> List<T> parseMapOrDataArray(String jsonStr, String operationName, Class<T> clazz) {
if (StringUtils.isEmpty(jsonStr)) {
String errorMsg = operationName + "失败:接口返回空";
log.error(errorMsg);
throw new RuntimeException(errorMsg);
}
JSONObject jsonObject;
try {
jsonObject = JSON.parseObject(jsonStr);
} catch (Exception e) {
String errorMsg = operationName + "失败:响应格式错误(非合法 JSON";
log.error("{} | 原始响应: {}", errorMsg, jsonStr, e);
throw new RuntimeException(errorMsg, e);
}
if (jsonObject == null) {
String errorMsg = operationName + "失败:响应格式错误(解析后为 null";
log.error("{} | 原始响应: {}", errorMsg, jsonStr);
throw new RuntimeException(errorMsg);
}
JSONArray targetArray = getJSONArraySafe(jsonObject, "map");
if (targetArray == null || targetArray.isEmpty()) {
targetArray = getJSONArraySafe(jsonObject, "data");
}
if (targetArray == null || targetArray.isEmpty()) {
String errorMsg = operationName + "失败业务数据为空map/data 字段无有效数组)";
log.error("{} | 原始响应: {}", errorMsg, jsonStr);
throw new RuntimeException(errorMsg);
}
List<T> dataList;
try {
dataList = JSON.parseArray(targetArray.toJSONString(), clazz);
} catch (Exception e) {
String errorMsg = operationName + "失败:数据解析失败(数组元素与目标类不匹配)";
log.error("{} | 原始响应: {} | 目标类: {}", errorMsg, jsonStr, clazz.getName(), e);
throw new RuntimeException(errorMsg, e);
}
if (dataList == null || dataList.isEmpty()) {
String errorMsg = operationName + "失败:解析后业务数据为空";
log.error("{} | 原始响应: {}", errorMsg, jsonStr);
throw new RuntimeException(errorMsg);
}
return dataList;
}
/**
* 避免空值和类型转换异常
* @param jsonObject
* @param key
* @return
*/
private static JSONArray getJSONArraySafe(JSONObject jsonObject, String key) {
if (jsonObject == null || StringUtils.isEmpty(key)) {
return null;
}
if (!jsonObject.containsKey(key)) {
return null;
}
Object value = jsonObject.get(key);
return (value instanceof JSONArray) ? (JSONArray) value : null;
}
/**
* 静态返回解析
* @param <T>
*/
public static class Response<T> { public static class Response<T> {
private String appcode; private String appcode;
private String code;
private String msg; private String msg;
private T data; private T data;
private Object map;
// Getter仅保留需要的字段setter可选
public String getAppcode() { return appcode; } public String getAppcode() { return appcode; }
public String getMsg() { return msg; } public String getMsg() { return msg; }
public T getData() { return data; } public T getData() { return data; }
public void setAppcode(String appcode) { this.appcode = appcode; }
public void setMsg(String msg) { this.msg = msg; }
public void setData(T data) { this.data = data; }
} }
public class BusinessException extends RuntimeException {
private String code; // 错误码(可关联门户错误码)
public BusinessException(String message) {
super(message);
this.code = "BUSINESS_ERROR";
}
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
// Getter
public String getCode() {
return code;
}
}
public interface ErrFlagResponse {
String getErrflag();
String getErrtext();
// 默认方法判断是否成功0=成功1=失败)
default boolean isSuccess() {
return "0".equals(getErrflag());
}
}
} }

View File

@@ -0,0 +1,24 @@
package com.ruoyi.common.core.domain.entity.tymh.authority;
import lombok.Data;
/**
* 用户角色
*/
@Data
public class QxUserRole {
private Long roleId;
private String roleName;
private String roleType;
private String appId;
private String roleLevel;
private String aae100;
private String aae011;
private String postType;
private String agf001;
private String aae036;
private String agf003;
private String agf002;
private String agf004;
private String folderId;
}

View File

@@ -1,6 +1,6 @@
package com.ruoyi.common.core.domain.entity.tymh.nwToken; package com.ruoyi.common.core.domain.entity.tymh.nwToken;
import com.fasterxml.jackson.annotation.JsonProperty; import com.alibaba.fastjson2.annotation.JSONField;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
@@ -13,10 +13,11 @@ public class NwTokenResult {
@ApiModelProperty("错误文本") @ApiModelProperty("错误文本")
private String errtext; private String errtext;
@JsonProperty("Access-Token") @JSONField(name = "access_token")
@ApiModelProperty("访问令牌") @ApiModelProperty("访问令牌")
private String accessToken; private String accessToken;
@JSONField(name = "expires_in")
@ApiModelProperty("access_token接口调用凭证超") @ApiModelProperty("access_token接口调用凭证超")
private Long expiresIn; private Long expiresIn;
} }

View File

@@ -3,6 +3,7 @@ package com.ruoyi.framework.web.service;
import com.ruoyi.cms.util.oauth.OauthClient; import com.ruoyi.cms.util.oauth.OauthClient;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.entity.tymh.authority.QxUserRole;
import com.ruoyi.common.core.domain.entity.tymh.nwToken.NwTokenResult; import com.ruoyi.common.core.domain.entity.tymh.nwToken.NwTokenResult;
import com.ruoyi.common.core.domain.entity.tymh.nwToken.NwUserInfoResult; import com.ruoyi.common.core.domain.entity.tymh.nwToken.NwUserInfoResult;
import com.ruoyi.common.core.domain.entity.tymh.nwToken.PortalTokenCacheDTO; import com.ruoyi.common.core.domain.entity.tymh.nwToken.PortalTokenCacheDTO;
@@ -19,15 +20,18 @@ import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.security.context.AuthenticationContextHolder; import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import com.ruoyi.system.service.ISysUserService; import com.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
@Service @Service
public class OauthLoginService { public class OauthLoginService {
@@ -42,7 +46,7 @@ public class OauthLoginService {
@Autowired @Autowired
private ISysUserService sysUserService; private ISysUserService sysUserService;
@Autowired @Autowired
private AuthenticationManager authenticationManager; private UserDetailsService userDetailsService;
// Redis缓存门户UserID → 若依本地用户名(避免重复匹配数据库) // Redis缓存门户UserID → 若依本地用户名(避免重复匹配数据库)
private static final String REDIS_KEY_PORTAL_USER_MAPPING = "portal:user:mapping:"; private static final String REDIS_KEY_PORTAL_USER_MAPPING = "portal:user:mapping:";
// 门户 Token 存储前缀Redis 键:门户 userId → 门户 Token 信息) // 门户 Token 存储前缀Redis 键:门户 userId → 门户 Token 信息)
@@ -92,6 +96,12 @@ public class OauthLoginService {
String cacheKey = REDIS_KEY_PORTAL_USER_MAPPING + portalUserId; String cacheKey = REDIS_KEY_PORTAL_USER_MAPPING + portalUserId;
String localUsername = redisCache.getCacheObject(cacheKey); String localUsername = redisCache.getCacheObject(cacheKey);
if (StringUtils.isNotBlank(localUsername)) { if (StringUtils.isNotBlank(localUsername)) {
try {
//更新用户信息
//updateUserInfo(portalUser);
}catch (Exception e){
e.printStackTrace();
}
return localUsername; return localUsername;
} }
@@ -134,9 +144,20 @@ public class OauthLoginService {
newUser.setUserId(portalUserId); newUser.setUserId(portalUserId);
newUser.setPassword(SecurityUtils.encryptPassword("123456")); newUser.setPassword(SecurityUtils.encryptPassword("123456"));
newUser.setDelFlag("0"); newUser.setDelFlag("0");
try {
// 调用若依原生方法新增用户(自动处理角色关联,需提前配置默认角色) //查询权限,保存权限
sysUserService.insertUser(newUser); List<QxUserRole> userRoleList=oauthClient.getUserRoleList(portalUserId);
if(userRoleList!=null&&userRoleList.size()>0){
Long[] longs=userRoleList.stream().mapToLong(QxUserRole::getRoleId).boxed().toArray(Long[]::new);;
newUser.setRoleIds(longs);
}else {
throw new Exception("未查询到用户角色,请授权后访问!");
}
// 调用若依原生方法新增用户(自动处理角色关联,需提前配置默认角色)
sysUserService.insertUser(newUser);
}catch (Exception e){
e.printStackTrace();
}
return newUser; return newUser;
} }
@@ -147,15 +168,16 @@ public class OauthLoginService {
private Authentication authenticateLocalUser(String localUsername) { private Authentication authenticateLocalUser(String localUsername) {
Authentication authentication = null; Authentication authentication = null;
try { try {
// 构建认证令牌:用户名+空密码(因为门户已验证身份,本地仅需加载用户信息) UserDetails userDetails = userDetailsService.loadUserByUsername(localUsername);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(localUsername, "");
AuthenticationContextHolder.setContext(authenticationToken);
// 触发认证:会调用 UserDetailsServiceImpl.loadUserByUsername(localUsername) authentication = new UsernamePasswordAuthenticationToken(
// 该方法会加载用户权限、角色,返回 LoginUser userDetails,
authentication = authenticationManager.authenticate(authenticationToken); null, // 密码为 null彻底绕过 Spring Security 的密码校验
userDetails.getAuthorities()
);
AuthenticationContextHolder.setContext(authentication);
} catch (Exception e) { } catch (Exception e) {
// 捕获认证异常(如用户被禁用、权限加载失败等)
throw new ServiceException("本地用户认证失败:" + e.getMessage()); throw new ServiceException("本地用户认证失败:" + e.getMessage());
} finally { } finally {
AuthenticationContextHolder.clearContext(); AuthenticationContextHolder.clearContext();
@@ -178,6 +200,34 @@ public class OauthLoginService {
redisCache.setCacheObject(redisKey, tokenCache, safeLongToInt(tokenResult.getExpiresIn()), TimeUnit.SECONDS); redisCache.setCacheObject(redisKey, tokenCache, safeLongToInt(tokenResult.getExpiresIn()), TimeUnit.SECONDS);
} }
/**
* 修改用户信息
* @param portalUser
*/
private void updateUserInfo(NwUserInfoResult portalUser){
SysUser sysUser=new SysUser();
Long portalUserId=parsePortalUserId(portalUser.getUserid());
//查询用户角色
try {
List<QxUserRole> userRoleList=oauthClient.getUserRoleList(portalUserId);
if(userRoleList!=null&&userRoleList.size()>0){
Long[] longs=userRoleList.stream().mapToLong(QxUserRole::getRoleId).boxed().toArray(Long[]::new);;
sysUser.setRoleIds(longs);
}else {
throw new Exception("未查询到用户角色,请授权后访问!");
}
}catch (Exception e){
e.printStackTrace();
}
String localUsername = "portal_" + portalUserId;
sysUser.setUserName(localUsername);
sysUser.setNickName(portalUser.getName());
sysUser.setIdCard(portalUser.getIdcardno());
sysUser.setUserId(portalUserId);
sysUserService.updateUser(sysUser);
}
/** /**
* 记录登录信息(复用若依原生逻辑,直接复制过来) * 记录登录信息(复用若依原生逻辑,直接复制过来)
*/ */

View File

@@ -53,8 +53,8 @@ public class UserDetailsServiceImpl implements UserDetailsService
log.info("登录用户:{} 已被停用.", username); log.info("登录用户:{} 已被停用.", username);
throw new ServiceException(MessageUtils.message("user.blocked")); throw new ServiceException(MessageUtils.message("user.blocked"));
} }
////单点跳过验证密码阶段
passwordService.validate(user); //passwordService.validate(user);
return createLoginUser(user); return createLoginUser(user);
} }