登录实现
登录流程是 Web 应用的核心功能,核心目标是:验证用户身份合法性 → 维护登录状态 → 保障访问安全。Java 中实现登录流程的方式多样,根据技术栈和场景可分为以下几类,涵盖从原生实现到框架集成,从有状态到无状态:
一、基于 Servlet 原生实现(理解底层原理)
适合简单 Web 应用,直接使用 Servlet API 处理请求、验证身份、管理 Session,适合理解登录流程的底层逻辑。
核心流程
-
前端提交用户名 / 密码到后端 Servlet;
-
后端接收参数,与数据库中存储的用户信息(密码需加密)比对;
-
验证通过:创建 Session,存储用户信息(如用户 ID、角色),返回登录成功;
-
验证失败:返回错误信息(如 “用户名或密码错误”)。
实现代码
1. 实体类(User)
java
运行
public class User {
private Integer id;
private String username;
private String password; // 存储加密后的密码(如BCrypt加密)
// get/set
}
2. 数据访问层(模拟数据库查询)
java
运行
public class UserDao {
// 模拟数据库:key=用户名,value=用户对象(实际项目中从数据库查询)
private static Map<String, User> userMap = new HashMap<>();
static {
// 初始化用户:密码用BCrypt加密(原始密码123456)
String encryptedPwd = BCrypt.hashpw("123456", BCrypt.gensalt());
userMap.put("zhangsan", new User(1, "zhangsan", encryptedPwd));
}
// 根据用户名查询用户
public User findByUsername(String username) {
return userMap.get(username);
}
}
3. 登录 Servlet
java
运行
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private UserDao userDao = new UserDao();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取前端参数
String username = req.getParameter("username");
String password = req.getParameter("password");
// 2. 验证参数非空
if (username == null || password == null) {
resp.getWriter().write("用户名或密码不能为空");
return;
}
// 3. 查询用户
User user = userDao.findByUsername(username);
if (user == null) {
resp.getWriter().write("用户名不存在");
return;
}
// 4. 验证密码(BCrypt解密比对)
if (!BCrypt.checkpw(password, user.getPassword())) {
resp.getWriter().write("密码错误");
return;
}
// 5. 验证通过:创建Session,存储用户信息(避免存储敏感信息)
HttpSession session = req.getSession();
session.setAttribute("userId", user.getId());
session.setAttribute("username", user.getUsername());
session.setMaxInactiveInterval(3600); // Session有效期1小时
// 6. 返回成功(实际项目中可能跳转页面或返回JSON)
resp.getWriter().write("登录成功");
}
}
4. 登出 Servlet
java
运行
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 销毁Session
HttpSession session = req.getSession();
session.invalidate();
resp.getWriter().write("登出成功");
}
}
核心技术点
-
密码加密:使用
BCrypt(不可逆加密,带盐值),避免存储明文密码(依赖org.mindrot:jbcrypt:0.4); -
Session 管理:通过
HttpSession存储登录状态,依赖 Servlet 容器(如 Tomcat)的 Session 存储(默认内存,分布式场景需优化); -
安全性:需通过
HTTPS传输避免密码泄露,可添加验证码防止暴力破解。
优缺点
-
优点:原理清晰,无框架依赖,适合学习和简单应用;
-
缺点:需手动处理参数校验、异常、Session 分布式问题,功能扩展繁琐(如权限控制)。
二、Spring MVC + Session(企业级基础实现)
基于 Spring MVC 框架简化开发,使用注解接收请求,依赖 Spring 的 IoC 管理组件,保留 Session 有状态登录,适合中小型 Web 应用。
核心流程
-
前端通过表单 / JSON 提交用户名 / 密码到
@PostMapping("/login")接口; -
后端 Controller 接收参数,调用 Service 层验证;
-
验证通过:将用户信息存入
HttpSession,返回登录成功(如 JWT 令牌或 SessionId); -
验证失败:返回错误信息(通过
@ControllerAdvice统一异常处理)。
实现代码
1. 依赖(Maven)
xml
<dependencies> <!-- Spring MVC --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- BCrypt加密 --> <dependency> <groupId>org.mindrot</groupId> <artifactId>jbcrypt</artifactId> <version>0.4</version> </dependency> </dependencies>
2. Controller 层
java
运行
@RestController
public class LoginController {
@Autowired
private UserService userService;
@PostMapping("/login")
public Result login(@RequestBody LoginDTO loginDTO, HttpSession session) {
// 调用Service验证
User user = userService.login(loginDTO.getUsername(), loginDTO.getPassword());
// 存储用户信息到Session(仅存必要字段)
session.setAttribute("loginUser", user.getId());
session.setMaxInactiveInterval(3600);
return Result.success("登录成功");
}
@GetMapping("/logout")
public Result logout(HttpSession session) {
session.invalidate();
return Result.success("登出成功");
}
// 测试登录状态:需登录才能访问
@GetMapping("/user/info")
public Result getUserInfo(HttpSession session) {
Integer userId = (Integer) session.getAttribute("loginUser");
if (userId == null) {
return Result.fail("未登录");
}
User user = userService.findById(userId);
return Result.success(user);
}
}
// 数据传输对象(DTO)
@Data
class LoginDTO {
private String username;
private String password;
}
// 统一返回结果
@Data
class Result {
private int code;
private String msg;
private Object data;
// 静态success/fail方法省略
}
3. Service 层(核心验证逻辑)
java
运行
@Service
public class UserService {
@Autowired
private UserMapper userMapper; // 假设通过MyBatis访问数据库
public User login(String username, String password) {
// 1. 查询用户
User user = userMapper.selectByUsername(username);
if (user == null) {
throw new BusinessException("用户名不存在");
}
// 2. 验证密码(BCrypt比对)
if (!BCrypt.checkpw(password, user.getPassword())) {
throw new BusinessException("密码错误");
}
return user;
}
public User findById(Integer userId) {
return userMapper.selectById(userId);
}
}
4. 登录拦截器(验证登录状态)
java
运行
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
if (session.getAttribute("loginUser") == null) {
// 未登录:返回401
response.setStatus(401);
response.getWriter().write("请先登录");
return false;
}
return true;
}
}
// 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/login", "/logout"); // 排除登录/登出接口
}
}
核心技术点
-
Spring MVC 注解:
@PostMapping接收请求,@RequestBody绑定 JSON 参数,简化参数处理; -
拦截器:通过
HandlerInterceptor统一验证登录状态,避免在每个接口重复判断; -
异常处理:自定义
BusinessException,结合@ControllerAdvice统一返回错误信息。
优缺点
-
优点:开发效率高,Spring 生态支持完善,适合中小型 Web 应用;
-
缺点:依赖 Session(有状态),分布式部署时需解决 Session 共享问题(如 Redis 存储 Session)。
三、Spring Security(企业级安全框架)
Spring Security 是功能完善的安全框架,内置登录验证、Session 管理、权限控制等功能,适合复杂企业级应用(需权限管理、记住我等高级特性)。
核心流程
-
框架自动拦截
/login请求,接收用户名 / 密码; -
通过
UserDetailsService查询用户信息,PasswordEncoder验证密码; -
验证通过:生成 Session(或 JWT),触发登录成功事件;
-
验证失败:返回错误页面 / 信息(可自定义)。
实现代码
1. 依赖(Spring Boot)
xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
2. 配置类(核心)
java
运行
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 密码编码器(BCrypt)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 自定义用户查询服务
@Bean
public UserDetailsService userDetailsService() {
return username -> {
// 模拟查询用户(实际从数据库获取)
if ("zhangsan".equals(username)) {
// 密码:123456(已加密)
String encryptedPwd = passwordEncoder().encode("123456");
// 返回用户信息(包含用户名、密码、权限)
return User.withUsername("zhangsan")
.password(encryptedPwd)
.roles("USER")
.build();
}
throw new UsernameNotFoundException("用户名不存在");
};
}
// 配置安全规则
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // 开发环境关闭CSRF(生产环境需开启)
.authorizeRequests()
.antMatchers("/login", "/logout").permitAll() // 登录/登出接口允许匿名访问
.anyRequest().authenticated() // 其他接口需登录
.and()
.formLogin() // 启用表单登录
.loginProcessingUrl("/login") // 登录提交地址
.successHandler((req, resp, auth) -> { // 登录成功处理
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write("{\"code\":200,\"msg\":\"登录成功\"}");
})
.failureHandler((req, resp, e) -> { // 登录失败处理
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write("{\"code\":400,\"msg\":\"" + e.getMessage() + "\"}");
})
.and()
.logout() // 登出配置
.logoutUrl("/logout")
.logoutSuccessHandler((req, resp, auth) -> {
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write("{\"code\":200,\"msg\":\"登出成功\"}");
});
}
}
3. 测试接口(需登录)
java
运行
@RestController
public class UserController {
// 登录后可访问
@GetMapping("/user/info")
public String getUserInfo(Authentication authentication) {
// 获取当前登录用户信息
String username = authentication.getName();
return "当前登录用户:" + username;
}
}
核心技术点
-
UserDetailsService:框架回调该接口查询用户信息(用户名、加密密码、权限);
-
PasswordEncoder:统一密码加密 / 验证策略(默认
BCryptPasswordEncoder); -
安全配置:通过
HttpSecurity配置哪些接口需要登录、登录 / 登出的处理逻辑、CSRF 防护等。
优缺点
-
优点:功能全面(支持记住我、验证码、OAuth2.0 等),安全性高(内置防 XSS、CSRF 等),适合复杂企业应用;
-
缺点:学习成本高,配置较繁琐,简单应用可能 “过重”。
四、JWT 无状态登录(分布式系统)
适合分布式 / 微服务架构,通过 JWT(JSON Web Token)存储用户信息,服务器不保存 Session,避免 Session 共享问题。
核心流程
-
前端提交用户名 / 密码,后端验证通过后生成 JWT 令牌(包含用户 ID、过期时间等,签名防篡改);
-
前端存储 JWT(如 localStorage),后续请求在 Header 中携带
Authorization: Bearer {token}; -
后端通过过滤器验证 JWT 合法性(签名、过期时间),解析用户信息,允许访问;
-
登出:前端删除 JWT(服务器无需处理,因无状态)。
实现代码
1. 依赖(JWT 工具)
xml
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
2. JWT 工具类(生成、验证、解析)
java
运行
@Component
public class JwtUtils {
// 密钥(生产环境需复杂且保密)
private static final String SECRET = "your-secret-key-123456";
// 过期时间:2小时
private static final long EXPIRATION = 2 * 60 * 60 * 1000;
// 生成JWT
public String generateToken(Integer userId, String username) {
return Jwts.builder()
.setSubject(userId.toString()) // 存储用户ID
.claim("username", username) // 附加用户名
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) // 过期时间
.signWith(SignatureAlgorithm.HS256, SECRET) // 签名算法+密钥
.compact();
}
// 验证JWT并解析用户ID
public Integer getUserIdFromToken(String token) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(SECRET)
.build()
.parseClaimsJws(token)
.getBody();
return Integer.parseInt(claims.getSubject());
} catch (Exception e) {
// 解析失败(过期、签名错误等)
throw new BusinessException("令牌无效或已过期");
}
}
}
3. 登录接口(生成 JWT)
java
运行
@RestController
public class JwtLoginController {
@Autowired
private UserService userService;
@Autowired
private JwtUtils jwtUtils;
@PostMapping("/login")
public Result login(@RequestBody LoginDTO loginDTO) {
User user = userService.login(loginDTO.getUsername(), loginDTO.getPassword());
// 生成JWT
String token = jwtUtils.generateToken(user.getId(), user.getUsername());
return Result.success(token);
}
}
4. JWT 过滤器(验证令牌)
java
运行
@Component
public class JwtFilter extends OncePerRequestFilter {
@Autowired
private JwtUtils jwtUtils;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 1. 获取请求头中的token
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(401);
response.getWriter().write("请先登录(未携带令牌)");
return;
}
token = token.substring(7); // 去掉"Bearer "前缀
// 2. 验证token并解析用户ID
Integer userId;
try {
userId = jwtUtils.getUserIdFromToken(token);
} catch (BusinessException e) {
response.setStatus(401);
response.getWriter().write(e.getMessage());
return;
}
// 3. 将用户ID存入请求属性,供后续接口使用
request.setAttribute("userId", userId);
// 4. 继续执行过滤器链
filterChain.doFilter(request, response);
}
}
// 注册过滤器
@Configuration
public class JwtConfig {
@Bean
public FilterRegistrationBean<JwtFilter> jwtFilter() {
FilterRegistrationBean<JwtFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new JwtFilter());
registrationBean.addUrlPatterns("/**"); // 拦截所有请求
registrationBean.addInitParameter("excludeUrls", "/login,/logout"); // 排除登录接口
return registrationBean;
}
}
核心技术点
-
JWT 结构:由
Header(算法)、Payload(用户信息 + 过期时间)、Signature(签名,防篡改)三部分组成; -
无状态:服务器不存储登录状态,令牌本身包含所有必要信息,适合分布式系统;
-
安全性:密钥必须保密,令牌需通过 HTTPS 传输,避免前端存储敏感信息(如密码)。
优缺点
-
优点:适合分布式 / 微服务架构,无 Session 共享问题,扩展性好;
-
缺点:令牌无法主动失效(除非服务器维护黑名单),过期时间需合理设置(过长不安全,过短影响体验)。
五、OAuth2.0 第三方登录(社交登录)
通过第三方平台(如微信、QQ、GitHub)的身份认证实现登录,无需用户注册,提升用户体验。
核心流程(以 GitHub 登录为例)
-
前端点击 “GitHub 登录”,跳转至 GitHub 授权页;
-
用户授权后,GitHub 回调后端接口,携带
code; -
后端用
code向 GitHub 换取access_token; -
用
access_token获取 GitHub 用户信息(如用户名、邮箱); -
后端关联本地用户(新用户自动注册),生成登录状态(Session 或 JWT)。
实现代码(Spring Security OAuth2.0)
1. 依赖
xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency>
2. 配置文件(application.yml)
yaml
spring: security: oauth2: client: registration: github: # GitHub登录配置 client-id: 你的GitHub客户端ID(从GitHub开发者平台申请) client-secret: 你的GitHub客户端密钥 scope: user:email # 申请的权限(获取用户邮箱) provider: github: user-info-uri: https://api.github.com/user # GitHub用户信息接口
3. 安全配置
java
运行
@Configuration
@EnableWebSecurity
public class OAuth2Config extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login() // 启用OAuth2登录
.defaultSuccessUrl("/login/success", true) // 登录成功跳转地址
.userInfoEndpoint()
.userService(oauth2UserService()); // 自定义用户信息处理
}
// 自定义用户信息处理(关联本地用户)
@Bean
public OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
return request -> {
// 1. 调用默认服务获取GitHub用户信息
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oauth2User = delegate.loadUser(request);
// 2. 解析GitHub用户信息(如用户名、邮箱)
Map<String, Object> attributes = oauth2User.getAttributes();
String githubUsername = (String) attributes.get("login");
String email = (String) attributes.get("email");
// 3. 关联本地用户(新用户自动注册)
User localUser = userService.findByThirdPartyId("github_" + githubUsername);
if (localUser == null) {
localUser = new User();
localUser.setUsername(githubUsername);
localUser.setThirdPartyId("github_" + githubUsername);
userService.register(localUser); // 自动注册
}
// 4. 返回自定义用户信息(包含本地用户ID)
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")),
attributes,
"login"
);
};
}
}
4. 登录成功接口
java
运行
@RestController
public class OAuth2Controller {
@GetMapping("/login/success")
public Result loginSuccess(Authentication authentication) {
// 获取登录用户信息(包含第三方平台信息)
OAuth2User oauth2User = (OAuth2User) authentication.getPrincipal();
String username = oauth2User.getAttribute("login");
return Result.success("GitHub登录成功,用户:" + username);
}
}
核心技术点
-
OAuth2.0 角色:资源所有者(用户)、客户端(你的应用)、授权服务器(GitHub)、资源服务器(存储用户信息的服务器);
-
授权流程:常用
Authorization Code模式(安全,适合 Web 应用),通过code换取access_token,再获取用户信息; -
用户关联:将第三方用户 ID(如
github_xxx)与本地用户表关联,实现 “一次授权,多端登录”。
优缺点
-
优点:无需用户注册,降低使用门槛,适合社交类、工具类应用;
-
缺点:依赖第三方平台,需申请开发者账号,用户信息获取受第三方限制。
总结:不同方法的选择依据
| 实现方式 | 核心特点 | 适用场景 |
|---|---|---|
| Servlet 原生 | 底层原理,无框架依赖 | 学习理解、极简应用 |
| Spring MVC + Session | 简单易用,有状态 | 中小型 Web 应用,单体架构 |
| Spring Security | 功能全面,安全可靠 | 企业级应用,需权限管理、复杂安全控制 |
| JWT 无状态登录 | 分布式友好,无 Session | 微服务 / 分布式架构,前后端分离应用 |
| OAuth2.0 第三方登录 | 社交登录,用户体验好 | 需要快速获客的应用(如电商、工具类) |
无论哪种方式,核心原则不变:密码必须加密存储、传输需用 HTTPS、登录状态需验证,根据项目规模和架构选择最合适的方案即可。
多 Java 登录实现方案对比与深度讲解
核心结论
不同登录方案的本质是「状态管理方式 + 技术栈适配」的选择:JWT 双 Token 适合前后端分离 / 分布式架构,Session 适合传统单体应用,Redis 替代 Session 解决分布式 Session 共享问题,通用模板适合多场景复用,过滤器校验适合传统 Web 的页面权限控制。以下从核心逻辑、关键差异、适用场景展开对比讲解。
一、各方案核心逻辑与亮点提炼
1. 方案 1:JWT 双 Token 登录(accessToken+refreshToken)
核心逻辑
-
技术栈:Spring Boot + JWT + Spring Security PasswordEncoder(BCrypt 加密)
-
流程:用户验证→状态校验→密码加密比对→生成双 Token(accessToken 短期访问 + refreshToken 长期刷新)→返回给前端
-
关键设计:
-
accessToken(2 小时):日常接口访问,避免敏感信息,降低被盗风险
-
refreshToken(7 天):accessToken 过期后无感刷新,无需重复登录
-
前端捕捉 401 异常,用 refreshToken 自动刷新 Token 并重试请求
-
核心亮点
-
无状态:服务器不存储登录状态,Token 本身携带用户信息,适配分布式 / 微服务
-
安全:双 Token 分离职责,密码 BCrypt 不可逆加密,签名防篡改
-
体验好:Token 过期无感刷新,用户无需重复登录
2. 方案 2:通用登录模板(AbstractLoginService)
核心逻辑
-
技术栈:泛型抽象类 + JWT + PasswordEncoder
-
流程:通过抽象方法封装通用登录流程(校验→查询用户→状态校验→密码比对→Token 生成→结果封装),子类实现具体业务细节(如用户查询、状态规则)
-
关键设计:
-
泛型适配:支持不同 User 实体、DTO/VO 结构,无需重复编写登录流程
-
钩子方法:预留前置校验、后置操作(如登录日志)扩展点
-
配置分离:JWT 参数通过配置类注入,支持多环境切换
-
核心亮点
-
高复用:一套模板适配多系统登录需求,减少重复开发
-
扩展灵活:抽象方法 + 钩子方法,可定制业务细节(如多端登录、特殊状态校验)
-
规范统一:强制登录流程标准化,避免团队开发的逻辑混乱
3. 方案 3:基于 Session 的登录
核心逻辑
-
技术栈:Servlet/Spring MVC + HttpSession + ThreadLocal
-
流程:发送验证码(存储到 Session)→ 登录校验(手机号 + 验证码)→ 查询 / 创建用户→ 存储用户信息到 Session→ 拦截器校验 Session 存在性
-
关键设计:
-
状态存储:Session 由 Servlet 容器(如 Tomcat)管理,默认内存存储
-
线程隔离:用 ThreadLocal 存储当前用户信息,接口直接获取,无需重复从 Session 查询
-
敏感信息隐藏:用 UserDTO 剥离敏感字段(如密码),避免前端泄露
-
核心亮点
-
实现简单:无需额外中间件(如 Redis/JWT),依赖原生 API
-
适合单体:Session 天然适配单体应用,无分布式同步成本
-
安全性:基于 Cookie 的 JSESSIONID 传输,默认防 CSRF(需配合框架配置)
4. 方案 4:Redis 替代 Session 的登录
核心逻辑
-
技术栈:Redis + UUID Token + 拦截器
-
流程:发送验证码(存储到 Redis)→ 登录校验→ 查询 / 创建用户→ 生成 UUID Token→ 用 Hash 结构存储用户信息到 Redis→ 前端携带 Token 访问,拦截器校验 Redis 中用户存在性
-
关键设计:
-
Key 设计:
LOGIN_USER_KEY + Token(唯一标识用户),LOGIN_CODE_KEY + 手机号(存储验证码) -
过期时间:Redis 设置 Token 有效期(如 30 分钟),自动过期清理
-
双拦截器:刷新 Token 拦截器(所有路径)+ 登录校验拦截器(需登录路径)
-
核心亮点
-
解决 Session 共享:Redis 分布式存储,适配多 Tomcat 集群部署
-
性能优:Redis 读写速度快,支持高并发,避免 Session 内存溢出
-
灵活扩展:支持多端登录(不同 Token 对应不同设备),便于踢人下线等功能
5. 方案 5:瑞吉外卖过滤器登录校验(传统 Web)
核心逻辑
-
技术栈:Servlet Filter + HttpSession + AntPathMatcher
-
流程:拦截所有请求→ 匹配无需登录路径(如登录 / 退出接口)→ 放行无需登录请求→ 校验 Session 中用户存在性→ 未登录则返回 NOTLOGIN,前端跳转登录页
-
关键设计:
-
路径匹配:用 AntPathMatcher 支持通配符(如
/backend/**),灵活配置放行路径 -
响应方式:通过输出流返回 JSON 结果,前端根据
NOTLOGIN状态跳转 -
组件扫描:用
@ServletComponentScan自动注册过滤器
-
核心亮点
-
适配传统 Web:专注页面访问权限控制,未登录直接跳转登录页
-
配置简单:无需复杂框架,依赖 Servlet 原生 Filter,学习成本低
-
轻量高效:拦截器链执行早,性能损耗小,适合中小规模传统 Web 应用
二、关键维度横向对比
| 对比维度 | 方案 1:JWT 双 Token | 方案 2:通用登录模板 | 方案 3:基于 Session | 方案 4:Redis 替代 Session | 方案 5:过滤器校验(瑞吉) |
|---|---|---|---|---|---|
| 状态类型 | 无状态 | 可无状态 / 有状态 | 有状态 | 无状态(Token+Redis) | 有状态(Session) |
| 核心技术 | JWT + BCrypt | 泛型抽象 + JWT | HttpSession + ThreadLocal | Redis + UUID + 拦截器 | Servlet Filter + Session |
| 适用架构 | 前后端分离 / 微服务 | 多系统复用场景 | 单体传统 Web / 前后端分离 | 分布式单体 / 微服务 | 传统 Web 应用(页面访问) |
| 分布式支持 | 天然支持 | 支持(依赖 JWT/Redis) | 需 Session 共享(复杂) | 天然支持 | 需 Session 共享(复杂) |
| 安全特性 | 双 Token + 签名 + BCrypt | 继承 JWT / 加密特性 | 依赖容器安全 | 过期自动清理 + 加密存储 | 路径拦截 + Session 隔离 |
| 扩展性 | 高(支持多端 / 刷新) | 极高(泛型 + 钩子) | 低(Session 局限) | 高(Redis 灵活操作) | 中(路径配置灵活) |
| 实现复杂度 | 中(需理解 JWT) | 中(需设计抽象) | 低(原生 API) | 中(需 Redis 操作) | 低(Filter 简单配置) |
| 核心优势 | 无状态 + 无感刷新 | 高复用 + 标准化 | 简单直接 | 分布式友好 + 性能优 | 轻量 + 页面权限控制 |
| 核心劣势 | Token 无法主动失效 | 学习成本略高 | 分布式部署麻烦 | 依赖 Redis 中间件 | 仅适配传统 Web |
三、深度讲解:核心差异与选型逻辑
1. 无状态 vs 有状态:架构选型的核心
-
无状态方案(JWT 双 Token、Redis 替代 Session):
-
核心思想:服务器不存储登录状态,身份信息通过 Token/Redis 键值对携带
-
适合场景:微服务、前后端分离、高并发分布式架构(如电商、IM)
-
关键解决:避免 Session 共享的复杂性(如 Redis Session 同步、Tomcat 集群 Session 拷贝)
-
-
有状态方案(Session、过滤器校验):
-
核心思想:服务器存储用户状态(Session),通过 JSESSIONID 关联用户
-
适合场景:单体传统 Web 应用(如管理后台、内部系统)
-
关键优势:实现简单,无需额外中间件,开发成本低
-
2. 密码加密:安全性的基础
-
方案 1/2/4:用 BCrypt 加密(推荐)
-
特点:不可逆,自带盐值,相同明文加密结果不同,防彩虹表攻击
-
适配场景:生产环境,对安全性要求高的系统
-
-
方案 3(部分):用 MD5 加密(不推荐)
-
特点:不可逆但可通过彩虹表破解,需手动加盐,安全性低于 BCrypt
-
适配场景: legacy 系统兼容,新系统不建议使用
-
3. Token vs Session:状态存储的选择
-
Token(JWT):
-
优点:无状态、分布式友好、无需服务器存储
-
缺点:无法主动失效(需维护黑名单)、过期时间难平衡(过长不安全,过短影响体验)
-
-
Session:
-
优点:可主动失效(invalidate)、自带过期管理、开发简单
-
缺点:分布式部署需同步、内存占用随用户量增长而增加
-
-
Redis 替代 Session:
-
结合两者优势:分布式友好(Redis 共享)+ 可主动失效(删除 Key)+ 性能优
-
4. 拦截器 vs 过滤器:权限校验的执行时机
-
过滤器(Filter):
-
执行时机:请求进入 Servlet 容器后,Controller 前
-
适用场景:全局请求拦截(如所有路径的登录校验、字符编码过滤)
-
特点:依赖 Servlet API,不依赖 Spring,执行早,性能损耗小
-
-
拦截器(HandlerInterceptor):
-
执行时机:Spring MVC 的 DispatcherServlet 之后,Controller 方法前
-
适用场景:Spring 生态内的权限校验(如登录状态、接口权限)
-
特点:可访问 Spring 容器中的 Bean,支持更细粒度的控制(如方法级拦截)
-
四、选型建议
-
若为前后端分离 + 微服务 / 分布式架构:优先选「方案 1(JWT 双 Token)」,无状态适配分布式,无感刷新提升用户体验;若需多系统复用,叠加「方案 2(通用登录模板)」。
-
若为单体传统 Web 应用(如管理后台):优先选「方案 3(基于 Session)」或「方案 5(过滤器校验)」,实现简单,无需额外中间件。
-
若为单体但需支持分布式部署:优先选「方案 4(Redis 替代 Session)」,解决 Session 共享问题,兼顾性能与简单性。
-
若为多系统开发(如公司内部多个产品):优先选「方案 2(通用登录模板)」,统一登录流程,减少重复开发,降低维护成本。
-
若安全性要求高(如金融、电商):选「方案 1 + 方案 2」,双 Token+BCrypt 加密 + 通用模板标准化,兼顾安全与复用。
五、总结
所有登录方案的核心目标都是「安全验证身份 + 维护登录状态 + 保障访问权限」,差异仅在于技术栈适配和架构兼容:
-
简单场景选有状态(Session / 过滤器),复杂架构选无状态(JWT/Redis);
-
单系统选专用方案,多系统选通用模板;
-
传统 Web 重页面跳转,前后端分离重 Token 交互。
选择时无需追求 “最先进”,只需匹配自身项目的架构规模、开发成本和安全需求,即可实现高效可靠的登录功能。

2525

被折叠的 条评论
为什么被折叠?



