在开发微信授权登入,访问用户信息,就会发现,在微信开发平台调用接口的流程如下:
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
().eq("app_name", appId.getAppName()).eq("phone", appId.getPhone())); - // 同步redis中
- BoundHashOperations
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
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
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可直接访问

详细代码可留言邮箱。