RestTemplate设置动态token

x33g5p2x  于2021-12-06 转载在 其他  
字(5.2k)|赞(0)|评价(0)|浏览(336)

前言

这里服务之间调用使用的是RestTemplate,因为在某些特殊的场景下RestTemplate相比FeignDubbo来说也是有它的方便之处的,这里我就不细说了,知道这里用的RestTemplate来调用上游微服务就可以了

为什么需要动态获取token?

我们在调用上游服务时大多数情况是需要认证的,这时我们是需要把认证信息(这里是token)放到请求头header里。但是我们肯定不能把token字符串写死,因为token一般都是有过期时间的。
那我们该怎么办,每次向上游服务请求时都先获取一下新的token?
这样token确实动态了,但是每次请求都获取一次token、生成一个新的token,每生成一次token都会把新的token放到Redis服务器上的,这样一来就有点浪费Redis的内存了,虽然token都会过期、会移除,但那也挡不住你每次向上游服务都发送请求都生成新的token往Redis里塞得快呀,这样太浪费Redis内存空间了。

解决思路

我们可以自定义一个XxxRestTemplate类继承一下RestTemplate,重写RestTemplate的exchange方法。

@Component
public class FireFlyRestTemplate extends RestTemplate {
    
    public static final String CODE_401 = "401";
    private static final Logger log = LoggerFactory.getLogger(FireFlyRestTemplate.class);
    

    @Autowired
    private TokenProvider tokenProvider;

    @Override
    public <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity,
                                          Class<T> responseType, Object... uriVariables) throws RestClientException {
        log.info("【FireFlyRestTemplate.exchange】");
        ResponseEntity<T> result = super.exchange(url, method, requestEntity, responseType, uriVariables);
        T body = result.getBody();
        // 强转成JSONObject类型
        JSONObject jsonResult = (JSONObject) body;
        boolean isUnAuthentication = CODE_401.equals(jsonResult.getString("code"));
        // 如果返回的code是401
        if (isUnAuthentication) {
            log.info("【401:token过期了】");
            // 刷新本地缓存里的token
            tokenProvider.refreshToken(1000*60*10L);
            // 利用新的token发送请求
            return super.exchange(url, method, tokenProvider.createNewEntity(requestEntity), responseType, uriVariables);
        }
        return result;
    }
}

请看重写的exchange的代码,有如下几个步骤:

  1. 首先调用父类的exchange方法,根据传来的参数发送请求
  2. 获取请求返回的结果,随后我将返回结果转成JSONObject类型、并获取里面的code这个key是因为我们公司的编程习惯,一般返回的结果都是JSON类型、且都有code这个字段(这也是这个代码的局限性)
  3. 根据获取的code进行判断,如果code为401,那么很显然是你的token过期了、或者没传toekn,我就又重新调用exchage(父类的exchage)发送了一个请求,不过这里我传的HttpEntity是tokenProvider.createNewEntity(requestEntity),这里tokenProvider是我自己写的工具类,实现效果就是往HttpEntity对象里的header里加上新的token。
  4. 回到第3步,如果code不是401,那么就直接返回前面返回的结果

接下来看一下TokenProvider这个工具类:

@Component
public class TokenProvider {
    private static final Logger log = LoggerFactory.getLogger(TokenProvider.class);
    private static final String TOKEN_STR = "token";

    @Autowired
    private RestTemplate restTemplate;

    @Value("${api.firefly.url}")
    private String BASE_URL;

    @Value("${api.firefly.auth-username}")
    private String autUsername;

    @Value("${api.firefly.auth-password}")
    private String authPassword;

    @Value("${auth.defaultTokenExpireTime}")
    private Long defaultTokenExpireTime;

    /** * 获取新的token * @return */
    public String getNewToken() {
        log.info("【getNewToken】");
        // 先拿到 公钥
        String publicKey = this.getPublicKey();
        String encryptedPassword = "";
        // 对密码进行加密
        try {
            // 利用公钥 使用RSA算法为密码进行加密
            encryptedPassword = RSAUtils.encrypt(authPassword, publicKey);
        } catch (Exception e) {
            log.info("密码加密失败");
            e.printStackTrace();
        }
        // 向上游服务发送请求、获取token。 参数:用户名、加密后的密码
        JSONObject loginResult = this.login(autUsername, encryptedPassword);
        return loginResult.getString(TOKEN_STR);
    }

    /** * 请求上游服务获取 公钥 * @return */
    private String getPublicKey() {
        String FULL_URL = BASE_URL + "/getPublicKey";
        ResponseEntity<JSONObject> result = restTemplate.exchange(FULL_URL, HttpMethod.GET, null, JSONObject.class);
        return result.getBody().getString("msg");
    }

    /** * 真正发起请求获取token的方法 * @param username * @param password * @return */
    public JSONObject login(String username, String password) {
        log.info("【login】");
        String FULL_URL = BASE_URL + "/login";
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("username", username);
        requestBody.put("password", password);
        HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<>(requestBody, null);
        ResponseEntity<JSONObject> result = restTemplate.exchange(FULL_URL, HttpMethod.POST, httpEntity, JSONObject.class);
        return result.getBody();
    }

    /** * 1、请求获取新的token * 2、并将其设置到LocalCacheUtil中 */
    public void refreshToken(Long expireTime) {
        log.info("【refreshToken】");
        // 获取新token
        String newToken = getNewToken();
        // 将获取的新token设置到缓存类(LocalCacheUtil)中
        if (expireTime == 0) expireTime = defaultTokenExpireTime;
        LocalCacheUtil.set(TOKEN_STR, newToken, expireTime);
    }

    /** * 从缓存中获取token,这个方法只能保重可以从本地缓存中拿到token,但是获取的token可能对上游服务来说是过期的。 * @return */
    public String getTokenFromCache() {
        log.info("【getTokenFromCache】");
        Object token = LocalCacheUtil.get(TOKEN_STR);
        // 如果拿到的数据为null 则说明cache中的token已经过期不存在了,要重新请求一次token放到缓存中,再从缓存中获取token
        if (token == null) {
            refreshToken(1000*60*10L);
            // 再次从缓存中获取token
            token = LocalCacheUtil.get(TOKEN_STR);
        }
        return (String)token;
    }

    /** * 根据老的HttpEntity 获取新的 HttpEntity * @param requestEntity * @return */
    public HttpEntity<Object> createNewEntity(HttpEntity<?> requestEntity) {
        log.info("【createNewEntity】");
        Object body = requestEntity.getBody();
        HttpHeaders headers = new HttpHeaders();
        String token = (String)LocalCacheUtil.get(TOKEN_STR);
        headers.add("Authorization", token);
        HttpEntity<Object> newHttpEntity = new HttpEntity<>(body, headers);
        return newHttpEntity;
    }

}

TokenProvider这个工具类的主要有如下功能:

  • 从本地缓存中获取token的方法
  • 向上游服务发送获取token的请求的方法
  • 将获取的新的token存到本地缓存中(这里是本地缓存,可不是redis那个)怎么做本地缓存?你可以使用一些jar,这些有好多开源的jar可以下载;也可以自定义一个LocalCache类,里面加个静态的map集合,你可以把你要存到数据都放里面,这个你自己实现,这样数据就相当于存在JVM的方法区的常量池里了。我用的本地缓存是第二个方案
  • 刷新token,将新的token更新到本地缓存中的方法
  • createNewEntity方法将传过来的requestEntity封装一下,在header里面加入token

结语

上述代码直接拷贝下来是不能直接执行的,我只是提供一个方案,希望这篇文章能帮到你。

相关文章

微信公众号

最新文章

更多