[小设计] 之 Token 存储

1,221 阅读3分钟

一、概述

在前后端交互的过程中,校验权限和认证信息是必不可少的。

后端生成对应 token,通过 token 可以校验此 API 的权限,认证用户信息。

那么 token 需要满足哪些需求?

需求,例如:

  1. 简单的凭证:证明用户登录且时间内有效。
  2. 存储用户信息:能够通过 token 里的信息,确认某一用户,具有哪个权限。
  3. web 端只能登录一个:PC 端浏览器只能登录一个。
  4. 各个端只能登录一个:iOSAndroidPC 端均只能登录一个。

二、Token 生成方式

下面介绍,常见的生成方式:

  1. 存储在 Redis
  2. 使用 JWT

(1)存储在 Redis

1. 简单存储用户信息:

可以用 UUIDMD5 作为 token 例如:传给前端:812130c27bcbc997f37e714bbf108fc7

例如:

{
   "token:812130c27bcbc997f37e714bbf108fc7" : {
       "userId": "u_395479916231565",
       "username" : "卷卷啊",
       "isAdmin": true
   }
}

2. web 端只登录一个

即每次登录生成的 用户的tokenRedis 只能一个。 那么就要做好用户tokenkey 的关联。

主要是两点:

  1. 传递给前端的 tokenuserIdMD5 + 随机数
// 例如,对 u_395479916231565 MD5 + 随机数
be9c629efec1cde341d0ae38acc94758_5908f28aa73ffe56
  1. 后端根据 userIdMD5 拼接成key

根据 redis 里存储的随机数 跟 前端传递过来的random做对比。

// token:拼接
token:be9c629efec1cde341d0ae38acc94758

// redis中存储对应的:
{
   "token:be9c629efec1cde341d0ae38acc94758" : {
       "userId": "u_395479916231565",
       "username" : "卷卷啊",
       "isAdmin": true,
       "random": "5908f28aa73ffe56"
   }
}

3. 各端只能登录一个:iOSAndroidPC 端均只能登录一个。

处理方式,相似方式2, 只不过有 deviceId 再做鉴别。


(2)JWT

信息凭证存储在 JWT 中,可以作为简单的凭证。

敏感信息不能存储在 JWT,因为 JWT 并不保密。 可以将 JWT 放到此网站上查看相应的信息:jwt.io/#debugger

一个 JWT token 分三部分:

  1. 头部(header)
  2. 载荷(payload)
  3. 签证(signature)

部分之间用“.”号做分隔,例如:

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ5eWYiLCJleHAiOjE2MTAxMTYzMDYsImlhdCI6MTYxMDA5ODMwNn0.NLtpAo_mD_zcrXRWJa_AiWWnHjCvVtlHDSRDmpyrfXekpyfDsS6CUHwqEY1iFQ1pMUw04QHEjtrHWgX7fkmeRQ

Java 中使用如下:

  1. 加入 pom 配置
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
  1. 工具类
public class JwtTokenUtil {

    private static final long JWT_TOKEN_VALIDITY = 4 * 60 * 60 * 1000;

    private String secret = "secret";

    public Token getUserInfoFromToken(String token) {

        if (isTokenExpired(token)) {

            throw new ServerException(ErrorCode.UN_AUTH);
        }

        final Claims claims = getAllClaimsFromToken(token);

        return new Token(claims.get(Constants.USER_ID_FIELD).toString(),
                claims.get(Constants.ORG_ID_FIELD).toString());
    }

    //retrieve expiration date from jwt token
    private Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {

        final Claims claims = getAllClaimsFromToken(token);

        return claimsResolver.apply(claims);
    }

    // for retrieving any information from token we will need the secret key
    private Claims getAllClaimsFromToken(String token) {

        try {

            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();

        } catch (Exception e) {

            throw new ServerException(ErrorCode.UN_AUTH);
        }
    }

    // check if the token has expired
    private Boolean isTokenExpired(String token) {

        final Date expiration = getExpirationDateFromToken(token);

        return expiration.before(new Date());
    }

    // generate token for user
    public String generateToken(String userId) {

        Map<String, Object> claims = new HashMap<>();
        claims.put(Constants.USER_ID_FIELD, userId);

        return doGenerateToken(claims);
    }

    private String doGenerateToken(Map<String, Object> claims) {

        return Jwts.builder().setClaims(claims).setSubject("Subject")
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY))
                .signWith(SignatureAlgorithm.HS512, secret).compact();
    }
}