Spring Boot后台脚手架搭建 [四] jwt redis方式实现

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获取登录用户的信息,进行判断用户登录是否过期等操作。



猜你喜欢

转载自blog.csdn.net/qq_36076256/article/details/80402795
今日推荐