前后端分离项目中JWT Token身份验证全流程详解

在前后端分离的Web开发架构中,身份验证是保障系统安全的核心环节,JWT(JSON Web Token)凭借其无状态、易扩展的特性成为主流方案。本文将结合实际代码,详细拆解Token身份验证的完整实现思路、核心代码逻辑及设计原则。

 

一、Token身份验证的核心设计理念

 

传统的Session-Cookie验证依赖服务端存储会话信息,在分布式系统中需解决会话共享问题;而JWT Token采用无状态验证模式,将用户身份信息加密后存储在Token中,服务端只需验证Token的合法性即可完成身份校验,无需存储会话数据,大幅提升系统扩展性。

 

核心优势:

 

1. 无状态:服务端无需存储会话,便于集群部署;

2. 跨域友好:Token通过请求头传递,适配前后端分离的跨域场景;

3. 过期可控:可自定义Token有效期,降低被盗用风险;

4. 轻量化:基于JSON格式,传输和解析成本低。

 

二、Token身份验证完整流程(附核心代码)

 

阶段1:登录生成Token(客户端→服务端)

 

1. 前端登录请求代码

 

用户输入账号密码后,前端通过AJAX发起登录请求,将参数转为JSON格式传递:

 

javascript

function login() {

    $.ajax({

        type: "post",

        url: "/user/login",

        contentType: "application/json",

        data: JSON.stringify({

            userName: $("#username").val(),

            password: $("#password").val()

        }),

        success: function(result) {

            if (result.code == 200 && result.data != null) {

                // 登录成功后存储Token和用户ID

                localStorage.setItem("loginUserId", result.data.userId);

                localStorage.setItem("token", result.data.token);

                location.href = "blog_list.html"; // 跳转到业务页面

            }

        }

    });

}

 

 

2. 后端验证并生成Token

 

后端接收登录请求后,校验账号密码合法性,验证通过则生成JWT Token:

 

java

// UserServiceImpl核心逻辑

public LoginResponse check(LoginRequest loginRequest) {

    // 1. 从数据库查询用户信息

    LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();

    queryWrapper.eq(UserInfo::getUserName, loginRequest.getUserName());

    UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);

    

    // 2. 校验用户是否存在、密码是否正确

    if (userInfo == null) {

        throw new BlogException("用户名不正确");

    }

    if (!userInfo.getPassword().equals(loginRequest.getPassword())) {

        throw new BlogException("密码不正确");

    }

    

    // 3. 生成JWT Token

    String token = JwtUtils.createToken(userInfo.getId().toString());

    

    // 4. 返回Token和用户ID

    LoginResponse loginResponse = new LoginResponse();

    loginResponse.setUserId(userInfo.getId());

    loginResponse.setToken(token);

    return loginResponse;

}

 

 

阶段2:Token的本地存储(客户端)

 

登录成功后,前端将后端返回的Token存储在浏览器的 localStorage 中:

 

javascript

localStorage.setItem("token", result.data.token);

 

 

 localStorage 是浏览器的本地存储机制,Token会持久化保存(除非手动清除),保证用户在关闭浏览器重新打开后仍可保留登录状态。

 

阶段3:请求自动携带Token(客户端→服务端)

 

为避免在每个请求中手动添加Token,前端通过jQuery的全局AJAX拦截器,在所有请求发送前自动将Token放入请求头:

 

javascript

// 全局AJAX请求拦截器:添加Token到请求头

$(document).ajaxSend(function(e, xhr, opt){

    let userToken = localStorage.getItem("token");

    xhr.setRequestHeader("user_token", userToken);

});

 

 

核心作用:所有需要权限的请求都会自动携带 user_token 请求头,无需重复编写Token传递逻辑。

 

阶段4:服务端Token校验(拦截器实现)

 

1. 登录拦截器(LoginInterceptor):核心校验逻辑

 

java

@Component

public class LoginInterceptor implements HandlerInterceptor {

    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 1. 从请求头获取Token

        String userToken = request.getHeader("user_token");

        

        // 2. 校验Token合法性(是否过期、签名是否正确)

        Boolean check = JwtUtils.check(userToken);

        if (check) {

            // Token有效,放行请求

            return true;

        } else {

            // Token无效,返回401未授权状态码

            response.setStatus(401);

            return false;

        }

    }

}

 

 

2. 拦截器配置(WebConfig):规则管理

 

java

@Configuration

public class WebConfig implements WebMvcConfigurer {

    // 无需拦截的路径:登录接口、静态资源等

    List<String> excludePath = List.of(

        "/user/login",

        "/**/**.html",

        "/pic/**",

        "/js/**",

        "/css/**"

    );

 

    @Override

    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new LoginInterceptor())

            .addPathPatterns("/**") // 拦截所有请求

            .excludePathPatterns(excludePath); // 排除公开路径

    }

}

 

 

-  LoginInterceptor :负责具体的Token校验逻辑(执行者);

-  WebConfig :负责拦截器的注册和规则配置(配置者);

- 分工设计遵循“单一职责原则”,便于后续扩展(如添加日志拦截器、权限拦截器)。

 

阶段5:Token失效处理(客户端)

 

当Token过期/无效时,服务端返回401状态码,前端通过全局AJAX错误拦截器统一处理,自动跳转到登录页:

 

javascript

// 全局AJAX错误拦截器:处理401未授权

$(document).ajaxError(function (event, xhr, options, exc) {

    if (xhr.status == 401) {

        location.href = "blog_login.html"; // 跳转到登录页

    }

});

 

 

三、关键配套代码说明

 

1. 全局异常处理(保证错误信息统一返回)

 

@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
    // 处理自定义业务异常
    @ExceptionHandler(BlogException.class)
    public Result error(BlogException e) {
        log.error("业务异常:", e);
        return Result.error(e.getMessage());
    }
 
    // 处理全局异常
    @ExceptionHandler(Exception.class)
    public Result error(Exception e) {
        log.error("系统异常:", e);
        return Result.error(e.getMessage());
    }
}
 

 

2. 统一响应结果(Result类)

 
 @Data
public class Result<T> {
    private int code; // 响应码:200成功/其他失败
    private String errMsg; // 错误信息
    private T data; // 响应数据
 
    // 成功响应
    public static <T> Result<T> ok(T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setData(data);
        return result;
    }
 
    // 失败响应
    public static <T> Result<T> error(String errMsg) {
        Result<T> result = new Result<>();
        result.setCode(-1);
        result.setErrMsg(errMsg);
        return result;
    }
}
 
 

四、核心设计原则与最佳实践

 

1. 职责分离原则

 
- 前端:Token的存储、自动携带、失效跳转分离实现;
- 后端:Token生成、校验、拦截规则配置分离实现;
- 优势:代码模块化,便于维护和扩展(如新增权限拦截器只需修改配置类)。
 

2. 安全性设计

 
- 密码校验:将数据库密码放在equals方法前,避免空指针( userInfo.getPassword().equals(loginRequest.getPassword()) );
- Token传输:通过HTTPS传输,防止Token被窃取;
- 过期控制:JWT Token设置合理有效期(如2小时),降低被盗用风险。
 

3. 用户体验优化

 

- 全局拦截:前端自动携带Token、自动处理401跳转,无需用户手动操作;
- 统一错误信息:后端通过自定义异常返回明确的错误提示(如“用户名不正确”),前端无需解析复杂错误格式。
 

五、总结

 
JWT Token身份验证的核心是“生成-存储-携带-校验-失效处理”的闭环流程:
 
1. 登录时生成Token并返回前端;
2. 前端存储Token并在请求时自动携带;
3. 后端通过拦截器统一校验Token合法性;
4. Token失效时前端自动引导用户重新登录。
 
该方案既解决了传统Session的分布式共享问题,又通过前后端的全局拦截器实现了代码的统一管理,是前后端分离项目中身份验证的最优实践之一。
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐