【JavaWeb后端学习笔记】登录校验(JWT令牌技术、Interceptor拦截器、Filter过滤器)

登录校验

  • 1、JWT令牌技术
    • [1.1 JWT令牌介绍](#1.1 JWT令牌介绍)
    • [1.2 Java代码生成与校验JWT令牌](#1.2 Java代码生成与校验JWT令牌)
  • 2、Filter过滤器
    • [2.1 Filter过滤器的简单实现](#2.1 Filter过滤器的简单实现)
    • [2.2 配置拦截路径](#2.2 配置拦截路径)
    • [2.3 Filter接口中的三个方法:](#2.3 Filter接口中的三个方法:)
    • [2.4 Filter过滤器登录校验](#2.4 Filter过滤器登录校验)
    • [2.5 过滤器链](#2.5 过滤器链)
  • 3、Interceptor拦截器
    • [3.1 拦截器(Interceptor)的简单实现](#3.1 拦截器(Interceptor)的简单实现)
    • [3.2 配置拦截路径](#3.2 配置拦截路径)
    • [3.3 拦截器中的三个方法](#3.3 拦截器中的三个方法)
    • [2.4 Interceptor拦截器登录校验](#2.4 Interceptor拦截器登录校验)

登录校验是系统开发中不可缺少的一环。

1、JWT令牌技术

1.1 JWT令牌介绍

JWT全称为 JSON Web Token(https://jwt.io/)。JWT定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。

JWT令牌由三部分组成:

  1. 第一部分Header(头):记录令牌类型、签名算法等。例如:{"alg":"HS256","type":"JWT"}
  2. 第二部分Payload(有效载荷):携带一些自定义的信息,默认信息等。例如:{"id":"1","username":"Tom"}
  3. 第三部分Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。

Header和Payload会通过Base64编码生成。Signature部分会通过Header指定的签名算法,以Header、Payload以及指定的秘钥作为签名算法输入计算得到。这三部分共同组成了JWT令牌。

1.2 Java代码生成与校验JWT令牌

Java代码生成与校验令牌需要引入JWT的相关依赖:

java 复制代码
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

生成JWT令牌需要准备四个数据:签名算法、秘钥、有效时间和载荷(自定义内容)

签名算法有很多种,可以在JWT官网查看。

下面通过一个简单的例子感受一下:

生成JWT令牌:

java 复制代码
@Test
public void testJWT(){
	// 准备Map集合载荷
    Map<String, Object> claims = new HashMap<>();
    claims.put("id", 1);
    claims.put("name", "Tom");
    String jwt = Jwts.builder() // 使用Jwts中的builder()方法构造
            .signWith(SignatureAlgorithm.HS256, "wrj-web") // 指定签名算法和秘钥
            .setClaims(claims) // 加入载荷
            .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) // 设置有效时间:当前时间 + 有效时长,单位为ms
            .compact();
    System.out.println(jwt);
}
// 运行输出:
// eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTczMzU0MTk2MX0.AgNXthmTMBJHKIeSaacBf-wThVNwsPi1F63sAqsuJkY

解析JWT令牌:

java 复制代码
    @Test void testParseJWT(){
        Claims claims = Jwts.parser() // 使用Jwts中的parser()方法开始解析
                .setSigningKey("wrj-wrb") // 指定秘钥
                .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTczMzU0MTk2MX0.AgNXthmTMBJHKIeSaacBf-wThVNwsPi1F63sAqsuJkY") // 输入JWT令牌,此处直接输入上述代码生成的JWT令牌
                .getBody(); // 获取载荷
        System.out.println(claims);
    }
// 运行输出:
// {name=Tom, id=1, exp=1733541961}

通常会将JWT生成与解析的代码封装成JWT工具类来使用,JwtUtil工具类中只有两个成员方法,一个用于生成JWT令牌,一个用于解析JWT令牌:

java 复制代码
public class JwtUtil {
    /**
     * 生成jwt
     * 使用Hs256算法, 私匙使用固定秘钥
     *
     * @param secretKey jwt秘钥
     * @param ttlMillis jwt过期时间(毫秒)
     * @param claims    设置的信息
     * @return
     */
    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        // 指定签名的时候使用的签名算法,也就是header那部分
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT有效时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);

        // 设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置过期时间
                .setExpiration(exp);

        return builder.compact();
    }

    /**
     * Token解密
     *
     * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
     * @param token     加密后的token
     * @return
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }
}

对于一般项目而言,JWT令牌需要的签名秘钥、有效时长以及前端传递过来的令牌名称是较为固定的,可以在配置到配置文件中,以便修改。

步骤如下:

  1. 在配置文件中自定义配置参数,代码如下:
java 复制代码
web:
  jwt:
    # 设置jwt签名加密时使用的秘钥
    secret-key: itcast
    # 设置jwt过期时间 单位ms
    ttl: 7200000
    # 设置前端传递过来的令牌名称
    token-name: token
  1. 定义一个properties类用于接收配置信息,类名一般为JwtProperties,注意成员变量名与配置信息中设置的名称要对应。
java 复制代码
@Component // 注册成Bean
@ConfigurationProperties(prefix = "web.jwt") // 关联到配置文件中web->jwt下的配置信息
@Data // Lombok注解,生成get/set等方法
public class JwtProperties {
	// 签名秘钥,与配置文件中secret-key对应
    private String secretKey;
    // jwt过期时间,与配置文件中ttl对应
    private long ttl;
    // 前端传递过来的令牌名称,与配置文件中token-name对应
    private String tokenName;
}

在需要使用这些信息时,只需要将JwtProperties 对象注入,然后通过get/set方法获取即可。

2、Filter过滤器

过滤器能够把对资源的请求拦截下来,从而实现一些特殊的功能,例如登录校验、敏感字符处理等。

2.1 Filter过滤器的简单实现

Filter过滤器使用分两步:

  1. 定义Filter:定义一个类,实现Filter接口,并重写其所有方法。Filter接口是javax.servlet.*包下的Filter接口。
  2. 配置Filter:Filter类上加@WebFilter注解,配置拦截资源路径。引导类(启动类)上加@ServletComponentScan开启Servlet组件支持。
java 复制代码
@WebFilter(urlPatterns = "/*") // 通过WebFilter注解表示这是一个web过滤器组件,通过urlPatterns属性配置拦截路径
public class DemoFilter implements Filter {

    // 初始化方法、Web服务器启动,创建Filter时调用,只调用一次。通常在此方法中完成资源和环境的准备操作
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    // 每次拦截到请求时会调用
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("拦截请求...放行前逻辑");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("拦截请求...放行后逻辑");
    }

    // 销毁方法,Web服务器关闭时调用,只调用一次。通常完成资源的释放等操作
    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

2.2 配置拦截路径

Filter过滤器的拦截路径通过 @WebFilter 注解中的 urlPatterns 属性设置,有三种方式:

拦截路径 urlPatterns 含义
拦截具体路径 /login 只有访问 /login 路径时,才会被拦截
目录拦截 /emps/* 访问 /emps 下的所有资源,都会被拦截
拦截所有 /* 访问所有资源,都会被拦截

2.3 Filter接口中的三个方法:

Filter接口中的三个方法有各自的执行时机和作用。

  • init():初始化方法、Web服务器启动,创建Filter时调用,只调用一次。通常在此方法中完成资源和环境的准备操作;
  • doFilter():每次拦截到请求时会调用。其中可包含放行前逻辑和放行后逻辑;
  • destroy():销毁方法,Web服务器关闭时调用,只调用一次。通常完成资源的释放等操作

在使用Filter过滤器时需要注意几点:

  1. 执行doFilter()方法时首先执行其中的放行前逻辑,然后放行。
  2. 过滤器放行,访问完对应路径的资源之后会回到过滤器。
  3. 回到过滤器之后还会执行放行后逻辑。

2.4 Filter过滤器登录校验

下面改造doFilter()方法,实现登录校验功能。

场景:用户进行登录操作不进行拦截,其他操作进行拦截。也就是不拦截"/login"路径,拦截其他所有路径。

拦截逻辑分为以下几步:

1.获取请求路径;

2.判断路径中是否包含"login",包含则直接放行,不包含则需拦截,进行登录校验;Filter放行是通过执行filterChain调用doFilter()方法;

3.进行登录校验,首先获取JWT令牌;

4.判断JWT令牌是否存在,或是否为空。不存在或为空则校验失败,不放行,直接return结束。

5.解析JWT令牌,不成功则不放行;

6.解析JWT令牌成功则放行。

java 复制代码
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse resp = (HttpServletResponse) servletResponse;
        // 1.获取请求的URL
        String url = req.getRequestURL().toString();

        // 2.判断login是否存在,存在则放行,不存在则拦截
        if(url.contains("login")) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }

        // 3.获取JWT令牌
        String jwt = req.getHeader("token");

        // 4.判断JWT是否存在
        if(!StringUtils.hasLength(jwt)) {
            Result error = Result.error("NOT_LOGIN");
            String s = JSONObject.toJSONString(error);
            resp.getWriter().write(s);
            return;
        }

        // 5.判断JWT是否能解析成功
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            e.printStackTrace();
            Result error = Result.error("NOT_LOGIN");
            String s = JSONObject.toJSONString(error);
            resp.getWriter().write(s);
            return;
        }

        // 6.解析成功,放行
        filterChain.doFilter(servletRequest, servletResponse);
    }

这部分登录校验的代码是可以进行优化的。因为实际业务中是不会对 /login 登录请求进行拦截的。

2.5 过滤器链

一个Web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链。

由于存在多个过滤器,过滤器的执行顺序会按照过滤器类名(字符串)的自然顺序

3、Interceptor拦截器

拦截器是一种动态拦截方法调用的机制,类似与过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行。拦截器通常用来拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。

3.1 拦截器(Interceptor)的简单实现

Interceptor拦截器的使用分两步:

  1. 定义拦截器:实现HandlerInterceptor接口,并重写其所有方法,在实现类上加上@Component注解,交给IOC容器管理;
java 复制代码
@Component // 注册成Bean,交给IOC容器管理
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;
    }

    // 目标资源方法执行后执行
    @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...");
    }
}
  1. 注册配置拦截器。创建一个配置类,实现WebMvcConfigurer接口。在配置类上加@Configuration注解,代表当前类是配置类。重写WebMvcConfigurer接口的addInterceptors方法,使用该方法的形参registry调用addInterceptor方法,将创建好的拦截器注册进去。因此需要先将拦截器注入。然后接着调用addPathPatterns()和excludePathPatterns()方法配置拦截哪些路径,不拦截哪些路径。
java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private DemoInterceptor demoInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(demoInterceptor) // 注册拦截器
                .addPathPatterns("/**") // 配置拦截资源
                .excludePathPatterns("/login"); // 配置不拦截的资源               
    }
}

3.2 配置拦截路径

Interceptor拦截器的拦截路径是在配置类中进行配置的。可借鉴上述配置类的代码。

下面介绍Interceptor的拦截路径:

拦截路径 含义 举例
/* 一级路径 能匹配 /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

3.3 拦截器中的三个方法

Interceptor接口中的三个方法有各自的执行时机和作用。

  • preHandle():目标资源方法执行前执行,返回true:放行,返回false:不放行;
  • postHandle():目标资源方法执行后执行;
  • afterCompletion():视图渲染完毕后执行,最后执行。

2.4 Interceptor拦截器登录校验

Interceptor拦截器进行登录校验与Filter过滤器实现登录校验的逻辑相同,只是部分实现细节不同。这里提供一个简单的实现。

java 复制代码
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		// 1. 获取请求路径
        String url = request.getRequestURL().toString();
        // 2. 判断请求路径是否包含 /login
        if(url.contains("login")) {
            return true;
        }
        // 3. 从请求头获取token,一般把JWT令牌的key名设置为token
        String jwt = request.getHeader("token");

		// 4. 判断是否获取到JWT令牌
        if(!StringUtils.hasLength(jwt)) {
            Result error = Result.error("NOT_LOGIN");
            String s = JSONObject.toJSONString(error);
            response.getWriter().write(s);
            return false;
        }

		// 5. 解析JWT令牌
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            e.printStackTrace();
            Result error = Result.error("NOT_LOGIN");
            String s = JSONObject.toJSONString(error);
            response.getWriter().write(s);
            return false;
        }
		
        return true;
    }
相关推荐
腻害兔3 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社4 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
克里斯蒂亚诺更新4 小时前
ruoyi角色权限的对应关系
笔记
萧瑟余晖5 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
a1117767 小时前
2FA 验证码生成器(github登录验证 app)
笔记·学习
我的xiaodoujiao7 小时前
API 接口自动化测试详细图文教程学习系列32--Allure测试报告2
python·学习·测试工具·pytest
维天说8 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json
破碎的南瓜9 小时前
sqli--sql注入过关笔记(1,2,3,4,5,9)
数据库·笔记·sql
Zane19949 小时前
并发 vs 并行:别再傻傻分不清了,一文讲透 Java 并发编程的第一课
java·后端