Json Web token
通过json形式作为Web应用的令牌,用于各方之间安全的将信息作为JSON对象传送,在传输过程中可以完成数据加密,签名等处理
JWT能做什么
1、授权
一但用户登录,每个后续请求都会包括JWT,从而允许用户访问该令牌允许的路由,服务和资源 单点登录就是JWT实现的 开销小,再不同域中可以轻松访问
2、信息交换
JWT可以签名确保数据发件人的正确性,由于签名是使用标头和有效负载计算的,因此我们还可以验证内容是否遭到篡改。
基于传统的Session认证
http协议本身是无状态的,如果用户向我们提供用户名和密码进行认证后,下一次登录还是需要用户提供认证信息。因为我们并不知道是那个用户发出的请求。
当用户认证成功后我们在服务器创建一个会话,也就是一个Session,然后把用户信息保存到Session中 然后服务器会返回一个Sessionid的 cookie。
那么用户之后每次登录都会带着自己的Sessionid 那么如果服务器有就证明我们之前认证过。
问题
1、每个用户认证后都会在服务器上存储一个Session,这个东西一般是在内存中的,用户增多服务器开销太大
2、一个Session是存在一个服务器上的,使得分布式应用没有意义
3、cookie如果被截获,用户就饿会很容易受到跨站请求伪造攻击
4、在前后端分离的项目中及其痛苦,
JWT认证过程
首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload (负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同111.ZZZ.xxx的字符串。token head.payload.singurater
后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localstorage或sessionStorage上,退出登录时前端删除保存的JWT即可。
前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF(问题) HEADER axios h
后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确﹔检查Token是否过期;检查Token的接收方是否是自己(可选)。
–验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。
JWT结构
token其实就是一个string 但是是三段header.payload.signature
1、标头(Header) 通常由两部分组成 令牌的类型(JWT)和所使用的签名算法,然后使用Base64编码组成JWT的结构的第一部分
{
"alg":"HS256",
"type":"JWT"
}
2、有效载荷(Payload) 其中包含声明,声明是有关实体(通常是用户)和其他数据的声明,同样也会用Base64编码组成JWT的第二部分 注意不要放敏感信息不安全
{
"name":"shuaikb",
"admin":true
}
3、签名(Signature) 签名需要使用编码后的header和payload以及我们提供的一个随机密钥,然后使用header中指定的签名算法进行签名。签名的作用时保证JWT没有被篡改过。
签名的目的:实际上时对头部已经负载内容进行标记,防止被篡改。
因为Base64是可逆的所以不要在 负载中放任何敏感信息。
jwt的应用
依赖导入
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.1</version>
</dependency>
JWT的生成
@Test
public void test(){
HashMap<String, Object> map = new HashMap<>();
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,60);
String token = JWT.create()
.withHeader(map)//header
.withClaim("userid",21)//payload
.withClaim("username","shuaikb")
.withExpiresAt(instance.getTime())//过期时间
.sign(Algorithm.HMAC256("DuoShuaio_Shuaikb"));//签名
System.out.println(token);
}
//进阶写法
private static final String Signature = "DuoShuaio_Shuaikb";
public static String getToken(Map<String,String> map){
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE,7);
JWTCreator.Builder builder = JWT.create();
map.forEach((k,v)->{
builder.withClaim(k,v);
});
String token = builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(Signature));
return token;
}
token验证
//验证对象
JWTVerifier duoShuaio_shuaikb = JWT.require(Algorithm.HMAC256("DuoShuaio_Shuaikb")).build();
DecodedJWT verify = duoShuaio_shuaikb.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MjkzOTA0ODMsInVzZXJpZCI6MjEsInVzZXJuYW1lIjoic2h1YWlrYiJ9.ezfUFdZ9BpeOBI0drrl2VE2HceX2znyjDc4H2Iwhsx8");
System.out.println(verify.getClaim("userid").asInt());
System.out.println(verify.getClaim("username").asString());
//必须再token有效期才能拿到
接口测试
@Data
public class YongHu {
private int id;
private String name;
private String password;
}
@Repository
public interface YongHuDao {
YongHu login(YongHu yongHu);
}
public interface YongHuService {
YongHu login(YongHu yongHu);
}
@Service
@Transactional
public class YongHuServiceImpl implements YongHuService{
@Autowired
private YongHuDao YONG_HU_DAO;
@Override
public YongHu login(YongHu yongHu){
YongHu yongHu1 = YONG_HU_DAO.login(yongHu);
if (yongHu1!=null){
return yongHu1;
}
throw new RuntimeException("登录失败");
}
}
@RestController
@Slf4j
public class YongHuController {
@Autowired
private YongHuService yongHuService;
@GetMapping("/login")
public Map<String,Object> login(YongHu yongHu) {
log.info("用户名:[{}]",yongHu.getName());
log.info("密码:[{}]",yongHu.getPassword());
Map<String,Object> map = new HashMap<>();
try {
YongHu yongHu1 = yongHuService.login(yongHu);
Map<String,String> payload = new HashMap<>();
payload.put("id", String.valueOf(yongHu1.getId()));
payload.put("name", yongHu1.getName());
//生成JWT令牌
String token = JwtUtils.getToken(payload);
map.put("token",token);
map.put("state",true);
map.put("msg","认证成功");
}catch (Exception e){
map.put("state",false);
map.put("msg",e.getMessage());
}
return map;
}
@PostMapping("/test")
public Map<String,Object> test(String token){
log.info("当前token为:[{}]",token);
Map<String,Object> map =new HashMap<>();
//验证令牌
try{
JwtUtils.verify(token);
map.put("state",true);
map.put("msg","请求成功");
return map;
}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","签名算法不一致");
}catch (Exception e){
e.printStackTrace();
map.put("msg","Token无效");
}
map.put("state",false);
return map;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shuaikb.dao.YongHuDao">
<select id="login" parameterType="YongHu" resultType="YongHu">
select * from jwt where name=#{name} and password=#{password}
</select>
</mapper>
问题就出现了如果每一个接口我们都要去写一边token验证,那代码就太冗余了。所以我们可以选择AOP 写一个拦截器 springcloud就写在网关里。
拦截器
package com.shuaikb.interceptors;
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.shuaikb.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;
/**
* @Author Shuaikb
* @create 2021/8/20 17:44
*/
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
Map<String,Object> map = new HashMap<>();
try{
JwtUtils.verify(token);
map.put("state",true);
map.put("msg","请求成功");
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","签名算法不一致");
}catch (Exception e){
e.printStackTrace();
map.put("msg","Token无效");
}
map.put("state",false);
//想应到前台为json
//resposebody底层 是用的jackson
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
配置类(过滤器)
package com.shuaikb.config;
import com.shuaikb.interceptors.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Author Shuaikb
* @create 2021/8/20 17:52
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/yonghu/test")
.excludePathPatterns("/yonghu/login");
}
}
数据验证
@PostMapping("/test")
public Map<String,Object> test(HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
String token = request.getHeader("token");
DecodedJWT verify = JwtUtils.verify(token);
log.info("用户id:[{}]:",verify.getClaim("id").asString());
log.info("用户name:[{}]",verify.getClaim("name").asString());
map.put("test","成功");
return map;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。