广东省统一身份认证平台对接笔记

赖卓成2022年11月12日
大约 5 分钟

对接首先需要有申请好秘钥,也就是clientId和clientSecret,设置好登录成功跳转的地址。其他平台也是类似的方案,Oauth2.0。

sso:
  # 用于用户登录成功后重定向
  vue-url: http://localhost:9200/corp/sso
  # vue-url: http://localhost:9200/corp/#/sso/
  # 申请到的id
  client-id: xxx
  # 申请到的秘钥
  client-secret: xxx
  # 对接平台的baseUrl
  tyrz-url: https://tyrztest.gd.gov.cn

我们先要在前端页面,跳转到统一身份认证平台,进行认证,再认证成功后跳转回我们的前端地址。步骤如下:

  • 检查用户是否已登录我们的系统(检查cookie或localSessionStorage)

  • 用户未登录则进行跳转,跳转到:

    https://tyrztest.gd.gov.cn/tif/sso/connect/page/oauth2/authorize?service=initService&response_type=code&client_id=申请的id&scope=all&redirect_uri=http%3A%2F%2Flocalhost%3A9200%2Fcorp%2Fsso
    

    需要注意的是,这里的redirect_url是配置好的,如果url不对是会报错的。并且要进行encodeURL

  • 登录成功后,会进行跳转,并且会携带一个参数,code

    image-20221112154158391

  • 跳转到我们的前端页面后,我们需要把这个code传给我们的后端服务器由服务器再次发送请求到平台获取token,前端向后端发请求时可以携带上自己的业务参数。

  • 请求到达服务器,服务器使用http工具,向平台发送请求:

    image-20221112154728174

  • 获取到token以后,用这个token,调用平台接口获取用户基本信息,进行判断来校验用户是否可用

    image-20221112154832724

  • 可用则将用户信息存到我们自己的系统,生成本系统的token返回给前端,保存到cookie

    image-20221112154912232

工具类:

/**
 * sso工具
 *
 * @author 赖卓成
 * @date 2023/02/21
 */
@Component
@Slf4j
public class SsoUtil {


    private final ObjectMapper OBJECTMAPPER = new ObjectMapper();

    @Autowired
    private ApplicationProperties applicationProperties;



    /**
     * 获取省厅令牌
     *
     * @param code 代码
     * @param path 跳转路径
     * @return {@link ProvinceTokenDto}
     */
    public ProvinceTokenDto getProvinceToken(String code, String path) {
        // 跳转地址encode
        path = null == path ? "" : path;
        String redirectUri = "";
        try {
            redirectUri =  URLEncoder.encode(applicationProperties.getSso().getSsoVueUrl()+ "?type=" + path + "&ywlx=" + "path", "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new BusinessException(BusinessExceptionEnum.CAN_NOT_GET_JUMP_ADDRESS);
        }

        // 调用接口地址拼接
        String serviceUrl = applicationProperties.getSso().getTyrzUrl()
                + "/tif/sso/connect/page/oauth2/access_token?client_id="
                + applicationProperties.getSso().getClientId()+ "&code=" + code + "&scope=all&client_secret="
                + applicationProperties.getSso().getClientSecret()  + "&grant_type=authorization_code&redirect_uri="
                + redirectUri;
        // 发起GET请求
        String json = HttpUtil.get(serviceUrl);
        log.info("登录返回信息:{}",json);
        ProvinceTokenDto provinceTokenDto = null;
        try {
            provinceTokenDto = OBJECTMAPPER.readValue(json, ProvinceTokenDto.class);
        } catch (JsonProcessingException e) {
            throw new BusinessException(BusinessExceptionEnum.AUTHENTICATION_FAILURE);
        }

        if (StringUtils.isBlank(provinceTokenDto.getAccessToken())){
            throw new BusinessException(BusinessExceptionEnum.AUTHENTICATION_FAILURE);
        }
        return provinceTokenDto;

    }


    public ProvinceUserResultDto getProvinceUserDto(String accessToken) {
        if (StringUtils.isBlank(accessToken)){
            throw new BusinessException(BusinessExceptionEnum.NOT_FIND_PROVINCE_ACCESS_TOKEN);
        }
        // 调用接口地址拼接
        String userInfoUrl = applicationProperties.getSso().getTyrzUrl() + "/tif/sso/connect/page/oauth2/tokeninfo?access_token=" + accessToken;
        // 发起GET请求
        String json = HttpUtil.get(userInfoUrl);
        ProvinceUserResultDto provinceUserResultDto = null;
        try {
            provinceUserResultDto = OBJECTMAPPER.readValue(json, ProvinceUserResultDto.class);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        // 处理额外属性 转为java对象
        List<String> extProperties = provinceUserResultDto.getUserObj().getExtProperties();
        Map<String, String> map = extProperties.stream().map(s -> s.split("=")).collect(Collectors.toMap(s -> s[0], s -> s[1]));
        ProvinceUserExtDataDto provinceUserExtDataDto = new ProvinceUserExtDataDto(map);
        provinceUserResultDto.getUserObj().setProvinceUserExtDataDto(provinceUserExtDataDto);
        provinceUserResultDto.getUserObj().setExtProperties(null);

        return provinceUserResultDto;
    }

}

实现类:

    @Override
    public BasLoginVo clientSsoLogin(String code, String path) {
        // 登录-》通过身份证查询,优先查询已入库的记录(始终保持只有一条已入库的,且id不能变),有已入库的则直接用已入库的id生成token
        // 没有已入库的,则拿未入库的生成token(始终保持只有一条未入库的)
        if (StringUtils.isBlank(code)) {
            throw new BusinessException(BusinessExceptionEnum.OBTAIN_PROVINCE_PARAMETER_FAILURE);
        }
        // 通过授权码获取省厅访问令牌
        ProvinceTokenDto provinceTokenDto = ssoUtil.getProvinceToken(code, path);
        // 通过令牌获取用户信息
        ProvinceUserResultDto provinceUserResultDto = ssoUtil.getProvinceUserDto(provinceTokenDto.getAccessToken());
        ProvinceUserDataDto userObj = provinceUserResultDto.getUserObj();
        String creditableLevelOfAccount = userObj.getCreditableLevelOfAccount();
        String userType = userObj.getUserType();
        String idCardType = userObj.getIdCardType();
        String idCardNumber = userObj.getIdCardNumber();
        boolean real = userObj.isReal();

        // TODO 移动端可能会用到以下校验,可以封装工具类 暂时先这样写

        // 校验身份信息
        if (StringUtils.isBlank(userType) || StringUtils.isBlank(idCardType) || StringUtils.isBlank(idCardNumber)) {
            throw new BusinessException(BusinessExceptionEnum.ID_INFORMATION_IS_NOT_COMPLETE);
        }
        // 个人用户、已实名认证、用户等级为L2才行,否则不让使用
        if (!ProvinceUserLevelEnum.USER_LEVEL_L2.getValue().equals(creditableLevelOfAccount)) {
            throw new BusinessException(BusinessExceptionEnum.PARAMETERS_ERROR);
        }
        if (!ProvinceUserTypeEnum.USER_TYPE_INDIVIDUAL_USER.getValue().equals(userType)){
            throw new BusinessException(BusinessExceptionEnum.LEGAL_USERS_CANT_USE);
        }
        if (!real){
            throw new BusinessException(BusinessExceptionEnum.NOT_REAL_NAME_AUTHENTICATION);
        }

        // 校验专家是否已经注册 已注册返回token,未注册返回信息,提示需要同意注册保留信息致本系统

        boolean registerStatus = bizExpertService.getExpertRegisterStatus(idCardNumber);
        BasLoginVo basLoginVo = new BasLoginVo();
        basLoginVo.setRegisterStatus(registerStatus);

        if (registerStatus){
            // 已注册,则返回已入库信息,如还未入库,则返回未入库的信息
            BizExpert bizExpert=bizExpertService.getValidExpert(idCardNumber);
            if (null==bizExpert){
                bizExpert = bizExpertService.getNotInStoreExpertByIdCardNumber(idCardNumber);
            }

            String id = bizExpert.getId();
            try {
                String token = jwtUtil.createToken(id);
                basLoginVo.setAccessToken(token);
            } catch (Exception e) {
                throw new BusinessException(BusinessExceptionEnum.CANT_CREATE_TOKEN);
            }
        }
//        else {
//            basLoginVo.setProvinceUserResultDto(provinceUserResultDto);
//        }
        basLoginVo.setProvinceUserResultDto(provinceUserResultDto);
        basLoginVo.setExpertName(provinceUserResultDto.getUserObj().getName());
        return basLoginVo;
    }

传输对象:

/**
 * 省厅返回的用户信息-返回结果最外层对象
 *
 * @author 赖卓成
 * @date 2023/02/21
 */
@Data
public class ProvinceUserResultDto implements Serializable {
    private static final long serialVersionUID = 6790808153472792218L;

    /**
     * 签名
     */
    @JsonProperty("signdata")
    private String signData;

    /**
     * 用户信息
     */
    @JsonProperty("userobj")
    private ProvinceUserDataDto userObj;

    /**
     * pareObj
     */
    @JsonProperty("pareobj")
    private ProvinceUserDataDto pareObj;

    /**
     * 用户等级信息
     */
    @JsonProperty("user_creditable_level")
    private ProvinceUserCreditableLevelDto userCreditableLevel;
}
/**
 * 省用户数据dto
 *
 * @author 赖卓成
 * @date 2023/02/21
 */
@Data
public class ProvinceUserDataDto implements Serializable {
    private static final long serialVersionUID = 6169661489111021236L;


    /**
     * 用户id
     */
    @JsonProperty("uid")
    private String userId;

    /**
     * 核验方 如YSS 粤省事
     */
    @JsonProperty("realtype")
    private String realType;

    /**
     * 名字
     */
    @JsonProperty("cn")
    private String name;

    /**
     * 令牌标识
     */
    @JsonProperty("tokenid")
    private String tokenId;

    /**
     * 用户类型
     */
    @JsonProperty("usertype")
    private String userType;

    /**
     * 实名状态
     */
    @JsonProperty("isreal")
    private boolean isReal;

    /**
     * 电话号码
     */
    @JsonProperty("telephonenumber")
    private String phoneNumber;

    /**
     * 邮件
     */
    @JsonProperty("mail")
    private String mail;

    /**
     * 身份证类型
     */
    @JsonProperty("idcardtype")
    private String idCardType;

    /**
     * 创建时间
     */
    @JsonProperty("createtime")
    private String createTime;

    /**
     * ext属性
     */
    @JsonProperty("extproperties")
    private List<String> extProperties;

    /**
     * 省用户ext数据
     */
    private ProvinceUserExtDataDto provinceUserExtDataDto;

    /**
     * 身份证号码
     */
    @JsonProperty("idcardnumber")
    private String idCardNumber;

    /**
     * 用户id代码
     */
    @JsonProperty("useridcode")
    private String userIdCode;

    /**
     * 身份验证代码行
     */
    @JsonProperty("authloc")
    private String authLoc;

    /**
     * 信誉级别账户
     */
    @JsonProperty("creditable_level_of_account_way")
    private String creditableLevelOfAccountWay;

    /**
     * 信誉级别账户
     */
    @JsonProperty("creditable_level_of_account")
    private String creditableLevelOfAccount;

    /**
     * flag
     */
    @JsonProperty("anon_flag")
    private boolean anonFlag;

    /**
     * 联系人名字
     */
    @JsonProperty("link_person_name")
    private String linkPersonName;
}

/**
 * 省用户可信dto
 *
 * @author 赖卓成
 * @date 2023/02/22
 */
@Data
public class ProvinceUserCreditableLevelDto {

    /**
     * 账户可信级别以及核验方式字符串
     */
    @JsonProperty("creditable_level_of_account_way")
    private String creditableLevelOfAccountWay;

    /**
     * 账户当前最高的可信等级
     */
    @JsonProperty("creditable_level_of_account")
    private String creditableLevelOfAccount;

    /**
     * 账户可信等级的核验方式明细
     */
    @JsonProperty("creditable_level_of_account_way_list")
    private List<ProvinceCreditableLevelOfAccountWayListDto> creditableLevelOfAccountWayList;

}

/**
 * 诚信信息
 *
 * @author 赖卓成
 * @date 2023/02/22
 */
public class ProvinceCreditableLevelOfAccountWayListDto {

    /**
     * 认证时间
     */
    @JsonProperty("auth_time")
    private String authTime;

    /**
     * 用户名
     */
    @JsonProperty("user_name")
    private String userName;

    /**
     * 认证标识
     */
    @JsonProperty("auth_identification")
    private String authIdentification;

    /**
     * 身份级别
     */
    @JsonProperty("identity_level")
    private String identityLevel;

    /**
     * 证书号
     */
    @JsonProperty("credential_no")
    private String credentialNo;

    /**
     * 方法代码
     */
    @JsonProperty("way_code")
    private String wayCode;

    /**
     * 惟一id
     */
    @JsonProperty("uniqueid")
    private String uniqueId;
}

/**
 * 省厅认证获取令牌返回对象
 *
 * @author 赖卓成
 * @date 2023/02/21
 */
@Data
public class ProvinceTokenDto implements Serializable {

    private static final long serialVersionUID = 8083867889121871276L;

    /**
     * 有效期
     */
    @JsonProperty("expires_in")
    public String expiresIn;

    /**
     * 令牌类型
     */
    @JsonProperty("token_type")
    public String tokenType;

    /**
     * 访问令牌
     */
    @JsonProperty("access_token")
    public String accessToken;

    /**
     * 刷新令牌
     */
    @JsonProperty("refresh_token")
    public String refreshToken;

    /**
     * 范围
     */
    @JsonProperty("scope")
    public String scope;
}
Loading...