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


    ⼀、开放接⼝设计说明:

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

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

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

     时间戳在线工具

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

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

    Action:

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

    1. CREATE TABLE `t_open_api` (
    2.   `id` int(11NOT NULL AUTO_INCREMENT, 
    3.   `app_name` varchar(255DEFAULT NULL,
    4.   `app_id` varchar(255DEFAULT NULL,
    5.   `app_secret` varchar(255DEFAULT NULL,
    6.   `is_flag` varchar(255DEFAULT NULL
    7.   PRIMARY KEY (`id`)
    8. ) 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  //联系人

    }

    1. @RequestMapping("/getApp")
    2. public R getApp(@Validated({AddGroup.class}) @RequestBody TOpenApi appId) {
    3. appId.setCreateDate(new Date());
    4. String idStr = IdWorker.getIdStr();
    5. String uuid = IdWorker.get32UUID();
    6. appId.setAppSecret(uuid);
    7. appId.setAppId(idStr);
    8. appId.setIsFlag(1);
    9. TOpenApi app = iTOpenApiService.getOne(new QueryWrapper().eq("app_name", appId.getAppName()).eq("phone", appId.getPhone()));
    10. // 同步redis中
    11. BoundHashOperations operations = redisTemplate.boundHashOps(appKey);
    12. if (app == null) {
    13. iTOpenApiService.save(appId);
    14. operations.put(appId.getAppId(), JSON.toJSONString(appId));
    15. return R.ok().put("app", appId);
    16. } else {
    17. operations.put(app.getAppId(), JSON.toJSONString(app));
    18. return R.ok().put("app", app);
    19. }
    20. }

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

    成功返回:appId,appSecret

    校验失败提示:

    four: 生成access_token

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

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

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

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

    1. @RequestMapping("/getAccessToken")
    2. public R getAccessToken(@Validated({AccessGroup.class}) @RequestBody TOpenApi appEntity) {
    3. //校验传来数据的有效性
    4. if (checkData(appEntity)) {
    5. return R.error("没有权限生成AccessToken");
    6. }
    7. // 1.生成新的AccessToken
    8. String accessToken = UUID.randomUUID().toString().replace("-", "");
    9. String accessKey = ACCESSKEY + appEntity.getAppId();
    10. if (redisTemplate.hasKey(accessKey)) {
    11. accessToken = redisTemplate.opsForValue().get(accessKey);
    12. } else {
    13. redisTemplate.opsForValue().set(accessKey, accessToken, timeToken, TimeUnit.SECONDS);
    14. redisTemplate.opsForValue().set(CHECKKEY + accessToken, appEntity.getAppId(), timeToken, TimeUnit.SECONDS);
    15. }
    16. return R.ok().put("accessToken", accessToken);
    17. }
    18. /**
    19. * 检查是否满足申请,1. 秘钥不一致,或者秘钥不存在 2 已经停用 都不能获取accessToken
    20. *
    21. * @param appEntity
    22. * @return
    23. */
    24. private boolean checkData(TOpenApi appEntity) {
    25. String appId = appEntity.getAppId();
    26. String appSecret = appEntity.getAppSecret();
    27. //获取redis中数据校验
    28. BoundHashOperations operations = redisTemplate.boundHashOps(appKey);
    29. String str = operations.get(appId);
    30. TOpenApi t = JSON.parseObject(str, TOpenApi.class);
    31. String secret = t.getAppSecret();
    32. if (!secret.equals(appSecret)) {
    33. //标识不满足
    34. return true;
    35. }
    36. //已经停用
    37. Integer flag = t.getIsFlag();
    38. if (1 != flag) {
    39. return true;
    40. }
    41. return false;
    42. }

    成功返回:

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

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

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

    1. package com.whm.code.demo.interceptor;
    2. import cn.hutool.core.util.StrUtil;
    3. import com.alibaba.fastjson.JSON;
    4. import com.alibaba.fastjson.JSONObject;
    5. import com.whm.code.demo.entity.TOpenApi;
    6. import lombok.extern.slf4j.Slf4j;
    7. import org.springframework.beans.factory.annotation.Autowired;
    8. import org.springframework.data.redis.core.BoundHashOperations;
    9. import org.springframework.data.redis.core.StringRedisTemplate;
    10. import org.springframework.stereotype.Component;
    11. import org.springframework.util.AntPathMatcher;
    12. import org.springframework.util.StringUtils;
    13. import org.springframework.web.servlet.HandlerInterceptor;
    14. import org.springframework.web.servlet.ModelAndView;
    15. import javax.servlet.http.HttpServletRequest;
    16. import javax.servlet.http.HttpServletResponse;
    17. import java.io.IOException;
    18. import java.io.PrintWriter;
    19. @Slf4j
    20. @Component
    21. public class AccessTokenInterceptor implements HandlerInterceptor {
    22. private static final String CHECKKEY = "checkKey:";
    23. private static final String appKey = "appKey";
    24. @Autowired
    25. private StringRedisTemplate redisTemplate;
    26. public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o)
    27. throws Exception {
    28. String uri = httpServletRequest.getRequestURI();
    29. AntPathMatcher antPathMatcher = new AntPathMatcher();
    30. // // 标识除了openApi/** 的接口都拦截 + 配置WebAppConfig 重写WebMvcConfigurer WebMvcConfigurer
    31. // boolean match = antPathMatcher.match("/openApi/**", uri);
    32. // if (match) {
    33. // return true;
    34. // }
    35. //accessToken 放入请求头中
    36. String accessToken = httpServletRequest.getHeader("accessToken");
    37. if (StringUtils.isEmpty(accessToken)) {
    38. // 返回错误消息
    39. resultError(" this is parameter accessToken null ", httpServletResponse);
    40. return false;
    41. }
    42. // 验证accessToken
    43. String appId = redisTemplate.opsForValue().get(CHECKKEY + accessToken);
    44. if (StrUtil.isEmpty(appId)) {
    45. resultError("this is bad accessToken", httpServletResponse);
    46. return false;
    47. }
    48. //判断appId的状态
    49. BoundHashOperations operations = redisTemplate.boundHashOps(appKey);
    50. String str = operations.get(appId);
    51. TOpenApi t = JSON.parseObject(str, TOpenApi.class);
    52. //已经停用
    53. Integer flag = t.getIsFlag();
    54. if (1 != flag) {
    55. resultError("this is bad accessToken", httpServletResponse);
    56. return false;
    57. }
    58. log.info("AccessTokenInterceptor---" + uri + "--访问成功");
    59. return true;
    60. }
    61. public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o,
    62. ModelAndView modelAndView) {
    63. log.info("AccessTokenInterceptor---处理请求完成后视图渲染之前的处理操作");
    64. }
    65. public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
    66. Object o, Exception e) throws Exception {
    67. log.info("AccessTokenInterceptor---视图渲染之后的操作");
    68. }
    69. public void resultError(String errorMsg, HttpServletResponse httpServletResponse) throws IOException {
    70. PrintWriter printWriter = httpServletResponse.getWriter();
    71. // setResultError为封装的返回信息,请⾃定义
    72. printWriter.write(new JSONObject().toJSONString(errorMsg));
    73. }
    74. }

     //配置拦截范围

    1. @Configuration
    2. public class MyWebConfig implements WebMvcConfigurer {
    3. @Autowired
    4. private AccessTokenInterceptor accessTokenInterceptor;
    5. @Override
    6. public void addInterceptors(InterceptorRegistry registry) {
    7. //设置拦截的请求
    8. registry.addInterceptor(accessTokenInterceptor).addPathPatterns("/api/**");
    9. }
    10. }

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

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

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

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

    这里也校验accessToken的有效性

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

    详细代码可留言邮箱。

  • 相关阅读:
    矩阵分析与应用+张贤达
    【vue】vue+easyPlayer 实现宫格布局及视频播放
    [重庆思庄每日技术分享]-ORA-16018 异常处理记录
    视频流PS打包方式详解
    《向量数据库指南》——Milvus Cloud和Elastic Cloud 特性对比
    2023年数维杯数学建模A题河流-地下水系统水体污染研究求解全过程文档及程序
    Leetcode力扣 MySQL数据库 1892 页面推荐
    【Datawhale AI 夏令营】讯飞“基于术语词典干预的机器翻译挑战赛”
    torch.cuda
    【每日训练】进制转换
  • 原文地址:https://blog.csdn.net/wu6cfp38/article/details/127915560