前言
前面我们已经学习了三层架构、参数接收、统一返回结果、动态 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. 文字说明
登录接口的核心逻辑是:
- 前端传入用户名和密码
- Controller 调用 Service 进行登录校验
- Service 调用 Mapper 查询数据库
- 如果查到了员工信息,说明登录成功
- 后端生成 JWT 返回给前端
- 如果没有查到,返回错误提示
这里要注意,真实项目中密码一般不会明文存储,通常会使用加密方式保存。学习阶段可以先用明文理解流程。
三、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 执行之前运行。
这里的逻辑是:
- 从请求头中获取 token
- 如果 token 为空,说明用户没有登录
- 如果 token 解析失败,说明 token 不合法或已过期
- 校验通过后,返回
true放行请求 - 校验失败后,返回
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 更贴近真实项目,因为它解决的是接口安全访问的问题。