1.什么是JWT
JWT简称JSON Web Token,也就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。
2.JWT token授权
这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。
3.为什么是JWT
基于传统的Session认证
1.认证方式
我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
基于JWT认证
1.认证流程
首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同lll.zzz.xxx的字符串。 token head.payload.singurater 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。
前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题) HEADER后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。
4.JWT的令牌结构
1.标头(Header)
2.有效载荷(Payload)
3.签名(Signature)
因此,JWT通常如下所示:xxxxx.yyyyy.zzzzz Header.Payload.Signature
5.使用JWT
引入依赖
<!--引入jwt 用户token生成,用户登录验证-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
编写工具类
package com.xy.mastergoose.Utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
public class JWTUtils {
private static final String SECRET = "thisismastergoose";
/**
* 生成token header.payload.sing
*/
public static String getToken(Map<String, String> map){
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE,7);//默认7天过期
//创建jwt builder
JWTCreator.Builder builder = JWT.create();
//payload
map.forEach((k,v)->{
builder.withClaim(k,v);
});
String token = builder.withExpiresAt(instance.getTime())//指定令牌过期时间
.sign(Algorithm.HMAC256(SECRET));//sign
return token;
}
/**
* 验证token 合法性
*/
public static DecodedJWT verify(String token){
return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
}
}
编写拦截器
package com.xy.mastergoose.Intercepter;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xy.mastergoose.Utils.JWTUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/*
* jwt拦截器
* */
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Map<String, Object> map = new HashMap<>();
//获取请求头中令牌
String token = request.getHeader("token");
try {
JWTUtils.verify(token);//验证令牌
return true;//放行请求
} catch (SignatureVerificationException e) {
e.printStackTrace();
map.put("msg","无效签名");
}catch (TokenExpiredException e){
e.printStackTrace();
map.put("msg","token过期");
}catch (AlgorithmMismatchException e){
e.printStackTrace();
map.put("msg","token算法不一致");
}catch (Exception e){
e.printStackTrace();
map.put("msg","token无效");
}
map.put("success",false);//设置状态
//将map 专为json jackson
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);//返回给前端结果
return false;
}
}
注册拦截器(注册后前端请求应带有token请求头)
package com.xy.mastergoose.Config;
import com.xy.mastergoose.Intercepter.JWTInterceptor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
//注册拦截器
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/user/**","/column/**","/order/**","/product/**") //其他接口token验证
.excludePathPatterns("/user/login"); //所有用户都放心
}
//解决跨域问题
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}
controller使用token
//用户登录:(1)功能:按手机号生成token。 1、登录成功返回token。 2、失败返回提示信息。3、第一次登录的手机号直接注册,并返回token; (2)参数:1、user(对象,json格式),必须含有手机号和密码
@PostMapping("/login")
@ApiOperation("用户登录:(1)功能:按手机号生成token。 1、登录成功返回token。 2、失败返回提示信息。3、第一次登录的手机号直接注册,并返回token; (2)参数:1、user(对象,json格式),必须含有手机号和密码")
public Result login(@RequestBody User user){
int flag = userService.loginVerifyForToken(user);
if (flag >= 5){
Result rs = Result.error();
if(flag==5){
return rs.data("msg","手机号不能来个空的啊!");
}else if(flag == 6){
return rs.data("msg","密码是不是输入错误啦!!");
}
}
String token="";
try{
Map<String,String> payload = new HashMap<>();
payload.put("phone",user.getPhone());
//生成JWT的令牌
token = JWTUtils.getToken(payload);
}catch (Exception e){
return Result.error().data("msg","发生异常");
}
return Result.ok().data("token",token);
}
//获取用户参数:(1)功能:通过token获取用户信息;
@GetMapping("/userInfo")
@ApiOperation("获取用户参数:(1)功能:通过请求头中的token获取用户信息;")
public Result userInfo(HttpServletRequest request){
String token = request.getHeader("token");
DecodedJWT verify = JWTUtils.verify(token);
User user = userService.findUserByPhone(verify.getClaim("phone").asString());
return Result.ok().data("user",user);
}
6.总结
使用token验证比传统session更加安全、快捷,也能够很好的处理后端服务器集群带来的用户登录session传递难题,token主要存放于用户端浏览器,在每次请求时携带在请求头中,后端可以通过唯一的签名密钥来检测token是否合法,从而实现登录验证的功能