JWT
文章目录
如果可以的话,请点个小小的赞可以吗?
以下的代码都基于springboot开发
一、什么是JWT
- WT(JSON WEBTOKEN):JSON网络令牌,JWT是一个轻便的安全跨平台传输格式,定义了一个紧凑的自包含的方式在不同实体之间安全传输信息(JSON格式)。它是在Web环境下两个实体之间传输数据的一项标准。实际上传输的就是一个字符串。广义上讲JWT是一个标准的名称;狭义上JWT指的就是用来传递的那个token字符串
JSON Web Token (JWT),它是目前最流行的跨域身份验证解决方案
二、为什么要使用JWT
-
为了保护项目之中的数据资源,那么一定就需要采用认证检测机制,于是SpringCloud进行认证处理,就可以使用SpringSecurity 来实现了,但是如果你真的去使用了SpringSecurity进行开发,因为维护的成本实在是太高了。
-
在后来的时候有很多的开发者开始尝试通过OAuth2统一认证来进行SpringCloud认证与授权服务,这种操作也属于较早期的实现了,这种实现最大的问题在于随着版本的更新会出现代码不稳定的情况,很多的开发者就开始尝试自己去独立的实现认证与授权的操作机制,于是就有了JWT开发技术。
-
使用JWT最大的特点在于不需要维护所有的数据的状态,同时可以自己包含有过期的时间,以及通过附加数据的形式传送所需要的额外的数据内容((可以保存认证以及授权信息)。
在WEB开发中需要维护Session的状态,所以如果用户访问量过大,那么必然会出现Session内容过多而导致服务器处理性能下降的惨剧,所以后面就需要引入WEB服务器的集群,但是为了便于服务器集群之中的Session管理,那么又需要进行分布式的Session存储,总之就一点:有状态的用户需要维护Session,Session维护成本很高。
三、JWT的工作原理
-
客户端通过Web表单将正确的用户名和密码发送到服务端的接口。这一过程一般是POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
-
服务端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT。形成的JWT就是一个形lll.zzz.xxx的字符串,并设置有效时间。
-
服务端将JWT字符串作为登录成功的返回结果返回给客户端。
-
客户端将返回的JWT以cookie的形式保存在浏览器上,并设置cookie的有效时间(建议客户端cookie和服务端JWT的有效时间设置为一致),用户登出时客户端需删除cookie。
-
客户端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)
-
服务端对收到的JWT进行解密和校验,如检查签名是否正确、Token是否过期、Token的接收方是否是自己等。
-
验证通过后服务端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果,否则返回401。
四、JWT的组成
- Header(头部)
- Payload(负载)
- Signature(签名)
header.payload.signature
五、JWT的验证过程
-
它验证的方法其实很简单,只要把header做base64url解码,就能知道JWT用的什么算法做的签名,然后用这个算法,再次用同样的逻辑对header和payload做一次签名,并比较这个签名是否与JWT本身包含的第三个部分的串是否完全相同,只要不同,就可以认为这个JWT是一个被篡改过的串,自然就属于验证失败了。接收方生成签名的时候必须使用跟JWT发送方相同的密钥。
-
验证一个JWT的时候,签名认证是每个实现库都会自动做的,但是payload的认证是由使用者来决定的。因为JWT里面可能会包含一个自定义claim,所以它不会自动去验证这些claim
-
如果签名认证失败会抛出如下的异常:
io.jsonwebtoken.SignatureException
即签名错误,JWT的签名与本地计算机的签名不匹配
-
JWT过期异常
io.jsonwebtoken.ExpiredJwtException
就是令牌超过了过期时间
六、使用JWT
创建一个工具类 - JwtUtil
-
import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.Map; public class JwtUtil { /** * 生成jwt * 使用Hs256算法, 私匙使用固定秘钥 * * @param secretKey jwt秘钥 * @param ttlMillis jwt过期时间(毫秒) * @param claims 设置的信息 * @return */ public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) { // 指定签名的时候使用的签名算法,也就是header那部分 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; // 生成JWT的时间 long expMillis = System.currentTimeMillis() + ttlMillis; Date exp = new Date(expMillis); // 设置jwt的body JwtBuilder builder = Jwts.builder() // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的 .setClaims(claims) // 设置签名使用的签名算法和签名使用的秘钥 .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8)) // 设置过期时间 .setExpiration(exp); return builder.compact(); } /** * Token解密 * * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个 * @param token 加密后的token * @return */ public static Claims parseJWT(String secretKey, String token) { // 得到DefaultJwtParser Claims claims = Jwts.parser() // 设置签名的秘钥 .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8)) // 设置需要解析的jwt .parseClaimsJws(token).getBody(); return claims; } }
创建一个JwtProperties类
-
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "com.jwt") @Data public class JwtProperties { private String adminSecretKey;//秘钥 private long adminTtl;//过期时间 private String adminTokenName;//有效负载 }
在配置文件中加入
-
com: jwt: # 设置jwt签名加密时使用的秘钥 admin-secret-key: itcast # 设置jwt过期时间 admin-ttl: 7200000 # 设置前端传递过来的令牌名称 admin-token-name: token
使用
-
@Autowired private JwtProperties jwtProperties; public Class TestJWt{ //生成jwt令牌 HashMap<String, Object> claims = new HashMap<>(); claims.put("1","1"); String jwt = JwtUtil.createJWT( jwtProperties.getAdminSecretKey(), jwtProperties.getAdminTtl(), claims ); //解析jwt令牌 Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), jwt); claims.get("1"); }