JSON Web Tokens(JWT),是目前最流行的跨域身份验证解决方案。
为什么要采用JWT?
传统的Session校验,需要将用户登录信息保存在服务端,导致扩展性不强。当遇到分布式服务时,Session数据往往需要共享,就需要通过手段将Session数据持久化,达到这一目采用的最多的方式就是用Redis来实现Session共享。
有没有方法不需要服务端来保存用户登录信息,把用户信息放在客户端,服务端只要根据客户端带入的参数校验客户端有效性就可以了?JWT就是这样的。
JWT的数据结构
JWT分成三段,为Header(头部)、Payload(负载)、Signature(签名)
三段数据以.
隔开
Header.Payload.Signature
请看下面一段JWT数据
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJhZG1pbiJ9.Jwb2uY2Sb0SPsQavnpC3irfmNUCt6OjdmLZSRiD_8Bs
JWT Header是元数据,描述了签名算法和token类型,payload就是客户端要保存的数据、Signature是数据签名,用来防止保存在客户端的数据被篡改。
创建SpringBoot项目
打开https://start.spring.io/
,创建一个spring boot的maven项目,并导入到开发工具中。
创建接口
创建两个接口为/login
、/hello-jwt
。/hello-jwt
接口必须要登录后才能访问。
导入web依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
创建/hello-jwt
接口
package com.javafm.jwt.helljwt;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloJwtController {
@RequestMapping("/hello-jwt")
public String helloJwt() {
return "hello jwt";
}
}
导入jwt依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
创建/login
接口
package com.javafm.jwt.helljwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class LoginController {
private String createToken(String username, String password) {
return JWT.create().withAudience(username).sign(Algorithm.HMAC256(password));
}
@RequestMapping("/login")
public Map<String, String> login(@RequestParam("username") String username, @RequestParam("password") String password) {
if ("admin".equals(username) && "admin".equals(password)) {
// 登录成功后创建jwt
Map<String, String> resMap = new HashMap<>();
resMap.put("token", createToken(username, password));
resMap.put("code", "00000");
resMap.put("msg", "success");
return resMap;
}
Map<String, String> resMap = new HashMap<>();
resMap.put("code", "A0200");
resMap.put("msg", "用户登录异常");
return resMap;
}
}
创建JWT校验拦截器
package com.javafm.jwt.helljwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class AuthInterceptor implements HandlerInterceptor {
public void error(HttpServletResponse response) {
response.setHeader("Content-Type", "text/plain;charset=utf-8");
try {
response.getWriter().write("未授权的请求");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
// 登录接口 `/login` 不需要校验
if (uri.indexOf("/login") == 0) {
return true;
}
// 获取header中的token
String token = request.getHeader("Authorization");
if (token == null) {
error(response);
return false;
}
// 从token中提取出username,这个是在登录创建token时放进去的
String username;
try {
username = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException e) {
error(response);
return false;
}
// 假设这里根据 username 去数据库查询出用户信息,在这里我们写死是password
String password = "admin"; // 假设这里是根据用户名从数据库查询出来的用户信息
// 验证token
// Algorithm.HMAC256 参数放的不一定是密码,这个的看你创建token时,放的是什么内容,这里就传入什么内容
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(password)).build();
try {
jwtVerifier.verify(token);
return true;
}catch (JWTVerificationException e) {
error(response);
return false;
}
}
}
注册拦截器
package com.javafm.jwt.helljwt;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/**");
}
}
在未登录前直接请求/hell-jwt
接口,被服务端拦截器拦截,直接返回未授权的请求
先登录获取token,再将token放到header中请求/hello-jwt
源代码地址:https://gitee.com/dev-tang/hell-jwt.git