Spring Boot Android应用REST API用户认证方案选型及过滤器查库合理性探讨
三种用户数据库认证方案的对比与最佳实践
嗨,我来帮你梳理这三种用户认证方案的优劣,以及你关心的Filter中认证是否为最佳实践的问题——先给你个明确结论:Spring Security是绝大多数场景下的理想方案,另外两种方案要么耦合性太高,要么需要自己处理大量繁琐的安全细节。
1. 在Controller类中查询数据库认证
这种方案是最不推荐的,问题非常明显:
- 业务逻辑和认证逻辑完全耦合,每个需要认证的接口都要重复写查询用户、校验权限的代码,冗余度拉满。
- 无法统一处理认证失败的场景(比如每个Controller都要手动返回401),完全违背了单一职责原则。
举个反例,你可能会在每个接口开头写:
@GetMapping("/user/profile") public ResponseEntity<UserProfile> getProfile(@RequestHeader("Authorization") String token) { User user = userRepository.findByToken(token); if (user == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } // 业务逻辑... }
这种写法后期维护起来简直是噩梦。
2. 在Filter类中查询数据库认证
这种方案比Controller里写要好,能把认证逻辑抽离出来统一拦截请求,但绝对不是最佳实践,主要问题有这些:
- 你提供的代码里直接在Filter里创建
Connection,这会导致数据库连接泄漏、线程安全问题,而且完全没利用Spring的依赖注入(比如不能直接用UserRepository)。 - 需要自己手动处理认证结果的上下文传递(比如把用户信息放到Request属性里,后续Controller还要手动取),没有Spring Security自带的
SecurityContext方便。 - 缺少Spring Security内置的权限校验、会话管理、CSRF防护等安全特性,你得自己实现这些,很容易踩安全坑。
如果实在要使用Filter,也得规范写法,比如利用Spring的Bean注入来管理数据库操作,改进后的示例代码如下:
@Component public class TokenValidationFilter implements Filter { private final UserRepository userRepository; // 用构造器注入Spring管理的Repository,避免手动管理连接 public TokenValidationFilter(UserRepository userRepository) { this.userRepository = userRepository; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; final String accessToken = req.getHeader("Authorization"); if (accessToken == null || !accessToken.startsWith("Bearer ")) { res.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid or missing token"); return; } String token = accessToken.substring(7); User user = userRepository.findByToken(token); // 用Spring的Repository查库 if (user == null) { res.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token"); return; } // 将用户信息放入请求上下文,方便后续Controller使用 req.setAttribute("currentUser", user); chain.doFilter(req, res); } }
3. 使用Spring Security(强烈推荐)
这是Spring生态下做认证的标准方案,它已经帮你封装了所有安全细节,你只需要专注于用户信息的获取逻辑,优势非常明显:
- 完全遵循Spring的设计原则,依赖注入、AOP等特性都能用上,代码优雅易维护。
- 自带各种安全防护(CSRF、XSS、会话固定攻击防护等),不用自己造轮子踩坑。
- 扩展性极强,后续要加OAuth2、LDAP认证只需要简单配置即可。
核心代码示例
1. 自定义UserDetailsService(从数据库查询用户)
@Service public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String token) throws UsernameNotFoundException { // 这里根据token查询用户,你也可以根据用户名,取决于你的认证逻辑 User user = userRepository.findByToken(token); if (user == null) { throw new UsernameNotFoundException("User not found with token: " + token); } // 封装成Spring Security的UserDetails对象,包含权限信息 return org.springframework.security.core.userdetails.User.builder() .username(user.getUsername()) .password(user.getPassword()) // 如果是JWT认证,这里可以忽略,或者存加密后的密码 .authorities(user.getRoles().stream().map(SimpleGrantedAuthority::new).toList()) .build(); } }
2. Spring Security配置类
@Configuration @EnableWebSecurity public class SecurityConfig { private final CustomUserDetailsService userDetailsService; public SecurityConfig(CustomUserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(authenticationManagerBean()); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) // REST API通常禁用CSRF .authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() // 公开接口无需认证 .anyRequest().authenticated() // 其他接口必须认证 ) .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }
3. JWT认证过滤器(处理token校验)
public class JwtAuthenticationFilter extends OncePerRequestFilter { private final AuthenticationManager authenticationManager; public JwtAuthenticationFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = extractTokenFromRequest(request); if (token != null && validateToken(token)) { // 创建认证请求,交给AuthenticationManager处理 Authentication authentication = new UsernamePasswordAuthenticationToken(token, null, Collections.emptyList()); Authentication authenticated = authenticationManager.authenticate(authentication); // 将认证信息放入SecurityContext,后续可通过SecurityContextHolder获取 SecurityContextHolder.getContext().setAuthentication(authenticated); } filterChain.doFilter(request, response); } private String extractTokenFromRequest(HttpServletRequest request) { String header = request.getHeader("Authorization"); if (header != null && header.startsWith("Bearer ")) { return header.substring(7); } return null; } private boolean validateToken(String token) { // 这里实现JWT的校验逻辑,比如签名、过期时间等 try { Jwts.parser().setSigningKey("your-secret-key").parseClaimsJws(token); return true; } catch (Exception e) { return false; } } }
总结
如果你是开发生产环境的API,优先选择Spring Security,它能帮你避免很多安全漏洞,代码也更易维护。Filter方案只适合一些简单的小项目,或者需要高度定制认证逻辑的场景,但也要规范写法,利用Spring的依赖注入,不要手动管理数据库连接。
内容的提问来源于stack exchange,提问作者Ashish Rathee
相关产品推荐
相关产品推荐

