统一身份认证接口开发

This commit is contained in:
黎润豪 2026-03-02 15:50:34 +08:00
parent 9864fe8822
commit 12dd783147
5 changed files with 590 additions and 0 deletions

View File

@ -0,0 +1,97 @@
package com.chinaweal.aiccs.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 统一认证平台OAuth2配置
*
* @author lroyia
* @since 2025/03/02
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "oauth2.unified-auth")
public class UnifiedAuthConfig {
/**
* 认证地址
*/
private String authUrl;
/**
* 客户端ID
*/
private String clientId;
/**
* 客户端秘钥
*/
private String clientSecret;
/**
* 回调地址
*/
private String redirectUri;
/**
* 授权码接口路径
*/
private String authorizePath = "/authcenter/getOauth2Authorize";
/**
* 获取令牌接口路径
*/
private String tokenPath = "/authcenter/getOauth2Token";
/**
* 获取用户信息接口路径
*/
private String userinfoPath = "/authcenter/getOauth2UserInfo";
/**
* 验证令牌有效性接口路径
*/
private String checkTokenPath = "/authcenter/checkTAValid";
/**
* 单点登出接口路径
*/
private String logoutPath = "/authcenter/userLogout";
/**
* 获取完整授权码接口URL
*/
public String getAuthorizeUrl() {
return authUrl + authorizePath;
}
/**
* 获取完整令牌接口URL
*/
public String getTokenUrl() {
return authUrl + tokenPath;
}
/**
* 获取完整用户信息接口URL
*/
public String getUserinfoUrl() {
return authUrl + userinfoPath;
}
/**
* 获取完整验证令牌接口URL
*/
public String getCheckTokenUrl() {
return authUrl + checkTokenPath;
}
/**
* 获取完整单点登出接口URL
*/
public String getLogoutUrl() {
return authUrl + logoutPath;
}
}

View File

@ -14,6 +14,7 @@ import com.chinaweal.aiccs.org.entity.dto.*;
import com.chinaweal.aiccs.org.service.IOauthAccessTokenService; import com.chinaweal.aiccs.org.service.IOauthAccessTokenService;
import com.chinaweal.aiccs.org.service.IOauthAuthorizationCodeService; import com.chinaweal.aiccs.org.service.IOauthAuthorizationCodeService;
import com.chinaweal.aiccs.org.service.IOauthClientService; import com.chinaweal.aiccs.org.service.IOauthClientService;
import com.chinaweal.aiccs.org.service.IUnifiedAuthService;
import com.chinaweal.aicorg.model.AICUser; import com.chinaweal.aicorg.model.AICUser;
import com.chinaweal.aicorg.services.OrgUM; import com.chinaweal.aicorg.services.OrgUM;
import com.chinaweal.youfool.framework.springboot.rest.RestResult; import com.chinaweal.youfool.framework.springboot.rest.RestResult;
@ -28,6 +29,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
/** /**
@ -52,6 +54,8 @@ public class OAuth2Controller extends BaseController {
private IOauthAccessTokenService oauthTokenService; private IOauthAccessTokenService oauthTokenService;
@Autowired @Autowired
private OrgUM orgUM; private OrgUM orgUM;
@Autowired
private IUnifiedAuthService unifiedAuthService;
/** /**
* OAuth授权端点 * OAuth授权端点
@ -534,4 +538,142 @@ public class OAuth2Controller extends BaseController {
jsonObject.put("expired", System.currentTimeMillis() + 1000 * 60 * 5);// 增加有效期 jsonObject.put("expired", System.currentTimeMillis() + 1000 * 60 * 5);// 增加有效期
return RestResult.ok(SM4Utils.encrypt(jsonObject.toJSONString(), sm4Key)); return RestResult.ok(SM4Utils.encrypt(jsonObject.toJSONString(), sm4Key));
} }
// ==================== 统一认证平台相关接口 ====================
/**
* 跳转到统一认证平台登录页面
*
* @param state 状态参数用于防止CSRF攻击
* @return 登录URL
*/
@ApiOperation("跳转到统一认证平台登录页面")
@GetMapping("/unified/login")
public ResponseEntity<?> unifiedLogin(
@RequestParam(value = "state", required = false) String state) {
try {
String loginUrl = unifiedAuthService.buildLoginUrl(state);
log.info("跳转到统一认证平台登录页面: {}", loginUrl);
return ResponseEntity.status(HttpStatus.FOUND)
.location(java.net.URI.create(loginUrl))
.build();
} catch (Exception e) {
log.error("跳转到统一认证平台登录页面失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("跳转失败: " + e.getMessage());
}
}
/**
* 统一认证平台OAuth2回调接口
*
* @param code 授权码
* @param state 状态参数
* @return 登录结果
*/
@ApiOperation("统一认证平台OAuth2回调接口")
@GetMapping("/callback")
public ResponseEntity<?> callback(
@RequestParam(value = "code", required = false) String code,
@RequestParam(value = "state", required = false) String state,
@RequestParam(value = "error", required = false) String error,
@RequestParam(value = "error_description", required = false) String errorDescription) {
try {
// 检查是否有错误
if (StringUtils.isNotBlank(error)) {
log.error("统一认证平台返回错误: {} - {}", error, errorDescription);
// 重定向到错误页面或首页
return ResponseEntity.status(HttpStatus.FOUND)
.location(java.net.URI.create("/integration/#/login?error=" + URLEncoder.encode(error + ":" + errorDescription, "UTF-8")))
.build();
}
// 检查授权码
if (StringUtils.isBlank(code)) {
log.error("未收到授权码");
return ResponseEntity.status(HttpStatus.FOUND)
.location(java.net.URI.create("/integration/#/login?error=no_code"))
.build();
}
log.info("收到统一认证平台回调code: {}, state: {}", code, state);
// 使用授权码获取访问令牌
UnifiedAuthDTO.TokenResponse tokenResponse = unifiedAuthService.getAccessToken(code);
if (StringUtils.isNotBlank(tokenResponse.getError())) {
log.error("获取访问令牌失败: {} - {}", tokenResponse.getError(), tokenResponse.getError_description());
return ResponseEntity.status(HttpStatus.FOUND)
.location(java.net.URI.create("/integration/#/login?error=" + URLEncoder.encode(tokenResponse.getError_description(), "UTF-8")))
.build();
}
// 使用访问令牌获取用户信息
UnifiedAuthDTO.UserInfoResponse userInfo = unifiedAuthService.getUserInfo(tokenResponse.getAccess_token());
if (StringUtils.isNotBlank(userInfo.getError())) {
log.error("获取用户信息失败: {} - {}", userInfo.getError(), userInfo.getError_description());
return ResponseEntity.status(HttpStatus.FOUND)
.location(java.net.URI.create("/integration/#/login?error=" + URLEncoder.encode(userInfo.getError_description(), "UTF-8")))
.build();
}
// 获取到用户信息,进行本地登录处理
log.info("获取到用户信息: {}", JSON.toJSONString(userInfo));
// 这里需要根据业务需求,使用用户信息创建本地会话
// 例如调用本地登录逻辑设置session等
// 这里简化处理,实际需要根据系统现有的登录机制实现
// TODO:实际登录逻辑
// 跳转到首页
return ResponseEntity.status(HttpStatus.FOUND)
.location(java.net.URI.create("/integration/#/"))
.build();
} catch (Exception e) {
log.error("统一认证平台回调处理失败", e);
try {
return ResponseEntity.status(HttpStatus.FOUND)
.location(java.net.URI.create("/integration/#/login?error=" + URLEncoder.encode("系统错误: " + e.getMessage(), "UTF-8")))
.build();
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException(ex);
}
}
}
/**
* 统一认证平台单点登出
*
* @param request HTTP请求
* @return 登出结果
*/
@ApiOperation("统一认证平台单点登出")
@GetMapping("/unified/logout")
public ResponseEntity<?> unifiedLogout(HttpServletRequest request) {
try {
// 获取当前用户的访问令牌需要从session或其他存储中获取
// 这里简化处理,实际需要根据系统现有的会话管理机制实现
String accessToken = request.getParameter("access_token");
if (StringUtils.isBlank(accessToken)) {
return ResponseEntity.ok(RestResult.error(ResultCode.PARAM_IS_BLANK, "访问令牌不能为空"));
}
// 调用统一认证平台登出接口
UnifiedAuthDTO.LogoutResponse response = unifiedAuthService.logout(accessToken);
if ("0".equals(response.getCode())) {
// 清除本地会话
// 这里需要根据系统现有的会话管理机制实现
return ResponseEntity.ok(RestResult.ok("登出成功"));
} else {
return ResponseEntity.ok(RestResult.error(ResultCode.BUSINESS_LOGIC_ERROR, response.getMessage()));
}
} catch (Exception e) {
log.error("统一认证平台单点登出失败", e);
return ResponseEntity.ok(RestResult.error(ResultCode.BUSINESS_LOGIC_ERROR, "登出失败: " + e.getMessage()));
}
}
} }

View File

@ -0,0 +1,92 @@
package com.chinaweal.aiccs.org.entity.dto;
import lombok.Data;
/**
* 统一认证平台相关DTO
*
* @author lroyia
* @since 2025/03/02
*/
@Data
public class UnifiedAuthDTO {
/**
* 授权码
*/
private String code;
/**
* 访问令牌
*/
private String accessToken;
/**
* 令牌有效期
*/
private Integer expiresIn;
/**
* 状态参数
*/
private String state;
/**
* 错误码
*/
private String error;
/**
* 错误描述
*/
private String errorDescription;
/**
* 用户ID
*/
private String userId;
/**
* 用户名
*/
private String userName;
/**
* 姓名
*/
private String name;
/**
* 登录返回对象
*/
@Data
public static class TokenResponse {
private String access_token;
private Integer expires_in;
private String error;
private String error_description;
}
/**
* 用户信息返回对象
*/
@Data
public static class UserInfoResponse {
private String userId;
private String userName;
private String name;
private String code;
private String message;
private String error;
private String error_description;
}
/**
* 登出返回对象
*/
@Data
public static class LogoutResponse {
private String code;
private String message;
}
}

View File

@ -0,0 +1,52 @@
package com.chinaweal.aiccs.org.service;
import com.chinaweal.aiccs.org.entity.dto.UnifiedAuthDTO;
/**
* 统一认证平台OAuth2服务接口
*
* @author lroyia
* @since 2025/03/02
*/
public interface IUnifiedAuthService {
/**
* 生成统一认证平台登录URL
*
* @param state 状态参数用于防止CSRF攻击
* @return 登录URL
*/
String buildLoginUrl(String state);
/**
* 通过授权码获取访问令牌
*
* @param code 授权码
* @return 令牌响应
*/
UnifiedAuthDTO.TokenResponse getAccessToken(String code);
/**
* 通过访问令牌获取用户信息
*
* @param accessToken 访问令牌
* @return 用户信息
*/
UnifiedAuthDTO.UserInfoResponse getUserInfo(String accessToken);
/**
* 单点登出
*
* @param accessToken 访问令牌
* @return 登出结果
*/
UnifiedAuthDTO.LogoutResponse logout(String accessToken);
/**
* 验证令牌有效性
*
* @param accessToken 访问令牌
* @return 是否有效
*/
boolean checkToken(String accessToken);
}

View File

@ -0,0 +1,207 @@
package com.chinaweal.aiccs.org.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.chinaweal.aiccs.common.util.StringUtils;
import com.chinaweal.aiccs.config.UnifiedAuthConfig;
import com.chinaweal.aiccs.org.entity.dto.UnifiedAuthDTO;
import com.chinaweal.aiccs.org.service.IUnifiedAuthService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.util.UUID;
/**
* 统一认证平台OAuth2服务实现
*
* @author lroyia
* @since 2025/03/02
*/
@Slf4j
@Service
public class UnifiedAuthServiceImpl implements IUnifiedAuthService {
@Autowired
private UnifiedAuthConfig unifiedAuthConfig;
@Autowired
private RestTemplate restTemplate;
@Override
public String buildLoginUrl(String state) {
if (StringUtils.isBlank(state)) {
state = UUID.randomUUID().toString().replace("-", "");
}
StringBuilder url = new StringBuilder(unifiedAuthConfig.getAuthorizeUrl());
url.append("?response_type=code");
url.append("&client_id=").append(unifiedAuthConfig.getClientId());
url.append("&redirect_uri=").append(unifiedAuthConfig.getRedirectUri());
url.append("&state=").append(state);
log.info("构建统一认证平台登录URL: {}", url.toString());
return url.toString();
}
@Override
public UnifiedAuthDTO.TokenResponse getAccessToken(String code) {
try {
// 构建请求参数
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", unifiedAuthConfig.getClientId());
params.add("client_secret", unifiedAuthConfig.getClientSecret());
params.add("code", code);
params.add("redirect_uri", unifiedAuthConfig.getRedirectUri());
// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);
// 发送POST请求
ResponseEntity<String> responseEntity = restTemplate.postForEntity(
unifiedAuthConfig.getTokenUrl(),
requestEntity,
String.class
);
String responseBody = responseEntity.getBody();
log.info("获取访问令牌响应: {}", responseBody);
// 解析响应
UnifiedAuthDTO.TokenResponse response = JSON.parseObject(responseBody, UnifiedAuthDTO.TokenResponse.class);
if (StringUtils.isNotBlank(response.getError())) {
log.error("获取访问令牌失败: {} - {}", response.getError(), response.getError_description());
}
return response;
} catch (Exception e) {
log.error("获取访问令牌异常", e);
UnifiedAuthDTO.TokenResponse errorResponse = new UnifiedAuthDTO.TokenResponse();
errorResponse.setError("server_error");
errorResponse.setError_description("服务器内部错误: " + e.getMessage());
return errorResponse;
}
}
@Override
public UnifiedAuthDTO.UserInfoResponse getUserInfo(String accessToken) {
try {
// 构建请求URL
String url = String.format("%s?access_token=%s&client_id=%s",
unifiedAuthConfig.getUserinfoUrl(),
accessToken,
unifiedAuthConfig.getClientId());
// 发送GET请求
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
String responseBody = responseEntity.getBody();
log.info("获取用户信息响应: {}", responseBody);
// 解析响应
UnifiedAuthDTO.UserInfoResponse response = JSON.parseObject(responseBody, UnifiedAuthDTO.UserInfoResponse.class);
// 统一处理返回数据
if (StringUtils.isNotBlank(response.getError())) {
log.error("获取用户信息失败: {} - {}", response.getError(), response.getError_description());
} else if (StringUtils.isNotBlank(response.getCode()) && !"0".equals(response.getCode())) {
log.error("获取用户信息失败: {} - {}", response.getCode(), response.getMessage());
response.setError(response.getCode());
response.setError_description(response.getMessage());
}
return response;
} catch (Exception e) {
log.error("获取用户信息异常", e);
UnifiedAuthDTO.UserInfoResponse errorResponse = new UnifiedAuthDTO.UserInfoResponse();
errorResponse.setError("server_error");
errorResponse.setError_description("服务器内部错误: " + e.getMessage());
return errorResponse;
}
}
@Override
public UnifiedAuthDTO.LogoutResponse logout(String accessToken) {
try {
// 构建请求参数
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("access_token", accessToken);
params.add("client_id", unifiedAuthConfig.getClientId());
// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);
// 发送POST请求
ResponseEntity<String> responseEntity = restTemplate.postForEntity(
unifiedAuthConfig.getLogoutUrl(),
requestEntity,
String.class
);
String responseBody = responseEntity.getBody();
log.info("单点登出响应: {}", responseBody);
// 解析响应
UnifiedAuthDTO.LogoutResponse response = JSON.parseObject(responseBody, UnifiedAuthDTO.LogoutResponse.class);
return response;
} catch (Exception e) {
log.error("单点登出异常", e);
UnifiedAuthDTO.LogoutResponse errorResponse = new UnifiedAuthDTO.LogoutResponse();
errorResponse.setCode("error");
errorResponse.setMessage("服务器内部错误: " + e.getMessage());
return errorResponse;
}
}
@Override
public boolean checkToken(String accessToken) {
try {
// 构建请求参数
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("access_token", accessToken);
params.add("client_id", unifiedAuthConfig.getClientId());
// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);
// 发送POST请求
ResponseEntity<String> responseEntity = restTemplate.postForEntity(
unifiedAuthConfig.getCheckTokenUrl(),
requestEntity,
String.class
);
String responseBody = responseEntity.getBody();
log.info("验证令牌响应: {}", responseBody);
if (StringUtils.isBlank(responseBody)) {
return false;
}
// 解析响应JSON
JSONObject jsonObject = JSON.parseObject(responseBody);
String code = jsonObject.getString("code");
return "0".equals(code);
} catch (Exception e) {
log.error("验证令牌异常", e);
return false;
}
}
}