这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战
JWT
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
Json Web Token ,通过JSON形式作为Web应用的令牌,多方交互时需要通过这个令牌进行授权才能访问。 也可以对信息进行加密来信息交换防止被篡改。
为什么使用它
传统的Session认证
认证通过,将用户信息存入服务器的Session中,并将sessionid存放到浏览器的cookie当中。
当用户再次请求,会携带cookie,通过sessionid找到服务器中自己的 session信息。来进行验证是否登录。
Session的弊端
-
每个用户认证都会在服务器存放对应的session,随着用户增多,服务器开销增大
-
session存放在一台服务器中,当下次请求时,还需要访问同一台服务器才能成功,这样就会限制分布式的能力,减弱了负载均衡
-
cookie被拦截,容易造成伪造cookie攻击
-
前后端分离情况下更加复杂:
- 用户一次请求要转发多次,每次都要携带sessionid,每次都要验证,增加服务器的负担
基于JWT
认证流程
1.前端通过Web表单将自己的用户名和密码发送到后端的接口。一般使用post请求或者SSL加密传输
2.后端核对用户名和密码成功后,将用户的id等信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成JWT。 ** token的格式**:head.payload.singurature
3.后端将JWT作为登录成功的结果返回给前端,前端将结果保存在浏览器中localStorage,退出登录时前端删除JWT
4.前端每次请求将JWT放入HTTP Header中的Authorization位,解决伪造攻击
5.后端检查JWT是否过期,检查签名是否正确,检查Token是否发送给自己。
6.验证通过,后端使用JWT中的用户信息来进行业务操作,并返回结果
优点
- 简介:可以通过post参数或者http header发送,数据量小,传输速度快
- 自包含:负载中包含了所有用户所需的信息,避免多次查询数据库
- 以JSON加密的形式保存在客户端,因此JWT是跨语言,任何web形式都支持
- 不需要在服务端保存会话信息,特别适用于分布式微服务
结构
1.令牌组成
token 的格式:xxxx.yyyy..zzzz
内容为:header.payload.singnature
-
header标头
-
标头通常由两部分组成:令牌的类型和所使用的签名算法
-
经过Base64编码后就会变成JWT格式的第一部分xxxx
{ //使用的加密方式为HS256 "alg" : "HS256", //使用的令牌类型为JWT "typ" : "JWT" }
-
-
Payload有效载荷
- 自包含,包含声明。声明是有关实体(通常是用户)和其他数据的声明
- 同样会Bse64编码处理,变成JWT格式的第二部分yyyy
-
由于Base64不是加密,而是编码,所以如果被拦截通过解码就能获得全部用户信息,官方建议不要放置用户的敏感信息如密码
{ "sub" : "123456", "name" : "yzy", "admin" : true } -
Singature签名
-
签名需要使用编码后的header和payload以及后端提供的一个密钥,通过header指定的加密算法进行签名,作用是保证JWT没有被篡改
- header指定了加密算法:HS256
- 已知编码后的header和payload:xxxx.yyyy
- 后端提供密钥 secret
- 进行加密:Signatrue = HMACSHA256(xxxx+"."+yyyy,secret)
-
后端如何进行验证:获得JWT中的xxxx.yyyy与后端自己的密钥进行加密然后和JWT的zzzz进行对比是否相同
- 当JWT被篡改时,无论xxxx,yyyy,zzzz的哪一部分发生改变,都不能验证通过
-
编码前
编码后
使用
1.导入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
2.手动模拟一个token的生成
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,20); //设置20秒的时间
String token = JWT.create().withClaim("username", "yy") //设置payload的内容.可以有多个
.withClaim("id", "yy")
.withExpiresAt(instance.getTime()) //指定令牌过期时间
.sign(Algorithm.HMAC256("1231231"));//设置签名
System.out.println(token);
3.令牌的认证
JWTVerifier verifier = JWT.require(Algorithm.HMAC256("1231231")).build(); //创建一个相同算法的解密对象
/*模拟前端的话就是将前端传过来的token让解密对象进行解码*/
//解码token获得一个包含信息的对象
DecodedJWT verify =verifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6Inl5MSIsImV4cCI6MTU5OTk3OTM4OCwidXNlcm5hbWUiOiJ5eSJ9.iQnJ4JnwEGWDEPYzrk-f_Rq8LCqPetm7kYYeAdoUweY");
//对象中可以获取载体中的信息,此时是对象名,如果想要获得具体信息,可以.asString
System.out.println(verify.getClaim("username"));
System.out.println(verify.getClaim("id"));
异常
异常的次序:
- 1.算法匹配(与生成JWT的算法不同)
- 2.验证签名(与生成JWT的签名不同)
- 3.过期时间(超时)
- 4.失效的JWT(JWT信息不对)