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

Spring网关+WebFlux+Security登录未认证及弹窗重复问题排查

问题:Basic认证下登录弹窗重复出现,无法完成角色获取

我开发了一个登录表单,提交到后端后调用GET接口获取已认证用户角色,但始终无法完成认证。后端采用Spring Gateway、Spring WebFlux、Spring Security、Lombok技术栈,登录弹窗会出现两次。

第一次登录截图:第一次登录截图
第二次登录截图:第二次登录截图


前端代码

let body = {
      "username": username,
      "password": password
    }

    let headers = new HttpHeaders();
    let data = username + ':' + password
    let encodedCredentials = Buffer.from(data).toString('base64');
    // let encodedCredentials = btoa(username + ':' + password); // encode as base64
    headers = headers.append('Authorization', 'Basic ' + encodedCredentials);
    const observer = {
      next: (x: any) => { console.log(x); this.redirecting() },
      error: (err: any) => { console.log(err.error) },
      complete: () => console.log('Observer got a complete notification'),
    }
    this.http.post(loginUrl, body, { headers: headers, responseType: 'text', withCredentials: true }).subscribe(observer);

redirecting() {
    console.log("called");
    const roleUrl = this.url + "role";
    this.userService.logIn();
    const observer = {
      next: (x: any) => { console.log(x) },
      error: (err: any) => { console.log(err.error) }
    }
    this.http.get(roleUrl, { responseType: 'text', withCredentials: true }).subscribe(observer);

    // this.router.navigate(['home']);
  }

后端Security配置

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http
                .authorizeExchange()
                .pathMatchers("/api/login/").permitAll()
                // .pathMatchers("/api/role/").permitAll()
                // .pathMatchers("/api/student/**").permitAll()
                .anyExchange().authenticated()
                .and()
                .httpBasic(Customizer.withDefaults())
                .csrf().disable()
                // .cors() // Customizer.withDefaults()
                .build();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200")); 
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Bean
    public MapReactiveUserDetailsService userDetailsService() {
        // System.out.println("{bcrypt}" + encoder().encode("admin"));
        UserDetails u1 = User
                .withUsername("admin")
                .password("{noop}admin") // encoder().encode(
                .authorities("ADMIN")
                .build();

        UserDetails u2 = User
                .withUsername("prof")
                .password("{noop}prof")
                .authorities("PROF")
                .build();

        UserDetails u3 = User
                .withUsername("abc")
                .password("{noop}abc")
                .authorities("STUDENT")
                .build();
        return new MapReactiveUserDetailsService(u1, u2, u3);
    }

}

Application.yml配置

server:
    port: 8080

# logging:
#   level:
#     org:
#       springframework:
#         security: DEBUG

spring:
    # security:
    #     user:
    #       name: admin
    #       password: admin
    cloud:
        gateway:
          routes:
          - id: user_service
            uri: http://localhost:8081/
            
            predicates:
            - Path=/**
            # - Path=/api/student/**
            # - Path=/api/teacher/**
            # - Path=/api/admin/**

LoginController代码

@RestController
public class LoginController {

    @PostMapping("/api/login")
    public Mono<String> myEndpoint(ServerHttpResponse response, Authentication auth) {
        // System.out.println("Message from Login");
        if(auth.isAuthenticated()) {
            return Mono.just(auth.getName());
        }
        return Mono.just("not authenticated");
    }

    @GetMapping("/api/role")
    public Mono<String> getRole(ServerHttpResponse response, Authentication auth) {
        // System.out.println("Message from Login");
        // response.setStatusCode(HttpStatus.OK);
        Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
        List<String> roles = new ArrayList<>();
    
        for (GrantedAuthority authority : authorities) {
            roles.add(authority.getAuthority());
        }

        if (roles.isEmpty()) {
            return Mono.just("None");
        }
        
        return Mono.just(roles.get(0));
    }
}

已尝试的无效操作

  • 移除CORS配置代码
  • 注释登录接口的permitAll配置

内容的提问来源于stack exchange,提问作者daybreak

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 20:45:15