diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java index e9f38c6..e95c098 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java @@ -3,8 +3,10 @@ package com.ruoyi.web.controller.system; import java.util.List; import java.util.Set; +import com.ruoyi.common.core.domain.entity.tymh.wwToken.WwUserLogin; import com.ruoyi.common.core.domain.model.RegisterBody; import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.OauthLoginHlwService; import com.ruoyi.framework.web.service.OauthLoginService; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; @@ -37,6 +39,8 @@ public class SysLoginController private SysPermissionService permissionService; @Autowired private OauthLoginService oauthLoginService; + @Autowired + private OauthLoginHlwService oauthLoginHlwService; /** * 登录方法 @@ -183,4 +187,20 @@ public class SysLoginController .put("token", token) .put("accessUrl", ""); } + + /** + *互联网获取token + * @param wwUserLogin + * @return + */ + @GetMapping("/getWwTjmhToken") + public AjaxResult getWwTjmhToken(@RequestBody WwUserLogin wwUserLogin){ + if(wwUserLogin==null){ + return AjaxResult.error("参数wwUserLogin为空,请传递wwUserLogin实体参数"); + } + String token = oauthLoginHlwService.getWwTjmhToken(wwUserLogin); + return AjaxResult.success("门户登录成功") + .put("token", token) + .put("accessUrl", ""); + } } diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 9acc283..b6066d1 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -160,13 +160,15 @@ oauth: #外网 usptww: #门户注册 - wwRegisterPostUrl: http://ip:80/whiteListServiceAPI/doWebRegister + wwRegisterPostUrl: http://10.98.80.50/uspt/webWhiteListServiceAPI/doWebRegister #门户登录 - wwTokenPostUrl: http://ip:80/whiteListServiceAPI/doWebLogon + wwTokenPostUrl: http://10.98.80.50/uspt/webWhiteListServiceAPI/doWebLogon + #查询用户信息 + wwQueryWebUserInfo: http://10.98.80.50/uspt/webServiceAPI/queryWebUserInfo #查询个人信息 - wwQueryWebPersonalInfoPostUrl: http://ip:80/serviceAPI/queryWebPersonalInfo + wwQueryWebPersonalInfoPostUrl: http://10.98.80.50/uspt/webServiceAPI/queryWebPersonalInfo #查询单位信息 - wwQueryWebEnterpriseInfoPostUrl: http://ip:80/serviceAPI/queryWebEnterpriseInfo + wwQueryWebEnterpriseInfoPostUrl: http://10.98.80.50/uspt/webServiceAPI/queryWebEnterpriseInfo #用户新增接口 tyAddUserUrl: http://10.98.80.146/qxgl_backend/security/add_user #获取当前用户有权系统列表 diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/oauth/HttpUtils.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/oauth/HttpUtils.java index db1d65f..b9ea0bb 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/oauth/HttpUtils.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/oauth/HttpUtils.java @@ -56,6 +56,22 @@ public class HttpUtils { return doPost(url, jsonParams, JSON_MEDIA_TYPE, null, connectTimeout, readTimeout, writeTimeout); } + /** + * POST JSON请求(默认超时,支持自定义 headers) + */ + public static String doPostJson(String url, Map params, Map headers) throws TimeoutException, IOException { + return doPostJson(url, params, headers, DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT, DEFAULT_WRITE_TIMEOUT); + } + + /** + * POST JSON请求(自定义超时和 headers) + */ + public static String doPostJson(String url, Map params, Map headers, + int connectTimeout, int readTimeout, int writeTimeout) throws TimeoutException, IOException { + String jsonParams = CollectionUtils.isEmpty(params) ? "{}" : JSON.toJSONString(params); + return doPost(url, jsonParams, JSON_MEDIA_TYPE, headers, connectTimeout, readTimeout, writeTimeout); + } + /** * POST 表单请求(默认超时) */ diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/oauth/OauthClient.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/oauth/OauthClient.java index 682740f..b6fac01 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/oauth/OauthClient.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/oauth/OauthClient.java @@ -7,6 +7,9 @@ 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.NwUserInfoResult; +import com.ruoyi.common.core.domain.entity.tymh.wwToken.WwTokenResult; +import com.ruoyi.common.core.domain.entity.tymh.wwToken.WwTyInfo; +import com.ruoyi.common.core.domain.entity.tymh.wwToken.WwUserLogin; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.crypto.CryptoUtil; import org.springframework.beans.factory.annotation.Value; @@ -38,6 +41,8 @@ public class OauthClient { private String wwRegisterPostUrl; @Value("${oauth.usptww.wwTokenPostUrl}") private String wwTokenPostUrl; + @Value("${oauth.usptww.wwQueryWebUserInfo}") + private String wwQueryWebUserInfo; @Value("${oauth.usptww.wwQueryWebPersonalInfoPostUrl}") private String wwQueryWebPersonalInfoPostUrl; @Value("${oauth.usptww.wwQueryWebEnterpriseInfoPostUrl}") @@ -173,6 +178,31 @@ public class OauthClient { return parseMapOrDataArray(responseJson, operationName, clazz); } + /** + * 传递heades + * @param url + * @param params + * @param typeReference + * @param operationName + * @return + * @param + * @throws IOException + * @throws TimeoutException + */ + private T executePostRequestHeaders(String url, Map params,Map headers, + TypeReference> typeReference, String operationName) + throws IOException, TimeoutException { + String responseJson = HttpUtils.doPostJson( + url, + params, + headers, + connectTimeout, + readTimeout, + writeTimeout + ); + return validateResponse(responseJson, operationName, typeReference); + } + /** * 简化后的响应校验方法(已移除网关返回码判断) */ @@ -277,6 +307,78 @@ public class OauthClient { return (value instanceof JSONArray) ? (JSONArray) value : null; } + /** + * 互联网-获取token + * @param wwUserLogin + * @return + * @throws Exception + */ + public WwTokenResult wwGetToken(WwUserLogin wwUserLogin) throws Exception { + if (wwUserLogin == null) { + log.error("获取互联网Token失败:登录参数不能为空"); + throw new IllegalArgumentException("登录参数不能为空"); + } + if (StringUtils.isEmpty(wwUserLogin.getUsername())) { + log.error("获取互联网Token失败:身份证号/统一社会信用代码不能为空"); + throw new IllegalArgumentException("身份证号/统一社会信用代码不能为空"); + } + if (StringUtils.isEmpty(wwUserLogin.getUsertype())) { + log.error("获取互联网Token失败:用户类型(usertype)不能为空"); + throw new IllegalArgumentException("用户类型(usertype)不能为空"); + } + + Map params = new HashMap<>(4); + params.put("username", wwUserLogin.getUsername()); + params.put("usertype", wwUserLogin.getUsertype()); + if (StringUtils.isNotEmpty(wwUserLogin.getLogontype())) { + params.put("logontype", wwUserLogin.getLogontype()); + } + if (StringUtils.isNotEmpty(wwUserLogin.getLogonchannel())) { + params.put("logonchannel", wwUserLogin.getLogonchannel()); + } + + WwTokenResult tokenResult = executePostRequest( + wwTokenPostUrl, + params, + new TypeReference>() {}, + "获取互联网Token" + ); + + if (tokenResult == null) { + log.error("获取互联网Token失败:接口返回业务数据为空"); + throw new Exception("获取互联网Token失败:返回数据异常"); + } + log.info("获取互联网Token成功 | username:{} | accessToken:{}", wwUserLogin.getUsername(), tokenResult); + return tokenResult; + } + + /** + * 互联网-端根据token获取用户信息 + * @param token + * @return + * @throws IOException + * @throws TimeoutException + */ + public WwTyInfo wwGetUserInfo(String token) throws IOException, TimeoutException{ + if(StringUtils.isBlank(token)){ + throw new IllegalArgumentException("token不能为空"); + } + //headers + Map headers = new HashMap<>(); + headers.put("Access-Token", token); + headers.put("Content-Type", "application/json"); + + //parm + Map params = new HashMap<>(0); + return executePostRequestHeaders( + wwQueryWebUserInfo, + params, + headers, + new TypeReference>() {}, + "获取互联网用户信息" + ); + } + /** * 静态返回解析 * @param diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/tymh/wwToken/WwTokenResult.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/tymh/wwToken/WwTokenResult.java new file mode 100644 index 0000000..43423e7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/tymh/wwToken/WwTokenResult.java @@ -0,0 +1,10 @@ +package com.ruoyi.common.core.domain.entity.tymh.wwToken; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +@Data +public class WwTokenResult { + @JSONField(name = "Access-Token") + private String accessToken; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/tymh/wwToken/WwTyInfo.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/tymh/wwToken/WwTyInfo.java new file mode 100644 index 0000000..8fc1ebc --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/tymh/wwToken/WwTyInfo.java @@ -0,0 +1,22 @@ +package com.ruoyi.common.core.domain.entity.tymh.wwToken; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class WwTyInfo { + @ApiModelProperty("用户类型(1:个人 2:单位)") + private String usertype; + @ApiModelProperty("单位统一身份社会信用代码") + private String enterprisecode; + @ApiModelProperty("单位名称") + private String enterprisename; + @ApiModelProperty("单位经办人姓名") + private String contactperson; + @ApiModelProperty("单位经办人联系电话") + private String contactphone; + @ApiModelProperty("个人身份证号") + private String idno; + @ApiModelProperty("个人姓名") + private String name; +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java index 47e4a3c..77639be 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -111,7 +111,7 @@ public class SecurityConfig .authorizeHttpRequests((requests) -> { permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll()); // 对于登录login 注册register 验证码captchaImage 允许匿名访问 - requests.antMatchers("/login", "/register", "/captchaImage","/app/login","/websocket/**","/speech-recognition","/speech-synthesis","/cms/company/listPage","/cms/appUser/noTmlist","/getTjmhToken").permitAll() + requests.antMatchers("/login", "/register", "/captchaImage","/app/login","/websocket/**","/speech-recognition","/speech-synthesis","/cms/company/listPage","/cms/appUser/noTmlist","/getTjmhToken","/getWwTjmhToken").permitAll() // 静态资源,可匿名访问 .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() // 移动端公用查询,可匿名访问 diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/OauthLoginHlwService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/OauthLoginHlwService.java new file mode 100644 index 0000000..109b890 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/OauthLoginHlwService.java @@ -0,0 +1,199 @@ +package com.ruoyi.framework.web.service; + +import com.ruoyi.cms.util.StringUtil; +import com.ruoyi.cms.util.oauth.OauthClient; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.entity.CompanyContact; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.entity.tymh.nwToken.PortalTokenCacheDTO; +import com.ruoyi.common.core.domain.entity.tymh.wwToken.WwTokenResult; +import com.ruoyi.common.core.domain.entity.tymh.wwToken.WwTyInfo; +import com.ruoyi.common.core.domain.entity.tymh.wwToken.WwUserLogin; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.security.context.AuthenticationContextHolder; +import com.ruoyi.system.service.ISysUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +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 java.util.Date; +import java.util.concurrent.TimeUnit; + +@Service +public class OauthLoginHlwService { + + @Autowired + private OauthClient oauthClient; + + @Autowired + private TokenService tokenService; + @Autowired + private RedisCache redisCache; + @Autowired + private ISysUserService sysUserService; + @Autowired + private UserDetailsService userDetailsService; + // Redis缓存:门户UserID → 若依本地用户名(避免重复匹配数据库) + private static final String REDIS_KEY_PORTAL_USER_MAPPING = "hlw:user:mapping:"; + // 门户 Token 存储前缀(Redis 键:门户 userId → 门户 Token 信息) + private static final String REDIS_KEY_PORTAL_TOKEN = "hlw:token:"; + private static final String USER_KEY="hlw_"; + + final private int expireTime=30; + protected static final long MILLIS_SECOND = 1000; + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + /** + * OAuth 登录流程:通过授权码获取系统令牌 + * @return 系统内部令牌(供前端后续使用) + */ + public String getWwTjmhToken(WwUserLogin wwUserLogin){ + try { + //获取门户token + WwTokenResult wwTokenResult = oauthClient.wwGetToken(wwUserLogin); + String wwToken=wwTokenResult.getAccessToken(); + System.out.println("wwToken======================="+wwToken); + if (StringUtils.isBlank(wwToken)) { + throw new ServiceException("获取门户 Token 失败:" + wwToken); + } + //获取门户userInfo + WwTyInfo portalUser = oauthClient.wwGetUserInfo(wwToken); + //匹配/创建本地用户 + String localUsername = getOrCreateLocalUser(portalUser); + //执行原来的登录流程 + Authentication authentication = authenticateLocalUser(localUsername); + AsyncManager.me().execute(AsyncFactory.recordLogininfor(localUsername, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) authentication.getPrincipal(); + + storePortalToken(loginUser.getUserId(), wwToken); + recordLoginInfo(loginUser.getUserId()); + return tokenService.createToken(loginUser); + } catch (Exception e) { + throw new ServiceException("OAuth 登录失败:" + e.getMessage()); + } + } + + /** + * 匹配/创建本地用户,返回若依本地用户名 + */ + private String getOrCreateLocalUser(WwTyInfo wwTyInfo) { + SysUser localUser=sysUserService.selectUserByIdCard(wwTyInfo.getIdno()); + // 先从Redis查询缓存的本地用户名 + String cacheKey = REDIS_KEY_PORTAL_USER_MAPPING + localUser.getUserId(); + String localUsername = redisCache.getCacheObject(cacheKey); + if (StringUtils.isNotBlank(localUsername)) { + return localUsername; + } + + if (localUser == null) { + // 本地无用户,自动创建 + localUser = createLocalUser(wwTyInfo); + // 缓存门户UserID与本地用户名的映射(有效期1天,可调整) + redisCache.setCacheObject(cacheKey, localUser.getUserName(), 1, TimeUnit.DAYS); + return localUser.getUserName(); + } + + // 缓存映射关系(更新有效期) + redisCache.setCacheObject(cacheKey, localUser.getUserName(), 1, TimeUnit.DAYS); + return localUser.getUserName(); + } + + /** + * 门户UserID字符串转Long + */ + private Long parseStringToLoing(String portalUserIdStr) { + try { + return Long.parseLong(portalUserIdStr); + } catch (NumberFormatException e) { + throw new ServiceException("门户用户ID格式错误:" + portalUserIdStr); + } + } + + /** + * 自动创建本地用户 + */ + private SysUser createLocalUser(WwTyInfo wwTyInfo) { + SysUser newUser = new SysUser(); + switch (wwTyInfo.getUsertype()) { + case "1"://个人 + newUser.setNickName(wwTyInfo.getName()); + newUser.setIdCard(wwTyInfo.getIdno()); + newUser.setRoleIds(new Long[]{parseStringToLoing(StringUtil.SYS_QZZ)}); + newUser.setUserName(wwTyInfo.getName()); + break; + default://单位 + newUser.setNickName(wwTyInfo.getEnterprisename()); + newUser.setIdCard(wwTyInfo.getEnterprisecode()); + newUser.setRoleIds(new Long[]{parseStringToLoing(StringUtil.SYS_QY)}); + newUser.setUserName(wwTyInfo.getEnterprisename()); + //企业联系人 + CompanyContact companyContact=new CompanyContact(); + companyContact.setContactPerson(wwTyInfo.getContactperson()); + companyContact.setContactPersonPhone(wwTyInfo.getContactphone()); + } + newUser.setPassword(SecurityUtils.encryptPassword("123456")); + newUser.setDelFlag("0"); + sysUserService.insertUser(newUser); + return newUser; + } + + /** + * 复用若依认证机制,触发 UserDetailsService 加载 LoginUser + * (关键:用本地用户名构建认证令牌,无需密码,因为门户已完成身份校验) + */ + private Authentication authenticateLocalUser(String localUsername) { + Authentication authentication = null; + try { + UserDetails userDetails = userDetailsService.loadUserByUsername(localUsername); + + authentication = new UsernamePasswordAuthenticationToken( + userDetails, + null, // 密码为 null,彻底绕过 Spring Security 的密码校验 + userDetails.getAuthorities() + ); + + AuthenticationContextHolder.setContext(authentication); + } catch (Exception e) { + throw new ServiceException("本地用户认证失败:" + e.getMessage()); + } finally { + AuthenticationContextHolder.clearContext(); + } + return authentication; + } + + /** + * 存储门户 Token 到 Redis(结构化存储,含过期时间) + */ + private void storePortalToken(Long portalUserId, String accessToken) { + String redisKey = REDIS_KEY_PORTAL_TOKEN + portalUserId; + + PortalTokenCacheDTO tokenCache = new PortalTokenCacheDTO(); + tokenCache.setAccessToken(accessToken); + tokenCache.setExpireTimestamp(System.currentTimeMillis() + expireTime * MILLIS_MINUTE); + + redisCache.setCacheObject(redisKey, tokenCache, expireTime, TimeUnit.SECONDS); + } + + /** + * 记录登录信息(复用若依原生逻辑,直接复制过来) + */ + private void recordLoginInfo(Long userId) { + SysUser sysUser = new SysUser(); + sysUser.setUserId(userId); + sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest())); + sysUser.setLoginDate(new Date()); + sysUserService.updateUserProfile(sysUser); + } +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java index e8f088e..9d14732 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java @@ -129,4 +129,11 @@ public interface SysUserMapper public SysUser checkEmailUnique(String email); AppUser selectAppUserById(long id); + + /** + * 通过身份证查询信息 + * @param idCard + * @return + */ + SysUser selectUserByIdCard(String idCard); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java index 443ea42..7cdc638 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java @@ -207,4 +207,6 @@ public interface ISysUserService public String importUser(List userList, Boolean isUpdateSupport, String operName); AppUser selectAppUserById(long id); + + SysUser selectUserByIdCard(String idCard); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java index 9fcc218..38b6c72 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java @@ -554,4 +554,9 @@ public class SysUserServiceImpl implements ISysUserService public AppUser selectAppUserById(long id) { return userMapper.selectAppUserById(id); } + + @Override + public SysUser selectUserByIdCard(String idCard) { + return userMapper.selectUserByIdCard(idCard); + } } diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml index 68747c0..fe5ccd5 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -233,5 +233,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{userId} + + \ No newline at end of file