Summary
I've already configured SecurityWebFilterChain to enable formLogin in a Spring Cloud Gateway application, but when I open /login page, it response 404 not found. The full source code can be found at Spring Cloud in Practice.
Configuration
package net.jaggerwang.scip.gateway.api.config;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
@EnableWebFluxSecurity
public class SecurityConfig {
private ObjectMapper objectMapper;
private ReactiveUserDetailsService userDetailsService;
public SecurityConfig(ObjectMapper objectMapper, ReactiveUserDetailsService userDetailsService) {
this.objectMapper = objectMapper;
this.userDetailsService = userDetailsService;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public ReactiveAuthenticationManager authManager(
) {
var authManager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
authManager.setPasswordEncoder(passwordEncoder());
return authManager;
}
private Mono<Void> responseJson(ServerWebExchange exchange, HttpStatus status, RootDto data) {
var response = exchange.getResponse();
response.setStatusCode(status);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
var body = new byte[0];
try {
body = objectMapper.writeValueAsBytes(data);
} catch (IOException e) {
}
return response.writeWith(Flux.just(response.bufferFactory().wrap(body)));
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.csrf(csrf -> csrf.disable())
.authenticationManager(authManager())
.exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint((exchange, exception) -> {
if (exchange.getRequest().getHeaders().getAccept()
.contains(MediaType.APPLICATION_JSON)) {
return responseJson(exchange, HttpStatus.UNAUTHORIZED,
new RootDto("unauthenticated", "未认证"));
} else {
var response = exchange.getResponse();
response.setStatusCode(HttpStatus.FOUND);
response.getHeaders().setLocation(
UriComponentsBuilder.fromPath("/login").build().toUri());
return response.writeWith(Flux.just(
response.bufferFactory().wrap("".getBytes())));
}
})
.accessDeniedHandler((exchange, accessDeniedException) -> {
if (exchange.getRequest().getHeaders().getAccept()
.contains(MediaType.APPLICATION_JSON)) {
return responseJson(exchange, HttpStatus.FORBIDDEN,
new RootDto("unauthorized", "未授权"));
} else {
var response = exchange.getResponse();
response.setStatusCode(HttpStatus.FORBIDDEN);
return response.writeWith(Flux.just(
response.bufferFactory().wrap("未授权".getBytes())));
}
})
)
.authorizeExchange(authorizeExchange -> authorizeExchange
.pathMatchers("/favicon.ico", "/*/actuator/**", "/login", "/logout",
"/auth/login", "/auth/logout", "/auth/logged",
"/user/register").permitAll()
.anyExchange().authenticated())
.formLogin(formLogin -> {})
.logout(logout -> {})
.build();
}
}
Version
Spring Boot: 2.2.2.RELEASE Spring Cloud: Hoxton.SR1
<?xml version="1.0" ?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.jaggerwang</groupId>
<artifactId>spring-cloud-in-practice</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<groupId>net.jaggerwang</groupId>
<artifactId>spring-cloud-in-practice-gateway</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>spring-cloud-in-practice-gateway</name>
<description>Spring cloud in practice gateway</description>
<dependencies>
<dependency>
<groupId>net.jaggerwang</groupId>
<artifactId>spring-cloud-in-practice-common</artifactId>
<version>${scip-common.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-spring-boot-starter-webflux</artifactId>
<version>${graphql-starter.version}</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-extended-scalars</artifactId>
<version>${graphql-extended-scalars.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Comment From: rwinch
Thanks for getting in touch, but it feels like this is a question that would be better suited to Stack Overflow. As mentioned in the guidelines for contributing, we prefer to use GitHub issues only for bugs and enhancements. Feel free to update this issue with a link to the re-posted question (so that other people can find it) or add some more details if you feel this is a genuine bug.
Comment From: jaggerwang
It seems to related with ServerHttpSecurity.exceptionHandling config. If I comment this config, the default login page can be accessed.
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.csrf(csrf -> csrf.disable())
.authenticationManager(authManager())
// .exceptionHandling(exceptionHandling -> exceptionHandling
// .authenticationEntryPoint((exchange, exception) ->
// responseJson(exchange, HttpStatus.UNAUTHORIZED,
// new RootDto("unauthenticated", "未认证")))
// .accessDeniedHandler((exchange, accessDeniedException) ->
// responseJson(exchange, HttpStatus.FORBIDDEN,
// new RootDto("unauthorized", "未授权")))
// )
.authorizeExchange(authorizeExchange -> authorizeExchange
.pathMatchers("/favicon.ico", "/*/actuator/**", "/", "/login", "/logout",
"/auth/login", "/auth/logout", "/auth/logged",
"/user/register").permitAll()
.anyExchange().authenticated())
.formLogin(formLogin -> {})
.logout(logout -> {})
.build();
}
Is it possible to customize the exceptionHandling to return json response for rest api, and keep the default login page?
Comment From: jaggerwang
There is a better way to use defaultAuthenticationEntryPointFor.
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.exceptionHandling(exceptionHandling -> exceptionHandling
.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new AntPathRequestMatcher("/**"))
)
.authorizeRequests(authorizeRequests -> authorizeRequests
.antMatchers("/favicon.ico", "/csrf", "/vendor/**", "/webjars/**",
"/actuator/**", "/v2/api-docs", "/swagger-ui.html",
"/swagger-resources/**", "/", "/graphql", "/login", "/logout",
"/auth/login", "/auth/logout", "/auth/logged", "/user/register",
"/files/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(formLogin -> {})
.logout(logout -> {});
}