栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

RestTemplate设置动态token

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

RestTemplate设置动态token

RestTemplate设置动态token
  • 前言
  • 为什么需要动态获取token?
  • 解决思路
  • 结语

前言

这里服务之间调用使用的是RestTemplate,因为在某些特殊的场景下RestTemplate相比Feign和Dubbo来说也是有它的方便之处的,这里我就不细说了,知道这里用的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  ResponseEntity exchange(String url, HttpMethod method, HttpEntity requestEntity,
                                          Class responseType, Object... uriVariables) throws RestClientException {
        log.info("【FireFlyRestTemplate.exchange】");
        ResponseEntity 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;


    
    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);
    }

    
    private String getPublicKey() {
        String FULL_URL = base_URL + "/getPublicKey";
        ResponseEntity result = restTemplate.exchange(FULL_URL, HttpMethod.GET, null, JSONObject.class);
        return result.getBody().getString("msg");
    }

    
    public JSonObject login(String username, String password) {
        log.info("【login】");
        String FULL_URL = base_URL + "/login";
        Map requestBody = new HashMap<>();
        requestBody.put("username", username);
        requestBody.put("password", password);
        HttpEntity> httpEntity = new HttpEntity<>(requestBody, null);
        ResponseEntity result = restTemplate.exchange(FULL_URL, HttpMethod.POST, httpEntity, JSONObject.class);
        return result.getBody();
    }

    
    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);
    }

    
    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;
    }

    
    public HttpEntity 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 newHttpEntity = new HttpEntity<>(body, headers);
        return newHttpEntity;
    }

}
 

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

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

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

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号