一、概述
本指南旨在为“浙里办”单点登录组件提供接入指南,“浙里办”单点登陆组件,上架在IRS,为上架在IRS的应用,提供统一的单点登录解决方案,现阶段仅支持微信端的接入。
二、服务创建
3、应用接入“浙里办”单点登录组件前,需要先获取AK&SK。
三、接入说明
四、接入规范
- 接入“浙里办”微信小程序的 H5 应用(以下简称应用), 应当符合“同源发布”及无障碍适老化等要求。
- 应用应当在浙江省一体化数字资源系统(以下简称 IRS)发布,并使用统一的域名 https://mapi.zjzwfw.gov.cn/,作为接入微信小程序的前置条件。应用上架 IRS,应遵循 IRS 相关规范。
- 应用接入“浙里办”微信小程序,应当按照本指南操作步骤与注意事项,进行微信端的兼容适配。
五、操作步骤
1、单点登录适配
API
|
接口说明
|
访问地址
|
atg.biz.userquery |
验证令牌并
获取用户的
登录信息
|
政务外网地址:
https://bcdsg.zj.gov.cn:8443/restapi/prod/IC
33000020220309000001/rest/user/query
|
互联网地址:
https://ibcdsg.zj.gov.cn:8443/restapi/prod/I
C33000020220309000001/rest/user/query
|
||
atg.biz.callb
ackurl
|
业务系统回
调地址添加
|
政务外网地址:
https://bcdsg.zj.gov.cn:8443/restapi/prod/IC
33000020220309000002/rest/callbackUrl
|
互联网地址:
https://ibcdsg.zj.gov.cn:8443/restapi/prod/I
C33000020220309000002/rest/callbackUrl
|
- IRS 应用管理员在 IRS 申请【浙江政务服务网个人单点登录】组件,前端通过调用登录地址获取 ticket 票据后,服务端可通过 ticketvalidation 和 getuserinfo接口。
- 登录地址 spappurl 参数回调地址用于接收 ssotoken 的信息。
Java代码案例:
(1)Constants 定义所有常量
-
/**
-
* @author jie.chen
-
* @date 2022-03-30 15:24
-
*/
-
public
interface
Constants {
-
-
/**
-
* 单点登录 ticketId换token的地址
-
*/
-
// String ACCESS_TOKEN_URL = "https://bcdsg.zj.gov.cn:8443/restapi/prod/IC33000020220329000007/uc/sso/access_token";政务外网
-
//互联网
-
String
ACCESS_TOKEN_URL
=
"https://ibcdsg.zj.gov.cn:8443/restapi/prod/IC33000020220329000007/uc/sso/access_token";
-
/**
-
* 单点登录 token获取用户信息地址
-
*/
-
// String GET_USER_INFO_URL = "https://bcdsg.zj.gov.cn:8443/restapi/prod/IC33000020220329000008/uc/sso/getUserInfo";政务外网
-
//互联网
-
String
GET_USER_INFO_URL
=
"https://ibcdsg.zj.gov.cn:8443/restapi/prod/IC33000020220329000008/uc/sso/getUserInfo";
-
-
/**
-
* IRS请求携带的请求头
-
*/
-
String
X_BG_HMAC_ACCESS_KEY
=
"X-BG-HMAC-ACCESS-KEY";
-
String
X_BG_HMAC_SIGNATURE
=
"X-BG-HMAC-SIGNATURE";
-
String
X_BG_HMAC_ALGORITHM
=
"X-BG-HMAC-ALGORITHM";
-
String
X_BG_DATE_TIME
=
"X-BG-DATE-TIME";
-
-
/**
-
* IRS签名算法
-
*/
-
String
DEFAULT_HMAC_SIGNATURE
=
"hmac-sha256";
-
-
/**
-
* 应用ID
-
*/
-
String
APP_ID
=
"20******33";
-
/**
-
* 微信端固定值为weixin
-
*/
-
String
WEIXIN_ENDPOINT_TYPE
=
"weixin";
-
-
-
/**
-
* IRS 申请组件生成的AK
-
*/
-
String
IRS_AK
=
"********************************";
-
/**
-
* IRS 申请组件生成的SK
-
*/
-
String
IRS_SK
=
"********************************";
-
-
-
String
TOKEN_SESSION_KEY
=
"sessionAccessToken";
-
String
USER_INFO_KEY
=
"sessionUserInfo";
-
-
}
(2)IrsUtils
-
/**
-
* @author jie.chen
-
* @date 2022-03-30 15:28
-
*/
-
public
class
IrsUtils {
-
-
-
@SneakyThrows
-
public
static IrsSignRes
sign
(String url, String method) {
-
UriComponents
uriComponents
= UriComponentsBuilder.fromHttpUrl(url).build();
-
uriComponents = uriComponents.encode();
-
List<String> queryArr =
new
ArrayList<>();
-
MultiValueMap<String, String> queryParams = uriComponents.getQueryParams();
-
for (Map.Entry<String, List<String>> next : queryParams.entrySet()) {
-
for (String va : next.getValue()) {
-
if (va ==
null) {
-
queryArr.add(next.getKey() +
"=");
-
}
else {
-
queryArr.add(next.getKey() +
"=" + va);
-
}
-
}
-
}
-
//按照字典排序
-
Collections.sort(queryArr);
-
///Tue, 09 Nov 2021 08:49:20 GMT
-
DateFormat
dateFormat
=
new
SimpleDateFormat(
"EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
-
dateFormat.setTimeZone(TimeZone.getTimeZone(
"GMT"));
-
String
dateTime
= dateFormat.format(
new
Date());
-
-
String
signStr
= method.toUpperCase() +
"\n" +
-
//拼接url path
-
uriComponents.getPath() +
"\n" +
-
//拼接url query
-
String.join(
"&", queryArr) +
"\n" +
-
Constants.IRS_AK +
"\n" +
-
dateTime +
"\n";
-
-
String
sign
= hmacSha256Base64(signStr, Constants.IRS_SK);
-
-
IrsSignRes
res
=
new
IrsSignRes();
-
-
res.setSignature(sign);
-
res.setAccessKey(Constants.IRS_AK);
-
res.setDateTime(dateTime);
-
res.setAlgorithm(Constants.DEFAULT_HMAC_SIGNATURE);
-
return res;
-
}
-
-
-
@SneakyThrows
-
private
static String
hmacSha256Base64
(String content, String key) {
-
Mac
hmacSHA256
= Mac.getInstance(
"HmacSHA256");
-
SecretKeySpec
secretKey
=
new
SecretKeySpec(key.getBytes(StandardCharsets.UTF_8),
"HmacSHA256");
-
hmacSHA256.init(secretKey);
-
byte[] bytes = hmacSHA256.doFinal(content.getBytes(StandardCharsets.UTF_8));
-
return Base64.getEncoder().encodeToString(bytes);
-
}
-
-
public
static
void
main
(String[] args) {
-
-
System.out.println(sign(
"https://bcdsg.zj.gov.cn:8443/restapi/prod/IC33000020220329000007/uc/sso/getUserInfo",
"POST"));
-
}
-
-
}
(3)IrsSignRes
-
/**
-
* @author jie.chen
-
* @date 2022-03-30 15:28
-
*/
-
@Data
-
public
class
IrsSignRes {
-
private String accessKey;
-
private String signature;
-
private String algorithm;
-
private String dateTime;
-
}
(4)AuthService 业务实现类
-
/**
-
* @author jie.chen
-
* @date 2022-03-30 15:49
-
*/
-
@Component
-
public
class
AuthService {
-
-
-
@Autowired
-
private RestTemplateBuilder restTemplateBuilder;
-
-
private RestTemplate restTemplate;
-
-
@PostConstruct
-
void
init
() {
-
restTemplate = restTemplateBuilder.build();
-
}
-
-
-
public String
getTokenByTicketId
(String ticketId) {
-
-
HttpHeaders
headers
= getHttpHeaders(Constants.ACCESS_TOKEN_URL);
-
JSONObject
body
=
new
JSONObject();
-
body.put(
"appId", Constants.APP_ID);
-
body.put(
"ticketId", ticketId);
-
-
HttpEntity<Map<String, Object>> request =
new
HttpEntity<>(body, headers);
-
-
ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(Constants.ACCESS_TOKEN_URL, request, String.class);
-
return checkResponse(stringResponseEntity).getJSONObject(
"data").getString(
"accessToken");
-
}
-
-
-
public JSONObject
getUserInfoByToken
(String accessToken) {
-
HttpHeaders
headers
= getHttpHeaders(Constants.GET_USER_INFO_URL);
-
JSONObject
body
=
new
JSONObject();
-
body.put(
"token", accessToken);
-
-
HttpEntity<Map<String, Object>> request =
new
HttpEntity<>(body, headers);
-
-
ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(Constants.GET_USER_INFO_URL, request, String.class);
-
return checkResponse(stringResponseEntity).getJSONObject(
"data");
-
}
-
-
private JSONObject
checkResponse
(ResponseEntity<String> stringResponseEntity) {
-
if (!stringResponseEntity.getStatusCode().is2xxSuccessful()) {
-
//请求失败
-
throw
new
RuntimeException(
"status:" + stringResponseEntity.getStatusCodeValue() +
" " + stringResponseEntity.getBody());
-
}
-
JSONObject
result
= JSON.parseObject(stringResponseEntity.getBody());
-
if (result.containsKey(
"errorCode") && result.getString(
"errorCode") !=
null && !result.getBooleanValue(
"success")) {
-
//业务错误
-
throw
new
RuntimeException(result.toString());
-
}
-
return result;
-
}
-
-
private HttpHeaders
getHttpHeaders
(String url) {
-
IrsSignRes
res
= IrsUtils.sign(url,
"POST");
-
-
HttpHeaders
headers
=
new
HttpHeaders();
-
headers.add(Constants.X_BG_HMAC_ACCESS_KEY, res.getAccessKey());
-
headers.add(Constants.X_BG_HMAC_ALGORITHM, res.getAlgorithm());
-
headers.add(Constants.X_BG_HMAC_SIGNATURE, res.getSignature());
-
headers.add(Constants.X_BG_DATE_TIME, res.getDateTime());
-
return headers;
-
}
(5)LoginController 接口测试
-
/**
-
* @author hejun
-
* @since 2022-02-22 10:46:11
-
*/
-
@RestController
-
@RequestMapping("/user")
-
@Api(tags="用户登录")
-
@Slf4j
-
public
class
LoginController
extends
ProBaseController {
-
@GetMapping(value = "zlbWxLoginTest")
-
@ApiOperation(value = "测试浙里办微信小程序登录接口", notes = "测试浙里办微信小程序登录接口后端接口")
-
public String
zlbWxLoginTest
(@RequestParam @ApiParam(name = "st", value = "浙里办 ticketId", required = true)String st) {
-
try {
-
-
return buildResultStr(buildSuccessResultData(userService.getUserBeanByTicketId(st)));
-
}
catch (Exception e) {
-
logError(log, e);
-
return buildResultStr(buildErrorResultData(e.getMessage()));
-
}
-
}
-
}
(6) UserServiceImpl 浙里办用户体系转换
-
/**
-
* 用户表(User)表服务实现类
-
*
-
* @author hejun
-
* @since 2022-02-22 10:02:16
-
*/
-
@Service("userService")
-
@Slf4j
-
public
class
UserServiceImpl
implements
UserService {
-
@Autowired
-
private AuthService authService;
-
-
@Override
-
public UserBean
getUserBeanByTicketId
(String ticketId){
-
UserBean
userBean
=
new
UserBean();
-
//1. 通过ticketId 换取 accessToken
-
String
token
= authService.getTokenByTicketId(ticketId);
-
//3. 通过accessToken 获取用户信息
-
JSONObject
userInfo
= authService.getUserInfoByToken(token);
-
JSONObject
personInfo
= userInfo.getJSONObject(
"personInfo");
-
String
phone
= personInfo.get(
"phone").toString();
-
userBean.setMobile(phone);
-
userBean.setUsername(personInfo.get(
"userName").toString());
-
userBean.setIdnum(personInfo.get(
"idNo").toString());
-
userBean.setUserid(personInfo.get(
"userId").toString());
-
String
login
=
null;
-
if (StringUtils.isNotNullString(phone)){
-
login =
this.login(phone);
-
userBean.setToken(login);
-
log.info(
"token------------------------------", login);
-
}
-
return userBean;
-
}
-
/**
-
* 通过手机号登录
-
*
-
* @return token
-
*/
-
@Override
-
public String
login
(String phone) {
-
User
user
=
this.getUserByPhone(phone);
-
if (user ==
null){
-
user =
new
User();
-
user.setMobile(phone);
-
this.insert(user);
-
}
-
//生成token
-
String
token
= getUserRsid(phone);
-
token = token.replaceAll(
"/",
"_");
-
//token放入缓存
-
JedisUtils.setObject(token,user,portalRsidCacheSeconds);
-
//返回token
-
return token;
-
}
-
-
-
}
六、接口调用方式
请以POST方式提交请求,参数以application/json形式提交。
“浙里办”单点登录,HTTP请求都必须在请求头(HTTP Header)中设置如下4个参数:
参数名 |
是否必填 |
类型 |
说明 |
X-BG-HMAC-SIGNATURE |
是 |
string |
API输入参数签名结果 |
X-BG-HMAC-ALGORITHM |
是 |
string |
签名的摘要算法,当前仅支持hmac-sha256。 |
X-BG-HMAC-ACCESS-KEY |
是 |
string |
分配给应用的accessKey,例如:12345678。 |
X-BG-DATE-TIME |
是 |
string |
时间戳,时区为GMT+8,格式为:Tue, 09 Nov 2021 08:49:20 GMT。服务端允许客户端请求最大时间误差为100秒。 |
其中X-BG-HMAC-SIGNATURE的计算公式为:
signature = HMAC-SHA256-HEX(secret_key,signing_string) |
各字段解释如下:
- secret_key为接口申请完成后获取到的secret_key
- signing_string由请求方法、URI、请求参数等拼接获得,具体如下:
HTTP_METHOD+\n+HTTP_URI+\n+QUERY_STREAM+\n+X-BG-HMAC-ACCESS-KEY+\n+X-BG-DATE+\n |
参数解释如下图,详细代码可参考签名计算代码
参数名 |
说明 |
HTTP METHOD |
指 HTTP 协议中定义的 GET、PUT、POST 等请求方法,必须使用全大写的形式。 |
HTTP URI |
请求路径,要求必须以“/”开头,不以“/”开头的需要补充上,空路径为“/” |
X-BG-DATE |
请求头中的 Date ( GMT 格式 )格式为:“Tue, 09 Nov 2021 08:49:20 GMT” |
QUERY_STREAM |
是对于 URL 中的 query( query 即 URL 中?后面的 key1=valve1&key2=valve2 字符串)进行编码后的结果。以 key 按照字典顺序( ASCII 码由小到大)排序,并使用 & 符号连接起来,生成相应的query_string。 |
参数 |
类型 |
描述 |
ticketId |
String |
单点登录票据 |
appId |
String |
AppId |
参数 |
类型 |
描述 |
errorCode |
String |
错误码 |
errorMsg |
String |
错误信息 |
success |
Boolean |
请求是否成功 |
data |
Object |
响应体 |
|- accessToken |
String |
获取用户信息token |
错误码 |
描述 |
C-USER-SSO-TICKET-INVALID |
ticket非法 |
参数 |
类型 |
描述 |
token |
String |
获取用户信息token |
参数 |
类型 |
描述 |
success |
Boolean |
请求是否成功 |
errorCode |
String |
错误码 |
errorMsg |
String |
错误信息 |
data |
Object |
响应体 |
|- userType |
String |
用户类型,PERSON 个人/LEGAL_PERSON 法人 |
|- personInfo |
Object |
个人用户信息,当前登陆自然人的信息 |
|-- userId |
String |
主键 |
|-- userName |
String |
个人姓名 |
|-- idType |
String |
ID_CARD:身份证,PASSPORT:护照,OFFICER_CARD:军官证,MAINLAND_TRAVEL_PERMIT_FOR_HONGKONG_AND_MACAO_RESIDENTS:港澳居民来往内地通行证,MAINLAND_TRAVEL_PERMIT_FOR_TAIWAN_RESIDENTS:台湾居民来往大陆通行证,FOREIGN_PERMANENT_RESIDENT_ID_CARD:外国人永久居留身份证,FOREIGN_PASSPORT:外籍人士护照,DIPLOMACY_PASSPORT:外交护照,OFFICIAL_PASSPORT:公务护照,SOLDIER_CARD:士兵证,OFFICER_RETIRE_CARD:军官离退休证,GANG_AO_TAI_RESIDENCE_CART:港澳台居民居住证,GANG_AO_ID_CART:港澳居民身份证,UNIFIED_SOCIAL_ID:统一社会信用代码,OTHER:其他 |
|-- outerIdType |
String |
外部证件类型 |
|-- idNo |
String |
证件编号 |
|-- attnUserType |
String |
法人经办人时用户类型,评级 |
|-- phone |
String |
手机号 |
|
String |
邮箱 |
|-- nation |
String |
民族 |
|-- gender |
String |
性别 |
|-- birthday |
String |
生日 |
|-- certKey |
String |
身份散列值 |
|-- attributes |
Object |
额外属性 |
|- legalPersonInfo |
Object |
法人用户信息,比如公司相关的信息 |
|-- name |
String |
法人名称 |
|-- unifiedSocialId |
String |
社会统一信用代码 |
|-- orgType |
String |
法人类型 |
|-- attnName |
String |
经办人姓名 |
|-- attnPhone |
String |
经办人手机号 |
|-- attnIdType |
String |
经办人证件类型 |
|-- attnIdNo |
String |
经办人证件号码 |
|-- attnUserType |
String |
经办人用户等级 |
|-- principal |
String |
法人代表人姓名 |
|-- gender |
Integer |
法人代表人性别 |
|-- nation |
Integer |
法人代表人民族 |
|-- idType |
Integer |
法人代表人证件类型 |
|-- outerIdType |
String |
法人代表人外部证件类型 |
|-- idNo |
String |
法人代表人证件号码 |
|-- principalUserId |
String |
法人代表唯一键 |
|-- corpId |
String |
法人唯一键 |
|-- attributes |
Object |
额外属性 |
|- organizationInfoList |
Array |
所属组织信息 |
|-- orgId |
String |
组织主键 |
|-- oid |
String |
Alias for orgId |
|-- parentId |
String |
父组织主键 |
|-- pid |
String |
Alias for parentId |
|-- name |
String |
组织机构简称 |
|--fullName |
String |
组织机构全称 |
|--devCoding |
String |
组织后缀 |
|--leafFlag |
Boolean |
是否叶子标志 |
|--orderBy |
Integer |
排序号,从小到大 |
错误码 |
描述 |
C-USER-SSO-TOKEN-INVALID |
token非法 |
C-USER-SSO-USER-EMPTY |
用户信息为空 |
转载:https://blog.csdn.net/Zyw907155124/article/details/128022335