JWT整合Springboot的方法步骤

目录
  • 1.基于JWT认证
    • 1.1 认证流程
    • 1.2 JWT优势
    • 1.3 JWT的结构是什么
  • 2.使用JWT
  • 3.整合Springboot

1.基于JWT认证

1.1 认证流程

  • 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
  • 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同lll.zzz.xxx的字符串。 token head.payload.singurater
  • 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStoragesessionStorage上,退出登录时前端删除保存的JWT即可。
  • 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题) HEADER
  • 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。
  • 验证通过后,后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

1.2 JWT优势

  • 简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
  • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
  • 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持
  • 不需要在服务端保存会话信息,特别适用于分布式微服务

1.3 JWT的结构是什么

token string ====> header.payload.singnature token

令牌组成

  • 标头(Header)
  • 有效载荷(Payload)
  • 签名(Signature)

因此,JWT通常如下所示:xxxxx.yyyyy.zzzzz Header.Payload.Signature

Header

标头通常由两部分组成:令牌的类型(即JWT)所使用的签名算法,例如HMAC SHA256或RSA。它会使用 Base64 编码组成 JWT 结构的第一部分。

注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用 Base64 编码组成 JWT 结构的第二部分

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

Signature

前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过如:HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload),secret);

签名目的

最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

信息安全问题

在这里大家一定会问一个问题:Base64是一种编码,是可逆的,那么我的信息不就被暴露了吗?

是的。所以,在JWT中,不应该在负载里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的User ID。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第三方通过Base64解码就能很快地知道你的密码了。因此JWT适合用于向Web应用传递一些非敏感信息。JWT还经常用于设计用户认证和授权系统,甚至
实现Web应用的单点登录。

2.使用JWT

引入依赖

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

生成token

@Test
void contextLoads() {
    HashMap<String, Object> map = new HashMap<>();
    Calendar instance=Calendar.getInstance();
    instance.add(Calendar.SECOND,200);
    //获取token
    String token = JWT.create().withHeader(map)  //header,默认,可以不写
        .withClaim("userId", 23)
        .withClaim("username", "xpp") //palyload
        .withExpiresAt(instance.getTime())  //指定令牌的过期时间
        .sign(Algorithm.HMAC256("xpp@ll"));//签名,指定秘钥
    System.out.println(token);

}

生成结果

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2Mzc5MDM2OTksInVzZXJJZCI6MjMsInVzZXJuYW1lIjoieHBwIn0.dnWldEBILZpt7Upz762VcMm-uTU9HI5jK-AHk3XyX0s

根据令牌和签名解析数据

@Test
public void test(){
    //创建验证对象
    JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("xpp@ll")).build();
    //验证token
    DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2Mzc5MDM2OTksInVzZXJJZCI6MjMsInVzZXJuYW1lIjoieHBwIn0.dnWldEBILZpt7Upz762VcMm-uTU9HI5jK-AHk3XyX0s");
    //获取存入palyload的信息
    Date expiresAt = verify.getExpiresAt();
    System.out.println(expiresAt);
    System.out.println(verify.getClaim("userId").asInt());
}

常见异常信息

  • SignatureVerificationException: 签名不一致异常
  • TokenExpiredException: 令牌过期异常
  • AlgorithmMismatchException: 算法不匹配异常
  • InvalidClaimException: 失效的payload异常

3.整合Springboot

引入依赖

<!--引入jwt-->
<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>java-jwt</artifactId>
  <version>3.4.0</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>
  <version>1.18.12</version>
</dependency>

<!--引入druid-->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.19</version>
</dependency>

<!--引入mysql-->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.38</version>
</dependency>

配置

server.port=8989
spring.application.name=jwt

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

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

logging.level.com.baizhi.dao=debug

数据库表

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(80) DEFAULT NULL COMMENT '用户名',
  `password` varchar(40) DEFAULT NULL COMMENT '用户密码',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

实体类

@Data
@Accessors(chain=true)
public class User {
    private String id;
    private String name;
    private String password;
}

开发DAO接口和mapper.xml

@Mapper
public interface UserDAO {
    User login(User user);
}
<mapper namespace="com.baizhi.dao.UserDAO">
    <!--这里就写的简单点了毕竟不是重点-->
    <select id="login" parameterType="User" resultType="User">
        select * from user where name=#{name} and password = #{password}
    </select>
</mapper>

开发Service 接口以及实现类

public interface UserService {
    User login(User user);//登录接口
}
@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDAO userDAO;
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public User login(User user) {
        User userDB = userDAO.login(user);
        if(userDB!=null){
            return userDB;
        }
        throw  new RuntimeException("登录失败~~");
    }
}

编写JWT工具类

/**
 * @Auther: xppll
 * @Date: 2021/11/26 13:25
 */
public class JWTUtils {
    //秘钥
    private static final String TOKEN = "xpp@ll";

    /**
     * 生成token(head.playload.sing)
     *
     * @param map 用户传过了的信息,例如id等
     * @return
     */
    public static String getToken(Map<String, String> map) {
        Calendar instance = Calendar.getInstance();
        //设置默认7天过期
        instance.add(Calendar.DATE, 7);

        //创建jwt builder
        JWTCreator.Builder builder = JWT.create();
        //payload
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });
        //指定令牌过期时间
        String token = builder.withExpiresAt(instance.getTime())
                //sign,传入加密盐
                .sign(Algorithm.HMAC256(TOKEN));
        return token;
    }

    /**
     * 验证token合法性
     *
     * @param token
     */
    public static void verify(String token) {
        JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
    }

    /**
     * 获取token信息方法
     *
     * @param token
     * @return
     */
    public static DecodedJWT getTokenInfo(String token) {
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
        return verify;
    }

}

开发controller

/**
 * @Auther: xppll
 * @Date: 2021/11/26 14:07
 */
@RestController
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user/login")
    public Map<String, Object> login(User user) {
        Map<String, Object> map = new HashMap<>();
        try {
            User userDB = userService.login(user);
            //用来存放payload
            Map<String, String> payload = new HashMap<>();
            payload.put("id", userDB.getId());
            payload.put("name", userDB.getName());
            //生成JWT的令牌
            String token = JWTUtils.getToken(payload);
            map.put("state", true);
            map.put("msg", "成功");
            //成功返回token信息
            map.put("token", token);
        } catch (Exception e) {
            map.put("state", false);
            map.put("msg", e.getMessage());
        }
        return map;
    }
}

数据库添加测试数据启动项目

通过postman模拟登录失败

通过postman模拟登录成功

编写测试接口

@PostMapping("user/test")
public Map<String, Object> test(String token) {
    //通过拦截器验证了,token验证成功
    Map<String, Object> map = new HashMap<>();
    map.put("state", true);
    map.put("msg", "请求成功!");
    return map;
}

使用拦截器验证toke

/**
 * @Auther: xppll
 * @Date: 2021/11/26 15:20
 */
@Component
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求头中存放的令牌token
        String token = request.getHeader("token");
        Map<String, Object> map = new HashMap<>();
        try {
            //验证令牌
            JWTUtils.verify(token);
            return true;
        } catch (SignatureVerificationException e) {
            e.printStackTrace();
            map.put("msg", "无效签名");
        } catch (AlgorithmMismatchException e) {
            e.printStackTrace();
            map.put("msg", "token算法不一致");
        } catch (TokenExpiredException e) {
            e.printStackTrace();
            map.put("msg", "token过期");
        } catch (Exception e) {
            e.printStackTrace();
            map.put("msg", "token无效");
        }

        map.put("state", false);
        //将map转为json,返回给前端
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().print(json);
        return false;
    }
}

使拦截器生效

/**
 * @Auther: xppll
 * @Date: 2021/11/26 15:28
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Autowired
    private JWTInterceptor jwtInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor)
                //其他接口需要token验证,拦截器拦截
                .addPathPatterns("/user/test")
                .excludePathPatterns("/user/login");
    }
} 

到此这篇关于JWT整合Springboot的方法步骤的文章就介绍到这了,更多相关JWT整合Springboot内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot整合JWT框架,解决Token跨域验证问题

    一.传统Session认证 1.认证过程 1.用户向服务器发送用户名和密码. 2.服务器验证后在当前对话(session)保存相关数据. 3.服务器向返回sessionId,写入客户端 Cookie. 4.客户端每次请求,需要通过 Cookie,将 sessionId 回传服务器. 5.服务器收到 sessionId,验证客户端. 2.存在问题 1.session保存在服务端,客户端访问高并发时,服务端压力大. 2.扩展性差,服务器集群,就需要 session 数据共享. 二.JWT简介 JWT

  • SpringBoot整合JWT Token的完整步骤

    目录 背景 一  JWT 消息构成 1.1 组成 1.2 header 1.3 playload 1.4 signature 二 Spring Boot 和 JWT集成实例 2.1 项目依赖 2.2 自定义注解 @JwtToken 2.3 JWT认证工具类 JwtUtil.java 2.4 拦截器拦截带有注解的接口 JwtInterceptor.java 2.5 全局异常捕获 2.6 接口 JwtController.java 2.7 Postman测试接口 2.7.1 在没有token的情况下

  • Springboot实现Shiro整合JWT的示例代码

    写在前面 之前想尝试把JWT和Shiro结合到一起,但是在网上查了些博客,也没太有看懂,所以就自己重新研究了一下Shiro的工作机制,然后自己想了个(傻逼)办法把JWT和Shiro整合到一起了 另外接下来还会涉及到JWT相关的内容,我之前写过一篇博客,可以看这里:Springboot实现JWT认证 Shiro的Session机制 由于我的方法是改变了Shiro的默认的Session机制,所以这里先简单讲一下Shiro的机制,简单了解Shiro是怎么确定每次访问的是哪个用户的 Servlet的Se

  • 详解SpringBoot+SpringSecurity+jwt整合及初体验

    原来一直使用shiro做安全框架,配置起来相当方便,正好有机会接触下SpringSecurity,学习下这个.顺道结合下jwt,把安全信息管理的问题扔给客户端, 准备 首先用的是SpringBoot,省去写各种xml的时间.然后把依赖加入一下 <!--安全--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-secu

  • SpringBoot整合JWT的入门指南

    目录 1.JWT 2.JWT登录执行流程图 3.为什么使用JWT? 4.JWT的组成 5.SpringBoot整合JWT 测试 总结 1.JWT JWT(JSON Web Token),为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准.将用户信息加密到token中,服务器不保存任何用户信息,服务器通过保存的密钥验证token的正确性. 优点 1.简介:可以通过 URL POST 参数或者在 HTTP header 发送,因为数据量小,传输速度也很快: 2.自包含:负载中可以包含用户

  • SpringBoot Security整合JWT授权RestAPI的实现

    本教程主要详细讲解SpringBoot Security整合JWT授权RestAPI. 基础环境 技术 版本 Java 1.8+ SpringBoot 2.x.x Security 5.x JWT 0.9.0 创建项目 初始化项目 mvn archetype:generate -DgroupId=com.edurt.sli.slisj -DartifactId=spring-learn-integration-security-jwt -DarchetypeArtifactId=maven-ar

  • JWT整合Springboot的方法步骤

    目录 1.基于JWT认证 1.1 认证流程 1.2 JWT优势 1.3 JWT的结构是什么 2.使用JWT 3.整合Springboot 1.基于JWT认证 1.1 认证流程 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口.这一过程一般是一个HTTP POST请求.建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探. 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个J

  • Storm框架整合springboot的方法

    Storm:最火的流式处理框架 伴随着信息科技日新月异的发展,信息呈现出爆发式的膨胀,人们获取信息的途径也更加多样.更加便捷,同时对于信息的时效性要求也越来越高.举个搜索场景中的例子,当一个卖家发布了一条宝贝信息时,他希望的当然是这个宝贝马上就可以被卖家搜索出来.点击.购买啦,相反,如果这个宝贝要等到第二天或者更久才可以被搜出来,估计这个大哥就要骂娘了.再举一个推荐的例子,如果用户昨天在淘宝上买了一双袜子,今天想买一副泳镜去游泳,但是却发现系统在不遗余力地给他推荐袜子.鞋子,根本对他今天寻找泳镜

  • Springboot整合activemq的方法步骤

    今天呢心血来潮,也有很多以前的学弟问到我关于消息队列的一些问题,有个刚入门,有的有问题都来问我,那么今天来说说如何快速入门mq. 一.首先说下什么是消息队列? 1.消息队列是在消息的传输过程中保存消息的容器. 二.为什么要用到消息队列? 主要原因是由于在高并发环境下,由于来不及同步处理,请求往往会发生堵塞,比如说,大量的insert,update之类的请求同时到达 MySQL ,直接导致无数的行锁表锁,甚至最后请求会堆积过多,从而触发too many connections错误.通过使用消息队列

  • Springboot整合Urule的方法步骤

    摘要: Urule决策引擎可简化开发校验.决策类代码,底层由java语言实现,可基于SpringBoot快速配置,因为Urule工具目前为非常用工具,网上关于SpringBoot整合Urule资料匮乏,一直自己摸索,简单的环境搭建也费了些功夫,遇到些坑,作此记录 本次记录主要记录Urule-Serve端Urule-Client端分开部署的模式,这种使用场景也会更多:嵌入式成一个项目的配置和Urule-Server端一致. 一.Urule-Server端: 1.1. 基于maven的SpringB

  • SpringBoot项目整合mybatis的方法步骤与实例

    1. 导入依赖的jar包 springboot项目整合mybatis之前首先要导入依赖的jar包,配置pom.xml文件如下: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  • 从SpringMVC迁移到Springboot的方法步骤

    在将SpringMVC项目转移到Springboot上的过程中,主要做了以下的事情 Profile配置 全局变量从properties文件读入 数据源与Mybatis配置 日志文件配置 WebConfig配置(包括原有的web.xml和spring-mvc.xml) 去掉多余的bean注入 本篇文章除了介绍做了些什么和怎么做之外,会多很多多余的废话,关于对原理的一些探讨,知其然也要知其所以然. Profile配置 在传统的Spring项目中,多个profile的配置方式首先是在pom.xml文件

  • activemq整合springboot使用方法(个人微信小程序用)

    主题 ActiveMQ Spring Boot 小程序开发 1.引入依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> <relativePath /> <!-- lookup

  • SpringBoot快速迁移至Quarkus的方法步骤

    Quarkus 是一个目前非常火的 Java 应用开发框架,定位是轻量级的微服务框架.,Quarkus 提供了优秀的容器化整合能力,相较于传统开发框架(Spring Boot)有着更快的启动速度.更小的内存消耗.更短的服务响应. 本文将演示将 SpringBoot 迁移至 Quarkus Spring Boot 示例程序 使用 JPA 完成 数据库的增删改查操作,基础代码如下 maven 依赖 <dependency> <groupId>org.springframework.bo

  • SpringBoot整合之SpringBoot整合MongoDB的详细步骤

    目录 一.创建项目,选择依赖 二.引入相关依赖(非必要) 三.如果是第一次使用MongoDB,首先先创建用户 四.定义核心配置文件 六.创建dao层,这里的dao层有两种写法 MongoDB 是一个基于分布式文件存储的数据库.由 C++ 语言编写.旨在为 WEB 应用提供可扩展的高性能数据存储解决方案. MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的.本文介绍SpringBoot整合之SpringBoot整合MongoDB的步骤. 一

  • SpringBoot实现整合微信支付方法详解

    目录 1.准备工作 1.1 数据库表 1.2 实体类 1.3 导入依赖 1.4 配置文件 1.5 创建读取微信支付相关信息的工具类 1.6 其他工具类 2.生成订单 2.1 远程调用用户模块和课程模块 2.2 远程调用方法的实现 2.3 根据课程id和用户id生成订单 3.查询订单信息 3.1 controller层 3.2 service层 4.生成微信支付的二维码 4.1 controller层 4.2 service层 5.查询订单支付状态 5.1 controller层 5.2 serv

随机推荐