关于如何使用JWT来进行用户登录验证,实现网络安全
1. JWT介绍
- JWT:JSON Web Token(JSON网路令牌)
- JWT官网:https://jwt.io
- JWT通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、前面等相关处理
2. JWT作用
- (1)授权:
- JWT的最常见方案,一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌徐云的路由,服务和资源。
- (2)信息交换
- JWT是各方之间安全传输信息的好方法。因为可以对JWT进行签名,可以确保发件人没有变化。
3.选择JWT理由
3.1 基于传统的Session认证
- 客户向服务器登录后,服务器会生成一个Session对象,将用户ID保存在里面,并在第一次响应客户端时,发送一个包含SessionID的Cookie;客户端每次访问服务器时都会带上次Cookie,来判断是否已经登录验证。
- 暴露问题:
- (1)Session保存在内存中,随着用户量增大,服务器开销增大
- (2)Session保存在内存中,导致在分布式系统中,用户永远只能访问此服务器,限制了负载均衡能力
- (3)基于Cookie进行用户验证,如果Cookie被拦截,容易受到攻击
- (4)前后端分离系统中,可以需要多个代理才发送带后端,每次转发都携带Cookie,十分麻烦
3.2 基于JWT认证
- JWT优势:
- (1)简洁:可以通过URL, POST参数或者在http header发送,因为数据量小,传输速度也很快
- (2)自包含:负载中包含了所有用户所需要的信息,避免了多次查询数据库
- (3)跨语言:基于Json加密形式保存在客户端,原则上任何web形式都支持
- (4)不需要再服务器的session中保存,特别适合永野芽郁分布式系统
4. JWT结构
4.1 令牌组成
- 由标头(header)、有效载荷(payload)、签名(signature)组成
- 组成形式为:header.payload.signature,中间有’.’隔开
- header由两部分组成:令牌类型和所使用的签名算法(HMAC、SHA256、RSA)。后端会使用Base64编码组成header
1 2 3 4
| { "alg": "HS356", "typ": "JWT" }
|
4.3 Payload
- payload,其中包含声明。声明式有关实体和其他数据,按照实际情况返回。同样会使用base64编码组成
1 2 3 4 5
| { "sub": 123456789, "name": "letere", "admin": true }
|
4.4 Signature
signature需要编码后的header和payload以及一个密钥,然后使用header中的算法进行签名,签名的作用是保证JWT没有被篡改过。
例:算法(编码后的header + “.” + 编码后的payload, 密钥)
5. JWT基本使用
5.1 依赖导入
1 2 3 4 5 6
| <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.12.0</version> </dependency>
|
5.2 生成令牌
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Test public void test() { Calendar date = Calendar.getInstance(); date.add(Calendar.SECOND, 120);
Map<String, Object> header = new HashMap<String, Object>(); String token = JWT.create() .withHeader(header) .withClaim("userId", 12) .withClaim("userName", "letere") .withClaim("sex", "男") .withExpiresAt(date.getTime()) .sign(Algorithm.HMAC256("XJBX"));
System.out.println(token); }
|
5.3 验证令牌
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test public void test2() { JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("XJBX")).build();
DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXgiOiLnlLciLCJ1c2VyTmFtZSI6ImxldGVyZSIsInVzZXJJZCI6MTJ9.DEwv6wVClwxjVO_wsMBojPSOkckJh3PEaAv543bSy-w");
System.out.println(verify.getClaim("userId").asInt()); System.out.println(verify.getClaim("userName").asString()); System.out.println(verify.getClaim("sex").asString()); }
|
6. 与SpringBoot的结合使用
6.1 依赖引入
1 2 3 4 5 6
| <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.12.0</version> </dependency>
|
6.2 接口创建
- 由于只是个简单的demo,所以身份验证连接数据库就行验证,只是个简单的判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public Map<String, Object> login(String userName, String password) { Map<String, Object> message = new HashMap<>(); if("admin".equals(userName) && "123456".equals(password)) { Calendar date = Calendar.getInstance(); date.add(Calendar.DATE, 7); String token = JWT.create() .withClaim("userName", userName) .withExpiresAt(date.getTime()) .sign(Algorithm.HMAC256("XJBX"));
message.put("state", true); message.put("msg", "认证成功"); message.put("token", token); }else { message.put("state", false); message.put("msg", "认证失败"); } return message; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| @RestController public class UserController { @Autowired private UserServiceImp usi;
@GetMapping("/login") public Map<String, Object> login(String userName, String password) { return usi.login(userName, password)); } }
|
6.3 token拦截
- 返回前端的token,在下次发送请求时,一般都将token放在请求头中,而后端用拦截器拦截请求,来验证token
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
public class JwtIntercepter implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("token");
try { JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("XJBX")).build(); return true; } catch (Exception e) { e.printStackTrace(); } Map<String, String> message = new HashMap<>(); message.put("wrong", "出现异常"); String json = new ObjectMapper().writeValueAsString(message); response.setContentType(json); response.getWriter().println(json); return false; } }
|
1 2 3 4 5 6 7 8 9 10 11
| @Configuration public class IntercepterConfig implements WebMvcConfigurer {
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new JwtIntercepter()) .addPathPatterns("/user/**"); } }
|