一、概述
在前后端交互的过程中,校验权限和认证信息是必不可少的。
后端生成对应 token
,通过 token
可以校验此 API
的权限,认证用户信息。
那么 token
需要满足哪些需求?
需求,例如:
- 简单的凭证:证明用户登录且时间内有效。
- 存储用户信息:能够通过
token
里的信息,确认某一用户,具有哪个权限。 web
端只能登录一个:PC
端浏览器只能登录一个。- 各个端只能登录一个:
iOS
、Android
和PC
端均只能登录一个。
二、Token
生成方式
下面介绍,常见的生成方式:
- 存储在
Redis
- 使用
JWT
(1)存储在 Redis
1. 简单存储用户信息:
可以用
UUID
的MD5
作为token
例如:传给前端:812130c27bcbc997f37e714bbf108fc7
例如:
{
"token:812130c27bcbc997f37e714bbf108fc7" : {
"userId": "u_395479916231565",
"username" : "卷卷啊",
"isAdmin": true
}
}
2. web
端只登录一个
即每次登录生成的 用户的
token
在Redis
只能一个。 那么就要做好用户token
跟key
的关联。
主要是两点:
- 传递给前端的
token
:userId
的MD5
+ 随机数
// 例如,对 u_395479916231565 MD5 + 随机数
be9c629efec1cde341d0ae38acc94758_5908f28aa73ffe56
- 后端根据
userId
的MD5
拼接成key
根据
redis
里存储的随机数 跟 前端传递过来的random
做对比。
// token:拼接
token:be9c629efec1cde341d0ae38acc94758
// redis中存储对应的:
{
"token:be9c629efec1cde341d0ae38acc94758" : {
"userId": "u_395479916231565",
"username" : "卷卷啊",
"isAdmin": true,
"random": "5908f28aa73ffe56"
}
}
3. 各端只能登录一个:iOS
、Android
和 PC
端均只能登录一个。
处理方式,相似方式2, 只不过有
deviceId
再做鉴别。
(2)JWT
信息凭证存储在 JWT
中,可以作为简单的凭证。
敏感信息不能存储在
JWT
,因为JWT
并不保密。 可以将JWT
放到此网站上查看相应的信息:jwt.io/#debugger
一个 JWT token
分三部分:
- 头部(
header
) - 载荷(
payload
) - 签证(
signature
)
部分之间用“.”号做分隔,例如:
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ5eWYiLCJleHAiOjE2MTAxMTYzMDYsImlhdCI6MTYxMDA5ODMwNn0.NLtpAo_mD_zcrXRWJa_AiWWnHjCvVtlHDSRDmpyrfXekpyfDsS6CUHwqEY1iFQ1pMUw04QHEjtrHWgX7fkmeRQ
在 Java
中使用如下:
- 加入
pom
配置
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
- 工具类
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();
}
}