token主要目的是什么?
因为http是无状态的(当前用户在loginURL登录之后,其他的URL并不能判断当前用户是否已经登录),所以需要一个标识来标记当前用户已经登录以及当前是哪个用户在发送请求
传统的方式是使用session加cookie的方式实现用户认证
session与cookie如何实现用户认证?
当用户登录成功之后,服务器会以Map的形式保存一份用户的认证信息,key为随机字符串,value为用户信息,将Map中的key返回给用户,用户浏览器保存到cookie,之后的每次请求都带上cookie,服务器收到请求查找并验证cookie的合法性,如果cookie存在则取出Map中的数据,给当前用户使用
seesion与cookie的弊端是什么?
1:用户端浏览器可以禁止cookie,cookie无法保存
2:每个用户访问都会在服务器生成session,浪费服务器资源
3:当服务器是分布式时,session无法共享(用户在A服务器登录,seesion保存当A服务器,访问B服务器,无法验证cookie)
解决session与cookie方案
首先cookie与session的目的是为了给用户一个标识,通过这个标识服务器可以判断用户是谁,以及用户的状态;类似于一把锁,用户登录后生成一个保险柜放在服务器,然后把钥匙给用户,每次用户访问服务器(需要使用到用户信息的URL)带上钥匙,服务器通过钥匙找到并打开指定的保险柜,获取到用户数据。
第一种方案:Token + redis:
session与cookie的问题是分布式之后无法共享session,那我们可以用redis来代替之前的服务器存储session,这样分布式的服务器主机共享redis就可以共享session 具体流程图:
第二种方案:JWT(java wbe token)
官网:jwt.io/
本质上来说就是获取到当前用户的登录信息,那么我们能不能把用户信息加密存储到token中呢?
JWT就是采用这种方式,客户端首次访问服务器,成功登录后,jwt会把用户基础信息(比如id,name....)加密,放到token中,返回给用户,用户访问其他url是带上token,服务器在拦截器中解析这个token就能拿到当前登录用户的基本信息,判断出是哪个用户。
具体流程图:
生成jwt样例:
pblic String createJwt(Long id, String name, String phone) {
return Jwts.builder()
.setHeaderParam("typ", "JWT") //声明类型(可选)
.setHeaderParam("alg", "HS256")//声明加密方式(可选)
.claim("id", id)//(用户数据)
.claim("name", name)
.claim("phone", phone)
.setExpiration(new Date(System.currentTimeMillis() + expire))//(过期时间)
.signWith(SignatureAlgorithm.HS256, secret)//(加密方式以及盐值)
.compact();
}
解析jwt样例:
public Optional verifyJwt(String token) {
SecretKey key = generalKey(jwtSecret);//获取64编码后的盐值
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException ignore) {
claims = null;
}
return Optional.ofNullable(claims);
}
public SecretKey generalKey(String jwtSecret) {
byte[] encodedKey = Base64.getDecoder().decode(jwtSecret);
return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
}
特别备注(关于拿到用户信息如何使用的问题):每次解析token拿到用户信息后,应该存储到哪里供service层使用呢?
第一种:可以建立一个本地线程数据变量,把用户信息保存到当前Map中(key: 统一名,value 用户对象),需要用到用户信息时,使用这个线程变量获取;
第二种:可以创建一个用户信息的bean,作用域为request(每次请求都会创建一个bean对象),将用户信息在拦截器中放入到这个bean中,需要使用到用户信息的时候可以注入这个bean;
!! JWT生成的token用户一旦泄露,其他人也可以用这个token访问原用户的资源,除了token到期之外无法销毁此token;redis+token的方式则不存在这种问题,用户token泄露可以删除redis缓存中的token