栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JWT 工作原理及其应用 从0~0.5 快速整合SpringBoot以及Mybatis 二刷绝对适合你~

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JWT 工作原理及其应用 从0~0.5 快速整合SpringBoot以及Mybatis 二刷绝对适合你~

本篇内容:JWT 工作原理及其应用 从0~0.5 快速整合SpringBoot以及Mybatis 二刷绝对适合你!

 文章专栏:前端知识(后端需掌握知识点)

前后端分离项目(Vue + SpringBoot)

最近更新:2022年2月2日 Vue中的路由 Router 从0 ~ 0.5 基础通晓到使用 (后端人员需要掌握的基础使用)

个人简介:一只二本院校在读的大三程序猿,本着注重基础,打卡算法,分享技术作为个人的经验总结性的博文博主,虽然可能有时会犯懒,但是还是会坚持下去的,如果你很喜欢博文的话,建议看下面一行~(疯狂暗示QwQ)

点赞  收藏 ⭐留言  一键三连 关爱程序猿,从你我做起

本文目录

JWT 原理及应用

1、 JWT的简介

1、什么是JWT?2、Token又是什么?3、概念 2、JWT的工作流程3、JWT的结构组成4、Hello JWT⭐5、JWT 中常见异常讲解⭐⭐6、JWT 工具封装⭐⭐7、整合SpringBoot+Mybatis的简单案例⭐总结

JWT 原理及应用 1、 JWT的简介 1、什么是JWT?

什么是 JWT ?

JWT(全称JSON Web Tokens)我们通常称之为 JSON Web 令牌,它是个十分流行的跨域认证的一种解决方案。

2、Token又是什么?

Token 又是什么呢?

Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。

使用Token的目的:Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。


JWT 实际上是 token 的一种具体实现方式。


3、概念

官方参考文献:

JSON Web Tokens - jwt.io

官方给出的概念:

Json web token(JWT)是为了网络应用环境间传递声明而执行的一种基于JSON的开发标准(RFC 7519),用于对双方之间以JSON对象安全的传输信息。

简单的来说:对于学过信息安全技术的同学就知道,为保证了数据的发送方不可抵赖,以及数据的接收方能够安全并且进行验证获取信息,通常都是要对数据进行加密、签名等相关操作。

JWT 就是用于在各个用户之间通过JSON对象数据进行的安全传输的令牌。

再简单点理解就是:我们把用户提出请求中的数据保存到一个JSON字符串中,然后通过某些算法进行编码,得到了一个JWT,此时这个 JWT 已经被加密签名这些操作了, 然后服务器对其进行接收,进行验证签名,并且对之解密获取用户提交的数据。

2、JWT的工作流程

其工作流程一般分为如下6步:

一开始,当用户在前端通过表单提交自身的数据发送到后端对应的API接口时,建议采用加密传输协议,防止信息泄露。后端对用户提交的数据进行验证,认证成功后,将Token的类型和使用的算法作为Header通过base64进行编码生成 JWT 的第一部分结构,xxxxx,将含有用户信息的数据作为Payload(负载)进行base64的编译生成 JWT 的第二部分结构,得到的编码作为yyyyy,随后与签名作为 JWT 的第三部分结构 (zzzzz)进行拼接,形成一个JWT Token的字符串:xxxxx,yyyyy,zzzzz后端将 JWT Token字符串 作为用户认证成功后的请求响应返回给前端。前端可以将返回的结果保存在浏览器当中,退出登录后会删除所对应的 JWT Token。前端会在每次请求时,将上面后端传给浏览器的JWT Token 一并放到HTTP请求头中的 **Authorization**属性 (解决恶意跨域、跨站请求访问的问题)。后端的拦截请求器会验证 JWT Token 其有效性,签名正确性以及是否过期等验证操作。JWT Token验证通过后,后端解析出 JWT Token中的用户信息,返回结果。

注意:因为base64是开放加密算法,所以一定不要在负载中存放敏感信息(用户密码等…)

3、JWT的结构组成

4、Hello JWT

步骤1:创建一个SpringBoot项目,整合 JWT 相关依赖

pom.xml


     com.auth0
     java-jwt
     3.4.0

步骤2:编写一个 JWT 工具类用于生成令牌

JWTUtils.java

public class JWTUtils {
    
    public static String createToken(){
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND,180);
    
        String token = JWT.create()//生成令牌
            .withClaim("username", "Alascanfu")//设置payload
            .withExpiresAt(instance.getTime())//设置过期时间
            .sign(Algorithm.HMAC256("token!Q#28$5RCCF"));//设置signature 
        
        return token;
    }
    
    public static DecodedJWT requireToken(String token){
        JWTVerifier jwtVerifier = JWT
            .require(Algorithm.HMAC256("token!Q#28$5RCCF"))
            .build();
    
        return jwtVerifier.verify(token);
        
    }
}

步骤3:创建一个测试类进行测试

JWTJunitTest.java

@SpringBootTest
public class JWTJunitTest {
    @Test
    public void test(){
        //创建一个token
        String token = JWTUtils.createToken();
        System.out.println("Token :" + token);
    
        //拿着获取的token得到decodedJWT
        DecodedJWT decodedJWT = JWTUtils.requireToken(token);
    	//通过decodedJWT获取数据
        Claim username = decodedJWT.getClaim("username");
        System.out.println(username);
        
        String username = decodedJWT.getClaim("username").asString();
        System.out.println(username);
    }
}

执行结果:

返回的Token令牌:

Token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDM4NTk5MDQsInVzZXJuYW1lIjoiQWxhc2NhbmZ1In0.HYPycutSIfgfh_vNFL6fp2P3mzlBiAmuH7vAy0D-64k
com.auth0.jwt.impl.JsonNodeClaim@5bc7e78e
Alascanfu
⭐5、JWT 中常见异常讲解⭐

我们可以根据jwt的依赖包找到对应的exceptions

SignatureVerificationException: 签名认证错误异常TokenExpiredException: 令牌过期错误异常。AlgorithmMismatchException:校验算法不匹配的错误异常。InvalidClaimException:失效负载数据异常。 ⭐6、JWT 工具封装⭐

JWTUtil.java

public class JWTUtil {
    private static String TOKEN = "&$s78"+UUID.randomUUID().toString()+"#34%6";
    
    
    public static String getToken(Map map){
        JWTCreator.Builder builder = JWT.create();
        
        //向其中添加负载数据
        //23种设计模式中典型的建造者模式
        map.forEach((key,val)->{
            builder.withClaim(key,val);
        });
        //设置过期时间
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND,90);
        builder.withExpiresAt(instance.getTime());
        //进行签名拼接
        return builder.sign(Algorithm.HMAC256(TOKEN)).toString();
    }
    
    
    
    public static void verify(String token){
        JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
    }
    
    
    public static DecodedJWT getToken(String token){
        return  JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
    }
}

用于测试封装类的测试类

JWTJunitTest.java

@SpringBootTest
public class JWTJunitTest {
    @Test
    public void test(){
    
        HashMap map = new HashMap<>();
        map.put("username","Alascanfu");
        map.put("id","201901094106");
    
        String token = JWTUtil.getToken(map);
        System.out.println(token);
        JWTUtil.verify(token);
    
        DecodedJWT token1 = JWTUtil.getToken(token);
        System.out.println(token1.getClaim("username").asString());
        System.out.println(Long.parseLong(token1.getClaim("id").asString()));
    }
}
⭐7、整合SpringBoot+Mybatis的简单案例⭐

步骤1:创建一个数据库并且生成一张user表填入部分数据

CREATE DATAbase jwt;

CREATE TABLE user (
    `id` int not null primary key auto_increment,
    `username` VARCHAR(64) not null ,
    `password` VARCHAR(64) not null
)engine = innodb default charset =utf8;

INSERT INTO `jwt`.`user`(`id`, `username`, `password`) VALUES (1, 'Alascanfu', '123456');

步骤2:创建SpringBoot项目导入相关所需依赖及部分配置

pom.xml


    org.springframework.boot
    spring-boot-starter-web



    org.springframework.boot
    spring-boot-devtools
    runtime
    true


    org.springframework.boot
    spring-boot-starter-test
    test


    com.auth0
    java-jwt
    3.4.0


    com.alibaba
    druid-spring-boot-starter
    1.2.8


    log4j
    log4j
    1.2.17


    mysql
    mysql-connector-java


    org.mybatis.spring.boot
    mybatis-spring-boot-starter
    2.2.1


    org.springframework.boot
    spring-boot-starter-jdbc

配置好application.yaml

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/jwt?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    druid:
      #Spring Boot默认是不注入这些属性值的,需要自我绑定
      #druid 数据源专有配置
      initialSize: 5
      minIdle: 5
      maxActive: 20
      # 配置获取连接等待超时的时间
      maxWait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECt 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      # 打开PSCache,并且指定每个连接上PSCache的大小
      poolPreparedStatements: true
      maxPoolPreparedStatementPerConnectionSize: 20
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙,此处是filter修改的地方
      filters: stat,wall,log4j
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      # 合并多个DruidDataSource的监控数据
      useGlobalDataSourceStat: true

配置好log4j.properties

log4j.rootLogger=DEBUG, Console,file
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

#文件输出的相关配置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File = ./log/DataLog.log
log4j.appender.file.MaxFileSize = 10mb
log4j.appender.file.Threshold = DEBUG
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = [%p][%d{yy-MM-dd}][%c]%m%n


#日志输出级别
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.org.apache=INFO
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

步骤3:编写用户类及其Mapper、service、controller等实例数据业务

User.java

public class User {
    private int id ;
    private String username;
    private String password;
    
    public User() {
    }
    
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
    
    //...get/set方法
    
    @Override
    public String toString() {
        return "User{" +
            "id=" + id +
            ", username='" + username + ''' +
            ", password='" + password + ''' +
            '}';
    }
}

UserMapper.java

@Mapper
@Repository
public interface UserMapper {
    
    public User queryUser(User user);
}

UserMapper.xml




    
    
        select id, username, password from user where user.username = #{username} and user.password = #{password}
    


UserService.java

@Service
public class UserService {
    @Autowired
    UserMapper userMapper;
	
    @Transactional(propagation = Propagation.REQUIRED)
    public User queryUser(User user) {
        if(userMapper.queryUser(user) != null){
            return userMapper.queryUser(user);
        }else {
            throw new RuntimeException("用户名或密码错误~");
        }
        
    }
}

注意:此时进行单元测试检阅是否能获得得到数据库中的数据

HelloJwtApplicationTests.java

@SpringBootTest
class HelloJwtApplicationTests {
    
    @Autowired
    UserService userService;
    @Test
    void contextLoads() {
        System.out.println(userService.queryUser(new User("Alascanfu", "123456")).toString());
    }
    
}

如果出现了绑定错误记得要去pom.xml中进行配置yaml、xml、properties等文件也要被打包到target中哦~标签中进行修改。


    src/main/java
    
        ***.yaml
        ***.properties
    
    true


    src/main/resources
    
        ***.yaml
        ***.properties
    
    true

步骤4:编写Controller

UserController.java

@RestController
public class UserController {
    
    @Autowired
    UserService userService;
    
    @RequestMapping("/user/login")
    public Map login(User user){
        Map map = new HashMap<>();
        try {
            User queryUser = userService.queryUser(user);
            Map payload = new HashMap<>();
            payload.put("id",String.valueOf(queryUser.getId()));
            payload.put("username",queryUser.getUsername());
            String token = JWTUtil.getToken(payload);
            map.put("status",true);
            map.put("msg","认证成功!");
            map.put("token",token);
        }catch (Exception e){
            map.put("status",false);
            map.put("msg","认证失败!");
        }
        return map;
    }
}

进行测试

可见用户登录成功后,我们将JWT工具类生成的token也一并返回给浏览器了。

当我们需要请求需要保护的业务接口时,可以验证其接口后再进行业务处理,或者之前用户登录过后不想再次登录时,都可以基于token免除登录,直接访问。

额外接口

@RequestMapping("/user/index")
    public Map userIndex(String token){
        System.out.println(token);
        Map map = new HashMap<>();
        try {
            JWTUtil.verify(token);
            DecodedJWT verify = JWTUtil.getToken(token);
            map.put("state",true);
            map.put("msg","请求成功!");
            return map;
        } catch (Exception e) {
            e.printStackTrace();
            map.put("msg",e.toString());
        }
        map.put("state",false);
        return map;
    }

接口测试

验证token成功所显示的数据

验证token失败所显示

注意:如果给每个业务API编写上述会使得代码冗余,为了减少代码冗余,我们可以将这个公共的操作放在单体JavaSpringBoot应用中的拦截器中去做,如果是SpringCloud分布式服务模块咱们就可以去网关进行配置。

编写拦截器

interceptors/JWTInterceptor.java

public class JWTInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("token");
        Map map = new HashMap<>();
        try {
            //验证
            JWTUtil.verify(token);
            //如果能验证成功直接放行
            return true;
        } catch (TokenExpiredException e) {
            map.put("msg", "Token已经过期~");
        } catch (SignatureVerificationException e){
            map.put("msg", "签名错误~");
        } catch (AlgorithmMismatchException e){
            map.put("msg", "加密算法不匹配~");
        } catch (Exception e) {
            e.printStackTrace();
            
            map.put("msg", "无效token~");
        }
        map.put("state", false);
    
        //通过jackson转化map为json数据
        String json = new ObjectMapper().writevalueAsString(map);
        
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
        return false;
    }
}

将我们的拦截器注入到Spring容器当中,我们需要写一个配置类继承WebMvcConfigurer对SpringMVC进行扩展

config/intercceptorConfig.java

@Configuration
public class IntercsptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())
            .addPathPatterns("/user/**")//需要拦截的路径
            .excludePathPatterns("/user/login");//但是生成token的路径不能拦截
    }
}

进行测试验证

POSTMAN测试

当我们没有任何token携带的时候前端页面返回的是无效token异常。后端显示的是前端控制器DispatcherServlet拦截器爆出的异常错误:

然后我们对其进行登录先生成一个token。


随后我们获得了后端发给我们的token数据。将其添加到请求头中。

此时我们再去访问需要验证token的部分API接口时就无须很复杂处的操作了,拦截器都帮我们执行好了。

总结

本次整理的是前后端分离项目或者单体SpringBoot应用中,基于JWT令牌认证的知识总结,可以有效避免了使用之前较为复杂的Session校验,同时安全性便捷性都也有一定的提高,对于每个业务API都有token认证,可以为我们省去开发中很多问题,同时后续结合SpringSecurity可以让系统更加的安全可靠,大概就是这么多了,大概一两个小时就可以入门到使用,也了解其中的工作原理,这就是后端人员需要掌握的JWT的知识咯~

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/727291.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号