Appearance
用户中心文档 V1.3.3
1. 概述
文档助力用户中心侧统一管控第三方访问权限,提升对接效率与系统安全性。 主要涵盖主流鉴权方式(OAuth2.0 等)的适用场景、具体对接流程(含凭证申请、鉴权请求、令牌刷新等步骤)、参数格式要求、错误码说明及安全合规规范,帮助第三方开发者快速理解并合规完成鉴权对接。
2. 前置准备/步骤
- 第三方申请时需提供:应用名称、回调地址
- 用户中心运维/开发收到请求后,在应用管理中创建应用并返回:clientId、clientSecret
3. 名词解释
- 回调地址:单点登录后跳转的地址,由第三方应用开发提供
- clientId及clientSecret:第三方应用在用户中心的ID及密钥,务必妥善保存,禁止向第三方泄露
4. 流程图

5. 接口文档
5.1 认证接口
- URL:
/api/smartpark-oauth/oauth/authorize - 请求方法:GET
5.1.2 请求头参数
| 参数key | 类型 | 是否必填 | 描述 |
|---|---|---|---|
| Authorization | String | 是 | Basic dGVzdDp0ZXN0(client_id:client_secret 转base64) |
| Content-Type | String | 是 | application/json |
5.1.3 请求参数
| 参数key | 类型 | 是否必填 | 描述 |
|---|---|---|---|
| client_id | String | 是 | 应用id |
| redirect_uri | String | 是 | 回调地址(需与申请一致),成功后携带code |
| response_type | String | 是 | 固定值:code |
5.1.4 返回参数
无,重定向至redirect_uri并携带code
5.1.5 请求样例
https://spup-uat.icloudcity.com/api/smartpark-oauth/oauth/authorize?client_id=test&redirect_uri=https://www.baidu.com&response_type=code5.2 code换取token
根据重定向携带的code换取access_token
- URL:
/api/smartpark-oauth/oauth/token - 请求方法:POST
5.2.2 请求头参数
| 参数key | 类型 | 是否必填 | 描述 |
|---|---|---|---|
| Authorization | String | 是 | Basic dGVzdDp0ZXN0 |
| Content-Type | String | 是 | application/json |
5.2.3 请求参数
| 参数key | 类型 | 是否必填 | 描述 |
|---|---|---|---|
| code | String | 是 | 5.1接口返回的code |
| redirect_uri | String | 是 | 回调地址(需与申请一致) |
| grant_type | String | 是 | 固定值:authorization_code |
5.2.4 返回参数
| 参数key | 类型 | 描述 |
|---|---|---|
| access_token | String | 当前用户token |
| token_type | String | Token类型 |
| refresh_token | String | 刷新token |
| expires_in | Long | token过期时间 |
5.2.5 请求样例
https://spup-uat.icloudcity.com/api/smartpark-oauth/oauth/token?code=85bVWq&grant_type=authorization_code&redirect_uri=https://www.baidu.com返回示例:
json
{
"code": 200,
"success": true,
"data": {
"access_token":"xxx",
"token_type": "bearer",
"refresh_token":"xxx",
"expires_in": 3599
},
"msg": "操作成功"
}5.3 根据access_token获取用户信息
- URL:
/api/smartpark-oauth/oauth/getCurrentUser - 请求方法:GET
5.3.2 请求头参数
| 参数key | 类型 | 是否必填 | 描述 |
|---|---|---|---|
| Authorization | String | 是 | Basic dGVzdDp0ZXN0 |
5.3.3 请求参数
| 参数key | 类型 | 是否必填 | 描述 |
|---|---|---|---|
| token | String | 是 | access_token |
5.3.4 返回参数
| 参数key | 类型 | 描述 |
|---|---|---|
| id | Long | 租户ID |
| code | String | 用户编码 |
| account | String | 账号 |
| name | String | 昵称 |
| realName | String | 真实姓名 |
| avatar | String | 头像 |
| String | 邮箱 | |
| phone | String | 手机 |
| birthday | String | 生日 |
| sex | String | 1男/2女 |
| roleId | String | 角色id,多角色逗号分隔 |
| deptId | String | 组织id,多组织逗号分隔 |
5.3.5 请求样例
返回示例:
json
{
"code": 200,
"success": true,
"data": {
"id": "1123598821738675201",
"code": null,
"account": "admin",
"name": "管理员",
"realName": "管理员",
"avatar": "https://xxx/zos/rmsportal.png",
"email": "admin@bladex.vip",
"phone": "123333333333",
"birthday": "2018-08-08 00:00:00",
"sex": 1,
"roleId": "1123598816738675201,1123598816738675203",
"deptId": "1123598813738675201,1123598816738675211"
},
"msg": "操作成功"
}5.4 refresh_token换取access_token接口
- URL:
/api/smartpark-oauth/oauth/token - 请求方法:POST
5.4.2 请求头参数
| 参数key | 类型 | 是否必填 | 描述 |
|---|---|---|---|
| Authorization | String | 是 | Basic dGVzdDp0ZXN0 |
| Content-Type | String | 是 | application/json |
5.4.3 请求参数
| 参数key | 类型 | 是否必填 | 描述 |
|---|---|---|---|
| refresh_token | String | 是 | 认证接口返回的refresh_token |
| grant_type | String | 是 | 固定值:refresh_token |
5.4.4 返回参数
| 参数key | 类型 | 描述 |
|---|---|---|
| access_token | String | 用户token |
| token_type | String | Token类型 |
| refresh_token | String | 刷新token |
| expires_in | Long | 过期时间 |
| scope | String | 权限范围 |
5.4.5 请求样例
返回示例:
json
{
"code": 200,
"success": true,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 3599,
"scope": "all"
},
"msg": "操作成功"
}5.5 通过组织id列表,查询所有项目接口
- URL:
/api/smartpark-system/dept/getDeptsAndChildrenProject - 请求方法:POST
5.5.2 请求头参数
| 参数key | 类型 | 是否必填 | 描述 |
|---|---|---|---|
| Authorization | String | 是 | Basic dGVzdDp0ZXN0 |
| Content-Type | String | 是 | application/json |
5.5.3 请求参数
| 参数key | 类型 | 是否必填 | 描述 |
|---|---|---|---|
| deptIds | [] | 是 | 组织ID集合 |
5.5.4 返回参数
| 参数key | 类型 | 描述 |
|---|---|---|
| id | Long | ID |
| name | String | 名称 |
| code | String | 编码 |
| parentId | String | 父ID |
| type | String | dept=组织,project=项目 |
5.5.5 请求样例
请求参数:
json
{
"deptIds": [2003645334965362690]
}返回示例:
json
{
"code": 200,
"success": true,
"data": [
{
"id": "2003645334965362690",
"name": "北京四维图新科技股份股份有限公司",
"code": "OO0000001",
"parentId": "1123598813738675201",
"type": "dept",
"parentCode": null,
"children": null
},
{
"id": "2003700874080854017",
"name": "四维图新集团",
"code": "P0001",
"parentId": "2003645334965362690",
"type": "project",
"parentCode": null,
"children": null
}
],
"msg": "操作成功"
}5.6 通过角色id,查当前用户是否有这个角色权限接口
- URL:
/api/smartpark-system/role/whetherHasRolePermission - 请求方法:POST
5.6.2 请求头参数
| 参数key | 类型 | 是否必填 | 描述 |
|---|---|---|---|
| Authorization | String | 是 | Basic dGVzdDp0ZXN0 |
| Content-Type | String | 是 | application/json |
5.6.3 请求参数
| 参数key | 类型 | 是否必填 | 描述 |
|---|---|---|---|
| roleId | Long | 是 | 角色ID |
| token | String | 是 | access_token |
5.6.4 返回参数
| 参数key | 类型 | 描述 |
|---|---|---|
| whetherHasRole | Boolean | true=有权限,false=无 |
5.6.5 请求样例
返回示例:
json
{
"code": 200,
"success": true,
"data": {
"whetherHasRole": false
},
"msg": "操作成功"
}6. Q&A
| 错误信息 | 原因 | 处理方式 |
|---|---|---|
| error_description="Invalid redirect: https://xxx match one of the registered values." | 回调地址错误 | 核对/修改注册的回调地址 |
| 系统未知异常[Httpstatus]:401 | 未携带Authorization请求头 | 补充必填Authorization请求头 |
| Missing grant type | grant_type错误 | 按文档传入固定grant_type值 |
7. 示例
java
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.alibaba.fastjson.JSON;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
@Slf4j
public class OpenApiClientAuth {
private OpenApiClientAuth() {
}
public static final String AUTHORIZATION = "Authorization";
public static final String CONTENT_TYPE = "Content-Type";
public static final String DATE = "Date";
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter
.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH);
/**
* @param httpMethod请求方式 GET,POST,PUT。。。
* @param urlhttp://baidu.com/xxx/xx?aa=11
* @param clientId123123
* @param clientSecret sdfdsf
* @param contentType请求类型
* @return 返回授权说需要的Header参数
*/
@SneakyThrows
public static Map<String, String> getSignHeaders(final String httpMethod, final String url,
final String clientId, final String clientSecret, final String contentType) {
final String pathResource = OpenApiClientAuth.getPathResource(url);
final String contentTypeValue = contentType != null ? contentType : "application/json";
final String dateValue = DATE_TIME_FORMATTER.format(ZonedDateTime.now(ZoneId.of("GMT")));
final String sign = OpenApiClientAuth
.calculateSing(httpMethod, contentTypeValue, dateValue, pathResource, clientSecret);
final String authorizationString = clientId + ":" + sign;
final Map<String, String> authHeaders = new HashMap<>(4);
authHeaders.put(OpenApiClientAuth.DATE, dateValue);
authHeaders.put(OpenApiClientAuth.AUTHORIZATION, authorizationString);
authHeaders.put(OpenApiClientAuth.CONTENT_TYPE, contentTypeValue);
return authHeaders;
}
protected static String getPathResource(final String url) {
final String substring = url.substring(url.indexOf("://") + 3);
return substring.substring(substring.indexOf('/'));
}
protected static String calculateSing(final String httpMethodString,
final String contentTypeValue, final String dateValue, final String pathResource,
final String appSecret) throws InvalidKeyException, NoSuchAlgorithmException {
final String signToString = OpenApiClientAuth
.builderStringToSign(httpMethodString, contentTypeValue, dateValue,
pathResource);
final String base64HashString = HMAC.hmacSha1Encrypt(signToString, appSecret);
return base64HashString.substring(5, 15);
}
protected static String builderStringToSign(final String method,
final String contentTypeValue, final String dateValue, final String pathResource) {
return method
+ "\n"
+ OpenApiClientAuth.handleNullString(contentTypeValue)
+ "\n"
+ OpenApiClientAuth.handleNullString(dateValue)
+ "\n"
+ pathResource;
}
protected static String handleNullString(final String str) {
return !StringUtils.hasText(str) ? "" : str;
}
protected static class HMAC {
private HMAC() {
}
private static final String KEY_MAC_SHA1 = "HmacSHA1";
public static String hmacSha1Encrypt(final String encryptText, final String encryptKey)
throws NoSuchAlgorithmException, InvalidKeyException {
final byte[] text = encryptText.getBytes(StandardCharsets.UTF_8);
final byte[] keyData = encryptKey.getBytes(StandardCharsets.UTF_8);
final SecretKeySpec secretKey = new SecretKeySpec(keyData, HMAC.KEY_MAC_SHA1);
final Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
return new String(Base64.getEncoder().encode(mac.doFinal(text)),
StandardCharsets.UTF_8);
}
}
/**
* @param url请求地址
* @param body请求参数 json格式
* @param httpMethod请求方法
* @param clientIdclientId
* @param clientSecret clientSecret
* @return 返回值
*/
public static String openApiClientExecute(String url, String body, HttpMethod httpMethod, String clientId, String clientSecret) {
log.info("request url:{},param:{},httpMethod:{},appKey:{},appSecret:{}", url, body, httpMethod, clientId, clientSecret);
RestTemplate restTemplate = new RestTemplate();
Map<String, String> signHeaders = OpenApiClientAuth.getSignHeaders(httpMethod.toString(), url, clientId, clientSecret, ContentType.APPLICATION_JSON.toString());
final HttpHeaders headers = new HttpHeaders();
signHeaders.forEach(headers::set);
if (!StringUtils.hasText(body)) {
body = "";
}
final HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
final ParameterizedTypeReference<String> parameterizedTypeReference = new ParameterizedTypeReference<String>() {};
String r = null;
try {
ResponseEntity<String> exchange = restTemplate.exchange(url, httpMethod, httpEntity, parameterizedTypeReference);
r = exchange.getBody();
} catch (Exception e) {
log.error("request error: ", e);
}
log.info("request url:{},param:{},httpMethod:{},appKey:{},appSecret:{} ,result:{} ", url, body, httpMethod, clientId, clientSecret, r);
return r;
}
public static void main(String[] args) {
String getUrl = "https://spup-uat.icloudcity.com/api/open/smartpark-notification/inform/getConfigPage";
String clientId = "d9cmxqqg8bwepv38";
String clientSecret = "3o1hhpfdy9yq6fpjqes6tnk6mxus5ngmthxdn1iprwelffio";
// 无body示例
String s1 = openApiClientExecute(String.format(getUrl, "i000000083"), null, HttpMethod.GET, clientId, clientSecret);
System.out.println(s1);
//有body示例
String postUrl = "https://spup-uat.icloudcity.com/api/open/smartpark-notification/inform/getServiceList";
List<String> body = new ArrayList<>();
body.add("165801378272102400113ljqw0");
body.add("wulingshili");
body.add("IBMS_tunnel_001");
String s2 = openApiClientExecute(postUrl, JSON.toJSONString(body), HttpMethod.POST, clientId, clientSecret);
System.out.println(s2);
}
}