关闭页面特效

Jwt使用笔记

1|0一、什么是JWT


JWT全称 Json·Web·Token,通俗地讲,也就是通过JSON形式作为Web应用中的令牌,用于在各⽅之间安全地将信息作为JSON对象传输,在数据传输地过程中还可以完成数据加密、签名等相关处理,是目前最流⾏的跨域身份解决⽅案

2|0二、JWT能做什么


2|11.授权(使用JWT最常见的方案)


一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。(即JWT能过够实现跨域)

2|22.信息交换


JWT是在各方之间安全地传输信息地好方法。因为可以对JWT进行签名(比例使用公钥/私钥对),所以可以确保发件人是他们所说的人。

3|0三、为什么是JWT


3|1基于传统的session认证


1.认证方式 HTTP协议是⽆状态的,也就是说,如果我们已经认证了⼀个⽤户,那么他下⼀次请求的时候,服务器不知道我是谁,我们必须再次认证。 传统的做法是将已经认证过的⽤户信息存储到服务器上,⽐如session。⽤户下次请求的时候带sessionId,然后服务器检查⽤户是否已经认证过。 2.暴露问题 这种基于服务器的身份认证⽅式存在⼀些问题: 1.Seesions:每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。通常而言session都是保存在内存中,当越来越多的用户发请求时,内存的开销也会不断增加。 2.可扩展性:由于sessions 存放在服务器内存中,这意味着用户下次请求还必须在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡的能力,这也意味着限制了应用的扩展能力。 3.CORS (跨域资源共享):当我们扩展应用程序,让数据能够从不同设备上访问时,跨域资源的共享会是一个让人头疼的问题,不方便集群应用。 4.CSRF (跨站请求伪造):因为是基于cookie来进行用户识别的,cookie如果被截获,用户就很容易受到跨站请求的攻击。

补充 1.那么究竟什么是跨站请求伪造呢?简单地说就是用你的身份去发送一些对你不友好的请求。 举个简单的例子: 小壮登录了某网上银行,他来到了网上银行的帖子区,看到一个帖子下面有一个链接写着“科学理财,年盈利率过万”,小壮好奇的点开了这个链接,结果发现自己的账户少了10000元。这是这么回事呢?原来黑客在链接中藏了一个请求,这个请求直接利用小壮的身份给银行发送了一个转账请求,也就是通过你的 Cookie 向银行发出请求。 导致这个问题很大的原因就是: Session 认证中 Cookie 中的 session_id 是由浏览器发送到服务端的,借助这个特性,攻击者就可以通过让用户误点攻击链接,达到攻击效果。 那么为什么token不存在这种问题呢? 一般情况下我们使用 JWT 的话,在我们登录成功获得 token 之后,一般会选择存放在 local storage 中。然后我们在前端通过某些方式会给每个发到后端的请求加上这个 token,这样就不会出现 CSRF 漏洞的问题。因为,即使有个你点击了非法链接发送了请求到服务端,这个非法请求是不会携带 token 的,所以这个请求将是非法的。(网上找的,个人有点不理解,坑待填) 2.注意区分跨站请求伪造与数字签名(个人理解,可能不对,只是看到CSRF,就突然想到了数字签名) 跨站请求伪造是用你的身份去发送一些对你不友好的请求,这个是基于请求信息上的攻击。而数字签名是防止别人伪造自己的身份发送信息,这个是基于身份验证上的。

3|2基于JWT认证


1.认证流程 在这里插入图片描述 2.jwt优势 ⽆状态和可拓展性:Token 存储在客户端,完全⽆状态,可拓展,特别适用于分布式微服务。我们的负载均衡器可以将⽤户传递到任意服务器,因为在任何地⽅都没有状态或会话信息。

简洁:每次请求的时候token都会被发送,可以作为请求参数发送,可以放在请求头⾥⾯发送,也可以放在cookie⾥⾯被发送,因为数据量小,传输速度也很快。

自包含:负载中包含了所有用户所需要的信息,避免了多次查询数据库。

补充:我刚开始不太理解为什么JWT在浏览器本地存储比session在服务器端存储在负载方面要好,这里我给出自己的理解: 服务器端保存所有的用户的数据,所以服务器端的开销较大,而浏览器端保存则把不同用户需要的数据分布保存在用户各自的浏览器中。

4|0四、JWT的结构是什么?


jwt由三部分组成,他们之间⽤圆点(·)连接,这三部分分别是: 1.标头Header 2.有效负载Payload 3.签名Signature

Header由两部分信息组成: type:声明类型,这⾥是jwt alg:声明加密的算法 通常直接使⽤ HMAC SHA256

Payload就是存放有效信息的地⽅(不强制) iss: jwt签发者 sub: jwt所⾯向的⽤户 aud: 接收jwt的⼀⽅ exp: jwt的过期时间,这个过期时间必须要⼤于签发时间 nbf: 定义在什么时间之前,该jwt都是不可⽤的 iat: jwt的签发时间 jti: jwt的唯⼀身份标识,主要⽤来作为⼀次性token,从⽽回避重放攻击 claim:jwt存放信息的地⽅

Signature就是签名信息 签名目的: 最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被篡改。

因此,⼀个典型的JWT看起来是这个样⼦的: xxxxxxxx·yyyyyyyyyy·zzzzzzzzzzz

5|0五、使用JWT


5|11.引入依赖


<dependency>
           <groupId>com.auth0</groupId>
           <artifactId>java-jwt</artifactId>
           <version>3.8.1</version>
       </dependency>                          
12345

5|22.生成token


@Test
   public void createJwtToken(){
       Calendar instance = Calendar.getInstance();
       instance.add(Calendar.SECOND,300);
       //生成令牌
       String token = JWT.create()
              .withClaim("username", "张三")//设置自定义用户名
              .withExpiresAt(instance.getTime())//设置过期时间
              .sign(Algorithm.HMAC256("token!123ab"));//设置签名 保密 复杂
       //输出令牌
       System.out.println(token);
  }
123456789101112

生成结果: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDQwMjY5ODUsInVzZXJuYW1lIjoi5byg5LiJIn0.cZf24DSxSX4LR-2Z6pTKufAg6kOSJiMANzuXgKz2BrE

5|33.根据令牌和签名解析数据


@Test
   public void freeJwt(){
       String token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDQwMjY5ODUsInVzZXJuYW1lIjoi5byg5LiJIn0.cZf24DSxSX4LR-2Z6pTKufAg6kOSJiMANzuXgKz2BrE";
       //创建验证对象
       JWTVerifier verifier = JWT.require(Algorithm.HMAC256("token!123ab")).build();
       DecodedJWT decodedJWT = verifier.verify(token);
       System.out.println("用户名:"+decodedJWT.getClaim("username").asString());
       System.out.println("过期时间;"+decodedJWT.getExpiresAt());
  }
123456789

6|0六、封装工具类


package com.example.demo.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.junit.Test;

import java.util.Calendar;
import java.util.Map;

/**
* @author chen
* @version 1.0
* @date 2020/10/30 10:25
*/
public class JwtUtils {

   private static final String secret="token!123ab";

   //生成token
   public static String createJwtToken(Map<String,String> map){
       Calendar instance = Calendar.getInstance();
       instance.add(Calendar.SECOND,300);
       //生成令牌
       JWTCreator.Builder builder = JWT.create();
       for(Map.Entry<String,String> entry : map.entrySet())
           builder.withClaim(entry.getKey(),entry.getValue());
       builder.withExpiresAt(instance.getTime());
       return builder.sign(Algorithm.HMAC256(secret));
  }

   //验证token合法性
   public static DecodedJWT verify(String token){
       //创建验证对象,验证通过则正常执行,不通过则会报错(抛出异常)
       return JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
  }


//   //获取token信息(前提是token验证通过)
//   public static DecodedJWT getTokenInfo(String token){
//       return JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
//   }
}


1234567891011121314151617181920212223242526272829303132333435363738394041424344454647

7|0七、整合springboot


7|10.搭建springboot+mybatis+jwt环境


引入依赖

<!--引入jwt-->
       <dependency>
           <groupId>com.auth0</groupId>
           <artifactId>java-jwt</artifactId>
           <version>3.8.1</version>
       </dependency>

<!--引入mybatis-->
       <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
           <version>2.1.3</version>
       </dependency>

<!--引入lombok-->
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>
       
<!--引入mysql-->
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <scope>runtime</scope>
       </dependency>  
         
<!--引入druid-->
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>druid</artifactId>
           <version>1.1.22</version>
       </dependency>          
12345678910111213141516171819202122232425262728293031323334

编写配置

server.port=8989

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/jwt
spring.datasource.username=root
spring.datasource.password=123456

mybatis.type-aliases-package=com.example.demo.entity
mybatis.mapper-locations=classpath:com/example/demo/mapper/*.xml
12345678910

7|21.开发数据库


这里采用最简单的表结构验证JWT使用 (这里我偷下懒,没有写sql语句,直接用navicat建表了) 在这里插入图片描述 在这里插入图片描述

7|32.开发entity


@Data
public class User {
   private Integer id;
   private String name;
   private String password;
}
123456

7|43.开发DAO接口和mapper.xml


@Mapper
public interface UserDAO {
   //直接根据用户名密码登录
   User login(User user);
}

123456
<?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.example.demo.dao.UserDAO">
   <select id="login" parameterType="com.example.demo.entity.User" resultType="com.example.demo.entity.User">
       select * from tb_user
       where name=#{name} and password =#{password}
   </select>
   
</mapper>

7|54.开发Service接口以及实现类


public interface UserService {
   User login(User user);
}
123
public class UserServiceImpl implements UserService{
   @Autowired
   private UserDAO userDAO;
   @Override
   public User login(User user) {
       return userDAO.login(user);
  }
}
12345678

7|65.开发Controller层


package com.example.demo.controller;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.demo.Service.UserService;
import com.example.demo.entity.User;
import com.example.demo.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
* @author chen
* @version 1.0
* @date 2020/10/30 16:28
*/
@RestController
@Slf4j
public class UserController {
   @Autowired
   UserService userService;

   @GetMapping("user/login")
   public Map<String,Object> login(User user){
       Map<String,Object> map = new HashMap<>();
       User userDB = userService.login(user);
       if(userDB!=null){
           //认证成功后,生成JWT令牌
           Map<String,String> payload = new HashMap<>();
           payload.put("id",userDB.getId().toString());
           payload.put("name",userDB.getName());
           String jwtToken = JwtUtils.createJwtToken(payload);
           map.put("state",true);
           map.put("message","认证成功");
           map.put("token",jwtToken);//响应jwtToken
      }
       else{
           map.put("state",false);
           map.put("message","认证失败");
      }
       return map;
  }

   @PostMapping("user/test")
   public Map<String,Object> test(HttpServletRequest request){
       Map<String,Object> map = new HashMap<>();
       String token = request.getHeader("token");
       try{
           DecodedJWT verify = JwtUtils.verify(token);
           log.info("用户id:[{}]",verify.getClaim("id").asString());
           log.info("用户name:[{}]",verify.getClaim("name").asString());
           map.put("state",true);
          map.put("message","请求成功");
          return map;
      }catch(Exception e){
          e.printStackTrace();
          map.put("state",false);
          map.put("message","请求失败");
      }
      return map;
  }
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667

7|76.问题?


—使用上述方式每次都要传递token数据,每个方法都需要验证token,会造成代码冗余,不够灵活的现象,那么如何优化它呢? —使用拦截器进行优化 实现拦截器

package com.example.demo.interceptor;

import com.alibaba.fastjson.JSON;
import com.example.demo.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 chen
* @version 1.0
* @date 2020/10/30 22:20
*/
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(Exception e){
           e.printStackTrace();
           map.put("state",false);
           map.put("message","请求失败");
      }
       response.setContentType("text/html;charset=UTF-8");
       response.getWriter().write(JSON.toJSON(map).toString());
       return false;
  }
}

123456789101112131415161718192021222324252627282930313233343536

7|87.配置拦截器


package com.example.demo.config;

import com.example.demo.interceptor.JwtInterceptor;
import org.springframework.beans.factory.annotation.Value;
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;

/**
* @author chen
* @version 1.0
* @date 2020/10/29 17:06
*/
//有关拦截器的配置
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

   @Override
   public void addInterceptors(InterceptorRegistry registry) {
       //指定拦截器并添加相应策略(拦截或放行)
       registry.addInterceptor(new JwtInterceptor())
              .addPathPatterns("/user/test")
              .excludePathPatterns("user/login");
  }
}

123456789101112131415161718192021222324252627

总结一下上面代码的思路:

用户登录时,验证用户的账户和密码 生成一个Token保存在数据库中,将Token写到Cookie中 将用户数据保存在Session中 请求时都会带上Cookie,检查有没有登录,如果已经登录则放行

7|98.postman检验


在这里插入图片描述


__EOF__

作  者BNDong
出  处https://www.cnblogs.com/losekay/p/15909189.html
关于博主:编程路上的小学生,热爱技术,喜欢专研。评论和私信会在第一时间回复。或者直接私信我。
版权声明:署名 - 非商业性使用 - 禁止演绎,协议普通文本 | 协议法律文本
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!

posted @   阿桐慕i  阅读(226)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
0
0
关注
跳至底部
点击右上角即可分享
微信分享提示