Spring MVC 拦截器实现登录验证
登录验证是Web应用的核心安全功能,拦截器提供了统一的入口点实现全局登录检查。
Session登录验证
拦截器实现
Java
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("user") == null) {
// 未登录,重定向到登录页
response.sendRedirect("/login");
return false;
}
// 已登录,将用户信息存入请求上下文
User user = (User) session.getAttribute("user");
request.setAttribute("currentUser", user);
return true;
}
}
配置拦截器
Java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/login",
"/register",
"/logout",
"/static/**",
"/public/**",
"/error"
);
}
}
Token登录验证
拦截器实现
Java
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Autowired
private TokenService tokenService;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 从请求头获取Token
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"message\":\"未登录\"}");
return false;
}
// 验证Token
token = token.substring(7); // 移除"Bearer "前缀
User user = tokenService.validateToken(token);
if (user == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"message\":\"Token无效或已过期\"}");
return false;
}
// 存入用户上下文
UserContext.setUser(user);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
UserContext.clear(); // 清理ThreadLocal
}
}
Token服务实现
Java
@Service
public class TokenService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public String generateToken(User user) {
String token = UUID.randomUUID().toString().replace("-", "");
String key = "token:" + token;
// Token有效期24小时
redisTemplate.opsForValue().set(key, user, 24, TimeUnit.HOURS);
return token;
}
public User validateToken(String token) {
String key = "token:" + token;
return (User) redisTemplate.opsForValue().get(key);
}
public void invalidateToken(String token) {
String key = "token:" + token;
redisTemplate.delete(key);
}
}
用户上下文工具类
Java
public class UserContext {
private static final ThreadLocal<User> userHolder = new ThreadLocal<>();
public static void setUser(User user) {
userHolder.set(user);
}
public static User getUser() {
return userHolder.get();
}
public static Long getUserId() {
User user = getUser();
return user != null ? user.getId() : null;
}
public static void clear() {
userHolder.remove();
}
}
JWT登录验证
拦截器实现
Java
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
throw new UnauthorizedException("未授权访问");
}
token = token.substring(7);
try {
Claims claims = jwtUtil.parseToken(token);
Long userId = claims.get("userId", Long.class);
String username = claims.get("username", String.class);
// 验证用户是否存在
User user = userService.findById(userId);
if (user == null) {
throw new UnauthorizedException("用户不存在");
}
UserContext.setUser(user);
return true;
} catch (ExpiredJwtException e) {
throw new UnauthorizedException("Token已过期");
} catch (Exception e) {
throw new UnauthorizedException("Token无效");
}
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
UserContext.clear();
}
}
JWT工具类
Java
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration:86400000}")
private Long expiration; // 默认24小时
public String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getUsername())
.claim("userId", user.getId())
.claim("username", user.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
public boolean isTokenExpired(String token) {
try {
Claims claims = parseToken(token);
return claims.getExpiration().before(new Date());
} catch (ExpiredJwtException e) {
return true;
}
}
}
注解式登录验证
定义注解
Java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireLogin {
boolean required() default true;
}
拦截器实现
Java
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 检查方法上的注解
RequireLogin methodAnnotation =
handlerMethod.getMethodAnnotation(RequireLogin.class);
// 检查类上的注解
RequireLogin classAnnotation =
handlerMethod.getBeanType().getAnnotation(RequireLogin.class);
if (methodAnnotation != null && methodAnnotation.required()
|| classAnnotation != null && classAnnotation.required()) {
// 验证登录状态
return checkLogin(request, response);
}
}
return true;
}
private boolean checkLogin(HttpServletRequest request,
HttpServletResponse response) throws Exception {
String token = request.getHeader("Authorization");
if (token == null) {
response.setStatus(401);
response.getWriter().write("{\"code\":401,\"message\":\"请先登录\"}");
return false;
}
// 验证Token...
return true;
}
}
Controller中使用
Java
@RestController
@RequestMapping("/api")
public class UserController {
// 需要登录
@RequireLogin
@GetMapping("/user/profile")
public User getProfile() {
return UserContext.getUser();
}
// 不需要登录
@GetMapping("/public/info")
public Map<String, Object> getPublicInfo() {
return Collections.singletonMap("version", "1.0.0");
}
}
白名单配置
Java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(
"/login",
"/register",
"/logout",
"/api/auth/**",
"/api/public/**",
"/swagger-ui/**",
"/v3/api-docs/**",
"/static/**",
"/favicon.ico",
"/error"
);
}
}
生产环境建议使用JWT + Redis实现登录验证,支持Token刷新和注销。
要点总结
- Session验证通过检查session中的用户信息
- Token验证从请求头获取并校验Token有效性
- JWT验证无需服务端存储,适合分布式系统
- 注解式验证灵活控制哪些方法需要登录
- 必须配置excludePathPatterns排除公开接口
- afterCompletion中清理ThreadLocal避免内存泄漏
📝 发现内容有误?点击此处直接编辑