| | |
| | | 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.exception.base.BaseException; |
| | | 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.ISysConfigService; |
| | | import com.ruoyi.system.service.ISysUserService; |
| | | import com.smartor.domain.SSOTokenResponse; |
| | | import com.smartor.domain.SSOUserInfo; |
| | |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | import org.springframework.web.client.RestTemplate; |
| | | import org.springframework.web.servlet.view.RedirectView; |
| | | |
| | | 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单点登录控制器 |
| | |
| | | @Autowired |
| | | private TokenService tokenService; |
| | | |
| | | @Autowired |
| | | private ISysConfigService sysConfigService; |
| | | |
| | | private final RestTemplate restTemplate; |
| | | |
| | | public SSOController() { |
| | |
| | | * 访问路径:http://域名:8095/sso/login |
| | | */ |
| | | @GetMapping("") |
| | | public void ssoLogin(HttpServletResponse response, HttpServletRequest request) throws IOException { |
| | | public void ssoLogin() { |
| | | log.info("收到SSO登录请求,开始重定向到授权服务器"); |
| | | |
| | | // 获取客户端IP |
| | | String clientIp = getClientIp(request); |
| | | boolean isInternal = isInternalNetwork(clientIp); |
| | | |
| | | // 构建授权URL |
| | | String authUrl = buildAuthorizationUrl(isInternal); |
| | | log.info("重定向到授权URL: {}", authUrl); |
| | | |
| | | response.sendRedirect(authUrl); |
| | | } |
| | | |
| | | /** |
| | | * SSO回调处理 |
| | | */ |
| | | @GetMapping("/callback") |
| | | public void ssoCallback(@RequestParam(required = false) String code, |
| | | @RequestParam(required = false) String state, |
| | | @RequestParam(required = false) String error, |
| | | HttpServletResponse response, |
| | | HttpServletRequest request) throws IOException { |
| | | |
| | | log.info("收到SSO回调,code: {}, state: {}, error: {}", code, state, error); |
| | | |
| | | if (error != null) { |
| | | log.error("SSO授权失败: {}", error); |
| | | try { |
| | | response.sendRedirect("/login?sso_error=" + URLEncoder.encode(error, "UTF-8")); |
| | | } catch (Exception e) { |
| | | log.error("重定向失败", e); |
| | | response.sendRedirect("/login?sso_error=unknown_error"); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | if (code == null || !this.state.equals(state)) { |
| | | log.error("SSO回调参数错误,code: {}, state: {}", code, state); |
| | | response.sendRedirect("/login?sso_error=invalid_callback"); |
| | | return; |
| | | } |
| | | |
| | | // 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 { |
| | | // 获取客户端IP |
| | | String clientIp = getClientIp(request); |
| | | boolean isInternal = isInternalNetwork(clientIp); |
| | | SSOTokenResponse accessToken = getAccessToken(code, true); |
| | | SSOUserInfo userInfo = getUserInfo(accessToken.getAccess_token(), true); |
| | | |
| | | // 1. 用code换取access_token |
| | | SSOTokenResponse tokenResponse = getAccessToken(code, isInternal); |
| | | log.info("获取到access_token: {}", tokenResponse.getAccess_token()); |
| | | createLocalSession(userInfo); |
| | | |
| | | // 2. 用access_token获取用户信息 |
| | | SSOUserInfo userInfo = getUserInfo(tokenResponse.getAccess_token(), isInternal); |
| | | log.info("获取到用户信息: {}", userInfo); |
| | | |
| | | // 3. 根据用户信息创建本地会话 |
| | | String token = createLocalSession(userInfo); |
| | | |
| | | // 4. 重定向到前端首页,携带token |
| | | String frontendUrl = "/#/index?token=" + token; |
| | | response.sendRedirect(frontendUrl); |
| | | |
| | | } catch (RuntimeException e) { |
| | | log.error("SSO业务处理失败: {}", e.getMessage(), e); |
| | | try { |
| | | response.sendRedirect("/login?sso_error=" + URLEncoder.encode(e.getMessage(), "UTF-8")); |
| | | } catch (Exception ex) { |
| | | log.error("重定向失败", ex); |
| | | response.sendRedirect("/login?sso_error=system_error"); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("SSO登录处理失败", e); |
| | | response.sendRedirect("/login?sso_error=login_failed"); |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 构建授权URL |
| | | * SSO登录入口 - 信通院会调用这个地址 |
| | | * 访问路径:http://域名:8095/sso/login |
| | | */ |
| | | private String buildAuthorizationUrl(boolean isInternal) { |
| | | @GetMapping("ssoLoginLyra") |
| | | public RedirectView ssoLoginLyra() { |
| | | log.info("收到SSO登录请求,开始重定向到授权服务器"); |
| | | String id = clientId; |
| | | String redirectUri = internalRedirectUri; |
| | | String scope = "openid"; |
| | | id = "1553548571532333056"; |
| | | redirectUri = URLEncoder.encode(internalRedirectUri); |
| | | // 重定向地址对象(重定向地址 |
| | | RedirectView redirectView = new RedirectView(); |
| | | |
| | | // Authorize鉴权接口 |
| | | String param = "client_id=" + id + "&redirect_uri=" + redirectUri + "&response_type=code" + |
| | | "&state=" + state + "&scope=" + scope; |
| | | log.info("【Authorize鉴权接口】入参为:{}", param); |
| | | String url = "https://9.208.39.29:13021" + "/mediinfo-lyra-authserver/connect/authorize"; |
| | | // String url = internalAuthorizeUrl; |
| | | String s = HttpUtils.sendGet(url, param); |
| | | Map<String, String> result = getResult(s); |
| | | String code = result.get("code"); |
| | | try { |
| | | String redirectUri = getRedirectUri(isInternal); |
| | | return getAuthorizeUrl(isInternal) + "?" + |
| | | "client_id=" + clientId + |
| | | "&redirect_uri=" + URLEncoder.encode(redirectUri, "UTF-8") + |
| | | "&response_type=code" + |
| | | "&state=" + state + |
| | | "&scope=" + URLEncoder.encode(scope, "UTF-8"); |
| | | SSOTokenResponse accessToken = getAccessTokenLyra(code, true); |
| | | SSOUserInfo userInfo = getUserInfoLyra(accessToken.getAccess_token(), true); |
| | | |
| | | createLocalSession(userInfo); |
| | | |
| | | String path = sysConfigService.selectConfigByKey("sys.qddz"); |
| | | if (StringUtils.isEmpty(path)) { |
| | | throw new BaseException("请配置前端地址"); |
| | | } |
| | | String reviewUrl = path + "/loginSSO?token=" + accessToken.getAccess_token() + "&orgid=" + |
| | | userInfo.getZuZhiJGID() + "&orgname=" + userInfo.getZuZhiJGMC() + |
| | | "&ZuHuID="+ userInfo.getYongHuID() +"&deptCode=null"; |
| | | log.info("单点登陆重定向地址为:{}", reviewUrl); |
| | | redirectView.setUrl(reviewUrl); |
| | | redirectView.setStatusCode(HttpStatus.MOVED_PERMANENTLY); |
| | | } catch (Exception e) { |
| | | log.error("构建授权URL失败", e); |
| | | throw new RuntimeException("构建授权URL失败", e); |
| | | e.printStackTrace(); |
| | | return new RedirectView(); |
| | | } |
| | | |
| | | return redirectView; |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers); |
| | | |
| | | ResponseEntity<String> response = restTemplate.exchange( |
| | | getTokenUrl(isInternal), HttpMethod.POST, request, String.class); |
| | | ResponseEntity<String> response = restTemplate.exchange(getTokenUrl(isInternal), HttpMethod.POST, request, String.class); |
| | | |
| | | log.info("Token响应: {}", response.getBody()); |
| | | |
| | |
| | | |
| | | HttpEntity<String> entity = new HttpEntity<>(headers); |
| | | |
| | | ResponseEntity<String> response = restTemplate.exchange( |
| | | getUserinfoUrl(isInternal), HttpMethod.GET, entity, String.class); |
| | | 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 SSOTokenResponse getAccessTokenLyra(String code, boolean isInternal) throws Exception { |
| | | HttpHeaders headers = new HttpHeaders(); |
| | | headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); |
| | | |
| | | MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); |
| | | String id = clientId; |
| | | String secret = clientSecret; |
| | | String url = getTokenUrl(isInternal); |
| | | String redirectUri = getRedirectUri(isInternal); |
| | | id = "1553548571532333056"; |
| | | secret = "suifangxt"; |
| | | url = "https://9.208.39.29:13021" + "/mediinfo-lyra-authserver/connect/token"; |
| | | params.add("client_id", id); |
| | | params.add("client_secret", secret); |
| | | params.add("code", code); |
| | | params.add("grant_type", "authorization_code"); |
| | | params.add("redirect_uri", redirectUri); |
| | | |
| | | HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers); |
| | | |
| | | ResponseEntity<String> response = restTemplate.exchange(url, 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 getUserInfoLyra(String accessToken, boolean isInternal) throws Exception { |
| | | HttpHeaders headers = new HttpHeaders(); |
| | | headers.set("Authorization", "Bearer " + accessToken); |
| | | |
| | | HttpEntity<String> entity = new HttpEntity<>(headers); |
| | | String url = getUserinfoUrl(isInternal); |
| | | url = "https://9.208.39.29:13021" + "/mediinfo-lyra-authserver/connect/userinfo"; |
| | | ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); |
| | | |
| | | log.info("用户信息响应: {}", response.getBody()); |
| | | |
| | |
| | | LoginUser loginUser = new LoginUser(localUser.getUserId(), localUser.getDeptId(), localUser, null); |
| | | |
| | | // 生成token |
| | | return tokenService.createToken(loginUser); |
| | | String token = tokenService.createToken(loginUser); |
| | | log.info("生成的token为:{}", token); |
| | | return token; |
| | | } |
| | | |
| | | /** |