基于JWT Token自动刷新机制维持长时间有效的美团开放平台访问凭证

基于JWT Token自动刷新机制维持长时间有效的美团开放平台访问凭证

美团开放平台采用OAuth 2.0协议颁发短期Access Token(通常有效期2小时),并提供Refresh Token用于获取新凭证。然而在高可用系统中,若每次API调用都同步刷新Token,将引入性能瓶颈与竞态风险。本文基于baodanbao.com.cn.*包结构,设计一种基于JWT解析+后台预刷新的机制,在保证线程安全的前提下实现无缝凭证续期。

1. 美团Token结构与刷新逻辑

美团返回的Token响应示例:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxxx",
  "expires_in": 7200,
  "refresh_token": "rtk_abc123def456",
  "scope": "food.order"
}

其中access_token为JWT格式,可通过解析其Payload中的exp字段获知精确过期时间,避免依赖expires_in的估算误差。
在这里插入图片描述

2. Token信息封装类

package baodanbao.com.cn.model.token;

import java.time.Instant;

public class MeituanToken {
    private String accessToken;
    private String refreshToken;
    private long expireAt; // Unix timestamp

    public boolean isExpired() {
        return System.currentTimeMillis() >= expireAt * 1000;
    }

    public boolean isAboutToExpire(long bufferSeconds) {
        return System.currentTimeMillis() >= (expireAt - bufferSeconds) * 1000;
    }

    // getters and setters
}

3. JWT解析工具类

使用jjwt库解析exp

package baodanbao.com.cn.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

public class JwtUtil {

    public static long getExpireAt(String jwtToken) {
        // 美团Token无签名验证需求,仅解析Payload
        Claims claims = Jwts.parserBuilder()
                .setAllowedClockSkewSeconds(30)
                .build()
                .parseClaimsJws(jwtToken)
                .getBody();
        return claims.getExpiration().toInstant().getEpochSecond();
    }
}

4. 线程安全的Token管理器

采用双重检查锁 + volatile 保证单例更新:

package baodanbao.com.cn.service.token;

import baodanbao.com.cn.model.token.MeituanToken;
import baodanbao.com.cn.util.JwtUtil;
import org.springframework.stereotype.Service;

@Service
public class MeituanTokenManager {

    private volatile MeituanToken currentToken;

    private final MeituanTokenClient tokenClient;

    public MeituanTokenManager(MeituanTokenClient tokenClient) {
        this.tokenClient = tokenClient;
    }

    public String getValidAccessToken() {
        MeituanToken token = currentToken;
        if (token == null || token.isAboutToExpire(300)) { // 提前5分钟刷新
            synchronized (this) {
                token = currentToken;
                if (token == null || token.isAboutToExpire(300)) {
                    refreshToken();
                    token = currentToken;
                }
            }
        }
        return token.getAccessToken();
    }

    private void refreshToken() {
        MeituanToken oldToken = currentToken;
        MeituanToken newToken;
        if (oldToken == null) {
            // 首次初始化:使用client_credential模式
            newToken = tokenClient.fetchTokenByClientCredential();
        } else {
            // 使用refresh_token续期
            newToken = tokenClient.refreshAccessToken(oldToken.getRefreshToken());
        }
        // 解析JWT获取精确过期时间
        long expireAt = JwtUtil.getExpireAt(newToken.getAccessToken());
        newToken.setExpireAt(expireAt);
        currentToken = newToken;
    }
}

5. Token客户端实现(调用美团接口)

package baodanbao.com.cn.service.token;

import baodanbao.com.cn.model.token.MeituanToken;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

@Component
public class MeituanTokenClient {

    private final RestTemplate restTemplate;
    private static final String TOKEN_URL = "https://openapi.meituan.com/oauth/access_token";

    public MeituanTokenClient(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public MeituanToken fetchTokenByClientCredential() {
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type", "client_credentials");
        params.add("client_id", "YOUR_APP_KEY");
        params.add("client_secret", "YOUR_APP_SECRET");
        return postForToken(params);
    }

    public MeituanToken refreshAccessToken(String refreshToken) {
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type", "refresh_token");
        params.add("refresh_token", refreshToken);
        params.add("client_id", "YOUR_APP_KEY");
        params.add("client_secret", "YOUR_APP_SECRET");
        return postForToken(params);
    }

    private MeituanToken postForToken(MultiValueMap<String, String> params) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);

        ResponseEntity<MeituanToken> response = restTemplate.postForEntity(TOKEN_URL, request, MeituanToken.class);
        if (!response.getStatusCode().is2xxSuccessful()) {
            throw new RuntimeException("获取美团Token失败: " + response.getStatusCode());
        }
        return response.getBody();
    }
}

6. 在API调用中透明使用

package baodanbao.com.cn.service.meituan;

import baodanbao.com.cn.service.token.MeituanTokenManager;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class MeituanOrderService {

    private final RestTemplate restTemplate;
    private final MeituanTokenManager tokenManager;

    public MeituanOrderService(RestTemplate restTemplate, MeituanTokenManager tokenManager) {
        this.restTemplate = restTemplate;
        this.tokenManager = tokenManager;
    }

    public String queryOrder(String orderId) {
        String accessToken = tokenManager.getValidAccessToken();
        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(accessToken);
        HttpEntity<Void> entity = new HttpEntity<>(headers);

        ResponseEntity<String> resp = restTemplate.exchange(
            "https://openapi.meituan.com/v1/order/" + orderId,
            HttpMethod.GET, entity, String.class
        );
        return resp.getBody();
    }
}

7. 异常处理与降级

若刷新失败(如Refresh Token过期),应触发重新授权流程或告警,但不影响当前请求立即失败。可在refreshToken()中加入重试与日志:

try {
    newToken = tokenClient.refreshAccessToken(oldToken.getRefreshToken());
} catch (Exception e) {
    log.error("刷新美团Token失败,尝试重新授权", e);
    newToken = tokenClient.fetchTokenByClientCredential(); // 降级
}

本文著作权归吃喝不愁app开发者团队,转载请注明出处!

### JWT Token 刷新机制实现及其最佳实践 JSON Web Tokens (JWTs) 是一种广泛使用的标准,用于安全传输信息作为 JSON 对象。为了处理访问令牌的有效期问题并增强安全性,通常会引入刷新令牌(Refresh Token)。以下是关于如何实现 JWT 的刷新机制以及一些最佳实践。 #### 实现 JWT Refresh Mechanism 1. **双令牌体系结构** 使用两个独立的令牌:一个是短期有效访问令牌(Access Token),另一个是长期有效或具有较长时间窗口的刷新令牌(Refresh Token)。当访问令牌过期时,客户端可以使用刷新令牌请求新的访问令牌[^1]。 2. **刷新流程描述** 客户端向服务器发送带有刷新令牌的 HTTP 请求到特定的 `/refresh` 或类似的端点。如果验证通过,则返回一个新的访问令牌和可能更新后的刷新令牌[^2]。 3. **存储方式** - 访问令牌可以直接保存在内存中或者本地存储里。 - 刷新令牌建议更安全地存放在 HttpOnly Cookie 中或其他受保护的地方以防止 XSS 攻击[^4]。 #### 最佳实践 1. **设置合理的生命周期** - 短期内允许访问资源的访问令牌应保持短寿命(几分钟至几小时之间)。 - 长时间存在的刷新令牌则需谨慎设定有效期,并定期轮换其值以防泄露风险增加。 2. **加强刷新令牌的安全措施** - 黑名单/撤销列表管理已注销或被盗取的刷新令牌实例。 - 增加额外的身份验证因子如设备指纹识别来确认发起者身份的真实性。 3. **采用 HTTPS 协议通信** 所有涉及敏感数据交换的过程都必须加密传输渠道以免被中间人窃听截获重要凭证信息. ```javascript // Example Code Snippet for refreshing token using Node.js & Express framework. const express = require('express'); const jwt = require('jsonwebtoken'); let app = express(); app.post('/auth/token', function(req, res){ const refreshToken = req.body.token; if (!isValidRefreshToken(refreshToken)) { return res.sendStatus(403); // Forbidden } try{ let payload = verifyAndDecode(refreshToken); var newAccessToken = generateNewAccessToken(payload); res.json({accessToken:newAccessToken}); }catch(err){ console.error("Error during token refresh:", err.message); res.status(500).send(); } }); function isValidRefreshToken(token){ /* Implementation */ }; function verifyAndDecode(token){ /* Implementation */}; function generateNewAccessToken(data){/*Implementation*/}; module.exports=app; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值