系统鉴权流程及签名生成规则

背景

QBS将来会接入更多第三方应用,当第三方应用访问QBS系统资源时,须要通过QBS认证系统认证,认证经过以后可对QBS资源进行访问。安全

基于公网的HTTP请求存在被拦截,篡改,重发的可能,须要对用户请求参数信息进行有效保护。服务器

认证流程采用OAuth受权码方式,经过受权码获取access_token,经过access_token结合sign方式对请求入参进行加密。app

设计原则:

  • 轻量级;
  • 适合于异构系统(跨操做系统,多语言简易实现);
  • 易于开发;
  • 易于测试;
  • 易于部署;
  • 知足接口安全要求,无过分设计;

名词解释

OAuth测试

开放协议标准,容许第三方应用访问服务提供者的资源,而不须要将用户名,密码提供给第三方应用加密

QBS认证系统url

独立的QBS资源系统的认证系统,对第三方应用进行备案及受权访问及限流/防刷操作系统

QBS资源服务器设计

题库系统,包含试题,试卷等信息code

client_keyorm

每一个准备访问QBS资源系统的第三方应用,须要在QBS认证系统进行备案,client_key能够为应用名称

client_secret

第三方应用进行备案时生成的密钥,用于接口请求sign的生成

redirect_url

第三方应用进行备案时配置的应用重定向地址,为安全起见,认证经过重定向到此地址,不依赖请求中的redirect_url

code(受权码)

第三方应用经过获取此受权码进而获取access_token

access_token

第三方应用经过access_token进行QBS的资源访问

refresh_token

当access_token过时以后,可经过refresh_token进行新的access_token获取

sign

签名,用于对请求入参加密,具体生成方法见后面

简要方案

输入图片说明

方案详情

应用备案

第三方受权管理配置界面,配置参数: client_key: xxx// 第三方平台名称 client_secret:xxx // 为第三方平台分配的密钥 redirect_url: xxx.com/authxxx // QBS认证系统下发受权码的重定向地址

受权

获取受权码

入参: response_type: code // 固定值 client_key: xxx// 配置约定 state: xxx // 第三方应用当前状态,QBS认证系统不关心,原样返回

响应参数: grant_type: authorization_code // 固定值 code: xxx // 认证系统下发的受权码

获取access_token

入参: grant_type: authorization_code // 固定值 code: xxx // 受权码 client_key: xxx// 配置约定 client_secret: xxx // 配置约定

响应参数: access_token: aaa // 发放的access_token expire_in: 3600 // 过时时间 refresh_token: bbb // access_token过时后,经过refresh_token更新access_token

sign生成规则:

根据参数名=参数值(参数值首尾不能包含空格)的格式,按首字符字典顺序 (ASCII值大小)升序排序。若遇到相同首字符,则判断第二个字符。以此类推,待签名字符串须要。

以参数名1=参数值1&参数名2=参数值2...&参数名N=参数值N的规则进行拼接。 参数首尾加上备案生成的client_secret, 最后得出的字符串进行md5Hex得出sign。

例子:

  • 有c=3,b=2,a=1 三个参
  • 把参数名和参数值连接组成字符串, a1b2c3timestamp12345678
  • 用申请获得的client_secret连接到拼接字符串的头部和尾部,而后进行md5Hex,最后获得加密摘要转换成大写,

示例:md5(client_secreta1b2c3timestamp12345678client_secret),取得md5Hex摘要值 C5F3EB5D7DC2748AED89E90AF00081E6 。

接口入参参考:

例子:

获取试题详情,HTTP请求入参:

{
client_key:xxx,
access_token: xxx //生成的资源访问access_token
sign:xxx // 加密校验值,生成方式见下面

questionId:1 //请求核心参数
}

实体类

# ClientKey

    private int id;
    private String company;
    private String clientKey;
    private String clientSecret;
    private String redirectUrl;
    private Date updateAt;
# 受权码

    private int id;
    private String clientKey;
    private String redirectUrl;
    private String code;
    private Date createdAt;
    private Date expiresAt;
# accessToken

    private int id;
    private String clientKey;
    private String redirectUrl;
    private String accessToken;
    private String refreshToken;
    private Date createdAt;
    private Date expiresAt;
    private Date refreshTokenExpiresAt;
# refreshToken

    private int id;
    private String clientKey;
    private String refreshToken;
    private Date refreshTokenExpiresAt;

获取受权码

/**
     * 获取受权码
     *
     * @param client_key
     * @param redirect_url
     * @param response_type
     * @param state
     * @throws IOException
     */
    @GetMapping("code")
    public RetDTO code(
            @RequestParam("response_type") String response_type, // 固定值 code
            @RequestParam("client_key") String client_key, // 配置项
            @RequestParam("redirect_url") String redirect_url, // 配置项
            @RequestParam("state") String state // 客户端状态,原样返回
    ) {
        if (!StringUtils.isEmpty(response_type.trim())
                && ("code").equals(response_type.trim())
                && !StringUtils.isEmpty(client_key.trim())
                && !StringUtils.isEmpty(redirect_url.trim())) {
            try {
                AuthClientCode code = clientService.createCode(client_key, redirect_url);
                if (null != code) { // 20 分钟
                    String redirectUrl = String.format(REDIRECT_URL, code.getRedirectUrl(), state, code.getCode());
                    logger.info("response_type: {}, client_key: {}, redirect_url: {}, code: {}", response_type, client_key, redirect_url, code);
                    return RetDTO.getSuccess(redirectUrl);
                }
            } catch (Exception e) {
                logger.error("response_type: {}, client_key: {}, redirect_url: {}, e: {}", response_type, client_key, redirect_url, e.getMessage());
                throw new BizException(e.getMessage());
            }
        }
        logger.info("response_type: {}, client_key: {}, redirect_url: {}, 获取受权码失败", response_type, client_key, redirect_url);
        return new RetDTO(HttpStatus.OK.value(), "获取受权码失败");
    }

获取access_token

/**
     * 获取access_token
     *
     * @param grant_type
     * @param code
     * @param client_key
     * @param redirect_url
     * @return
     */
    @PostMapping("access-token")
    public RetDTO accessToken(
            @RequestParam("grant_type") String grant_type, // 固定值 authorization_code
            @RequestParam("code") String code, // 受权码
            @RequestParam("client_key") String client_key, // 配置项
            @RequestParam("redirect_url") String redirect_url // 配置项
    ) {
        // code,client_key,redirect_uri,确认无误后,发放令牌access_token
        if (!StringUtils.isEmpty(grant_type.trim())
                && ("authorization_code").equals(grant_type.trim())
                && !StringUtils.isEmpty(code.trim())
                && !StringUtils.isEmpty(client_key.trim())
                && !StringUtils.isEmpty(redirect_url.trim())) {

            try {
                String clientCode = clientService.getCode(client_key, redirect_url);
                if("".equals(clientCode)){
                    return new RetDTO(HttpStatus.OK.value(), "备案信息无效");
                }
                if (code.equals(clientCode)) { // 受权码有效,生成access_token
                    AuthClientAccessToken authClientAccessToken = clientService.createAccessToken(client_key, redirect_url, code);
                    logger.info("grant_type: {}, client_key: {}, redirect_url: {}, code: {}, access_token: {}", grant_type, client_key, redirect_url, code, authClientAccessToken.getAccessToken());
                    return RetDTO.getSuccess(authClientAccessToken);
                }
            } catch (Exception e) {
                logger.error("grant_type: {}, client_key: {}, redirect_url: {}, code: {}, e: {}", grant_type, client_key, redirect_url, code, e.getMessage());
                throw new BizException(e.getMessage());
            }
        }
        logger.info("grant_type: {}, client_key: {}, redirect_url: {}, code: {}, 受权码无效", grant_type, client_key, redirect_url, code);
        return new RetDTO(HttpStatus.OK.value(), "受权码无效");
    }

更新access_token

/**
     * 更新token
     *
     * @param grant_type
     * @param refresh_token
     * @param client_key
     * @param redirect_url
     * @return
     */
    @PostMapping("refresh-token")
    public RetDTO refresh_token(
            @RequestParam("grant_type") String grant_type, // 固定值 authorization_code
            @RequestParam("refresh_token") String refresh_token, // refresh_token
            @RequestParam("client_key") String client_key, // 配置项
            @RequestParam("redirect_url") String redirect_url // 配置项
    ) {
        // refresh_token,client_key,redirect_uri,确认无误后,发放令牌access_token
        if (!StringUtils.isEmpty(grant_type.trim())
                && ("authorization_code").equals(grant_type.trim())
                && !StringUtils.isEmpty(refresh_token.trim())
                && !StringUtils.isEmpty(client_key.trim())
                && !StringUtils.isEmpty(redirect_url.trim())) {
            try {
                String refreshToken = clientService.getRefreshToken(client_key, redirect_url);
                if (!("").equals(refreshToken) && refresh_token.equals(refreshToken)) { // 受权码有效,生成access_token
                    AuthClientAccessToken authClientAccessToken = clientService.createAccessToken(client_key, redirect_url, refreshToken);
                    logger.info("grant_type: {}, client_key: {}, redirect_url: {}, refresh_token: {}, 新access_token: {}", grant_type, client_key, redirect_url, refresh_token, authClientAccessToken.getAccessToken());
                    return RetDTO.getSuccess(authClientAccessToken);
                } else {
                    return new RetDTO(HttpStatus.OK.value(), "refresh_token无效");
                }
            } catch (Exception e) {
                logger.info("grant_type: {}, client_key: {}, redirect_url: {}, refresh_token: {}, e: {}", grant_type, client_key, redirect_url, refresh_token, e.getMessage());
                throw new BizException(e.getMessage());
            }
        }
        logger.info("grant_type: {}, client_key: {}, redirect_url: {}, refresh_token: {}, access_token更新失败", grant_type, client_key, redirect_url, refresh_token);
        return new RetDTO(HttpStatus.OK.value(), "refresh_token更新失败");
    }
相关文章
相关标签/搜索