N

Norlcayn的博客

A text-focused Halo theme

  • 首页
  • 文章
  • 默认分类
  • 关于
主页 SpringBoot用户登录与校验功能
文章

SpringBoot用户登录与校验功能

发表于 2026-01-15 更新于 昨天
作者 Administrator
37~48 分钟 阅读

登录功能和登录校验

登录功能实质上就是获取前端发送过来的登录信息,与后端中的用户信息进行比较,这个功能只会在登录界面时生效

而登录校验则表示想要进行其他功能的操作时,先要明确当前用户的身份(防止用户跳过登录界面直接进入系统)

登录校验的实现相比登录功能来说更加复杂且多样,登录功能只需要在传统的三层架构中编写查询数据即可

所以主要学习的是登录校验

登录校验思路

Snipaste_2025-08-13_21-49-21

登录标记:用户登录成功之后,在后续的每一次请求中,都可以获取到该标记【会话技术】

统一拦截:过滤器Filter、拦截器Interceptor

会话技术

  • 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应
  • 会话建立或断开的概念:
  • 会话是逻辑概念,不是物理连接
  • 会话建立是通过标记进行约束的而建立的,断开就表示标记失效、不存在或非法修改
  • 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自同一浏览器,以便在同一次会话的多次请求间共享数据
  • 会话跟踪方案:
  • 客户端会话跟踪技术:Cookie
  • 服务端会话跟踪技术:Session
  • 令牌技术

Cookie

  • Cookie存在于请求头、响应头中
  • Cookie会自动存储到浏览器中(不需要前端配置)
  • Cookie可以由前端手动指定并发送给后端。但大部分情况下,都是发送被自动保存的Cookie
  • 前端会自动携带Cookie到请求头中(前提是在同一个网站下,且未过期)

优势:

  • Cookie最大的优势就在于是HTTP协议中支持的技术,很多部分都可以自动完成

缺点:

  • 移动端APP无法使用Cookie(不包括移动端浏览器)
  • 不安全,用户可以自己禁用Cookie
  • Cookie不能跨域

示例:

//设置Cookie
@GetMapping("/c1")
public Result cookie1(HttpServletResponse response){
    response.addCookie(new Cookie("login_username","itheima")); //设置Cookie/响应Cookie
    return Result.success();
}
//获取Cookie
@GetMapping("/c2")
public Result cookie2(HttpServletRequest request){
    Cookie[] cookies = request.getCookies();
    for (Cookie cookie : cookies) {
        if(cookie.getName().equals("login_username")){
            System.out.println("login_username: "+cookie.getValue()); //输出name为login_username的cookie
        }
    }
    return Result.success();
}

Session

优势:

  • Session是基于Cookie实现的,而且它存储在服务端,更安全

劣势:

  • 也由于是存储在服务端,如果存在集群就无法直接使用Session
  • 基于Cookie实现,所以也存在Cookie的所有缺点(除了安全性)

示例:

@GetMapping("/s1")
public Result session1(HttpSession session){
    log.info("HttpSession-s1: {}", session.hashCode());
    session.setAttribute("loginUser", "tom"); //往session中存储数据
    return Result.success();
}
@GetMapping("/s2")
public Result session2(HttpSession session){
    log.info("HttpSession-s2: {}", session.hashCode());
    Object loginUser = session.getAttribute("loginUser"); //从session中获取数据
    log.info("loginUser: {}", loginUser);
    return Result.success(loginUser);
}

简答理解:客户端持有 Session 的”钥匙”(Session ID),而真正的”保险箱”(HttpSession)保存在服务器端

令牌

优点:

  • 支持PC端、移动端
  • 解决集群环境下的认证问题
  • 减轻服务端存储压力

缺点:

  • 服务单令牌的生成、客户端的使用和存储都需要开发人员实现

JWT令牌

  • 全称:JSON Web Token(https://jwt.io)/)
  • 定义了一种简洁的、自包含的格式,用于在通信双方以JSON数据格式安全的传输信息
  • 组成:
  • 第一部分:Header(头),记录令牌类型、签名算法等。例如:{"alg":"HS256", "type":"JWT"}
  • 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{"id":"1", "username":"Tom"}
  • 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload融入,并加入指定密钥,通过指定签名算法计算而来

生成/解析

  1. 引入jjwt的依赖
  2. 调用官方提供的工具类Jwts来生成或解析jwt令牌
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

生成令牌:

@Test
public void testGenJwt() {
    Map<String, Object> claims = new HashMap<>();
    claims.put("id",10);
    claims.put("username","itheima");
    String jwt = Jwts.builder().
            signWith(SignatureAlgorithm.HS256,"Norlcyan").// 第一参数指定加密算法,第二个参数指定密钥
            addClaims(claims).
            setExpiration(new Date(System.currentTimeMillis() + 12*3600*1000)).// 参数表示令牌最大存活时间
            compact();
    System.out.println(jwt);
}

解析令牌:

@Test
public void testParseJwt() {
    String jwtToken = "令牌内容...";
    Claims claims = Jwts.parser().
            setSigningKey("Norlcyan").// 参数表示密钥,必须和生成令牌时的密钥保持一致
            parseClaimsJws(jwtToken).
            getBody();
    System.out.println(claims);
}

Filter过滤器

  • 概念:Filter过滤器,是JavaWeb三大组件之一(Servlet、Filter、Listener)
  • 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
  • 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等

Filter快速入门

  1. 定义Filter:定义一个类,实现Filter接口,并实现其所有方法
  2. 配置Filter:Filter类上加@WebFilter注解,配置拦截路径。引导类上加@ServletComponentScan开启Servlet组件支持

示例:

@Slf4j
@WebFilter(urlPatterns = {"/*"})
public class FilterDemo implements Filter {
    // 初始化方法,Web服务器启动时自动执行,只执行一次
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("init 初始化方法 ...");
    }

    // 销毁方法,web服务器关闭的时候执行,只执行一次
    @Override
    public void destroy() {
        log.info("destroy 销毁方法 ...");
    }

    // 拦截到对应的请求后执行,执行多次
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("拦截到了请求......");
        // 放行操作一定要配置,否则后续操作就停止了
        filterChain.doFilter(servletRequest,servletResponse);   // 放行操作
    }
}

别忘了启动项开启Servlet组件支持:

@ServletComponentScan   // 开启了SpringBoot对Servlet组件的支持
@SpringBootApplication
public class TliasWebManagementApplication {

    public static void main(String[] args) {
        SpringApplication.run(TliasWebManagementApplication.class, args);
    }

}

令牌校验

先创建令牌的工具类:

public class JwtUtils {
    private static final String SECRET_KEY = "Norycan";

    public static String generateToken(Integer id,String username) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("id",id);
        claims.put("username",username);
        return Jwts.builder().
                signWith(SignatureAlgorithm.HS256,SECRET_KEY).
                addClaims(claims).
                setExpiration(new Date(System.currentTimeMillis() + 12*3600*1000)).
                compact();
    }

    public static Claims parseToken(String token) throws Exception {
        return Jwts.parser().
                setSigningKey(SECRET_KEY).
                parseClaimsJws(token).
                getBody();
    }
}

拦截器:

@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;

        // 1. 获取请求路径
        String requestURI = request.getRequestURI();

        // 2. 判断路径是否为登陆/login,如果是直接放行
        if (requestURI.contains("/login")) {
            filterChain.doFilter(request,response);
            return;
        }

        // 3. 获取请求头中的token
        String token = request.getHeader("Token");

        // 4. 判断token是否存在,不存在返回401状态码
        if (token == null || token.isEmpty()) {
            log.info("令牌为空,响应401状态码");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

        // 5. token存在进行校验,校验失败返回401状态码
        try {
            JwtUtils.parseToken(token);
        } catch (Exception e) {
            log.info("令牌非法,响应401状态码");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

        // 6. 校验成功放行
        log.info("令牌合法,放行");
        filterChain.doFilter(request,response);
    }
}

对于登录页面,是不需要拦截的,否则用户连token都获取不了

Filter执行流程

Filter分为放行前操作、放行操作、放行后操作:

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletR
    log.info("拦截到了请求... 放行前 ...");
    filterChain.doFilter(servletRequest,servletResponse);   // 放行操作
log.info("拦截到了请求... 放行后 ...");
}

放行后访问对应资源,资源访问完成后,还会回到Filter中

回到Filter中,就会执行放行后的操作

拦截路径

  • Filter可以根据需求,配置不同的拦截资源路径
拦截路径 urlPatterns值 含义
拦截具体路径 /login 只有访问/login路径时,才会被拦截
目录拦截 /emps/* 访问/emps下的所有资源,都会被拦截
拦截所有 /* 访问所有资源,都会被拦截

过滤器链

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

图片2

存在多个过滤器,执行的顺序默认是按照过滤器类名(字符串)的自然排序

Interceptor拦截器

  • 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,主要用于动态拦截控制器方法的执行
  • 作用:拦截请求,在指定的方法调用前后,根据业务需求执行预定的代码

快速入门

  1. 定义拦截器,实现HandlerInterceptor接口,并实现其所有方法
  2. 注册拦截器
// 定义拦截器
@Component
public class InterceptorDemo implements HandlerInterceptor {
    @Override// 目标资源方法执行前执行,返回true放行,返回false不放行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    @Override// 目标资源方法执行后执行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("preHandle");
    }

    @Override// 视图渲染完毕后执行,最后执行(前后端分离用不到这个)
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}
// 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    private final InterceptorDemo interceptorDemo;

    @Autowired
    public WebConfig(InterceptorDemo interceptorDemo) {
        this.interceptorDemo = interceptorDemo;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptorDemo).addPathPatterns("/**");
    }
}

令牌校验

  1. 定义拦截器:
@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取Token
        String token = request.getHeader("Token");

        // 2. 判断token是否为空
        if (token == null || token.isEmpty()) {
            log.info("令牌为空,响应401状态码");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }

        // 3. 校验token
        try {
            JwtUtils.parseToken(token);
        } catch (Exception e) {
            log.info("令牌非法,响应401状态码");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }

        // 4. 放行
        log.info("令牌合法,放行");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
}

整个流程和Filter差不多,不过拦截器不需要自己定义拦截的路径,而是在注册时进行路径判断

  1. 注册拦截器
package com.norlcyan.config;

import com.norlcyan.interceptor.TokenInterceptor;
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;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    private final TokenInterceptor tokenInterceptor;

    @Autowired
    public WebConfig(TokenInterceptor tokenInterceptor) {
        this.tokenInterceptor = tokenInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }
}

addPathPatterns("/**"):表示拦截所有路径及其子路径

excludePathPatterns("/login"):表示排除拦截/login路径

拦截路径

  • 拦截器可以根据需求,配置不同的拦截路径
拦截路径 含义 举例
/* 一级路径 能匹配/depts,/emps等,不能匹配/depts/1
/** 任意级路径 所有路径都能匹配
/depts/* /depts下的一级路径 能匹配/depts/1,不能匹配/depts/1/2,/depts
/depts/** /depts下的任意级路径 /depts下的所有路径,如/depts/1,/depts/1/2

addPathPatterns:指定哪些路径拦截

excludePathPatterns:指定哪些请求不拦截

执行流程

拦截器也可以存在多个,只需要在注册类中进行多次注册即可:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    // 注册第一个拦截器
    registry.addInterceptor(tokenInterceptor)
            .addPathPatterns("/**")
            .excludePathPatterns("/login");

    // 注册第二个拦截器
    registry.addInterceptor(loggingInterceptor)
            .addPathPatterns("/**");

    // 注册第三个拦截器
    registry.addInterceptor(permissionInterceptor
            .addPathPatterns("/admin/**");
}

执行顺序:

  • 先注册的拦截器先执行 preHandle
  • postHandle 和 afterCompletion 按相反顺序执行

如果任何一个拦截器的 preHandle 返回 false,后续拦截器不会执行

Snipaste_2025-08-16_14-17-19

Filter和Interceptor的区别

  1. 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口
  2. 拦截范围不同:过滤器Filter会拦截所有请求,而Interceptor只会拦截Spring环境中的资源
后端 Java Web入门 SpringBoot 登录功能 登录校验 Cookie Session Filter JWT令牌 Interceptor
许可协议:  CC BY 4.0
分享

相关文章

下一篇

ThreadLocal

上一篇

SpringBoot异常处理

最近更新

  • Hello Halo
  • 状态机
  • 数据表设计规范
  • 缓存优化
  • 事务失效

热门标签

Halo 异常处理 Redisson 并行 Vue 并发 递归 请求限流 SpringAMQP 类加载器

目录

©2026 Norlcayn的博客. 保留部分权利。

使用 Halo 主题 Chirpy