基于Springboot实现JWT认证的示例代码
作者:言成言成啊 发布时间:2023-04-01 09:46:40
最近一直想写一个类似于待办的东西,由于不想用传统的session,就卡住了,后来在各种群里扯皮,发现除了用缓存之外,还可以通过 JWT 来实现。
一、了解JWT
概念
json web token 用于在各方之间以 json 对象安全地传输信息,比如在前端和后端进行传输,或者在A系统与B系统之间进行传输。因为它是用的数字签名,所以此信息能够进行验证的,验证的成功与否决定是否信任。
作用
授权:这是jwt应用最广泛的。一旦用户登录,每个后续请求都要包含jwt,从而验证用户是否有权限访问资源。单点登录是jwt应用最广泛的一个代表功能。
信息交换:在前后端之间、系统之间,对jwt进行签名,比如使用非对称加密算法(含有公钥和私钥的方式),可以保证信息被指定的人给获取到。
1.1 为什么授权要使用jwt
比较传统session认证与jwt认证的区别
基于传统的session认证
认证方式:http本身是一种是一种无状态的协议。这就意味着,每次进行请求,都要带着用户名和密码来进行用户认证。为了解决这个问题,我们需要在服务端存储一份用户登录的信息,这个登录信息会在响应时传递给客户端,保存成cookie,以便下次携带发送给服务端。这份服务端存储的登录信息就是session。
缺点
每个用户进行认证之后,服务器都要在服务端做一次记录,以便鉴别下次用户请求时的身份。通常而言,session都是保存在内存中,而随着认证用户的增多,服务端的开销会增大。
用户认证之后,服务端在内存中做认证记录,如果下次请求,还需要去访问有记录的服务器才行,这对于分布式的应用来说,体验不好。
基于Cookie识别用户,如果Cookie被抓包到,容易造成跨站请求伪造的攻击。
基于jwt的认证
认证方式:前端携带用户名密码发送到后端接口,后端核对用户名和密码成功后,会将用户的id等信息,作为payload,将其与header分别进行base64加密之后,拼接起来,进行加密,形成一种格式xxx.xxx.xxx的jwt字符串(三部分组成,header、payload、signature,中间用点隔开),返回给前端。前端可以将该信息存储在localStorage或者sessionStorage中,请求时,一般将jwt放入请求头里authorization中。后端会校验jwt是否正确、是否过期,然后拿jwt内部包含的用户信息去进行其他认证通过后的操作。
优点
简洁:jwt可以放在url、请求体、请求头中发送
自包含:payload中包含了用户所需要的所有信息,避免了多次查询数据库
跨语言:jwt是以json的格式保存在客户端的,原则上支持所有形式
分布式:不需要在服务端保存会话信息,特别适合分布式微服务
二、JWT结构
jwt的组成
header:标头
payload:负载
signature:签名
jwt通常如下所示,xxxx.xxxx.xxxx,也就是header.payload.signature
2.1 header
header通常是由两部分组成json。然后进行Base64编码,组成jwt的第一部分
令牌的类型
使用的签名算法,例如HmacSha256、Rsa。jwt推荐使用HmacSha256算法
header中也可以加入一些自定义的内容
例如下面的这种格式
{
"alg": "HS256",
"typ": "JWT"
}
jwt为了保证编码的简短,一般会简写过长的单词,如:
alg:algorithm,算法
typ:type,类型
2.2 payload
payload主要存储用户和其他的一些数据,但是不能存放敏感信息,如密码。然后进行Base64编码,组成jwt的第二部分
payload在java代码里面也叫做claim,即声明。
{
"userId": "000001",
"userName": "CCC",
"admin": 1
}
2.3 signature
header与payload是通过Base64进行编码的,前端是可以解开知道里面的内容的。
signature就是使用header中提供的算法,对经过Base64进行编码过的header和payload,使用我们提供的密钥来进行签名。签名的作用是保证jwt没有被篡改过,如果将signature解密后,与headerBase64.payloadBase64不一致,就是被篡改过的。signature是jwt的第三部分。
如:
String headerPayload=base64UrlEncodeHeader+"."+base64UrlEncodePayload;
String signature=HMACSHA256(headerPayload,secret)
jwt最终的结构,就是将header的base64,payload的base64,signature加密后的值,用.来分割,拼成一串字符串。
三、使用JWT
3.1 上手
引入依赖
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.2</version>
</dependency>
生成jwt
public class GenerateJWT {
public static void main(String[] args) {
Map<String,Object> map=new HashMap<>();
//获取过去时间
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND,10);
String jwtToken = JWT.create()
//header,map里面传值,表示在除type、algorithm之外,添加自定义的内容
.withHeader(map)
//payload
.withClaim("userId", "000001")
.withClaim("userName", "CCC")
.withClaim("admin", 1)
//指定过期时间
.withExpiresAt(calendar.getTime())
//signature
.sign(Algorithm.HMAC384("meethigher"));
System.out.println(jwtToken);
}
}
校验jwt
public class VerifyJWT {
public static void main(String[] args) {
Map<String,Object> map=new HashMap<>();
//获取过去时间
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND,60);
String jwtToken = JWT.create()
//header,map里面传值,表示在除type、algorithm之外,添加自定义的内容
.withHeader(map)
//payload
.withClaim("userId", "000001")
.withClaim("userName", "CCC")
.withClaim("admin", 1)
//指定过期时间
.withExpiresAt(calendar.getTime())
//signature
.sign(Algorithm.HMAC384("meethigher"));
System.out.println(jwtToken);
//创建验证对象
JWTVerifier verifier = JWT.require(Algorithm.HMAC384("meethigher")).build();
DecodedJWT decodedJWT = verifier.verify(jwtToken);
System.out.println(decodedJWT.getClaim("userId").asString());
System.out.println(decodedJWT.getClaim("userName").asString());
//注意,如果是个整型,用asString会返回一个null。可以点进去查看源码注释
System.out.println(decodedJWT.getClaim("admin").asInt());
System.out.println(decodedJWT.getExpiresAt());
}
}
3.2 封装工具类
JWTUtils.java
public class JWTUtils {
private static String SECRET = "meethigher";
/**
* 传入Payload生成jwt
*
* @param map
* @return
*/
public static String getToken(Map<String, String> map) {
Calendar calendar = Calendar.getInstance();
//7天过期
calendar.add(Calendar.DAY_OF_MONTH, 7);
//第一种存payload方式:遍历map存入
// JWTCreator.Builder builder = JWT.create();
// map.forEach(builder::withClaim);
//第二种存payload方式:直接放map,底层采用的也是第一种方式
return JWT.create()
.withPayload(map)
.withExpiresAt(calendar.getTime())
.sign(Algorithm.HMAC256(SECRET));
}
/**
* 校验签名是否正确,并返回token信息
*
* @param token
* @return
*/
public static DecodedJWT getTokenInfo(String token) {
return JWT.require(Algorithm.HMAC256(SECRET))
.build()
.verify(token);
}
}
3.3 整合springboot
关键代码
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"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>top.meethigher</groupId>
<artifactId>springboot-jwt</artifactId>
<version>1.0.0</version>
<name>springboot-jwt</name>
<description>chenchuancheng's demo</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.34.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.gwenn/sqlite-dialect -->
<dependency>
<groupId>com.github.gwenn</groupId>
<artifactId>sqlite-dialect</artifactId>
<version>0.1.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
#logging:
# config: classpath:logback.xml
server:
port: 9090
ssl:
enabled: false
spring:
datasource:
driver-class-name: org.sqlite.JDBC
url: jdbc:sqlite:D:/sqliteData/jwt.db
jpa:
database-platform: org.sqlite.hibernate.dialect.SQLiteDialect
hibernate:
ddl-auto: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
mvc:
async:
request-timeout: 30000
SwaggerConfig
@Configuration
public class SwaggerConfig {
//配置swagger的实例
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//只显示包含Api注解的,如果不加这个,会有basic-error-controller显示
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("API接口文档")
.description("API接口文档")
.version("1.0")
.build();
}
}
InterceptorConfig
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/user/*")
.excludePathPatterns("/user/login");
}
}
LoginInterceptor
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
HashMap<String, String> map = new HashMap<>();
try {
DecodedJWT tokenInfo = JWTUtils.getTokenInfo(token);
}catch (SignatureVerificationException e) {
e.printStackTrace();
map.put("desc","无效签名");
}catch (TokenExpiredException e) {
e.printStackTrace();
map.put("desc","token过期");
}catch (AlgorithmMismatchException e) {
e.printStackTrace();
map.put("desc","token算法不一致");
}catch (Exception e) {
e.printStackTrace();
map.put("desc","无效token");
}
//如果没有异常,就放行
if(!ObjectUtils.isEmpty(map)) {
//转为json,发回给前端
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(json);
return false;
}
return true;
}
}
参考
JSON Web Token Introduction - jwt.io
Spring Data JPA(二):SpringBoot集成H2_郑龙飞-CSDN博客
来源:https://blog.csdn.net/qq_30460361/article/details/121483529


猜你喜欢
- C#调用Twain接口实现扫描仪连续扫描。在监听的TwainCommand.TransferReady状态中,是调用扫描仪扫描图片的。我开始
- Cardview配合ImageView显示圆形图效果图:刚在看自定义View的知识点时,突然想起来,如果CardView宽高相等,CardV
- 本文实例讲述了C#双向链表LinkedList排序实现方法。分享给大家供大家参考。具体如下:1.函数打印链表函数PrintLinkedLis
- 一、引言在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,
- 最近的需求有一个自动发布的功能, 需要做到每次提交都要动态的添加一个定时任务代码结构1. 配置类package com.orion.ops.
- 在微服务中,需要我们在各个微服务中共享Session,使用Redis来共享Session是一个很好的解决方法,Redis是运行在内存中,查取
- 写在前面在Java8之前的版本中,接口中只能声明常量和抽象方法,接口的实现类中必须实现接口中所有的抽象方法。而在Java8中,接口中可以声明
- 简介:本文已一个简要的代码示例介绍ThreadLocal类的基本使用方式,在此基础上结合图片阐述它的内部工作原理。早在JDK1.2的版本中就
- 文章主要涉及到以下几个问题:怎么实现Java的序列化为什么实现了java.io.Serializable接口才能被序列化transient的
- 微信支付现在已经变得越来越流行了,随之也出现了很多以可以快速接入微信支付为噱头的产品,不过方便之余也使得我们做东西慢慢依赖第三方,丧失了独立
- 本文实例为大家分享了Javafx实现国际象棋游戏的具体代码,供大家参考,具体内容如下基本规则棋子马设计“日”的移动方式兵设计只能向前直走,每
- 线程的两种创建方式及优劣比较1、通过实现Runnable接口线程创建(1).定义一个类实现Runnable接口,重写接口中的run()方法。
- 关闭时可使用如下代码public static void waitUntilTerminate(final ExecutorService
- C# 反射(Reflection)反射指程序可以访问、检测和修改它本身状态或行为的一种能力。程序集包含模块,而模块包含类型,类型又包含成员。
- springBoot Junit测试用例出现@Autowired不生效前提条件:1,测试类上面添加支持的注解就能取到spring中的容器的实
- Optional在JAVA中被定义为一个容器类,更确切的说只存一个元素的容器。container object which may or m
- 在C#winform程序开发过程中,我们可能需要定期去设定一些变化的值,但这些值在程序中又要被用来做对比或参照,比如我们设定一个固定值让程序
- // 获取国家省市区信息$(document).ready(function(){//从程序
- 生成随机数在现实中我们经常用到随机数,可怎么实现呢,且听小乔慢慢道来。在C语言中,我们一般使用 <stdlib.h> 头文件中的
- 本文实例讲述了android从系统图库中取图片的实现方法。分享给大家供大家参考。具体如下:在自己应用中,从系统图库中取图片,然后截取其中一部