为什么要做双token?
双token系统通常用于提高安全性和分离不同级别的权限。在这种系统中,通常会有两个token:
Access Token
:这是用户直接使用来访问资源的token。它的有效期较短,一旦过期,用户需要重新认证来获取新的access token。这样做的好处是即使access token被泄露,由于其有效期短,攻击者利用它进行不当操作的时间窗口有限。Refresh Token
:Refresh token是用来在access token过期后重新获取新的access token的。它的有效期通常较长,甚至可以说是永久的。但是,refresh token通常不会直接发送给客户端,而是保存在服务器端。当需要刷新access token时,客户端通过提供refresh token来请求新的access token。
此外,使用双token的原因还包括:
- 安全性:通过短期的access token和长期的refresh token,可以在不影响用户体验的情况下,减少安全风险。即使access token被盗,由于其有效期短,损害可以被控制在一定范围内。
- 权限管理:双token系统可以更好地管理用户的权限。例如,在开放平台中,第三方应用可以通过refresh token来获取access token,而不需要知道用户的用户名和密码,这样既保证了用户信息的安全,又赋予了第三方应用一定的权限。
- 用户体验:双token系统可以在不影响用户体验的前提下,实现后台的安全管理和策略调整。用户不需要频繁登录,同时也能保证系统的安全性。
- 灵活性:在需要进行更细粒度的权限控制或者策略调整时,双token系统提供了更多的灵活性。例如,可以在不改变refresh token的情况下,调整access token的有效期或权限范围。
总之,双token系统是一种常见的安全设计模式,通过分离短期和长期凭证,以及用户直接使用的token和用于刷新的token,来提高系统的安全性和灵活性。这种设计特别适用于需要与第三方应用共享权限而又不泄露用户敏感信息的场景。
2 .流程图
3.前端代码实现 (不做要求,复制过去即可)
const refreshToken = async () => {
return await axios.get('/apis/bm-member-service-app/api/member/refresh_token', {
params: {
refreshToken: beimao_store.refreshToken
}
})
.then(function (response) {
const { code, msg, data } = response.data
if (code != 0) {
return Promise.reject(new Error(msg));
}
beimao_store.token = data.token;
beimao_store.refreshToken = data.refreshToken;
return Promise.resolve("成功");
})
.catch(function (error) {
return Promise.reject(new Error(error));
});
}
4.后端代码
public Map<String,String> login(LoginInfo loginInfo) {
Map<String, Object> map = new HashMap<String, Object>() {
private static final long serialVersionUID = 1L;
{
put("id", member.getId());
put("nickName", member.getNickName());
put("exp", System.currentTimeMillis()/1000 + 10); //access_token过期时间10s
}
};
String token = JWTUtil.createToken(map, key.getBytes());
Map<String, Object> map2 = new HashMap<String, Object>() {
private static final long serialVersionUID = 1L;
{
put("id", member.getId());
//过期时间一小时
put("exp", System.currentTimeMillis()/1000 + 10 * 60*6);//refreshToken过期时间1h
}
};
//创建刷新令牌refreshToken
String refreshToken = JWTUtil.createToken(map2, key.getBytes());
// 创建返回的登录结果Map对象
Map tokens = new HashMap<String, String>();
tokens.put("token", token);
tokens.put("refreshToken", refreshToken);
return tokens;
}
public Map<String, String> refreshToken(String refreshToken) {
if(ObjectUtil.isEmpty(refreshToken)){
throw new BizException(461,"refreshToken必传");
}
boolean b = false;
// 验证算法,JWTValidator包含过期的验证,验证比较全面
try {
JWTValidator.of(refreshToken).validateAlgorithm(JWTSignerUtil.hs256(key.getBytes())).validateDate();
b = true;
}catch (Exception ex){
ex.printStackTrace();
}
if(!b){
throw new BizException(462,"refreshToken不正确");
}
//能走到这说明refreshToken是我们之前下发的。
JSONObject jsonObject = JSONUtil.toBean(JWTUtil.parseToken(refreshToken).getPayload().toString(), JSONObject.class);
int id = Integer.parseInt(jsonObject.get("id").toString());
Member member = memberDao.selectById(id);
if (ObjectUtil.isEmpty(member)) {
log.error("手机号未注册");
throw new BizException(613, "手机号或密码错误");
}
Map<String, Object> map = new HashMap<String, Object>() {
private static final long serialVersionUID = 1L;
{
put("id", member.getId());
put("nickName", member.getNickName());
put("exp", System.currentTimeMillis()/1000 + 10); //access_token过期时间10s
}
};
String token = JWTUtil.createToken(map, key.getBytes());
Map<String, Object> map2 = new HashMap<String, Object>() {
private static final long serialVersionUID = 1L;
{
put("id", member.getId());
//过期时间一小时
put("exp", System.currentTimeMillis()/1000 + 10 * 60*6);//refreshToken过期时间1h
}
};
//创建刷新令牌refreshToken
String refreshToken2 = JWTUtil.createToken(map2, key.getBytes());
// 创建返回的登录结果Map对象
Map tokens = new HashMap<String, String>();
tokens.put("token", token);
tokens.put("refreshToken", refreshToken2);
return tokens;
}