JWT(Json Web Token)是一种认证协议,在前后端分离的项目中,客户端向服务端发送的请求存在“跨域”的问题。而在分布式架构体系下,一般会有多个服务器提供服务。例如使用Nginx进行反向代理,使用轮询或根据负载的策略对服务器进行分配时,客户端每次请求的可能是不同的服务器。
这两种情况下,传统的Session存储客户端身份的方式就会有很多的不便。而JWT是解决方案之一。
1. Token的组成
JWT的Token由三部分组成:头部(Header)、负载(Payload)、验证签名(Signature)。
这三部分之间,使用英文句号"."分割,举例如下:
首先有一个固定的明文的前缀:"Bearer "。
"token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE2MTExOTE3OTksImV4cCI6MTYxMTI3ODE5OX0.dCZFvxwJ0TS_fB_yj_6msveURl6xMYrsaaWraRqOA7Q"
Header默认内容如下:
{ 'alg': "HS256", 'typ': "JWT" }
将其进行Base64编码,得到的字符串为:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9。头部是紧跟在Bearer 后面的的内容。
Payload中存储的是有效的信息,也是一个json格式,例如:
{ 'id': 1, 'username': "zhangsan", 'gender': 0 }
对其进行Base64编码,得到字符串为:ewogICAgJ2lkJzogMSwKICAgICd1c2VybmFtZSc6ICJ6aGFuZ3NhbiIsCiAgICAnZ2VuZGVyJzogMAp9。
Signature是根据两部分的信息进行签名得到的字符串,其签名规则为:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
注:这里加密算法HMACSHA256由头部来指定,secret是存储在服务端的秘钥。
例如,对字符串"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE2MTExOTE3OTksImV4cCI6MTYxMTI3ODE5OX0",使用秘钥:"itcast",进行HMACSHA256进行加密,得到的结果为:"dCZFvxwJ0TS_fB_yj_6msveURl6xMYrsaaWraRqOA7Q"。这做为token的第三部分。
2.在Spring Boot项目中使用JWT进行Token验证
首先在pom文件中添加JWT的坐标
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.3</version> </dependency>
编写工具类,实现Token的生成和验证逻辑:
1 package com.example.demo.utils; 2 3 import com.auth0.jwt.JWT; 4 import com.auth0.jwt.JWTCreator; 5 import com.auth0.jwt.JWTVerifier; 6 import com.auth0.jwt.algorithms.Algorithm; 7 import com.auth0.jwt.interfaces.DecodedJWT; 8 9 import java.util.Calendar; 10 import java.util.Date; 11 import java.util.Map; 12 13 public class JWTUtils { 14 15 public static final String secret = "mysecret"; 16 17 //生成token 18 public static String getToken(Map<String,String> map){ 19 Calendar instance = Calendar.getInstance(); 20 Date date = instance.getTime(); 21 instance.add(Calendar.DATE,1); 22 23 JWTCreator.Builder builder = JWT.create(); 24 map.forEach( (k,v) -> { 25 builder.withClaim(k,v); 26 }); 27 28 String token = builder 29 .withIssuedAt(date) 30 .withExpiresAt(instance.getTime()) 31 .sign(Algorithm.HMAC256(secret)); 32 33 return token; 34 } 35 36 public static DecodedJWT verify(String token){ 37 JWTVerifier build = JWT.require(Algorithm.HMAC256(secret)).build(); 38 DecodedJWT verify = build.verify(token); 39 return verify; 40 } 41 }
编写拦截器,实现对HTTP请求中的Token自动验证:
1 package com.example.demo.interceptors; 2 3 import com.auth0.jwt.interfaces.DecodedJWT; 4 import com.example.demo.utils.JWTUtils; 5 import com.fasterxml.jackson.databind.ObjectMapper; 6 import org.springframework.web.servlet.HandlerInterceptor; 7 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 import java.util.HashMap; 11 import java.util.Map; 12 13 public class JWTInterceptor implements HandlerInterceptor { 14 @Override 15 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 16 Map<String,Object> map = new HashMap<>(); 17 String token = request.getHeader("Authorization"); 18 try{ 19 DecodedJWT verify = JWTUtils.verify(token); 20 return true; 21 } 22 catch (Exception e){ 23 e.printStackTrace(); 24 map.put("state",false); 25 map.put("msg","请求失败"); 26 } 27 String json = new ObjectMapper().writeValueAsString(map); 28 response.setContentType("application/json;charset=utf-8"); 29 response.getWriter().println(json); 30 return false; 31 } 32 }
编写Controller对Token接受请求,并处理:
1 package com.example.demo.controller; 2 3 import com.alibaba.fastjson.JSON; 4 import com.alibaba.fastjson.JSONObject; 5 import com.auth0.jwt.interfaces.Claim; 6 import com.auth0.jwt.interfaces.DecodedJWT; 7 import com.example.demo.model.dto.SPManager; 8 import com.example.demo.model.out.LoginData; 9 import com.example.demo.model.out.LoginResult; 10 import com.example.demo.model.out.Meta; 11 import com.example.demo.service.SPManagerService; 12 import com.example.demo.utils.JWTUtils; 13 14 import org.springframework.beans.factory.annotation.Autowired; 15 import org.springframework.web.bind.annotation.PostMapping; 16 import org.springframework.web.bind.annotation.RequestMapping; 17 import org.springframework.web.bind.annotation.RequestParam; 18 import org.springframework.web.bind.annotation.RestController; 19 20 import javax.servlet.http.HttpServletRequest; 21 import java.util.HashMap; 22 23 @RestController 24 public class ManagerController { 25 26 @Autowired 27 SPManagerService spManagerService; 28 29 @PostMapping("/login") 30 public JSONObject Login(@RequestParam String username, @RequestParam String password) { 31 System.out.println(username + "|" + password); 32 33 LoginData lt = new LoginData(); 34 Meta meta = new Meta(); 35 SPManager model = spManagerService.getManagerByNameAndPwd(username, password); 36 System.out.println(model); 37 if (model != null) { 38 HashMap<String, String> map = new HashMap<>(); 39 map.put("uid", model.getMgId().toString()); 40 map.put("rid", model.getRoleId().toString()); 41 String token = JWTUtils.getToken(map); 42 43 lt.setId(model.getMgId()); 44 lt.setRid(model.getRoleId()); 45 lt.setUsername(model.getMgName()); 46 lt.setEmail(model.getMgEmail()); 47 lt.setMobile(model.getMgMobile()); 48 lt.setToken(token); 49 50 meta.setMsg("登录成功"); 51 meta.setStatus(200); 52 }else{ 53 lt = null; 54 meta.setMsg("登录失败"); 55 meta.setStatus(400); 56 } 57 LoginResult lr = new LoginResult(); 58 lr.setData(lt); 59 lr.setMeta(meta); 60 61 String json = JSON.toJSONString(lr); 62 JSONObject jsonObject = JSONObject.parseObject(json); 63 return jsonObject; 64 } 65 }
在拦截器JWTInterceptor中已经定义了对request头[Authorization]中存储的Token自动验签的处理的逻辑,请求只有通过了拦截器的验证,才能进入到Controller中。这样在具体的业务逻辑处理时就无需编写重复的验证逻辑了。