You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,提问作者Вадим Парафенюк

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:20:01