Commit 14894580 by mahx

同步现场腾讯获取用户接口

parent 160c2fe1
......@@ -44,17 +44,43 @@ public class RESTLogoutSuccessHandler implements LogoutSuccessHandler {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
cookie.setValue("");
cookie.setPath(getCookiePath(request));
cookie.setMaxAge(0);
response.addCookie(cookie);
clearCookie(request, response, cookie.getName(), cookie.getPath(), cookie.getDomain());
}
}
}
private String getCookiePath(HttpServletRequest request) {
/**
* 清除指定 cookie,尝试多个路径和 domain 组合确保清除成功
*/
private void clearCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookiePath, String cookieDomain) {
String contextPath = request.getContextPath();
return contextPath != null && !contextPath.isEmpty() ? contextPath : "/";
String[] paths = {cookiePath, contextPath, contextPath + "/", "/", null};
for (String path : paths) {
if (path == null || path.isEmpty()) {
path = "/";
}
clearCookieWithPathAndDomain(request, response, cookieName, path, null);
clearCookieWithPathAndDomain(request, response, cookieName, path, "");
}
}
/**
* 使用指定路径和 domain 清除 cookie
*/
private void clearCookieWithPathAndDomain(HttpServletRequest request, HttpServletResponse response, String name, String path, String domain) {
try {
Cookie cookie = new Cookie(name, "");
cookie.setPath(path);
cookie.setMaxAge(0);
if (domain != null && !domain.isEmpty()) {
cookie.setDomain(domain);
}
cookie.setHttpOnly(true);
cookie.setSecure(request.isSecure());
response.addCookie(cookie);
} catch (Exception ignored) {
}
}
}
\ No newline at end of file
......@@ -3,6 +3,8 @@ package com.keymobile.sso.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import com.keymobile.authservice.common.UserGroup;
import java.util.List;
import java.util.Map;
......@@ -12,8 +14,39 @@ public interface AuthService {
@RequestMapping(value = "/users/findByName")
List<Map<String, Object>> getUserByName(@RequestParam(value = "match") String match);
@RequestMapping(value = "/users/{userId}", method = RequestMethod.GET)
Map<String, Object> getUserByUserId(@PathVariable(value = "userId") Long userId);
@PostMapping(value = "/users")
Map<String, Object> addUser(@RequestBody Map<String, Object> user);
@RequestMapping(value = "/users", method = RequestMethod.GET)
List<Map<String, Object>> getAllUsers(@RequestParam(value = "types", required = false) String[] types);
//添加用户组
@PostMapping(value = "/userGroups")
Map<String, Object> addUserGroup(@RequestBody Map<String, Object> userGroupAbstract);
//更新用户组
@PostMapping(value = "/userGroups/{userGroupId}")
Map<String, Object> updateUserGroup(@PathVariable(value = "userGroupId") Long userGroupId,
@RequestBody Map<String, Object> userGroupAbstract);
//查找用户组
@GetMapping("/userGroups/findByName")
List<Map<String, Object>> findUserGroupsByName(@RequestParam(value = "match", required = false) String match);
@RequestMapping(value = "/userGroups", method = RequestMethod.GET)
List<Map<String, Object>> findAllUserGroups();
//将用户加入用户组
@PostMapping("/userGroups/{userGroupId}/users")
Map<String, Object> addUser(@PathVariable(value = "userGroupId") Long userGroupId,
@RequestParam(value = "userIds", required = true) Long[] userIds);
//根据用户组ID获取用户组
@GetMapping("/userGroups/{userGroupId}")
Map<String, Object> getUserGroupById(@PathVariable(value = "userGroupId") Long userGroupId);
}
package com.keymobile.sso.ud;
import com.keymobile.sso.ud.util.TencentCloudSignV3;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
/**
* 签名验证测试类
* 使用腾讯云官方示例参数验证签名算法正确性
* 参考文档: https://cloud.tencent.com/document/product/628/45174
*/
public class SignVerificationTest {
public static void main(String[] args) throws Exception {
System.out.println("========== 腾讯云V3签名验证测试 ==========\n");
// 官方示例参数
String secretId = "AKID********************************";
String secretKey = "********************************";
String service = "cvm";
String host = "cvm.tencentcloudapi.com";
String action = "DescribeInstances";
String payload = "{\"Limit\": 1, \"Filters\": [{\"Values\": [\"\\u672a\\u547d\\u540d\"], \"Name\": \"instance-name\"}]}";
long timestamp = 1551113065; // 2019-02-25 08:44:25 UTC
// 官方预期签名
String expectedSignature = "10b1a37a7301a02ca19a647ad722d5e43b4b3cff309d421d85b46093f6ab6c4f";
String expectedAuthorization = "TC3-HMAC-SHA256 Credential=" + secretId + "/2019-02-25/cvm/tc3_request, SignedHeaders=content-type;host;x-tc-action, Signature=" + expectedSignature;
// 使用临时类来生成签名(使用cvm作为service)
TencentCloudSignV3 signer = new TencentCloudSignV3(secretId, secretKey, host) {
@Override
public String sign(String action, String payload, long timestamp) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
String date = sdf.format(new Date(timestamp * 1000));
String hashedPayload = sha256Hex(payload);
String canonicalRequest = buildCanonicalRequest(action, hashedPayload);
String credentialScope = date + "/" + service + "/" + "tc3_request";
String hashedCanonicalRequest = sha256Hex(canonicalRequest);
String stringToSign = buildStringToSign(String.valueOf(timestamp), credentialScope, hashedCanonicalRequest);
String signature = calculateSignature(date, stringToSign);
return buildAuthorization(credentialScope, signature);
}
private String buildCanonicalRequest(String action, String hashedPayload) {
String httpRequestMethod = "POST";
String canonicalUri = "/";
String canonicalQueryString = "";
String canonicalHeaders = "content-type:application/json; charset=utf-8\n" +
"host:" + host + "\n" +
"x-tc-action:" + action.toLowerCase() + "\n";
String signedHeaders = "content-type;host;x-tc-action";
return httpRequestMethod + "\n" +
canonicalUri + "\n" +
canonicalQueryString + "\n" +
canonicalHeaders + "\n" +
signedHeaders + "\n" +
hashedPayload;
}
private String buildStringToSign(String timestamp, String credentialScope, String hashedCanonicalRequest) {
return "TC3-HMAC-SHA256" + "\n" +
timestamp + "\n" +
credentialScope + "\n" +
hashedCanonicalRequest;
}
private String calculateSignature(String date, String stringToSign) throws Exception {
byte[] secretDate = hmac256(("TC3" + secretKey).getBytes(StandardCharsets.UTF_8), date);
byte[] secretService = hmac256(secretDate, service);
byte[] secretSigning = hmac256(secretService, "tc3_request");
return bytesToHex(hmac256(secretSigning, stringToSign));
}
private String buildAuthorization(String credentialScope, String signature) {
String signedHeaders = "content-type;host;x-tc-action";
return "TC3-HMAC-SHA256" + " " +
"Credential=" + secretId + "/" + credentialScope + ", " +
"SignedHeaders=" + signedHeaders + ", " +
"Signature=" + signature;
}
private byte[] hmac256(byte[] key, String msg) throws Exception {
javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256");
javax.crypto.spec.SecretKeySpec secretKeySpec = new javax.crypto.spec.SecretKeySpec(key, mac.getAlgorithm());
mac.init(secretKeySpec);
return mac.doFinal(msg.getBytes(StandardCharsets.UTF_8));
}
private String sha256Hex(String s) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] d = md.digest(s.getBytes(StandardCharsets.UTF_8));
return bytesToHex(d);
}
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
};
// 测试1: 生成签名
System.out.println("【测试1】使用官方示例参数验证签名");
System.out.println("SecretId: " + secretId);
System.out.println("SecretKey: " + secretKey);
System.out.println("Service: " + service);
System.out.println("Host: " + host);
System.out.println("Action: " + action);
System.out.println("Payload: " + payload);
System.out.println("Timestamp: " + timestamp);
System.out.println();
String signature = signer.sign(action, payload, timestamp);
System.out.println("生成的签名: " + signature);
System.out.println("预期签名: " + expectedAuthorization);
System.out.println("签名匹配: " + signature.equals(expectedAuthorization));
System.out.println();
// 测试2: 使用实际配置测试 (使用ApiTestRunner中的配置)
System.out.println("【测试2】使用实际配置生成签名");
String actualSecretId = "3822273eeb1a432a9041221b67f82979";
String actualSecretKey = "3ca5a9aeced9476dbe0ff8207b2363ca";
String actualHost = "portal-udadmin.sznsmic.com";
String actualAction = "DescribeOrganizationList";
String actualPayload = "{}";
long actualTimestamp = System.currentTimeMillis() / 1000;
TencentCloudSignV3 actualSigner = new TencentCloudSignV3(actualSecretId, actualSecretKey, actualHost);
String actualAuth = actualSigner.buildHeaders(actualAction, actualPayload, actualTimestamp).get("Authorization");
System.out.println("SecretId: " + actualSecretId);
System.out.println("Host: " + actualHost);
System.out.println("Action: " + actualAction);
System.out.println("Payload: " + actualPayload);
System.out.println("Timestamp: " + actualTimestamp);
System.out.println();
System.out.println("生成的Authorization:");
System.out.println(actualAuth);
System.out.println();
// 测试3: 生成curl命令
System.out.println("【测试3】生成curl命令");
String curlCommand = String.format(
"curl -X POST https://%s \\\n" +
"-H \"Authorization: %s\" \\\n" +
"-H \"Content-Type: application/json; charset=utf-8\" \\\n" +
"-H \"Host: %s\" \\\n" +
"-H \"X-TC-Action: %s\" \\\n" +
"-H \"X-TC-Timestamp: %d\" \\\n" +
"-H \"X-TC-Version: 2017-03-12\" \\\n" +
"-d '%s'",
actualHost, actualAuth, actualHost, actualAction, actualTimestamp, actualPayload
);
System.out.println(curlCommand);
System.out.println("\n========== 测试完成 ==========");
}
}
package com.keymobile.sso.ud.api;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.keymobile.sso.ud.api.common.BaseResponse;
import com.keymobile.sso.ud.config.ApiConfig;
import com.keymobile.sso.ud.util.HttpClient;
import com.keymobile.sso.ud.util.TencentCloudSignV3;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* 机构管理API测试类
*/
public class OrganizationApi {
private static final Logger logger = LoggerFactory.getLogger(OrganizationApi.class);
private static final Gson gson = new Gson();
private final ApiConfig config;
private final TencentCloudSignV3 signer;
public OrganizationApi(ApiConfig config) {
this.config = config;
this.signer = new TencentCloudSignV3(config.getSecretId(), config.getSecretKey(), config.getHost());
}
/**
* 获取认证域下所有机构列表
*/
public DescribeOrganizationListResponse describeOrganizationList() throws Exception {
String action = "DescribeOrganizationList";
String url = config.getBaseUrl() + "/auth/api/v1/DescribeOrganizationList";
String payload = "{}";
long timestamp = System.currentTimeMillis() / 1000;
Map<String, String> headers = buildHeaders(action, payload, timestamp);
String response = HttpClient.post(url, headers, payload);
return gson.fromJson(response, DescribeOrganizationListResponse.class);
}
/**
* 机构排序
*/
public SortOrganizationResponse sortOrganization(List<String> orgIdList) throws Exception {
String action = "SortOrganization";
String url = config.getBaseUrl() + "/auth/api/v1/SortOrganization";
Map<String, Object> params = new HashMap<>();
params.put("OrgIdList", orgIdList);
String payload = gson.toJson(params);
long timestamp = System.currentTimeMillis() / 1000;
Map<String, String> headers = buildHeaders(action, payload, timestamp);
String response = HttpClient.post(url, headers, payload);
return gson.fromJson(response, SortOrganizationResponse.class);
}
/**
* 获取机构信息
*/
public DescribeOrganizationResponse describeOrganization(String orgId) throws Exception {
String action = "DescribeOrganization";
String url = config.getBaseUrl() + "/auth/api/v1/DescribeOrganization";
Map<String, Object> params = new HashMap<>();
params.put("OrgId", orgId);
String payload = gson.toJson(params);
long timestamp = System.currentTimeMillis() / 1000;
Map<String, String> headers = buildHeaders(action, payload, timestamp);
String response = HttpClient.post(url, headers, payload);
return gson.fromJson(response, DescribeOrganizationResponse.class);
}
/**
* 构建请求头
*/
private Map<String, String> buildHeaders(String action, String payload, long timestamp) throws Exception {
Map<String, String> headers = signer.buildHeaders(action, payload, timestamp);
headers.put("UdServiceId", config.getUdServiceId());
return headers;
}
// ==================== 响应类 ====================
/**
* DescribeOrganizationList 响应
*/
public static class DescribeOrganizationListResponse extends BaseResponse<List<Organization>> {
public List<Organization> getData() {
return getResponse() != null ? getResponse().getData() : null;
}
public void setData(List<Organization> data) {
if (getResponse() != null) {
getResponse().setData(data);
}
}
}
/**
* SortOrganization 响应
*/
public static class SortOrganizationResponse extends BaseResponse<String> {
public String getOrgId() {
return getData();
}
public void setOrgId(String orgId) {
setData(orgId);
}
public String getData() {
return getResponse() != null ? getResponse().getData() : null;
}
public void setData(String data) {
if (getResponse() != null) {
getResponse().setData(data);
}
}
}
/**
* DescribeOrganization 响应
*/
public static class DescribeOrganizationResponse extends BaseResponse<Organization> {
public Organization getData() {
return getResponse() != null ? getResponse().getData() : null;
}
public void setData(Organization data) {
if (getResponse() != null) {
getResponse().setData(data);
}
}
}
/**
* 机构对象
*/
public static class Organization {
@SerializedName("OrgId")
private String orgId;
@SerializedName("CreatedDate")
private String createdDate;
@SerializedName("LastModifiedDate")
private String lastModifiedDate;
@SerializedName("UdServiceId")
private String udServiceId;
@SerializedName("DisplayName")
private String displayName;
@SerializedName("Description")
private String description;
@SerializedName("DefaultRootOrg")
private Boolean defaultRootOrg;
@SerializedName("Dn")
private String dn;
@SerializedName("Code")
private String code;
@SerializedName("Virtual")
private Boolean virtual;
@SerializedName("SearchPath")
private List<String> searchPath;
@SerializedName("DomainID")
private String domainID;
@SerializedName("UserTotal")
private Long userTotal;
// Getters and Setters
public String getOrgId() { return orgId; }
public void setOrgId(String orgId) { this.orgId = orgId; }
public String getCreatedDate() { return createdDate; }
public void setCreatedDate(String createdDate) { this.createdDate = createdDate; }
public String getLastModifiedDate() { return lastModifiedDate; }
public void setLastModifiedDate(String lastModifiedDate) { this.lastModifiedDate = lastModifiedDate; }
public String getUdServiceId() { return udServiceId; }
public void setUdServiceId(String udServiceId) { this.udServiceId = udServiceId; }
public String getDisplayName() { return displayName; }
public void setDisplayName(String displayName) { this.displayName = displayName; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Boolean getDefaultRootOrg() { return defaultRootOrg; }
public void setDefaultRootOrg(Boolean defaultRootOrg) { this.defaultRootOrg = defaultRootOrg; }
public String getDn() { return dn; }
public void setDn(String dn) { this.dn = dn; }
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
public Boolean getVirtual() { return virtual; }
public void setVirtual(Boolean virtual) { this.virtual = virtual; }
public List<String> getSearchPath() { return searchPath; }
public void setSearchPath(List<String> searchPath) { this.searchPath = searchPath; }
public String getDomainID() { return domainID; }
public void setDomainID(String domainID) { this.domainID = domainID; }
public Long getUserTotal() { return userTotal; }
public void setUserTotal(Long userTotal) { this.userTotal = userTotal; }
}
}
package com.keymobile.sso.ud.api.common;
import com.google.gson.annotations.SerializedName;
/**
* 基础响应类
*/
public class BaseResponse<T> {
@SerializedName("Response")
private Response<T> response;
public Response<T> getResponse() {
return response;
}
public void setResponse(Response<T> response) {
this.response = response;
}
}
\ No newline at end of file
package com.keymobile.sso.ud.api.common;
import com.google.gson.annotations.SerializedName;
/**
* Error
*/
public class Error {
@SerializedName("Code")
private String code;
@SerializedName("Message")
private String message;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
\ No newline at end of file
package com.keymobile.sso.ud.api.common;
import com.google.gson.annotations.SerializedName;
/**
* Response
*/
public class Response<T> {
@SerializedName("RequestId")
private String requestId;
@SerializedName("Error")
private Error error;
@SerializedName("Data")
private T data;
@SerializedName("Total")
private Long total;
@SerializedName("PageNumber")
private Long pageNumber;
@SerializedName("PageSize")
private Long pageSize;
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public Error getError() {
return error;
}
public void setError(Error error) {
this.error = error;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Long getTotal() {
return total;
}
public void setTotal(Long total) {
this.total = total;
}
public Long getPageNumber() {
return pageNumber;
}
public void setPageNumber(Long pageNumber) {
this.pageNumber = pageNumber;
}
public Long getPageSize() {
return pageSize;
}
public void setPageSize(Long pageSize) {
this.pageSize = pageSize;
}
public boolean isSuccess() {
return error == null;
}
}
\ No newline at end of file
package com.keymobile.sso.ud.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* API配置类
*/
@Component
@EnableConfigurationProperties(ApiConfig.class)
@ConfigurationProperties(prefix = "ud.api")
public class ApiConfig {
private String secretId;
private String secretKey;
private String host;
private String udServiceId;
private String baseUrl;
public ApiConfig() {
}
public ApiConfig(String secretId, String secretKey, String host, String udServiceId) {
this.secretId = secretId;
this.secretKey = secretKey;
this.host = host;
this.udServiceId = udServiceId;
this.baseUrl = "https://" + host;
}
public String getSecretId() {
return secretId;
}
public void setSecretId(String secretId) {
this.secretId = secretId;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
if (host != null && !host.isEmpty()) {
this.baseUrl = "https://" + host;
}
}
public String getUdServiceId() {
return udServiceId;
}
public void setUdServiceId(String udServiceId) {
this.udServiceId = udServiceId;
}
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
}
package com.keymobile.sso.ud.controller;
import com.keymobile.sso.ud.service.UserGroupResult;
import com.keymobile.sso.ud.service.UserGroupService;
import com.keymobile.sso.ud.service.UserGroupService.SyncResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 用户组管理接口
*/
@RestController
@RequestMapping("/ud/userGroups")
public class UserGroupController {
private static final Logger logger = LoggerFactory.getLogger(UserGroupController.class);
@Autowired
private UserGroupService userGroupService;
/**
* 获取所有用户组及其成员
* 先分页获取所有用户组,再根据每个GroupId分页获取成员
*
* @return 用户组及其成员列表
*/
@GetMapping("/withMembers")
public UserGroupResult getAllUserGroupsWithMembers() {
logger.info("开始获取所有用户组及其成员");
UserGroupResult result = userGroupService.getAllUserGroupsWithMembers();
logger.info("获取用户组及其成员完成, 总用户组数: {}", result.getTotalGroups());
return result;
}
/**
* 获取所有用户组列表(不包含成员)
*
* @return 用户组列表
*/
@GetMapping("/allUserGroups")
public UserGroupResult getAllUserGroups() {
logger.info("开始获取所有用户组列表");
UserGroupResult result = userGroupService.getAllUserGroups();
logger.info("获取用户组列表完成, 总数: {}", result.getTotalGroups());
return result;
}
/**
* 获取指定用户组的成员
*
* @param groupId 用户组ID
* @return 用户组成员列表
*/
@GetMapping("/{groupId}/members")
public UserGroupResult getGroupMembers(@PathVariable String groupId) {
logger.info("开始获取用户组 {} 的成员", groupId);
UserGroupResult result = userGroupService.getGroupMembers(groupId);
logger.info("获取用户组成员完成");
return result;
}
/**
* 同步用户组和用户数据到AuthService
* 1. 从UD服务获取所有用户组及其成员
* 2. 创建用户组(如果不存在)
* 3. 创建用户(如果不存在)
* 4. 将用户添加到用户组
*
* @return 同步结果
*/
@PostMapping("/sync")
public SyncResult syncUserGroupsAndUsers() {
logger.info("开始同步用户组和用户数据");
SyncResult result = userGroupService.syncUserGroupsAndUsers();
logger.info("同步完成: {}", result.getMessage());
return result;
}
}
package com.keymobile.sso.ud.service;
import com.keymobile.sso.ud.api.GroupApi;
import java.util.List;
/**
* 用户组查询结果类
*/
public class UserGroupResult {
private boolean success;
private String errorMessage;
private Long totalGroups;
private List<UserGroupWithMembers> groups;
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public Long getTotalGroups() {
return totalGroups;
}
public void setTotalGroups(Long totalGroups) {
this.totalGroups = totalGroups;
}
public List<UserGroupWithMembers> getGroups() {
return groups;
}
public void setGroups(List<UserGroupWithMembers> groups) {
this.groups = groups;
}
/**
* 用户组及其成员详情
*/
public static class UserGroupWithMembers {
private String groupId;
private String displayName;
private String description;
private String orgId;
private Long userTotal;
private List<GroupApi.GroupUser> members;
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
}
public Long getUserTotal() {
return userTotal;
}
public void setUserTotal(Long userTotal) {
this.userTotal = userTotal;
}
public List<GroupApi.GroupUser> getMembers() {
return members;
}
public void setMembers(List<GroupApi.GroupUser> members) {
this.members = members;
}
}
}
package com.keymobile.sso.ud.util;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* HTTP客户端工具类
*/
public class HttpClient {
private static final Logger logger = LoggerFactory.getLogger(HttpClient.class);
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
/**
* 发送POST请求
*
* @param url 请求URL
* @param headers 请求头
* @param payload 请求体JSON
* @return 响应JSON字符串
*/
public static String post(String url, Map<String, String> headers, String payload) throws Exception {
logger.info("Request URL: {}", url);
logger.info("Request Headers: {}", headers);
logger.info("Request Payload: {}", payload);
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpPost httpPost = new HttpPost(url);
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
httpPost.setHeader(entry.getKey(), entry.getValue());
}
}
if (payload != null && !payload.isEmpty()) {
httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8));
}
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
HttpEntity entity = response.getEntity();
String result = entity != null ? EntityUtils.toString(entity, StandardCharsets.UTF_8) : "";
logger.info("\nResponse: {}", result);
return result;
}
}
}
/**
* 发送POST请求并解析响应
*
* @param url 请求URL
* @param headers 请求头
* @param payload 请求体对象
* @param clazz 响应类型
* @param <T> 响应类型泛型
* @return 响应对象
*/
public static <T> T post(String url, Map<String, String> headers, Object payload, Class<T> clazz) throws Exception {
String payloadJson = payload != null ? gson.toJson(payload) : "";
String response = post(url, headers, payloadJson);
return gson.fromJson(response, clazz);
}
/**
* 格式化JSON字符串
*/
public static String formatJson(String json) {
try {
Object obj = gson.fromJson(json, Object.class);
return gson.toJson(obj);
} catch (Exception e) {
return json;
}
}
}
package com.keymobile.sso.ud.util;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.TreeMap;
/**
* 腾讯云V3签名工具类
* 参考文档: https://cloud.tencent.com/document/product/628/45174
*/
public class TencentCloudSignV3 {
private static final String ALGORITHM = "TC3-HMAC-SHA256";
private static final String SERVICE = "ud";
private static final String CT_JSON = "application/json; charset=utf-8";
private static final String TC3_REQUEST = "tc3_request";
private final String secretId;
private final String secretKey;
private final String host;
public TencentCloudSignV3(String secretId, String secretKey, String host) {
this.secretId = secretId;
this.secretKey = secretKey;
this.host = host;
}
/**
* 生成签名
*
* @param action 接口地址
* @param payload 请求体JSON字符串
* @param timestamp 时间戳
* @return 签名Authorization
*/
public String sign(String action, String payload, long timestamp) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
String date = sdf.format(new Date(timestamp * 1000));
String hashedPayload = sha256Hex(payload);
String canonicalRequest = buildCanonicalRequest(action, hashedPayload);
String credentialScope = date + "/" + SERVICE + "/" + TC3_REQUEST;
String hashedCanonicalRequest = sha256Hex(canonicalRequest);
String stringToSign = buildStringToSign(String.valueOf(timestamp), credentialScope, hashedCanonicalRequest);
String signature = calculateSignature(date, stringToSign);
return buildAuthorization(credentialScope, signature);
}
/**
* 构建规范请求串
*/
private String buildCanonicalRequest(String action, String hashedPayload) {
String httpRequestMethod = "POST";
String canonicalUri = "/";
String canonicalQueryString = "";
String canonicalHeaders = "content-type:" + CT_JSON + "\n" +
"host:" + host + "\n" +
"x-tc-action:" + action.toLowerCase() + "\n";
String signedHeaders = "content-type;host;x-tc-action";
return httpRequestMethod + "\n" +
canonicalUri + "\n" +
canonicalQueryString + "\n" +
canonicalHeaders + "\n" +
signedHeaders + "\n" +
hashedPayload;
}
/**
* 拼接待签名字符串
*/
private String buildStringToSign(String timestamp, String credentialScope, String hashedCanonicalRequest) {
return ALGORITHM + "\n" +
timestamp + "\n" +
credentialScope + "\n" +
hashedCanonicalRequest;
}
/**
* 计算签名
*/
private String calculateSignature(String date, String stringToSign) throws Exception {
byte[] secretDate = hmac256(("TC3" + secretKey).getBytes(StandardCharsets.UTF_8), date);
byte[] secretService = hmac256(secretDate, SERVICE);
byte[] secretSigning = hmac256(secretService, TC3_REQUEST);
return bytesToHex(hmac256(secretSigning, stringToSign));
}
/**
* 构建Authorization头
*/
private String buildAuthorization(String credentialScope, String signature) {
String signedHeaders = "content-type;host;x-tc-action";
return ALGORITHM + " " +
"Credential=" + secretId + "/" + credentialScope + ", " +
"SignedHeaders=" + signedHeaders + ", " +
"Signature=" + signature;
}
/**
* 构建HTTP请求头Map
*/
public Map<String, String> buildHeaders(String action, String payload, long timestamp) throws Exception {
Map<String, String> headers = new TreeMap<>();
headers.put("Authorization", sign(action, payload, timestamp));
headers.put("Content-Type", CT_JSON);
headers.put("Host", host);
headers.put("X-TC-Action", action);
headers.put("X-TC-Timestamp", String.valueOf(timestamp));
headers.put("X-TC-Version", "2017-03-12");
return headers;
}
/**
* HMAC-SHA256计算
*/
private byte[] hmac256(byte[] key, String msg) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
mac.init(secretKeySpec);
return mac.doFinal(msg.getBytes(StandardCharsets.UTF_8));
}
/**
* SHA256哈希
*/
private String sha256Hex(String s) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] d = md.digest(s.getBytes(StandardCharsets.UTF_8));
return bytesToHex(d);
}
/**
* byte数组转十六进制字符串
*/
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
......@@ -12,6 +12,12 @@ spring:
read-timeout: 5000
profiles:
active: default
ud:
api:
secret-id: 964a5eb6c336475caf9a9830d0ee357d
secret-key: fb7f49a789bd473681bdafef5770907a
host: portal-udadmin.sznsmic.com
ud-service-id: dfaaad6a-87f2-419b-98d0-9a950be8e479
management:
metrics:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment