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<String, String> 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<String, String> getResult(String param) {
|
Map<String, String> 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<String, String> 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<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
|
|
ResponseEntity<String> 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<String> entity = new HttpEntity<>(headers);
|
|
ResponseEntity<String> 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;
|
}
|
|
|
}
|