JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
1. 用户输入账号和密码发出POST请求;
2. 验证通过后服务器应用使用私钥创建一个JWT;
3. 服务器应用返回JWT;
4. 浏览器将JWT添加在请求头中向服务器发送请求;
5. 服务器应用验证JWT的正确性以及权限;
6. 验证通过后服务器应用返回响应的资源。
JWT标准的Token需要包含三个部分:
第一部分:Header(头部),头部主要包括参数的类型以及签名的算法。
第二部分:Payload(有效载荷),有效载荷中包含部分信息,例如用户名、登录状态、有效时间等,在非加密情况下,不要在有效载荷中存放敏感信息,例如用户ID、登陆密码等。
第三部分:Signature(签名),签名由加密后的头部以及加密后的负荷通过“.”拼接而成,服务器应用在收到JWT数据之后,会首先对头部和有效载荷用同一算法再次加密,验证签名的正确性,所以签名可以在消息传递的过程中判断数据是否被篡改,在生成签名的过程中使用非对称加密还可以判断JWT发送方的正确性。
第一种情况:授权(Authorization),授权是JWT的最常见的使用场景,一旦用户输入正确的验证信息,成功登陆之后,用户提交的每个请求都将包含JWT,从而允许用户访问指定的服务和资源。JWT也用于实现单点登陆,它占用的开销很小,而且它可以在跨域时使用。
第二种情况:信息交换(Information Exchange),JWT本身可以携带一些信息,而且可以被签名,服务器可以通过签名验证内容是否被篡改。
第一步,在服务器应用的maven依赖中添加对JWT的依赖
第二步,在登录接口验证登陆信息正确后,生成JWT
//生成JWT的工具类
public class JWTUtil {
/**
* @param userName 用户名
* @return token
*/
public static String createToken(String userName) {
try {
Map
header.put("alg", "HS256");//alg为header和payload的加密方式
header.put("typ","JWT");
// 设置过期时间为当前时间后俩小时
Date nowDate = new Date();
Date expireDate = DateUtils.getAfterDate(new Date(),0,0,0,2,0,0);
String token = JWT.create()
// 设置JWT中的header
.withHeader(header)
// 设置用户名
.withClaim("userName",userName)
// 设置当前时间
.withIssuedAt(nowDate)
// 设置到期时间
.withExpiresAt(expireDate)
// 生成签名的加密方式
.sign(Algorithm.HMAC256(Constant.SECRETKEY));
return token;
} catch (JWTCreationException e) {
e.printStackTrace();
return null;
}
}
// 刷新令牌中的当前时间与到期时间
public static String reFreshToken(String token){
// 首先获取到token中的userName信息,再生成新的token
try{
Claims claims = Jwts.parser()
.setSigningKey(Constant.SECRETKEY)
.parseClaimsJws(token).getBody();
return createToken(claims.get("userName").toString());
}catch (Exception e){
throw new RuntimeException("解密失败");
}
}
}
第三步,在服务器应用中添加验证JWT的拦截器
public class AuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 如果请求的不是action,就直接通过
if (!(handler instanceof HandlerMethod)) {
return true;
} else {
String token = request.getHeader("Authorization");
// 如果请求中存在token,则验证token,若token验证通过,则请求通过,本项目未涉及权限管理,只存在用户登录状态以及登陆超时的检测
if (token != null&&token.length()>0) {
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(Constant.SECRETKEY)).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("token未通过验证");
}
} else {
// 若不存在token,则是前台发来的请求或者是后台第一次发来的登陆请求,只允许请求部分接口
String uri = request.getRequestURI();
if (uri.indexOf("getList") != -1 || uri.indexOf("subscribe") != -1 || uri.indexOf("contact") != -1||uri.indexOf("login")!=-1) {
} else {
throw new RuntimeException("无权限访问");
}
}
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
第四步,注册拦截器
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册JWT拦截器,拦截所有请问
registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor(){
return new AuthenticationInterceptor();
}
}
第五步,实现登陆逻辑的控制器
@RestController
@RequestMapping("loginapi")
public class AdminLoginController {
// 登陆验证,用户名密码应从数据库中查询验证,当前设置为常量仅作展示
@PostMapping("login")
public ResultBean login(String username, String password, HttpServletRequest request){
String ip = request.getRemoteAddr();
if(Constant.USERNAME.equals(username)&&Constant.PASSWORD.equals(password)){
return ResultUtil.setOK("登陆成功", JWTUtil.createToken(username));
}else {
return ResultUtil.setError(100,"用户名或密码错误",null);
}
}
}
第六步,前端实现发送登录请求并记录的代码
$.ajax({
type: 'post',
url: geturl('/loginapi/login'),
async: true,
data: data.field,
success: function(data) {
layer.msg(data.msg);
if (data.code == 0) {
//登陆成功后会收到token数据,将token存储到localStorage中
localStorage['token']= data.data;
window.location.href='index.html';
} else {}
}
})
第七步,在下次进行发起操作请求时,在Header中添加token
$.ajax({
type: 'post',
url: url,
async: true,
data: {
type: articleTypeDetail,
_method: "PATCH",
//你的数据
},
beforeSend: function(request) {
request.setRequestHeader("Authorization", localStorage['token'] != null ? localStorage['token'] : null);
},
success: function(data) {
if (data.code == 0) {
layer.msg('修改成功');
} else {
layer.msg('修改失败');
}
},
error: function(XMLHttpResponse, textStatus, errorThrown) {
layer.msg('登陆超时,请重新登录');
}
})