本指南旨在为“浙里办”单点登录组件提供接入指南,“浙里办”单点登陆组件,上架在IRS,为上架在IRS的应用,提供统一的单点登录解决方案,现阶段仅支持微信端的接入。
3、应用接入“浙里办”单点登录组件前,需要先获取AK&SK。
“浙里办”微信小程序接入工作流程图:
Java代码案例:
/*** @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";}
/*** @author jie.chen* @date 2022-03-30 15:28*/
public class IrsUtils {@SneakyThrowspublic static IrsSignRes sign(String url, String method) {UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(url).build();uriComponents = uriComponents.encode();List queryArr = new ArrayList<>();MultiValueMap queryParams = uriComponents.getQueryParams();for (Map.Entry> 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 GMTDateFormat 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 pathuriComponents.getPath() + "\n" +//拼接url queryString.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;}@SneakyThrowsprivate 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"));}}
/*** @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;
}
/*** @author jie.chen* @date 2022-03-30 15:49*/
@Component
public class AuthService {@Autowiredprivate RestTemplateBuilder restTemplateBuilder;private RestTemplate restTemplate;@PostConstructvoid 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
/*** @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()));}}
}
/*** 用户表(User)表服务实现类** @author hejun* @since 2022-02-22 10:02:16*/
@Service("userService")
@Slf4j
public class UserServiceImpl implements UserService {@Autowiredprivate AuthService authService;@Overridepublic UserBean getUserBeanByTicketId(String ticketId){UserBean userBean = new UserBean();//1. 通过ticketId 换取 accessTokenString 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*/@Overridepublic String login(String phone) {User user = this.getUserByPhone(phone);if (user == null){user = new User();user.setMobile(phone);this.insert(user);}//生成tokenString token = getUserRsid(phone);token = token.replaceAll("/","_");//token放入缓存JedisUtils.setObject(token,user,portalRsidCacheSeconds);//返回tokenreturn 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) |
各字段解释如下:
| 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。 |
/uc/sso/access_token
| 参数 | 类型 | 描述 |
| ticketId | String | 单点登录票据 |
| appId | String | AppId |
| 参数 | 类型 | 描述 |
| errorCode | String | 错误码 |
| errorMsg | String | 错误信息 |
| success | Boolean | 请求是否成功 |
| data | Object | 响应体 |
| |- accessToken | String | 获取用户信息token |
| 错误码 | 描述 |
| C-USER-SSO-TICKET-INVALID | ticket非法 |
/uc/sso/getUserInfo
| 参数 | 类型 | 描述 |
| 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 | 用户信息为空 |