JWT Redis方式实现
1、Token dto
public class Token implements Serializable {
private static final long serialVersionUID = 6314027741784310221L;
private String token;
//登录时间戳
private Long loginTime;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Long getLoginTime() {
return loginTime;
}
public void setLoginTime(Long loginTime) {
this.loginTime = loginTime;
}
public Token(String token, Long loginTime) {
super();
this.token = token;
this.loginTime = loginTime;
}
}
4、LoginUser Dto
public class LoginUser extends SysUser implements UserDetails {
private static final long serialVersionUID = -1379274258881257107L;
private List<Permission> permissions;
private String token;
//登录时间戳(毫秒)
private Long LoginTime;
//过期时间戳
private Long expireTime;
public List<Permission> getPermissions() {
return permissions;
}
public void setPermissions(List<Permission> permissions) {
this.permissions = permissions;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Long getLoginTime() {
return LoginTime;
}
public void setLoginTime(Long loginTime) {
LoginTime = loginTime;
}
public Long getExpireTime() {
return expireTime;
}
public void setExpireTime(Long expireTime) {
this.expireTime = expireTime;
}
@Override
@JsonIgnore //返回的json数据即不包含该属性
public Collection<? extends GrantedAuthority> getAuthorities() {
return permissions.parallelStream().filter(p -> !StringUtils.isEmpty(p.getPermission()))
.map(p -> new SimpleGrantedAuthority(p.getPermission())).collect(Collectors.toSet());
}
public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
// do nothing
}
/**
* 判断账号是否过期
* @return
*/
@Override
@JsonIgnore
public boolean isAccountNonExpired() {
return true;
}
/**
* 判断账号是否锁定
* @return
*/
@Override
@JsonIgnore
public boolean isAccountNonLocked() {
return getStatus() != Status.LOCKED;
}
/**
* 判断密码是否过期
* @return
*/
@Override
@JsonIgnore
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 判断账号是否激活
* @return
*/
@Override
public boolean isEnabled() {
return true;
}
}
3、TokenServiceJwtImpl
这里主要是逻辑实现 详细说明一下
jwt的构成【转】原文地址
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)。
header
jwt的头部承载两部分信息:
声明类型,这里是jwt
声明加密的算法 通常直接使用 HMAC SHA256
完整的头部就像下面这样的JSON:
{
"typ": "JWT",
"alg": "HS256"
}
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
playload
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
标准中注册的声明
公共的声明
私有的声明
标准中注册的声明 (建议但不强制使用) :
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
定义一个payload:
{
"name":"Free码农",
"age":"28",
"org":"今日头条"
}
然后将其进行base64加密,得到Jwt的第二部分:
eyJvcmciOiLku4rml6XlpLTmnaEiLCJuYW1lIjoiRnJlZeeggeWGnCIsImV4cCI6MTUxNDM1NjEwMywiaWF0IjoxNTE0MzU2MDQzLCJhZ2UiOiIyOCJ9
signature
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分:
49UF72vSkj-sA4aHHiYN5eoZ9Nb4w5Vb45PsLF7x_NY
密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和验证,所以需要保护好。
具体实现
这里我们从配置文件中获取token的过期秒数和私钥。设置一个字符串LOGIN_USER_KEY作为找到登录用户的凭证。//token过期秒数 @Value("${token.expire.seconds}") private Integer expireSeconds; @Autowired private RedisTemplate<String, LoginUser> redisTemplate; @Autowired private SysLogService logService; //私钥 @Value("${token.jwtSecret}") private String jwtSecret; private static Key KEY = null; private static final String LOGIN_USER_KEY = "LOGIN_USER_KEY";
按照上文所说的加密方式进行加密生成token
private String createJWTToken(LoginUser loginUser){ Map<String, Object> claims = new HashMap<>(); claims.put(LOGIN_USER_KEY, loginUser.getToken());// 放入一个随机字符串,通过该串可找到登录用户 String jwtToken = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, getKeyInstance()) .compact(); return jwtToken; }
private Key getKeyInstance() { if (KEY == null) { synchronized (TokenServiceJwtImpl.class) { if (KEY == null) {//双重锁 byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(jwtSecret); KEY = new SecretKeySpec(apiKeySecretBytes, SignatureAlgorithm.HS256.getJcaName()); } } } return KEY; }
将登录用户信息储存到redis中
private void cacheLoginUser(LoginUser loginUser) { loginUser.setLoginTime(System.currentTimeMillis()); loginUser.setExpireTime(loginUser.getLoginTime() + expireSeconds * 1000); //根据uuid将loginUser缓存 redisTemplate.boundValueOps(getTokenKey(loginUser.getToken())).set(loginUser,expireSeconds, TimeUnit.SECONDS); }之后我们可以根据token从redis获取登录用户的信息,进行判断用户登录是否过期等操作。