JWT,全称(JSON Web Tokens),是一种认证授权的机制。当用户发送请求后,服务器端会根据请求中的token,解析并判断该用户是否可以调用接口,从而避免接口被直接调用引发安全问题。
例如,有一个删除用户的接口,只有管理员才能调用。服务器端需要知道发送删除用户的请求是否是管理员发出的。那么,客户端可以在请求头放置一个token,服务器端将会从token中解析用户信息,判断该用户是否是管理员,从而决定是否调用删除用户接口。
有了token,我们避免了每次发送删除请求都发送用户名密码让服务器判断用户角色,因为token已经承载了这些信息。并且,与另一个认证机制session相比,服务器解析token只需要判断token是否有效,而不需要在服务器中比对用户登录的信息,这将大大减少服务器的开销。
那么token是怎么生成的,而用户是怎么获取到,服务器是怎么解析的呢。
原理
token生成
首先我们得知道,token由这三部分构成,头部、载荷、签证
-
头部(header)
声明了类型和加密的算法{ 'typ': 'JWT', 'alg': 'HS256' }
头部是上述信息经过base64转换所得的字符串。
- 载荷(payload)
存储一些有效信息。
当用户登录成功时,服务器将去数据库查找该用户,把该用户的信息放在载荷中,并在载荷中附加其他信息,如jwt的签发时间iat,jwt的过期时间exp,jwt的签发者iss等,但这些附加信息并不强制加上。然后,服务器将载荷进行base64转换。 - 签证(signature)
签证这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行秘钥secret组合加密,然后就构成了jwt的第三部分。
token发送
然后,服务器将此token信息发在响应体的header中。当客户端接收到此响应,则获取响应体的token信息,下次发送请求则带上token。
token接收并解析
当服务器下次收到用户请求,获取到请求中的token信息后,需要经历几个步骤。
- 判断token是否过期,这一部分,服务器将根据token中前两部分的字符串解析出头部和载荷的json格式,根据解析后的载荷信息判断token是否过期。
- 之后再将头部和载荷重新加密得到签证。token的签证和计算后的签证进行比对,不一致则无效。
- 根据payload信息得到用户信息,判断该用户是否满足调用接口的权限。
代码实现思路
1.准备配置文件
2.token的生成,时间:在用户登录。
-
验证用户名密码
AuthService调用AuthenticationManager的authenticate方法,该方法有两个步骤:loadUserByUsername,verify
- loadUserByUsername:根据用户名去数据库查找用户,查找不到返回用户未找到信息,找到则将用户信息合成JWTUser(用于生成payload)。
- verify:验证JWTUser的密码是否与登录发送的密码匹配。
获取JWTUser,可调用Authentication类的getPrincipal方法
将获取到的JWTUser生成payload。
-
将payload生成token,可调用authRepository的generateToken方法
public String generateToken(Map<String, Object> payload) { return Jwts.builder() .setClaims(payload) .setExpiration(new Date(System.currentTimeMillis() + expirationInSeconds * 1000)) .signWith(SignatureAlgorithm.HS512, jwtSecret) .compact(); }
3.token的发送,时间:用户登录.
将获取后的token放置在响应体头部,可调用response.addHeader的方法。
4.token的解析
在配置文件JWTAuthenticationFilter:
获取token: String authorizationHeader = request.getHeader(header)
-
判断token是否存在
- 不存在:准备调用接口
- 存在:判断token是否有效,有效则到下一步,无效则返回无效信息
-
生成payload:
public String extractAuthorizedPayload(String jwtToken) { return StringUtils.writeObjectAsJsonString(Jwts.parser().setSigningKey(jwtSecret) .parseClaimsJws(jwtToken) .getBody()); }
设置上下文
判断用户是否有权限调用接口
调用接口