系统认证方式
session认证
http协议由于本身是无状态的协议,那就意味着当有用户向系统使用账户名称和密码进行用户认证之后,下一次请求还要再一次用户认证才行。因为我们不能通过http协议知道是哪个用户发出的请求,所以如果要知道是哪个用户发出的请求,那就需要在服务器保存一份用户信息(保存至session),然后在认证成功后返回cookie值传递给浏览器,那么用户在下一次请求时就可以带上cookie值,服务器就可以识别是哪个用户发送的请求,是否已认证,是否登录过期等等。这就是传统的session认证方式。
session认证的缺点其实很明显,由于session是保存在服务器里,所以如果分布式部署应用的话,会出现session不能共享的问题,很难扩展。于是乎为了解决session共享的问题,又引入了redis,即token认证。
token认证
这种方式跟session的方式流程差不多,不同的地方在于保存的是一个token值到redis,token一般是一串随机的字符(比如UUID),value一般是用户ID,并且设置一个过期时间。每次请求服务的时候带上token在请求头,后端接收到token则根据token查一下redis是否存在,如果存在则表示用户已认证,如果token不存在则跳到登录界面让用户重新登录,登录成功后返回一个token值给客户端
优点是多台服务器都是使用redis来存取token,不存在不共享的问题,所以容易扩展。缺点是每次请求都需要查一下redis,会造成redis的压力,还有增加了请求的耗时,每个已登录的用户都要保存一个token在redis,也会消耗redis的存储空间。
JWT认证
- JWT(JSON Web Token),通过数字签名的方式,以JSON对象为载体,在不同的服务终端之间安全传输信息,该信息可以被验证和信任,因为它是数字签名的。JWT本质其实就是字符串而已。
- JWT最常见的场景就是授权认证,一旦用户登录成功,后续的每个请求将包含JWT,系统在每次处理用户请求的之前,都会先进行JWT安全校验,通过之后再进行相应处理。
- JWT就是一种认证机制,让后台知道该请求是来自于受信的客户端。
JWT基本流程:
- 用户进行登录,登录请求发送给Authentication Server
- Authentication Server进行用户校验,成功后创建JWT字符串返回给客户端
- 客户端请求接口时,在请求带上JWT信息
- Application Server验证JWT合法性,如果合法则继续调用应用接口返回结果。
JWT认证与Session认证
JWT不需要存储,而是在服务端根据前端传回的token进行解密比对处理
session认证是在用户登录之后,给浏览器返回一个sessionid,另外在服务器端同时存储sessionid及必要的用户信息,在用户使用cookie把sessionid传回服务端,服务端基于sessionid去数据库查询,若查到则表示用户还在活跃期,同时也可以获取到存储的必要用户信息。
相同点就是都需要使用cookie。
JWT认证与Token认证
Token需要查库验证token 是否有效,而JWT不用查库,直接在服务端进行校验,因为用户的信息及加密信息,和过期时间,都在JWT里,只要在服务端进行校验就行,并且校验也是JWT自己实现的。JWT认证关键在于生成JWT,和解析JWT这两个地方。
JWT的结构
JWT由3部分组成,用 " . "拼接的字符串,如下所示
eyhbGcioiJIUzI1NiIsInR5cCI6IkpXvC9.ey31c2vybmFtzSI617RvbSIsIn3vbGuioihzG1pbiIsInN1YiI6ImFkbw1uLXR1c3QiLCleHAiojE2MjMyMjM2NzUsImp0asT6ImQ2MTJjzjcxLwI5ZmUtNGMwNy04MzQwLTViowvizmMyNjExNy39.Fo59Y7rYNdc2AoidnSPrgg2XTYePuOyGz598h2gtabE
这三部分分别为:
-
Header
JWT头,包含两个信息,一个为Type(token类型),一个为算法的名称alg(加密方式),我们会将这个信息进行Base64编码,就构成上述字符串的第一部分。
{ 'type':'JWT', 'alg':'HS256' }
JWT里验证和签名使用的算法列表如下:
JWS 算法名称 HS256 HMAC256 HS384 HMAC384 HS512 HMAC512 RS256 RSA256 RS384 RSA384 RS512 RSA512 ES256 ECDSA256 ES384 ECDSA384 ES512 ECDSA512 -
Payload
有效载荷,载荷就是存储有效信息的地方,也就是存储我们自己信息的地方。它包含三个内容,分别是标准中注册的声明,公共的声明,私有声明,也就是安全分类,类似与java的访问权限修饰符。
{ "name": 'wp', "age": '18', "amin":true }
-
Signature
第三部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret),该密钥仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用.分隔,就构成整个JWT对象。
var encodedstring = base64UrlEncode(header) + '.' +base64urlEncode(payload); var signature = HMACSHA256(encodedString, 'secret');
那么Application Server如何进行验证呢?可以利用JWT前两段,用同一套哈希算法和同一个secret计算一个签名值,然后把计算出来的签名值和收到的JWT第三段进行比较,如果相同则认证通过。
实践JWT
JWT认证关键在于生成JWT,和解析JWT这两个地方。
生成JWT、
String signature = "admin";
private long time = 1000*60*60*24;
@Test
public void jwt(){
//生成JWT
JwtBuilder builder = Jwts.builder();
String jwtToken = builder
//header头部
.setHeaderParam("type", "JWT")
.setHeaderParam("alg", "HS256")
//payload有效载荷
.claim("username", "wp")
.claim("role", "admin")
.setSubject("admin-test")
.setExpiration(new Date(System.currentTimeMillis()+time)) //设置有效期,为一天
.setId(UUID.randomUUID().toString())
//signature签名
.signWith(SignatureAlgorithm.HS256,signature)
.compact();
System.out.println(jwtToken);
}
运行结果
eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJ1c2VybmFtZSI6IndwIiwicm9sZSI6ImFkbWluIiwic3ViIjoiYWRtaW4tdGVzdCIsImV4cCI6MTY1MzgyMDQ5MywianRpIjoiMTYxMGMzMjQtMzVhNy00NjMzLTk3MmItOWVkYzQyNjA3Zjk4In0._4xmfhg_C5WVzk1rWF66iG2knr5nSxyUDgEytlTg394
解析JWT
@Test
public void parse(){
//解析JWT
String jwtToken = "eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJ1c2VybmFtZSI6IndwIiwicm9sZSI6ImFkbWluIiwic3ViIjoiYWRtaW4tdGVzdCIsImV4cCI6MTY1MzcxMTI5OSwianRpIjoiMWIxYjE0OTMtNmVlMy00YTZhLTkzNTUtMDdlOTJkNzhiNDk3In0.dtXMVzKt4zrfO5ml8KVV0bsHTJ4QxXEXf2YJ7RQ3ECs";
JwtParser jwtParser = Jwts.parser();
Jws<Claims> claimsJws = jwtParser.setSigningKey(signature).parseClaimsJws(jwtToken);
Claims body = claimsJws.getBody();
System.out.println(body.getSubject());
System.out.println(body.get("username"));
System.out.println(body.get("role"));
System.out.println(body.getId());
System.out.println(body.getExpiration());
}
运行结果
admin-test
wp
admin
1610c324-35a7-4633-972b-9edc42607f98
Sun May 29 18:34:53 CST 2022
java中的运用
Springboot中的运用
引入依赖