学习目标:
配置实名认证服务、实现实名认证
学习内容:
- 百度智能云实名认证对接
- 平台化提供可配置、解耦百度实名认证服务
- 封装统一业务服务接口
序
首先这里的接入方案是标准H5方案,目前看来,微信还没有对百度智能云的这个实名认证服务做技术壁垒,但是不排除LM的小马哥做这种骚操作。如果微信浏览器里要跳到外置浏览器做这个实名认证过程,那就体验不爽了。
一、流程说明
官方的接入步骤与文档地址自行百度。
官方时序图:
我这边进行的封装与解耦时序图。各个公司可以有自己的配置,我们的平台仅仅是做服务抽象与解耦,只需要各公司提供配置即可。
二、思路剖析
这里先要看百度云的接入步骤,理解它的设计思路,然后才能在它的思路上做服务封装(任何第三方服务想要平台化都是这个思路)
这边是希望各个公司自行申请百度云服务授权,然后我们再业务系统记录授权信息。公司业务在做服务请求时,通过配置的授权信息与百度云做交互,作为一个中间的纽带。我这边是springCloud微服务架构,对业务模块做了拆解,剖析分析提供以下服务支持,
第三方服务模块提供直接接口
- 获取AccesToken
- 获取VerifyToken
- 指定用户实名认证信息
- 获取实名认证url
- 查询实名认证结果
用户服务模块 - 用户发起实名认证
- 用户获取实名认证结果
三、部分core代码分享
第三方服务service层接口代码
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.URLEncodeUtil;
import cn.hutool.json.JSONUtil;
import com.baidubce.http.ApiExplorerClient;
import com.baidubce.http.HttpMethodName;
import com.baidubce.model.ApiExplorerRequest;
import com.baidubce.model.ApiExplorerResponse;
import com.fillersmart.fsihouse.data.common.api.ResponseCodeI18n;
import com.fillersmart.fsihouse.data.core.Result;
import com.fillersmart.fsihouse.data.core.ResultGenerator;
import com.fillersmart.fsihouse.data.vo.thirdapi.face.request.BaiduVerifyRequest;
import com.fillersmart.fsihouse.data.vo.thirdapi.face.verfity.FaceVerifyVo;
import com.fillersmart.fsihouse.thirdservice.controller.util.BaiduFaceUtil;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author zhengwen
**/
@Slf4j
@RestController
@RequestMapping("/faceVerfity")
public class FaceVerfityController {
/**
* 获取AccessToken
*
* @param baiduVerifyRequest 百度实名request
* @return 统一出参
*/
@PostMapping(value = "/getBaiduAccessToken")
public Result<?> getBaiduAccessToken(@RequestBody BaiduVerifyRequest baiduVerifyRequest) {
//参数校验
Result<?> res = BaiduFaceUtil.checkGetAccessTokenParam(baiduVerifyRequest);
if (res != null) {
return res;
}
String path = "https://aip.baidubce.com/oauth/2.0/token";
ApiExplorerRequest request = new ApiExplorerRequest(HttpMethodName.POST, path);
// 设置header参数
request.addHeaderParameter("Content-Type", "application/json;charset=UTF-8");
// 设置query参数
request.addQueryParameter("client_id", baiduVerifyRequest.getApiKey());
request.addQueryParameter("client_secret", baiduVerifyRequest.getSecretKey());
request.addQueryParameter("grant_type", "client_credentials");
// 设置jsonBody参数
String jsonBody = "{}";
if (MapUtil.isNotEmpty(baiduVerifyRequest.getJsonBody())) {
jsonBody = JSONUtil.toJsonStr(baiduVerifyRequest.getJsonBody());
}
request.setJsonBody(jsonBody);
ApiExplorerClient client = new ApiExplorerClient();
try {
ApiExplorerResponse response = client.sendRequest(request);
// 返回结果格式为Json字符串
return ResultGenerator.genSuccessResult(response.getResult());
} catch (Exception e) {
log.error("百度智能云获取AccessToken异常,原因:{}", e.getMessage(), e);
return ResultGenerator.genFailResult(ResponseCodeI18n.GET_TOKEN_FAILED.getMsg());
}
}
/**
* 获取验证token
*
* @param faceVerifyVo 人脸验证vo
* @return 统一出参
*/
@PostMapping(value = "/getBaiduVerifyToken")
public Result<?> getBaiduVerifyToken(@RequestBody FaceVerifyVo faceVerifyVo) {
String path = "https://aip.baidubce.com/rpc/2.0/brain/solution/faceprint/verifyToken/generate";
ApiExplorerRequest request = new ApiExplorerRequest(HttpMethodName.POST, path);
// 设置header参数
request.addHeaderParameter("Content-Type", "application/json;charset=UTF-8");
//转换百度请求request
Result<?> res = BaiduFaceUtil.checkAndAddVerifyTokenParam(faceVerifyVo, request);
if (res != null) {
return res;
}
ApiExplorerClient client = new ApiExplorerClient();
try {
ApiExplorerResponse response = client.sendRequest(request);
// 返回结果格式为Json字符串
return ResultGenerator.genSuccessResult(response.getResult());
} catch (Exception e) {
log.error("--获取百度人脸verifyToken异常,原因:{}", e.getMessage(), e);
return ResultGenerator.genFailResult(ResponseCodeI18n.GET_TOKEN_FAILED.getMsg());
}
}
/**
* 指定用户认证(指定用户信息上报)
*
* @param faceVerifyVo 人脸验证vo
* @return 统一出参
*/
@PostMapping(value = "/appointUserCertBaidu")
public Result<?> appointUserCertBaidu(@RequestBody FaceVerifyVo faceVerifyVo) {
//String path = "https://aip.baidubce.com/solution/faceprint/idcard/submit";
String path = "https://brain.baidu.com/solution/faceprint/idcard/submit";
ApiExplorerRequest request = new ApiExplorerRequest(HttpMethodName.POST, path);
//转换百度请求request
Result<?> res = BaiduFaceUtil.checkAppointUserCertParam(faceVerifyVo, request);
if (res != null) {
return res;
}
// 设置header参数
request.addHeaderParameter("Content-Type", "application/json;charset=UTF-8");
ApiExplorerClient client = new ApiExplorerClient();
try {
ApiExplorerResponse response = client.sendRequest(request);
// 返回结果格式为Json字符串
return ResultGenerator.genSuccessResult(response.getResult());
} catch (Exception e) {
log.error("--指定用户认证信息异常,原因:{}", e.getMessage(), e);
return ResultGenerator.genFailResult(ResponseCodeI18n.USER_CERT_FAIL.getMsg());
}
}
/**
* 获取实名认证url
*
* @param faceVerifyVo 人脸验证vo
* @return 统一出参
*/
@PostMapping(value = "/getFaceCertUrlBaidu")
public Result<?> getFaceCertUrlBaidu(@RequestBody FaceVerifyVo faceVerifyVo) {
//参数校验
Result<?> res = BaiduFaceUtil.checkGetFaceCertUrlParam(faceVerifyVo);
if (res != null) {
return res;
}
String verifyToken = faceVerifyVo.getVerifyToken();
if (StringUtils.isBlank(verifyToken)) {
//accessToken
String accessToken = faceVerifyVo.getAccessToken();
if (StringUtils.isBlank(accessToken)) {
BaiduVerifyRequest baiduVerifyRequest = BaiduFaceUtil.initBaiduVerifyRequest(faceVerifyVo);
res = getBaiduAccessToken(baiduVerifyRequest);
res = BaiduFaceUtil.transSetAccessToken(res, faceVerifyVo);
if (res != null) {
return res;
}
}
//verifyToken
//先取verifyToken
res = getBaiduVerifyToken(faceVerifyVo);
res = BaiduFaceUtil.transSetVerityToken(res, faceVerifyVo);
if (res != null) {
return res;
}
verifyToken = faceVerifyVo.getVerifyToken();
//指定用户认证
res = appointUserCertBaidu(faceVerifyVo);
res = BaiduFaceUtil.transSetAppointUserResult(res, faceVerifyVo);
if (res != null) {
return res;
}
}
Map<String, String> certAfterInfo = faceVerifyVo.getCertAfterInfo();
String successUrl = certAfterInfo.get("successUrl");
String failUrl = certAfterInfo.get("failUrl");
StringBuffer urlSbf = new StringBuffer();
urlSbf.append("https://brain.baidu.com/face/print/?token=");
urlSbf.append(verifyToken);
urlSbf.append("&successUrl=").append(URLEncodeUtil.encodeAll(successUrl));
urlSbf.append("&failedUrl=").append(URLEncodeUtil.encodeAll(failUrl));
faceVerifyVo.setCertUrl(urlSbf.toString());
//TODO 是否要避免重复请求呢?是否需要存库呢?
return ResultGenerator.genSuccessResult(faceVerifyVo);
}
/**
* 获取实名认证结果
*
* @param faceVerifyVo 人脸验证vo
* @return 统一出参
*/
@PostMapping(value = "/getFaceCertResultBaidu")
public Result<?> getFaceCertResultBaidu(@RequestBody FaceVerifyVo faceVerifyVo) {
//参数校验
String path = "https://aip.baidubce.com/rpc/2.0/brain/solution/faceprint/result/detail";
ApiExplorerRequest request = new ApiExplorerRequest(HttpMethodName.POST, path);
//转换百度请求request
Result<?> res = BaiduFaceUtil.checkGetFaceCertResultParam(faceVerifyVo, request);
if (res != null) {
return res;
}
ApiExplorerClient client = new ApiExplorerClient();
try {
ApiExplorerResponse response = client.sendRequest(request);
// 返回结果格式为Json字符串
res = ResultGenerator.genSuccessResult(response.getResult());
} catch (Exception e) {
log.error("--获取人脸身份实名认证结果异常,原因:{}", e.getMessage(), e);
res = ResultGenerator.genFailResult(ResponseCodeI18n.GET_CERT_RESULT_FAIL.getMsg());
}
return res;
}
}
第三方接口层代码
import com.alibaba.fastjson.JSONObject;
import com.fillersmart.fsihouse.data.common.api.ResponseCodeI18n;
import com.fillersmart.fsihouse.data.constant.ConstantsEnum;
import com.fillersmart.fsihouse.data.core.RedisUtil;
import com.fillersmart.fsihouse.data.core.Result;
import com.fillersmart.fsihouse.data.core.ResultCode;
import com.fillersmart.fsihouse.data.core.ResultGenerator;
import com.fillersmart.fsihouse.data.vo.thirdapi.face.request.BaiduVerifyRequest;
import com.fillersmart.fsihouse.data.vo.thirdapi.face.verfity.FaceVerifyVo;
import com.fillersmart.fsihouse.thirdweb.controller.util.FaceVerfityUtil;
import com.fillersmart.fsihouse.thirdweb.service.FaceVerfityRpcService;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author zhengwen
**/
@Slf4j
@RestController
@RequestMapping("/faceVerfity")
public class FaceVerfityController {
@Resource
private RedisUtil redisUtil;
@Resource
private FaceVerfityRpcService faceVerfityRpcService;
/**
* 获取AccessToken
*
* @param faceVerifyVo 人脸识别入参vo
* @return 统一出参
*/
@PostMapping(value = "/getAccessToken")
public Result<?> getAccessToken(@RequestBody FaceVerifyVo faceVerifyVo) {
Result<?> res = ResultGenerator.genFailResult(ResponseCodeI18n.GET_TOKEN_FAILED.getMsg());
String supportName = faceVerifyVo.getSupportName();
if (StringUtils.isBlank(supportName)) {
return ResultGenerator.genFailResult(ResponseCodeI18n.SERVICE_SUPPORT_NULL.getMsg());
}
ConstantsEnum.ServiceSupportType support = ConstantsEnum.ServiceSupportType.getEnum(
supportName);
switch (support) {
case BAIDU:
//先从缓存取
res = FaceVerfityUtil.getBaiduAccessTokenFromCache(redisUtil, faceVerifyVo);
if (res == null) {
BaiduVerifyRequest baiduVerifyRequest = FaceVerfityUtil.transToBaiduVerfityRequest(
faceVerifyVo);
res = faceVerfityRpcService.getBaiduAccessToken(baiduVerifyRequest);
res = FaceVerfityUtil.transBaiduRespToResult(res, faceVerifyVo, redisUtil);
}
break;
default:
break;
}
return res;
}
/**
* 获取验证token
*
* @param faceVerifyVo 人脸验证vo
* @return 统一出参
*/
@PostMapping(value = "/getVerifyToken")
public Result<?> getVerifyToken(@RequestBody FaceVerifyVo faceVerifyVo) {
Result<?> res = ResultGenerator.genFailResult(ResponseCodeI18n.GET_TOKEN_FAILED.getMsg());
String supportName = faceVerifyVo.getSupportName();
if (StringUtils.isBlank(supportName)) {
return ResultGenerator.genFailResult(ResponseCodeI18n.SERVICE_SUPPORT_NULL.getMsg());
}
ConstantsEnum.ServiceSupportType support = ConstantsEnum.ServiceSupportType.getEnum(
supportName);
switch (support) {
case BAIDU:
//先从缓冲取
res = FaceVerfityUtil.getBaiduAccessTokenFromCache(redisUtil, faceVerifyVo);
if (res == null) {
res = getAccessToken(faceVerifyVo);
}
if (res != null && res.getCode().equals(ResultCode.SUCCESS.code())) {
res = faceVerfityRpcService.getBaiduVerifyToken(faceVerifyVo);
res = FaceVerfityUtil.transBaiduVerifyTokenResp(res, faceVerifyVo);
}
break;
default:
break;
}
return res;
}
/**
* 指定用户认证(指定用户信息上报)
*
* @param faceVerifyVo 人脸验证vo
* @return 统一出参
*/
@PostMapping(value = "/appointUserCert")
public Result<?> appointUserCert(@RequestBody FaceVerifyVo faceVerifyVo) {
Result<?> res = ResultGenerator.genFailResult(ResponseCodeI18n.USER_CERT_FAIL.getMsg());
String supportName = faceVerifyVo.getSupportName();
if (StringUtils.isBlank(supportName)) {
return ResultGenerator.genFailResult(ResponseCodeI18n.SERVICE_SUPPORT_NULL.getMsg());
}
ConstantsEnum.ServiceSupportType support = ConstantsEnum.ServiceSupportType.getEnum(
supportName);
switch (support) {
case BAIDU:
res = faceVerfityRpcService.appointUserCertBaidu(faceVerifyVo);
//校验转换统一出参
res = FaceVerfityUtil.transBaiduAppointUserCertResp(res, faceVerifyVo);
break;
default:
break;
}
return res;
}
/**
* 获取实名认证url
*
* @param faceVerifyVo 人脸验证vo
* @return 统一出参
*/
@PostMapping(value = "/getFaceCertUrl")
public Result<?> getFaceCertUrl(@RequestBody FaceVerifyVo faceVerifyVo) {
//参数校验
Result<?> res = FaceVerfityUtil.checkGetFaceCertUrlParam(faceVerifyVo);
if (res != null) {
return res;
}
//先取verifyToken
res = getVerifyToken(faceVerifyVo);
if (res != null && res.getCode().equals(ResultCode.FAIL.code())) {
return res;
} else {
faceVerifyVo = JSONObject.parseObject(JSONObject.toJSONString(res.getData()),
FaceVerifyVo.class);
}
//指定用户认证
res = appointUserCert(faceVerifyVo);
if (res != null && res.getCode().equals(ResultCode.FAIL.code())) {
return res;
} else {
faceVerifyVo = JSONObject.parseObject(JSONObject.toJSONString(res.getData()),
FaceVerifyVo.class);
}
String supportName = faceVerifyVo.getSupportName();
ConstantsEnum.ServiceSupportType support = ConstantsEnum.ServiceSupportType.getEnum(
supportName);
switch (support) {
case BAIDU:
res = faceVerfityRpcService.getFaceCertUrlBaidu(faceVerifyVo);
break;
default:
break;
}
return res;
}
/**
* 获取实名认证结果
*
* @param faceVerifyVo 人脸验证vo
* @return 统一出参
*/
@PostMapping(value = "/getFaceCertResult")
public Result<?> getFaceCertResult(@RequestBody FaceVerifyVo faceVerifyVo) {
//参数校验
Result<?> res = FaceVerfityUtil.checkGetFaceCertResultParam(faceVerifyVo);
if (res != null) {
return res;
}
String supportName = faceVerifyVo.getSupportName();
ConstantsEnum.ServiceSupportType support = ConstantsEnum.ServiceSupportType.getEnum(
supportName);
switch (support) {
case BAIDU:
res = faceVerfityRpcService.getFaceCertResultBaidu(faceVerifyVo);
//转换结果
FaceVerfityUtil.transBaiduCertResultRespToResult(res, faceVerifyVo);
break;
default:
break;
}
return res;
}
}
在接口层是做了将service层返回的数据做转换未业务的统一出参的,用户服务层提供的2个接口的代码就不能分享了,各位可以根据我画的时序图自行实现。实际上里面也就是做了公司配置的查询,再组合调用第三方服务模块提供的接口,postman的接口请求示例倒是可以分享下,大家可以根据入参结合时序图理解实现这2个接口。
四、postman接口请求示例分享
- 获取第三方服务AccessToken
{ {apiUrl}}/faceVerfity/getAccessToken
{
"authInfo": {
"apiKey": "百度智能云apiKey", //apiKey 必传
"secretKey": "百度智能云secretKey" //secretKey 必传
},
"supportName": "baidu" //第三方服务费,必传
}
- 获取人脸校验verifyToken
{ {apiUrl}}/faceVerfity/getVerifyToken
{
"authInfo": {
"apiKey": "百度智能云apiKey", //apiKey 必传
"secretKey": "百度智能云secretKey" //secretKey 必传
},
"accessToken": "", //鉴权token,非必传,不传,先取缓存,缓存没有内部会主动去取
"planId": 新建的H5小程序方案ID, //方案ID,必传
"supportName": "baidu" //第三方服务费,必传
}
- 指定用户认证(指定用户信息上报)
{ {apiUrl}}/faceVerfity/appointUserCert
{
"authInfo": {
"apiKey": "百度智能云apiKey", //apiKey 必传
"secretKey": "百度智能云secretKey" //secretKey 必传
},
"certUserInfo": {
"idName": "xxx", //姓名,必传
"idNo": "42900身份证", //证件号码,必传
"certificateType": 0 //证件类型:非必传,0大陆居民二代身份证1港澳台居民来往内地通行证2外国人永久居留证3定居国外的中国公民护照
},
"verifyToken":"接口2返回的verifyToken",
"accessToken": "", //鉴权token,非必传,不传,先取缓存,缓存没有内部会主动去取
"planId": 新建的H5小程序方案ID, //方案ID,必传
"supportName": "baidu" //第三方服务费,必传
}
- 获取实名认证url
{ {apiUrl}}/faceVerfity/getFaceCertUrl
{
"authInfo": {
"apiKey": "xxxx", //apiKey 必传
"secretKey": "xxxx" //secretKey 必传
},
"certUserInfo": {
"idName": "xx", //姓名,必传
"idNo": "xxx", //证件号码,必传
"certificateType": 0 //证件类型:非必传,0大陆居民二代身份证1港澳台居民来往内地通行证2外国人永久居留证3定居国外的中国公民护照
},
"certAfterInfo": {
"successUrl": "https://blog.csdn.net/", //认证成功后跳转地址,必传。这里建议传业务url,从哪里进的跳回哪里
"failUrl": "https://blog.csdn.net/zwrlj527" //认证失败跳转地址,必传
},
"verifyToken": "", //验证token,非必传,不传会自行取,要自己传就通过前面的接口获取
"accessToken": "", //鉴权token,非必传,不传,先取缓存,缓存没有内部会主动去取
"planId": , //方案ID,必传
"supportName": "baidu" //第三方服务费,必传
}
- 获取实名认证结果
{ {apiUrl}}/faceVerfity/getFaceCertResult
{
"verifyToken": "xxx", //验证token,非必传,不传会自行取,要自己传就通过前面的接口获取
"accessToken": "xxx", //鉴权token,非必传,不传,先取缓存,缓存没有内部会主动去取
"supportName": "baidu" //第三方服务费,必传
}
以上每一步的入参对象都是环环相扣,上一步的出参可以作为下一步的入参。
用户模块的接口
1、 租户发起实名认证
{
{apiUrl}}/rent/user/rentUserIdCodeCert
{
"rentUserDto": {
"id": 1151, //租户id,必传
"custName": "xxx", //租户姓名,必传
"certType": 1, //认证类型,必传, 1 身份证 2 学生证 3 军官证 4 驾驶证 5护照 6港澳通行证 7外国人永久居留证
"certCode": "xxxx", //身份号,必传
"orgId": 60 //公司id,必传
},
"faceVerifyVo": {
"certAfterInfo": {
"successUrl": "https://blog.csdn.net/", //认证成功后跳转地址,必传。这里建议传业务url,从哪里进的跳回哪里
"failUrl": "https://blog.csdn.net/zwrlj527" //认证失败跳转地址,必传
}
}
}
2、获取租户实名认证结果
{
{apiUrl}}/rent/user/getRentUserCertResult
{
"rentUserDto": {
"id": 1151, //租户id,必传
"custName": "xx", //租户姓名,必传
"certType": 1, //认证类型,必传, 1 身份证 2 学生证 3 军官证 4 驾驶证 5护照 6港澳通行证 7外国人永久居留证
"certCode": "xxx", //身份号,必传
"orgId": 60 //公司id,必传
},
"faceVerifyVo": {
//获取人脸实名认证url返回的data对象
"accessToken": "xxx",
"authInfo": {
"secretKey": "xxx",
"apiKey": "xx"
},
"certAfterInfo": {
"successUrl": "https://blog.csdn.net/",
"failUrl": "https://blog.csdn.net/zwrlj527"
},
"certMatch": null,
"certResult": null,
"certUrl": "https://brain.baidu.com/face/print/?token=xxxx&successUrl=https%3A%2F%2Fblog.csdn.net%2F&failedUrl=https%3A%2F%2Fblog.csdn.net%2Fzwrlj527",
"certUserInfo": {
"idName": "xxx",
"idNo": "xxxx"
},
"planId": xx,
"supportName": "baidu",
"verifyToken": "xxx"
}
}
2个接口的入参是相扣的,接口1的出参人脸认证对象,直接可以整个在扔进去,其实也是为了方便,前端是需要做简单的组合就行了。
五、总结
- 接入还比较简单,流程清晰
- 官方文档也很清楚
- 服务方工单响应很快,过程中我们获取认证结果是遇到了报错的,但是不是我们设计的流程的问题,而是授权的问题。报错内容:
实际上上查看接口返回的message是有如下内容的
- 在线接口调试很酸爽
好了,就分享到这里,希望能帮到各位博友。最后愿世界和平,疫情早日消除,同呼吸共命运。