package com.ruoyi.web.controller.sso; import com.alibaba.fastjson.JSON; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.utils.HttpUtil; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.http.HttpUtils; import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.system.service.ISysUserService; import com.smartor.domain.SSOTokenResponse; import com.smartor.domain.SSOUserInfo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; 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 org.springframework.web.client.RestTemplate; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; /** * SSO单点登录控制器 */ @RestController @RequestMapping("/sso") @Slf4j public class SSOController { @Value("${sso.client_id}") private String clientId; @Value("${sso.client_secret}") private String clientSecret; @Value("${sso.internal.authorize_url}") private String internalAuthorizeUrl; @Value("${sso.internal.token_url}") private String internalTokenUrl; @Value("${sso.internal.userinfo_url}") private String internalUserinfoUrl; @Value("${sso.internal.redirect_uri}") private String internalRedirectUri; @Value("${sso.external.authorize_url}") private String externalAuthorizeUrl; @Value("${sso.external.token_url}") private String externalTokenUrl; @Value("${sso.external.userinfo_url}") private String externalUserinfoUrl; @Value("${sso.external.redirect_uri}") private String externalRedirectUri; @Value("${sso.state}") private String state; @Value("${sso.scope}") private String scope; @Autowired private ISysUserService userService; @Autowired private TokenService tokenService; private final RestTemplate restTemplate; public SSOController() { // 配置RestTemplate超时 SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(10000); // 连接超时10秒 factory.setReadTimeout(30000); // 读取超时30秒 this.restTemplate = new RestTemplate(factory); } /** * SSO登录入口 - 信通院会调用这个地址 * 访问路径:http://域名:8095/sso/login */ @GetMapping("") public void ssoLogin() { log.info("收到SSO登录请求,开始重定向到授权服务器"); // Authorize鉴权接口 String param = "client_id=" + clientId + "&redirect_uri=" + internalRedirectUri + "&response_type=code" + "&state=" + state + "&scope=" + scope; log.info("【Authorize鉴权接口】入参为:{}", param); String s = HttpUtils.sendGet(internalAuthorizeUrl, param); Map result = getResult(s); String code = result.get("code"); try { SSOTokenResponse accessToken = getAccessToken(code, true); SSOUserInfo userInfo = getUserInfo(accessToken.getAccess_token(), true); createLocalSession(userInfo); } catch (Exception e) { e.printStackTrace(); } } private Map getResult(String param) { Map paramMap = new HashMap<>(); if (param == null || !param.contains("?")) { return paramMap; } String query = param.substring(param.indexOf('?') + 1); String[] pairs = query.split("&"); for (String pair : pairs) { String[] kv = pair.split("=", 2); String key = kv[0]; String value = kv.length > 1 ? kv[1] : ""; paramMap.put(key, value); } return paramMap; } /** * 获取访问令牌 */ private SSOTokenResponse getAccessToken(String code, boolean isInternal) throws Exception { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("client_id", clientId); params.add("client_secret", clientSecret); params.add("code", code); params.add("grant_type", "authorization_code"); params.add("redirect_uri", getRedirectUri(isInternal)); HttpEntity> request = new HttpEntity<>(params, headers); ResponseEntity response = restTemplate.exchange(getTokenUrl(isInternal), HttpMethod.POST, request, String.class); log.info("Token响应: {}", response.getBody()); if (response.getBody() == null || response.getBody().trim().isEmpty()) { throw new RuntimeException("Token响应为空"); } SSOTokenResponse tokenResponse = JSON.parseObject(response.getBody(), SSOTokenResponse.class); if (tokenResponse == null || StringUtils.isEmpty(tokenResponse.getAccess_token())) { throw new RuntimeException("获取access_token失败"); } return tokenResponse; } /** * 获取用户信息 */ private SSOUserInfo getUserInfo(String accessToken, boolean isInternal) throws Exception { HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "Bearer " + accessToken); HttpEntity entity = new HttpEntity<>(headers); ResponseEntity response = restTemplate.exchange(getUserinfoUrl(isInternal), HttpMethod.GET, entity, String.class); log.info("用户信息响应: {}", response.getBody()); if (response.getBody() == null || response.getBody().trim().isEmpty()) { throw new RuntimeException("用户信息响应为空"); } SSOUserInfo userInfo = JSON.parseObject(response.getBody(), SSOUserInfo.class); if (userInfo == null || StringUtils.isEmpty(userInfo.getName())) { throw new RuntimeException("获取用户信息失败或用户名为空"); } return userInfo; } /** * 创建本地会话 */ private String createLocalSession(SSOUserInfo userInfo) { // 根据SSO用户信息查找本地用户(根据工号匹配) SysUser localUser = findLocalUserByName(userInfo.getName()); if (localUser == null) { throw new RuntimeException("用户不存在或未开通系统权限:" + userInfo.getName()); } // 创建登录用户对象 LoginUser loginUser = new LoginUser(localUser.getUserId(), localUser.getDeptId(), localUser, null); // 生成token String token = tokenService.createToken(loginUser); log.info("生成的token为:{}", token); return token; } /** * 根据工号查找本地用户 */ private SysUser findLocalUserByName(String workNumber) { if (StringUtils.isEmpty(workNumber)) { log.error("工号为空,无法查找用户"); return null; } try { SysUser user = userService.selectUserByUserName(workNumber); if (user != null) { log.info("找到用户: {} - {}", workNumber, user.getNickName()); } else { log.warn("未找到用户: {}", workNumber); } return user; } catch (Exception e) { log.error("查询用户失败: {}", workNumber, e); return null; } } /** * 根据客户端IP判断是否为内网 */ private boolean isInternalNetwork(String clientIp) { if (clientIp == null || clientIp.isEmpty()) { return false; } // 判断是否为内网网段 10.10.13.* return clientIp.startsWith("10.10.13."); } /** * 获取客户端真实IP */ private String getClientIp(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } // 如果有多个IP,取第一个 if (ip != null && ip.contains(",")) { ip = ip.split(",")[0].trim(); } log.info("客户端IP: {}", ip); return ip; } /** * 根据网络环境获取授权URL */ private String getAuthorizeUrl(boolean isInternal) { return isInternal ? internalAuthorizeUrl : externalAuthorizeUrl; } /** * 根据网络环境获取Token URL */ private String getTokenUrl(boolean isInternal) { return isInternal ? internalTokenUrl : externalTokenUrl; } /** * 根据网络环境获取用户信息URL */ private String getUserinfoUrl(boolean isInternal) { return isInternal ? internalUserinfoUrl : externalUserinfoUrl; } /** * 根据网络环境获取回调URI */ private String getRedirectUri(boolean isInternal) { return isInternal ? internalRedirectUri : externalRedirectUri; } }