简介
Jwt
全称是:json web token
。它将用户信息加密到token
里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token
的正确性,只要正确即通过验证。
优点
- 简洁: 可以通过
URL
、POST
参数或者在HTTP header
发送,因为数据量小,传输速度也很快; - 自包含:负载中可以包含用户所需要的信息,避免了多次查询数据库;
- 因为
Token
是以JSON
加密的形式保存在客户端的,所以JWT
是跨语言的,原则上任何web
形式都支持; - 不需要在服务端保存会话信息,特别适用于分布式微服务。
缺点
- 无法作废已颁布的令牌;
- 不易应对数据过期。
Spring Boot
和Jwt
集成示例
pom引入以下依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.8</version>
</dependency>
创建Jwt 认证工具类
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 com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
public class JwtUtil {
/**
* 过期时间60分钟
*/
private static final long EXPIRE_TIME = 60 * 60 * 1000;
/**
* jwt 密钥
*/
private static final String SECRET = "jwt_secret";
/**
* 生成签名,30分钟后过期
* @param userId
* @return
*/
public static String sign(String userId) {
try {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(SECRET);
return JWT.create()
// 将 user id 保存到 token 里面
.withAudience(userId)
// 60分钟后token过期
.withExpiresAt(date)
// token 的密钥
.sign(algorithm);
} catch (Exception e) {
return null;
}
}
/**
* 根据token获取userId
* @param token
* @return
*/
public static String getUserId(String token) {
try {
String userId = JWT.decode(token).getAudience().get(0);
return userId;
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 校验token
* @param token
* @return
*/
public static boolean checkSign(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm)
// .withClaim("username", username)
.build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (JWTVerificationException exception) {
throw new RuntimeException("token 无效,请重新获取");
}
}
}
自定义注解@JwtToken
,注意需要登录才能访问的接口要加上该注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JwtToken {
boolean required() default true;
}
创建JWT拦截器,拦截器拦截带有注解的接口
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) {
// 从 http 请求头中取出 token
String token = httpServletRequest.getHeader("token");
// 如果不是映射到方法直接通过
if(!(object instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod=(HandlerMethod)object;
Method method=handlerMethod.getMethod();
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(JwtToken.class)) {
JwtToken jwtToken = method.getAnnotation(JwtToken.class);
if (jwtToken.required()) {
// 执行认证
if (token == null) {
throw new RuntimeException("no have token,请重新登录");
}
// 获取 token 中的 userId
String userId = JwtUtil.getUserId(token);
System.out.println("用户id:" + userId);
// 验证 token
JwtUtil.checkSign(token);
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
创建web拦截器
import org.springframework.context.annotation.Bean;
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 WebConfig implements WebMvcConfigurer {
/**
* 添加jwt拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())
// 拦截所有请求,通过判断是否有 @JwtToken 注解 决定是否需要登录
.addPathPatterns("/**")
//排除登录、注册接口
.excludePathPatterns("/login/**", "/register/**","/swagger-resources/**"
,"/webjars/**"
,"/koTime/**"
,"/v2/**"
, "/doc/**"
,"/swagger-ui.html/**");
}
/**
* jwt拦截器
* @return
*/
@Bean
public JwtInterceptor jwtInterceptor() {
return new JwtInterceptor();
}
}
全局异常捕获
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(Exception.class)
public Object handleException(Exception e) throws JSONException {
String msg = e.getMessage();
if (msg == null || msg.equals("")) {
msg = "服务器出错";
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("message", msg);
return jsonObject;
}
}
Controller测试
import com.alibaba.fastjson.JSONObject;
import com.zx.zhuangxiu.jwt.JwtToken;
import com.zx.zhuangxiu.pojo.User;
import com.zx.zhuangxiu.service.LoginService;
import com.zx.zhuangxiu.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
@RestController
@RequestMapping("/jwt")
public class JwtController {
@Autowired
LoginService loginService;
/**
* 登录并获取token
* @param userName
* @param passWord
* @return
*/
@RequestMapping("/login")
public ModelAndView logins( String userName, String passWord)
{
ModelAndView mv=new ModelAndView();
User user=loginService.login(userName,passWord);//查询数据库
if(user!=null){
JSONObject object=new JSONObject();
object.put("name",user.getName());
object.put("userid",user.getId());
String token= JwtUtil.sign(object.toString());//将userjson加密为token
mv.addObject("name",user.getName());
mv.addObject("token",token);
}
mv.setViewName("/jwt");
return mv;
}
/**
* 该接口需要带签名才能访问
* @return
*/
@JwtToken
@GetMapping("/getMessage")
public JSONObject getMessage(String token){
JSONObject object=new JSONObject();
//解密token
String userjson = JwtUtil.getUserId(token);//获取json字符串
JSONObject jsonObject = (JSONObject) JSONObject.parseObject(userjson);
User user = JSONObject.toJavaObject(jsonObject, User.class);//将jsonObjct转为对象
object.put("message",user.getName()+"你已通过验证");
return object;
}
}
页面jwt.html,将登陆成功后的token存入本地localStorage或cookie,请求需要Jwt验证的接口通过ajax在请求头hearder中添加token。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title></title>
<link rel="stylesheet" href="/zx/js/index.css">
<script src="/zx/js/jquery-1.9.min.js"></script>
<script type="text/javascript" th:inline="javascript">
var localtoken = [[${token}]];
localStorage.token = localtoken;
function GetDateForServiceCustomer() {
$.ajax({
url: 'http://localhost:8080/zx/jwt/getMessage',
data: {//向接口传递token参数,可解析token获取用户信息
token: localtoken
},
beforeSend: function (request) {
//向发送头添加token后台拦截器会验证合法性
request.setRequestHeader("token", localStorage.token);
},
dataType: 'JSON',
async: false,//请求是否异步,默认为异步
type: 'GET',
success: function (data) {
alert(data.message);
},
error: function (data) {
alert(data.message);
}
});
}
</script>
</head>
<body>
<p th:text="${name}"></p>
<button onclick="GetDateForServiceCustomer()">点击查询信息</button>
</body>
</html>
登陆:http://ip:port/jwt/login?userName=xxx&passWord=xxx
登陆后在浏览器可以看到token
点击查询信息,弹出已通过验证,jwt整合完成。