- 前言
- 为什么需要动态获取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的代码,有如下几个步骤:
- 首先调用父类的exchange方法,根据传来的参数发送请求
- 获取请求返回的结果,随后我将返回结果转成JSONObject类型、并获取里面的code这个key是因为我们公司的编程习惯,一般返回的结果都是JSON类型、且都有code这个字段(这也是这个代码的局限性)
- 根据获取的code进行判断,如果code为401,那么很显然是你的token过期了、或者没传toekn,我就又重新调用exchage(父类的exchage)发送了一个请求,不过这里我传的HttpEntity是tokenProvider.createNewEntity(requestEntity),这里tokenProvider是我自己写的工具类,实现效果就是往HttpEntity对象里的header里加上新的token。
- 回到第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
TokenProvider这个工具类的主要有如下功能:
- 从本地缓存中获取token的方法
- 向上游服务发送获取token的请求的方法
- 将获取的新的token存到本地缓存中(这里是本地缓存,可不是redis那个)怎么做本地缓存?你可以使用一些jar,这些有好多开源的jar可以下载;也可以自定义一个LocalCache类,里面加个静态的map集合,你可以把你要存到数据都放里面,这个你自己实现,这样数据就相当于存在JVM的方法区的常量池里了。我用的本地缓存是第二个方案
- 刷新token,将新的token更新到本地缓存中的方法
- createNewEntity方法将传过来的requestEntity封装一下,在header里面加入token
上述代码直接拷贝下来是不能直接执行的,我只是提供一个方案,希望这篇文章能帮到你。



