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

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

背景

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

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

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

设计原则:

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

名词解释

OAuth

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

QBS认证系统

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

QBS资源服务器

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

client_key

每个准备访问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

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

简要方案

系统鉴权流程及签名生成规则_第1张图片

方案详情

应用备案

第三方授权管理配置界面,配置参数: 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更新失败");
    }

转载于:https://my.oschina.net/u/1000241/blog/1582160

你可能感兴趣的:(java,操作系统,python)