【Spring Boot + MyBatis|第7篇】JWT 登录认证与拦截器实现

前言

前面我们已经学习了三层架构、参数接收、统一返回结果、动态 SQL、分页查询和全局异常处理。

这些内容基本覆盖了普通 CRUD 接口的开发。但是在真实项目中,还有一个非常重要的问题:用户没有登录,能不能访问后端接口?

答案肯定是不可以。

所以这一篇我们来学习 Spring Boot 项目中常见的登录认证方案:JWT + 拦截器

一、JWT 是什么?

JWT 全称是 JSON Web Token,可以简单理解为一个登录令牌。

用户登录成功后,后端生成一个 token 返回给前端。前端以后每次请求接口时,都把 token 带上。后端拿到 token 后进行校验,如果 token 合法,就允许访问;如果 token 不合法,就拒绝访问。

整体流程是:

text 复制代码
用户登录
  ↓
后端校验用户名和密码
  ↓
登录成功后生成 JWT
  ↓
前端保存 JWT
  ↓
后续请求在请求头中携带 JWT
  ↓
后端拦截器校验 JWT
  ↓
校验通过,放行请求

二、登录接口实现

1. Controller 层

java 复制代码
@RestController
@RequestMapping("/login")
public class LoginController {

    @Autowired
    private EmpService empService;

    @PostMapping
    public Result login(@RequestBody Emp emp) {
        Emp loginEmp = empService.login(emp);

        if (loginEmp != null) {
            Map<String, Object> claims = new HashMap<>();
            claims.put("id", loginEmp.getId());
            claims.put("username", loginEmp.getUsername());

            String token = JwtUtils.generateJwt(claims);
            return Result.success(token);
        }

        return Result.error("用户名或密码错误");
    }
}

2. Service 层

java 复制代码
public interface EmpService {
    Emp login(Emp emp);
}
java 复制代码
@Service
public class EmpServiceImpl implements EmpService {

    @Autowired
    private EmpMapper empMapper;

    @Override
    public Emp login(Emp emp) {
        return empMapper.getByUsernameAndPassword(emp);
    }
}

3. Mapper 层

java 复制代码
@Mapper
public interface EmpMapper {

    @Select("select * from emp where username = #{username} and password = #{password}")
    Emp getByUsernameAndPassword(Emp emp);
}

4. 文字说明

登录接口的核心逻辑是:

  1. 前端传入用户名和密码
  2. Controller 调用 Service 进行登录校验
  3. Service 调用 Mapper 查询数据库
  4. 如果查到了员工信息,说明登录成功
  5. 后端生成 JWT 返回给前端
  6. 如果没有查到,返回错误提示

这里要注意,真实项目中密码一般不会明文存储,通常会使用加密方式保存。学习阶段可以先用明文理解流程。

三、JWT 工具类实现

java 复制代码
public class JwtUtils {

    private static final String SIGN_KEY = "zhiguang_secret_key";
    private static final Long EXPIRE = 43200000L;

    public static String generateJwt(Map<String, Object> claims) {
        return Jwts.builder()
                .addClaims(claims)
                .signWith(SignatureAlgorithm.HS256, SIGN_KEY)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .compact();
    }

    public static Claims parseJwt(String jwt) {
        return Jwts.parser()
                .setSigningKey(SIGN_KEY)
                .parseClaimsJws(jwt)
                .getBody();
    }
}

文字说明

这个工具类主要有两个方法:

java 复制代码
generateJwt()

用来生成 token。

java 复制代码
parseJwt()

用来解析 token。

其中:

java 复制代码
SIGN_KEY

表示签名密钥,真实项目中不要直接写死在代码里,可以放到配置文件或者环境变量中。

java 复制代码
EXPIRE

表示 token 的过期时间,这里设置为 12 小时。

四、拦截器校验 Token

1. LoginCheckInterceptor

java 复制代码
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {

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

        String token = request.getHeader("token");

        if (token == null || token.isEmpty()) {
            response.setStatus(401);
            response.getWriter().write("NOT_LOGIN");
            return false;
        }

        try {
            JwtUtils.parseJwt(token);
        } catch (Exception e) {
            response.setStatus(401);
            response.getWriter().write("NOT_LOGIN");
            return false;
        }

        return true;
    }
}

2. 文字说明

拦截器中的 preHandle 方法会在 Controller 执行之前运行。

这里的逻辑是:

  1. 从请求头中获取 token
  2. 如果 token 为空,说明用户没有登录
  3. 如果 token 解析失败,说明 token 不合法或已过期
  4. 校验通过后,返回 true 放行请求
  5. 校验失败后,返回 false 拦截请求

五、注册拦截器

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

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

文字说明

这里表示拦截所有请求:

java 复制代码
.addPathPatterns("/**")

但是登录接口不能拦截,否则用户还没登录就访问不了登录接口,所以要排除:

java 复制代码
.excludePathPatterns("/login")

六、前端请求应该怎么带 Token?

登录成功后,后端返回 token。

后续请求时,前端需要在请求头中携带:

text 复制代码
token: 后端返回的JWT字符串

比如请求员工列表时:

text 复制代码
GET /emps
token: eyJhbGciOiJIUzI1NiJ9...

后端拦截器就可以从请求头中拿到 token,并进行校验。

七、常见问题

1. 为什么登录接口要排除拦截?

因为登录接口本身就是用来获取 token 的。

如果登录接口也被拦截,用户没有 token,就永远无法登录。

2. token 过期后怎么办?

token 过期后,解析时会抛出异常,拦截器会返回未登录。

前端收到未登录状态后,一般会跳转到登录页面,让用户重新登录。

3. JWT 里面能不能放密码?

不建议。

JWT 中可以放用户 id、用户名、权限标识等信息,不要放密码、手机号、身份证号等敏感数据。

八、总结

这一篇主要学习了 Spring Boot 项目中 JWT 登录认证和拦截器的实现。

登录成功后,后端生成 JWT 返回给前端;前端后续请求携带 JWT;后端通过拦截器统一校验 token。这样就可以避免每个接口都手动判断用户是否登录。

这一篇相比前面的 CRUD 更贴近真实项目,因为它解决的是接口安全访问的问题。


相关推荐
东风破_4 小时前
ESLint 是什么?为什么你的项目需要它?
前端·后端·代码规范
嘻哈∠※4 小时前
0061基于 SpringBoot 的投稿与稿件处理系统设计与实现
java·spring boot·后端
卷无止境5 小时前
在 awesome-fastapi 里,哪些库值得一看?
后端·python
白狐_7985 小时前
408数据结构第8章:排序②——性质对比秒杀、场景选择与外部排序
java·数据结构·算法
zander2586 小时前
LeetCode 84:柱状图中的最大矩形——单调栈如何确定左右边界
java·数据结构·算法
码匠许师傅6 小时前
【C++ 面试真题】聊聊 C++ 的拷贝构造与拷贝赋值
java·c++·面试
卷无止境6 小时前
FastAPI 的Admin面板生态
后端·python
捡田螺的小男孩7 小时前
什么是 Skill?手把手带你写一个简单有用的 Skill!
前端·后端·程序员
IT_陈寒7 小时前
Redis集群这个坑,差点让我通宵
前端·人工智能·后端
极创信息7 小时前
系统安全隐患全方位排查方案:标准化渗透测试全流程
java·opencv·struts·数据挖掘·eclipse·语音识别·hibernate