Spring Security WebFlux基于请求体JSON的身份认证实现问询
实现Spring Security WebFlux的JSON格式登录功能
我来帮你把这个基于Spring Security WebFlux的JSON登录功能完整实现出来,咱们一步步拆解:
1. 补全完整的WebFluxSecurityConfig配置类
首先,咱们需要完善配置类,包括密码编码器、自定义登录流程的配置,以及处理登录成功/失败的逻辑:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint; import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler; import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler; import org.springframework.web.server.ServerWebExchange; import com.fasterxml.jackson.databind.ObjectMapper; import reactor.core.publisher.Mono; @Configuration @EnableWebFluxSecurity @EnableReactiveMethodSecurity(proxyTargetClass = true) public class WebFluxSecurityConfig { private final ReactiveUserDetailsService userDetailsService; private final ObjectMapper mapper; // 构造注入代替@Autowired(更符合Spring最佳实践) public WebFluxSecurityConfig(ReactiveUserDetailsService userDetailsService, ObjectMapper mapper) { this.userDetailsService = userDetailsService; this.mapper = mapper; } // 密码编码器:必须配置,Spring Security要求对密码进行加密存储 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 认证管理器:关联用户信息服务和密码编码器 @Bean public ReactiveAuthenticationManager authenticationManager() { UserDetailsRepositoryReactiveAuthenticationManager manager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService); manager.setPasswordEncoder(passwordEncoder()); return manager; } // 核心安全过滤器链配置 @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { return http // 禁用默认的表单登录,我们自定义JSON登录 .formLogin(form -> form.disable()) // 配置HTTP Basic认证(可选,不需要可以去掉) .httpBasic(basic -> basic.disable()) // 绑定认证管理器和自定义的JSON转换器 .authenticationManager(authenticationManager()) .authenticationConverter(jsonAuthenticationConverter()) // 登录成功处理器:返回自定义响应 .authenticationSuccessHandler(successHandler()) // 登录失败处理器:返回错误信息 .authenticationFailureHandler(failureHandler()) // 未认证时的入口(比如访问需权限接口时) .exceptionHandling(exceptions -> exceptions .authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED))) // 配置权限规则:允许/signin匿名访问,其他接口需要认证 .authorizeExchange(exchanges -> exchanges .pathMatchers("/signin").permitAll() .anyExchange().authenticated()) .build(); } // 自定义JSON认证转换器:解析请求体中的JSON参数 private ServerAuthenticationConverter jsonAuthenticationConverter() { return exchange -> { // 只处理POST请求的/signin接口 if (!exchange.getRequest().getPath().value().equals("/signin") || !exchange.getRequest().getMethodValue().equals("POST")) { return Mono.empty(); } // 读取请求体并转换为LoginRequest对象 return exchange.getRequest().getBody() .next() .flatMap(buffer -> { try { LoginRequest loginRequest = mapper.readValue(buffer.asInputStream(), LoginRequest.class); // 转换为Spring Security的认证Token return Mono.just(loginRequest.toAuthenticationToken()); } catch (Exception e) { return Mono.error(new IllegalArgumentException("Invalid JSON format")); } }); }; } // 登录成功处理器:返回200和自定义响应体 private ServerAuthenticationSuccessHandler successHandler() { return (exchange, authentication) -> { exchange.getResponse().setStatusCode(HttpStatus.OK); // 这里可以扩展返回用户信息或JWT令牌 return exchange.getResponse().writeWith(Mono.just( exchange.getResponse().bufferFactory().wrap("Login successful".getBytes()) )); }; } // 登录失败处理器:返回401和错误信息 private ServerAuthenticationFailureHandler failureHandler() { return (exchange, exception) -> { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().writeWith(Mono.just( exchange.getResponse().bufferFactory().wrap(("Login failed: " + exception.getMessage()).getBytes()) )); }; } // 内部静态类:封装登录请求的JSON参数 public static class LoginRequest { private String username; private String password; // 必须要有无参构造和getter/setter,否则Jackson无法解析 public LoginRequest() {} public LoginRequest(String username, String password) { this.username = username; this.password = password; } // 转换为UsernamePasswordAuthenticationToken public UsernamePasswordAuthenticationToken toAuthenticationToken() { return new UsernamePasswordAuthenticationToken(username, password); } // Getter和Setter public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } }
2. 实现ReactiveUserDetailsService示例
你需要提供自己的用户信息查询逻辑,这里给一个内存存储的示例,实际项目中可以改成从数据库(MongoDB、PostgreSQL等)查询:
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; @Service public class CustomReactiveUserDetailsService implements ReactiveUserDetailsService { private final PasswordEncoder passwordEncoder; public CustomReactiveUserDetailsService(PasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder; } @Override public Mono<UserDetails> findByUsername(String username) { // 模拟从数据库查询用户:创建admin用户,密码是加密后的"adminPassword" if ("admin".equals(username)) { UserDetails user = User.withUsername("admin") .password(passwordEncoder.encode("adminPassword")) .roles("ADMIN") .build(); return Mono.just(user); } // 用户不存在时返回空Mono,Spring Security会自动处理为认证失败 return Mono.empty(); } }
3. 测试登录接口
你可以用curl命令测试这个登录接口:
curl -X POST http://localhost:8080/signin \ -H "Content-Type: application/json" \ -d '{"username": "admin", "password": "adminPassword"}'
登录成功会返回Login successful和200状态码;密码错误或用户不存在时,会返回401和对应的错误提示。
额外说明
- 如果需要生成JWT令牌,可以在
successHandler()中添加JWT生成逻辑,把令牌返回给前端,后续接口用JWT做认证 - 确保项目已引入Spring Security WebFlux和Jackson依赖(Maven的话,pom.xml需要包含
spring-boot-starter-security和spring-boot-starter-webflux)
内容的提问来源于stack exchange,提问作者Вадим Парафенюк
相关产品推荐
相关产品推荐

