¶Ô±ÈÐÂÎļþ |
| | |
| | | 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.StringUtils; |
| | | 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; |
| | | |
| | | /** |
| | | * 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(HttpServletResponse response, HttpServletRequest request) throws IOException { |
| | | 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; |
| | | } |
| | | |
| | | try { |
| | | // è·å客æ·ç«¯IP |
| | | String clientIp = getClientIp(request); |
| | | boolean isInternal = isInternalNetwork(clientIp); |
| | | |
| | | // 1. ç¨codeæ¢åaccess_token |
| | | SSOTokenResponse tokenResponse = getAccessToken(code, isInternal); |
| | | log.info("è·åå°access_token: {}", tokenResponse.getAccess_token()); |
| | | |
| | | // 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"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æå»ºææURL |
| | | */ |
| | | private String buildAuthorizationUrl(boolean isInternal) { |
| | | 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"); |
| | | } catch (Exception e) { |
| | | log.error("æå»ºææURL失败", e); |
| | | throw new RuntimeException("æå»ºææURL失败", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è·å访é®ä»¤ç |
| | | */ |
| | | 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 |
| | | return tokenService.createToken(loginUser); |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®å·¥å·æ¥æ¾æ¬å°ç¨æ· |
| | | */ |
| | | 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; |
| | | } |
| | | |
| | | |
| | | } |