JWT令牌
其标准的格式如下: Header.Payload.Signature
1. 标头
标头Header 通常由两部分组成:令牌的类型(即 JWT)和所使用的签名算法(如 HS256 或 RS256)。
处理方式: 这段 JSON 会被使用 Base64Url 编码形成 JWT 的第一部分。
示例数据:
{
"alg": "HS256",
"typ": "JWT"
}
2. 有效载荷 (Payload)
有效载荷 (Payload)是令牌的核心,包含所谓的声明(Claims)。声明是关于实体(通常是用户)和其他数据的陈述。
示例数据:
{
"sub": "1234567890",
"name": "Suiyue",
"admin": true
}
- 处理方式: 同样使用 Base64Url 编码形成 JWT 的第二部分。
注意: Payload 只是编码,并没有加密。千万不要在 Payload 中存放密码、银行卡号等敏感信息,因为任何人拿到令牌都可以解码看到原始内容。
3. 签名
签名(Signature)是为了验证消息在传递过程中有没有被更改,并验证发送者的身份。
- 签名生成方式:
- 将编码后的 Header 和编码后的 Payload 用
.连接。 - 使用 Header 中指定的算法(如 HMAC SHA256)。
- 引入一个**只有服务器知道的密钥(Secret)**进行哈希计算。
- 将编码后的 Header 和编码后的 Payload 用
使用
<!-- JWT依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
package site.suiyue.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
/**
* JWT工具类,用于生成和解析JSON Web Token
*/
public class JwtUtils {
// 签名密钥
private static final String SECRET_KEY = "aXRjYXN0";
// 默认过期时间:12小时(单位:毫秒)
private static final long DEFAULT_EXPIRATION = 12 * 3600 * 1000L;
// 私有构造方法,防止实例化
private JwtUtils() {}
/**
* 生成JWT,使用默认过期时间(12小时)
* @param claims 要放入JWT的声明(如用户id、用户名等)
* @return JWT字符串
*/
public static String generateJwt(Map claims) {
return generateJwt(claims, DEFAULT_EXPIRATION);
}
/**
* 生成JWT,指定过期时间
* @param claims 要放入JWT的声明
* @param expirationMillis 过期时间(相对于当前时间的毫秒数)
* @return JWT字符串
*/
public static String generateJwt(Map claims, long expirationMillis) {
return Jwts.builder()
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.addClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + expirationMillis))
.compact();
}
/**
* 解析JWT,返回Claims对象
* @param token JWT字符串
* @return Claims对象,包含JWT中的所有声明
* @throws io.jsonwebtoken.ExpiredJwtException 如果JWT已过期
* @throws io.jsonwebtoken.SignatureException 如果签名验证失败
* @throws io.jsonwebtoken.MalformedJwtException 如果JWT格式错误
*/
public static Claims parseJwt(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
}
注意事项
- JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的。
- 如果JWT令牌解析校验时报错,则说明 JWT令牌被篡改 或 失效了,令牌非法。
Filter
使用
- 第1步,定义过滤器 :1.定义一个类,实现 Filter 接口,并重写所需方法。
- 第2步,配置过滤器:
Filter类上加 @WebFilter 注解,urlPatterns配置拦截资源的路径。
引导类上加 @ServletComponentScan 开启Servlet组件支持。
package site.suiyue.filter;
@WebFilter(urlPatterns = "/*")
//配置过滤器要拦截的请求路径( /* 表示拦截浏览器的所有请求 )
public class DemoFilter implements Filter {
//初始化方法, web服务器启动, 创建Filter实例时调用, 只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init ...");
}
//拦截到请求时,调用该方法,可以调用多次
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
System.out.println("拦截到了请求...");
}
//销毁方法, web服务器关闭时调用, 只调用一次
public void destroy() {
System.out.println("destroy ... ");
}
}
package site.suiyue;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@ServletComponentScan//开启对Servlet组件的支持
@SpringBootApplication
public class AitiliasApplication {
public static void main(String[] args) {
SpringApplication.run(AitiliasApplication.class, args);
}
}
登录校验过滤器
package site.suiyue.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import site.suiyue.utils.JwtUtils;
import java.io.IOException;
@Slf4j
//@WebFilter(urlPatterns = "/*")
public class TokenFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request=(HttpServletRequest)servletRequest;
HttpServletResponse response=(HttpServletResponse)servletResponse;
String url=request.getRequestURL().toString();
if(url.contains("login")){
log.info("登录请求,放行");
filterChain.doFilter(request,response);
return;
}
String jwt=request.getHeader("token");
if(jwt==null||jwt.isEmpty()){
log.info("令牌为空,返回错误结果");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
try{
JwtUtils.parseJwt(jwt);
}catch (Exception e){
e.printStackTrace();
log.info("解析令牌失败");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
log.info("令牌合法,放行");
filterChain.doFilter(request,response);
}
}
为何强转?
为什么将ServletRequest转为为HttpServletRequest?
因为 ServletRequest 接口里根本没有 getHeader() 或 getRequestURL() 这些方法。所以需要将ServletRequest强转为HttpServletRequest,
是否安全?
安全,因为在现有的 Java Web 开发(尤其是 Spring Boot)中,底层的 Web 容器(如 Tomcat)分发过来的请求 100% 都是 HTTP 请求。request底层的实现类实际上就是 RequestFacade(Tomcat 对 HttpServletRequest 的实现)。
拦截路径
| 拦截路径 | urlPatterns值 | 含义 |
| 拦截具体路径 | /login | 只有访问 /login 路径时,才会被拦截 |
| 目录拦截 | /emps/* | 访问/emps下的所有资源,都会被拦截 |
| 拦截所有 | /* | 访问所有资源,都会被拦截 |
过滤器链
执行顺序:

过滤器链上过滤器的执行顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序。 比如:
- AbcFilter
- DemoFilter
这两个过滤器来说,AbcFilter 会先执行,DemoFilter会后执行。
Interceptor
使用
- 第一步,实现HandlerInterceptor接口,并重写所需方法,定义拦截器
- 第二步,注册/配置拦截器,在
site.suiyue下创建interceptor包,然后创建一个配置类WebConfig, 实现WebMvcConfigurer接口,并重写addInterceptors方法
package site.suiyue.interceptor;
//自定义拦截器
@Component
public class DemoInterceptor implements HandlerInterceptor {
//目标资源方法执行前执行。 返回true:放行 返回false:不放行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle .... ");
return true; //true表示放行
}
//目标资源方法执行后执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle ... ");
}
//视图渲染完毕后执行,最后执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion .... ");
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
//自定义的拦截器对象
@Autowired
private DemoInterceptor demoInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(demoInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
}
}
登录校验拦截器
定义拦截器
package site.suiyue.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import site.suiyue.utils.JwtUtils;
@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String url = request.getRequestURL().toString();
if (url.contains("login")) {
log.info("登录请求,放行");
return true;
}
String token = request.getHeader("token");
if (!StringUtils.hasLength(token)) {
log.info("令牌为空,401");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
try {
JwtUtils.parseJwt(token);
} catch (Exception e) {
log.info("令牌解析异常");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
e.printStackTrace();
return false;
}
log.info("通过,放行");
return true;
}
}
注册/配置拦截器
package site.suiyue.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import site.suiyue.interceptor.TokenInterceptor;
@Configuration
public class WebConfig implements WebMvcConfigurer {
//拦截器对象
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");
}
}
拦截路径详解
调用addPathPatterns("要拦截路径")方法,就可以指定要拦截哪些资源。
调用excludePathPatterns("不拦截路径")方法,指定哪些资源不需要拦截。
@Configuration
public class WebConfig implements WebMvcConfigurer {
//拦截器对象
@Autowired
private DemoInterceptor demoInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(demoInterceptor)
.addPathPatterns("/**")//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
.excludePathPatterns("/login");//设置不拦截的请求路径
}
}
| 拦截路径 | 含义 | 举例 |
| /* | 一级路径 | 能匹配/depts,/emps,/login,不能匹配 /depts/1 |
| /** | 任意级路径 | 能匹配/depts,/depts/1,/depts/1/2 |
| /depts/* | /depts下的一级路径 | 能匹配/depts/1,不能匹配/depts/1/2,/depts |
| /depts/** | /depts下的任意级路径 | 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1 |
执行流程

- 当我们打开浏览器来访问部署在web服务器当中的web应用时,此时我们所定义的过滤器会拦截到这次请求。拦截到这次请求之后,它会先执行放行前的逻辑,然后再执行放行操作。而由于我们当前是基于springboot开发的,所以放行之后是进入到了spring的环境当中,也就是要来访问我们所定义的controller当中的接口方法。
- Tomcat并不识别所编写的Controller程序,但是它识别Servlet程序,所以在Spring的Web环境中提供了一个非常核心的Servlet:DispatcherServlet(前端控制器),所有请求都会先进行到DispatcherServlet,再将请求转给Controller。
- 当我们定义了拦截器后,会在执行Controller的方法之前,请求被拦截器拦截住。执行
preHandle()方法,这个方法执行完成后需要返回一个布尔类型的值,如果返回true,就表示放行本次操作,才会继续访问controller中的方法;如果返回false,则不会放行(controller中的方法也不会执行)。 - 在controller当中的方法执行完毕之后,再回过来执行
postHandle()这个方法以及afterCompletion()方法,然后再返回给DispatcherServlet,最终再来执行过滤器当中放行后的这一部分逻辑的逻辑。执行完毕之后,最终给浏览器响应数据。
过滤器拦截器区别
- 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。
- 配置方法不同:
配置Filter:
Filter类上加@WebFilter注解
urlPatterns配置拦截资源的路径
引导类上加@ServletComponentScan开启Servlet组件支持。
配置Interceptor:
在 site.suiyue下创建config包,然后创建一个配置类 WebConfig, 实现 WebMvcConfigurer 接口,将对应的拦截器注入(@Autowired),并重写addInterceptors方法,设置拦截路径通过:registry.addInterceptor(刚刚注入的拦截器).addPathPatterns("拦截路径"); - 拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。
Comments NOTHING