1. 登录校验基础与核心概念
在JavaWeb开发中,登录校验是保障系统安全的第一道防线。我见过太多项目因为校验不严谨导致数据泄露的案例。传统的校验方式主要依赖Cookie和Session,而现代应用则越来越多采用令牌技术。
1.1 Cookie的工作机制
Cookie的本质是服务器发送到浏览器并保存在本地的一小块数据。当我在Chrome开发者工具中查看Cookie时,常看到类似这样的结构:
code复制Set-Cookie: JSESSIONID=abc123; Path=/; HttpOnly; Secure
关键属性解析:
- Name/Value:必填项,如JSESSIONID=abc123
- Domain:指定哪些域名可以接收该Cookie
- Path:限制Cookie的URL路径范围
- Expires/Max-Age:控制有效期(会话级或持久化)
- HttpOnly:阻止JavaScript访问,防XSS
- Secure:仅HTTPS传输,防中间人攻击
实际开发中,Java设置Cookie的代码示例:
java复制Cookie cookie = new Cookie("username", "admin");
cookie.setMaxAge(60*60*24); // 1天有效期
cookie.setPath("/");
cookie.setHttpOnly(true);
response.addCookie(cookie);
1.2 Session的底层实现
Session的工作原理常被误解。当我在Tomcat中调试时发现,其本质流程是:
- 浏览器首次访问时,服务器创建Session并生成唯一ID
- 通过Set-Cookie将JSESSIONID返回浏览器
- 后续请求自动携带该ID,服务器据此找到对应Session
Tomcat的Session存储默认使用内存,但生产环境通常会配置持久化:
xml复制<Context>
<Manager className="org.apache.catalina.session.PersistentManager">
<Store className="org.apache.catalina.session.FileStore" directory="./sessions"/>
</Manager>
</Context>
1.3 令牌技术的演进
传统Cookie-Session的痛点在于服务器需要维护会话状态。在我最近参与的微服务项目中,JWT(JSON Web Token)成为首选方案。一个典型的JWT结构如下:
code复制eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
解码后可见三部分:
- Header:算法和类型
- Payload:实际存储的数据
- Signature:防篡改签名
Java生成JWT的示例:
java复制String token = Jwts.builder()
.setSubject("user123")
.setExpiration(new Date(System.currentTimeMillis() + 86400000))
.signWith(SignatureAlgorithm.HS512, "secretKey")
.compact();
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 过滤器与拦截器实战
2.1 过滤器的链式处理
在一次线上事故排查中,我发现过滤器的执行顺序直接影响系统行为。正确的配置方式是在web.xml中:
xml复制<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.example.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
过滤器链的典型实现:
java复制public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader("Authorization");
if(!validateToken(token)) {
((HttpServletResponse)response).sendError(401);
return;
}
chain.doFilter(request, response); // 关键:继续执行下一个过滤器
}
}
2.2 拦截器的Spring实现
与过滤器不同,Spring拦截器可以精确控制Controller方法的执行。我在项目中常用的配置模式:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/public/**");
}
}
一个记录请求耗时的拦截器实现:
java复制public class PerfInterceptor extends HandlerInterceptorAdapter {
private ThreadLocal<Long> startTime = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
startTime.set(System.currentTimeMillis());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
long duration = System.currentTimeMillis() - startTime.get();
log.info("{} 耗时 {}ms", request.getRequestURI(), duration);
}
}
3. 安全防护实战技巧
3.1 CSRF防御方案
我曾处理过因缺少CSRF防护导致的资金盗刷案例。有效的防护策略包括:
- 同步令牌模式:
jsp复制<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
- 双重Cookie验证:
javascript复制// 前端在请求头中添加
fetch('/transfer', {
headers: {
'X-CSRF-TOKEN': getCookie('csrfToken')
}
})
Spring Security的默认配置:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
3.2 会话固定攻击防护
在银行项目中,我们通过以下措施防御会话固定:
java复制HttpSession session = request.getSession(false);
if (session != null && !session.isNew()) {
session.invalidate(); // 使旧会话失效
session = request.getSession(true); // 创建新会话
session.setAttribute("auth", true);
}
4. 性能优化实践
4.1 Session存储优化
当用户量突破10万时,内存Session成为瓶颈。我们的解决方案:
- Redis集中存储:
java复制@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("redis", 6379));
}
@Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
- Session数据精简:
java复制// 错误示范 - 存储整个用户对象
session.setAttribute("user", user);
// 正确做法 - 只存储必要字段
session.setAttribute("userId", user.getId());
4.2 令牌刷新机制
JWT的过期处理是个难题,我们的双令牌方案:
java复制// 生成访问令牌(短有效期)
String accessToken = Jwts.builder()
.setExpiration(new Date(System.currentTimeMillis() + 15*60*1000)) // 15分钟
.signWith(SignatureAlgorithm.HS512, "accessSecret")
.compact();
// 生成刷新令牌(长有效期)
String refreshToken = Jwts.builder()
.setExpiration(new Date(System.currentTimeMillis() + 7*24*60*60*1000)) // 7天
.signWith(SignatureAlgorithm.HS512, "refreshSecret")
.compact();
刷新流程:
code复制1. 客户端用过期accessToken+refreshToken请求/auth/refresh
2. 服务端验证refreshToken有效性
3. 签发新的accessToken(原refreshToken继续有效)
5. 常见问题排查
5.1 Cookie丢失问题排查
上周生产环境出现的典型故障排查过程:
- 现象:部分用户登录状态随机丢失
- 排查步骤:
- 确认Domain设置包含子域名:
.example.com - 检查Nginx配置确保没有过滤Set-Cookie头
- 验证Secure标记与HTTPS使用情况
- 确认Domain设置包含子域名:
- 根因:移动端WebView默认禁用第三方Cookie
5.2 Session并发控制
财务系统遇到的典型问题:
java复制// 错误做法 - 导致数据覆盖
public void updateBalance(HttpSession session, BigDecimal amount) {
BigDecimal balance = (BigDecimal) session.getAttribute("balance");
session.setAttribute("balance", balance.add(amount));
}
// 正确方案 - 使用同步控制
public synchronized void updateBalance(HttpSession session, BigDecimal amount) {
// 或使用数据库事务
}
6. 现代架构演进
6.1 无状态设计实践
在最近的云原生项目中,我们完全弃用Session:
java复制@RestController
public class UserController {
@PostMapping("/login")
public ResponseEntity login(@RequestBody LoginRequest request) {
User user = authenticate(request);
String token = JwtUtil.generateToken(user);
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, buildCookie(token).toString())
.body(new LoginResponse(token));
}
private ResponseCookie buildCookie(String token) {
return ResponseCookie.from("token", token)
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(3600)
.sameSite("Strict")
.build();
}
}
6.2 跨域方案设计
处理前后端分离项目时的配置要点:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://frontend.com")
.allowedMethods("GET", "POST")
.allowCredentials(true)
.maxAge(3600);
}
}
关键细节:
allowCredentials(true)必须与具体origin配合使用- 不能设置
allowedOrigins("*")时启用凭证 - 生产环境应严格限制origin白名单
7. 监控与审计
7.1 会话监控看板
我们基于Prometheus的监控方案:
java复制@Bean
public MeterRegistryCustomizer<PrometheusMeterRegistry> sessionMetrics() {
return registry -> {
Gauge.builder("session.active.count", () -> {
return ((SessionRegistry)ctx.getBean("sessionRegistry")).getAllSessions().size();
}).register(registry);
};
}
关键指标:
- 活跃会话数
- 会话平均时长
- 并发登录设备数
- 异常登录地理位置
7.2 审计日志设计
合规要求的审计日志示例:
java复制@Aspect
@Component
public class AuthAuditAspect {
@AfterReturning(pointcut = "@annotation(requiresAuth)", returning = "result")
public void auditSuccess(JoinPoint jp, RequiresAuth requiresAuth, Object result) {
AuditEntry entry = new AuditEntry();
entry.setAction(requiresAuth.value());
entry.setUserId(SecurityContext.getCurrentUserId());
entry.setTimestamp(Instant.now());
auditRepository.save(entry);
}
}
日志字段应包含:
- 时间戳(UTC时区)
- 用户标识
- 操作类型
- 请求参数哈希
- 客户端指纹(IP+UA+设备指纹)
