一、跨域认证的问题
互联网做事离不开用户认证,一样平常流程是下面这样。
1、用户向做事器发送用户名和密码。2、做事器验证通过后,在当前对话(session)里面保存干系数据,比如用户角色、登录韶光等等。3、做事器向用户返回一个 session_id,写入用户的 Cookie。4、用户随后的每一次要求,都会通过 Cookie,将 session_id 传回做事器。5、做事器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式的问题在于扩展性不好。单机当然没有问题,如果是做事器集群,或者是跨域的架构,就哀求 session 数据共享,每台做事器都能够读取 session。
举例来说,A 网站和 B 网站是同一家公司的关联做事。现在哀求,用户只要在个中一个网站登录,再访问另一个网站就会自动登录,叨教怎么实现?
一种办理方案是 session 数据持久化,写入数据库或别的持久层。各种做事收到要求后,都向持久层要求数据。这种方案的优点是架构清晰,缺陷是工程量比较大。其余,持久层万一挂了,就会单点失落败。
另一种方案是做事器索性不保存 session 数据了,所有数据都保存在客户端,每次要求都发回做事器。JWT 便是这种方案的一个代表。
二、JWT的事理
JWT 的事理是,做事器认证往后,天生一个 JSON 工具,发回给用户,就像下面这样。
{ "姓名": "张三", "角色": "管理员", "过期韶光": "2024年8月1日12点59分" }
之后,用户与做事真个通信,都要发回这个 JSON 工具。做事器完备只靠这个工具认定用户身份。为了防止用户修改数据,做事器在天生这个工具的时候,会加上署名。
做事器就不保存任何 session 数据了,也便是说,做事器变成无状态了,从而比较随意马虎实现扩展。
三、JWT 的数据构造
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3Mjc1MzUxODMsImlhdCI6MTcyNzQ0ODc4MywidXNlcm5hbWUiOiJ5dW1pbiJ9.dzbPlRX0pI2CBhrPgS2QVZh6lEjGO7uTnEarnQydSDU
它是一个很长的字符串,中间用点(.)分隔成三个部分。把稳,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。
JWT 三个部分如下。
Header(头部)Payload(负载)Signature(署名)写成一行,便是下面的样子:
Header.Payload.Signature
一)Header
Header 部分是一个 JSON 工具,描述 JWT 的元数据,常日是下面的样子。
{ "alg": "HS256", "typ": "JWT"}
上面代码中,alg属性表示署名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。末了,将上面的 JSON 工具利用 Base64URL转成字符串。
二)Payload
Payload 部分也是一个 JSON 工具,用来存放实际须要通报的数据。JWT 规定了7个官方字段,供选用。
iss (issuer):签发人exp (expiration time):过期韶光sub (subject):主题aud (audience):受众nbf (Not Before):生效韶光iat (Issued At):签发韶光jti (JWT ID):编号
除了官方字段,还可以在这个部分定义私有字段,如:
{ "role": ["admin"], "username": "ouyang"}
JWT 默认是不加密的,任何人都可以读到,以是不要把私密信息放在这个部分,比如用户密码。这个 JSON 工具也要利用 Base64URL算法转成字符串。
三)Signature
Signature 部分是对前两部分的署名,防止数据修改。
首先,须要指定一个密钥(secret)。这个密钥只有做事器才知道,不能透露给用户。然后,利用 Header 里面指定的署名算法,按照下面的公式产生署名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出署名往后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用(.)分隔,就可以返回给用户。
四)Base64URL
前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有分外含义,以是要被更换掉:=被省略、+更换成-,/更换成_ 。这便是 Base64URL 算法。
四、JWT 的利用办法
客户端收到做事器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与做事器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,以是更好的做法是放在 HTTP 要求的头信息Authorization字段里面。
另一种做法是,跨域的时候,JWT 就放在 POST 要求的数据体里面。
一个大略的JWT示例
1、首先引入awt依赖包:
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.8.2</version></dependency>
2、掩护一个JWT工具类:
public enum JwtUtil { instance; private static final String PWD = "12345"; private static final int EXPIRY_HOURS = 24; private static final Algorithm algorithm = Algorithm.HMAC256(PWD); / 天生JWT token @param username 用户登录账号 @return JWT token / public String createToken(String username) { Map<String, Object> params = new HashMap<>(); params.put("alg", "HS256"); params.put("typ", "JWT"); Date now = new Date(); return JWT.create() .withHeader(params) .withClaim("username", username) .withIssuedAt(now) .withExpiresAt(new Date(now.getTime() + 1000 3600 EXPIRY_HOURS)) .sign(algorithm); } / 验证JWT token @param token JWT token @return 验证通过返回用户登录账号 @throws JWTVerificationException 验证失落败时抛JWTVerificationException非常 / public String verify(String token) { DecodedJWT decodedJWT = JWT.require(algorithm).build().verify(token); return decodedJWT.getClaims().get("username").asString(); }}
用户登录时,判断完用户名和密码后,通过createToken方法天生一个JWT token,这里作为示例,我们只把username写入token,你可以根据自己的须要灵巧利用。
verify方法用于验证token的合法性,并从token中解析出username返回。
3、用户登录成功后,天生token返回给前端
@RequestMapping(path="/login")public RestResult<String> login(@RequestBody @Valid LoginRequest loginRequest, BindingResult bindingResult) { User user = userRepos.queryByUsername(loginRequest.getUsername()); if (null == user || !user.getPassword().equals(loginRequest.getPassword())) { return RestResult.fail("1", "用户名 或 密码 禁绝确"); } return RestResult.ok(JwtUtil.instance.createToken(loginRequest.getUsername())); }
4、通过aop拦截其它资源要求。前端访问其它资源时,将JWT token放入http header中Authorization字段
if (!"/login".equals(request.getRequestURI())) { // 校验是否登录 String token = request.getHeader("Authorization"); if (null == token) { return RestResult.fail("0", "未登录"); } try { String username = JwtUtil.instance.verify(token); } catch (JWTVerificationException e) { return RestResult.fail("0", e.getMessage()); }}
登录成功后,后端返回token给到前端,随后访问其它资源时,将token带到http header中。
利用JWT 时的须要特殊关注的点
1、JWT 默认是不加密,但也是可以加密的。天生原始 Token 往后,可以用密钥再加密一次。
2、JWT 不加密的情形下,不能将私密数据写入 JWT。
3、JWT 不仅可以用于认证,也可以用于交流信息。有效利用 JWT,可以降落做事器查询数据库的次数。
4、JWT 的最大缺陷是,由于做事器不保存 session 状态,因此无法在利用过程中破除某个 token,或者变动 token 的权限。也便是说,一旦 JWT 签发了,在到期之前就会始终有效,除非做事器支配额外的逻辑。
5、JWT 本身包含了认证信息,一旦透露,任何人都可以得到该令牌的所有权限。为了减少盗用,JWT 的有效期该当设置得比较短。对付一些比较主要的权限,利用时该当再次对用户进行认证。
6、为了减少盗用,JWT 不应该利用 HTTP 协议明码传输,要利用 HTTPS 协议传输。