API开发接口设计 采用微信accessToken授权方式

⼀、开放接⼝设计说明:

在开发微信授权登入,访问用户信息,就会发现,在微信开发平台调用接口的流程如下:

1. 在开发平台申请到 appid 和 app_secret 

2. 通过appid 和 app_secret 访问获取access_token(一般都要带一个时间戳请求timestamp)

 时间戳在线工具

3.通过获取的access_token去调用开发接口 (有效期2小时)

按照这个流程,我们通过Java代码实现一下

Action:

One: 数据库表设计(记录申请访问接口的对象)

CREATE TABLE `t_open_api` (
  `id` int(11) NOT NULL AUTO_INCREMENT, 
  `app_name` varchar(255) DEFAULT NULL,
  `app_id` varchar(255) DEFAULT NULL,
  `app_secret` varchar(255) DEFAULT NULL,
  `is_flag` varchar(255) DEFAULT NULL
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

// 设计 申请app名称,appid,appSecret,状态(is_flag -1停用 1使用) 

two: 生成Token的方式 

1. UUID.randomUUID().toString().replace("-", "");
2. 雪花算法: 使用一个64bit的long型的数字作为全局唯一ID 分布式系统中应用广泛

three: 生成appId+appSecret

Post请求api获取appId,appSecret

{

"appName": "测试app",  //申请名称

"phone": xxxx  //联系人

}

    @RequestMapping("/getApp")
    public R getApp(@Validated({AddGroup.class}) @RequestBody TOpenApi appId) {
        appId.setCreateDate(new Date());
        String idStr = IdWorker.getIdStr();
        String uuid = IdWorker.get32UUID();
        appId.setAppSecret(uuid);
        appId.setAppId(idStr);
        appId.setIsFlag(1);
        TOpenApi app = iTOpenApiService.getOne(new QueryWrapper<TOpenApi>().eq("app_name", appId.getAppName()).eq("phone", appId.getPhone()));
        // 同步redis中
        BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(appKey);
        if (app == null) {
            iTOpenApiService.save(appId);
            operations.put(appId.getAppId(), JSON.toJSONString(appId));
            return R.ok().put("app", appId);
        } else {
            operations.put(app.getAppId(), JSON.toJSONString(app));
            return R.ok().put("app", app);
        }
    }

这里加入了 JSR303校验,并统一返回异常提示

成功返回:appId,appSecret

校验失败提示:

four: 生成access_token

伪代码:1. Post请求传入appId(唯一)+appSecret

               2. 判断商户是否存在,(优化,数据量大,放入redis中 key为appId+appSecret)

               3. 生成AccessToken 更新Redis中AccessToken,2小时TLL

              如果是分布式系统,需要考虑给每个appId加锁 (1单获取到锁已经生成AccessToken就直接返回)

 @RequestMapping("/getAccessToken")
    public R getAccessToken(@Validated({AccessGroup.class}) @RequestBody TOpenApi appEntity) {
        //校验传来数据的有效性
        if (checkData(appEntity)) {
            return R.error("没有权限生成AccessToken");
        }
        // 1.生成新的AccessToken
        String accessToken = UUID.randomUUID().toString().replace("-", "");
        String accessKey = ACCESSKEY + appEntity.getAppId();
        if (redisTemplate.hasKey(accessKey)) {
            accessToken = redisTemplate.opsForValue().get(accessKey);
        } else {
            redisTemplate.opsForValue().set(accessKey, accessToken, timeToken, TimeUnit.SECONDS);
            redisTemplate.opsForValue().set(CHECKKEY + accessToken, appEntity.getAppId(), timeToken, TimeUnit.SECONDS);
        }
        return R.ok().put("accessToken", accessToken);
    }

    /**
     * 检查是否满足申请,1. 秘钥不一致,或者秘钥不存在 2 已经停用 都不能获取accessToken
     *
     * @param appEntity
     * @return
     */
    private boolean checkData(TOpenApi appEntity) {
        String appId = appEntity.getAppId();
        String appSecret = appEntity.getAppSecret();
        //获取redis中数据校验
        BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(appKey);
        String str = operations.get(appId);
        TOpenApi t = JSON.parseObject(str, TOpenApi.class);
        String secret = t.getAppSecret();
        if (!secret.equals(appSecret)) {
            //标识不满足
            return true;
        }
        //已经停用
        Integer flag = t.getIsFlag();
        if (1 != flag) {
            return true;
        }
        return false;
    }

成功返回:

失败返回(校验提示+拦截异常提示下面five):

five 添加拦截器 AccessTokenInterceptor 判断请求参数accessToken (定义范围)

      or 使用Aop 切入每个请求处理   (注解中加入范围)

package com.whm.code.demo.interceptor;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.whm.code.demo.entity.TOpenApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Slf4j
@Component
public class AccessTokenInterceptor implements HandlerInterceptor {

    private static final String CHECKKEY = "checkKey:";

    private static final String appKey = "appKey";

    @Autowired
    private StringRedisTemplate redisTemplate;

    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o)
            throws Exception {
        String uri = httpServletRequest.getRequestURI();
        AntPathMatcher antPathMatcher = new AntPathMatcher();
//        // 标识除了openApi/** 的接口都拦截  + 配置WebAppConfig  重写WebMvcConfigurer WebMvcConfigurer
//        boolean match = antPathMatcher.match("/openApi/**", uri);
//        if (match) {
//            return true;
//        }
        //accessToken 放入请求头中
        String accessToken = httpServletRequest.getHeader("accessToken");
        if (StringUtils.isEmpty(accessToken)) {
            // 返回错误消息
            resultError(" this is parameter accessToken null ", httpServletResponse);
            return false;
        }
        // 验证accessToken
        String appId = redisTemplate.opsForValue().get(CHECKKEY + accessToken);
        if (StrUtil.isEmpty(appId)) {
            resultError("this is bad accessToken", httpServletResponse);
            return false;
        }
        //判断appId的状态
        BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(appKey);
        String str = operations.get(appId);
        TOpenApi t = JSON.parseObject(str, TOpenApi.class);
        //已经停用
        Integer flag = t.getIsFlag();
        if (1 != flag) {
            resultError("this is bad accessToken", httpServletResponse);
            return false;
        }
        log.info("AccessTokenInterceptor---" + uri + "--访问成功");
        return true;
    }

    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o,
                           ModelAndView modelAndView) {
        log.info("AccessTokenInterceptor---处理请求完成后视图渲染之前的处理操作");
    }

    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                                Object o, Exception e) throws Exception {
        log.info("AccessTokenInterceptor---视图渲染之后的操作");
    }

    public void resultError(String errorMsg, HttpServletResponse httpServletResponse) throws IOException {
        PrintWriter printWriter = httpServletResponse.getWriter();
        // setResultError为封装的返回信息,请⾃定义
        printWriter.write(new JSONObject().toJSONString(errorMsg));
    }
}

 //配置拦截范围

@Configuration
public class MyWebConfig implements WebMvcConfigurer {

    @Autowired
    private AccessTokenInterceptor accessTokenInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //设置拦截的请求
        registry.addInterceptor(accessTokenInterceptor).addPathPatterns("/api/**");
    }
}

six 获取accessToken访问别的开发接口 平台验证accessToken是否有效,有效后可访问数据

这里可以做数据的加密,解密操作。RSA/ AES

1 /api/** 请求的请求头带入accesstoken

 2 /api/** 请求的请求头不带入accesstoken

这里也校验accessToken的有效性

3 其他api请求不用accessToken可直接访问

详细代码可留言邮箱。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值