Skip to content

Instantly share code, notes, and snippets.

@itzg
Created April 22, 2018 13:10
Show Gist options
  • Save itzg/dc07a711295b4c5c1d1f93322df9634b to your computer and use it in GitHub Desktop.
Save itzg/dc07a711295b4c5c1d1f93322df9634b to your computer and use it in GitHub Desktop.
Custom Spring WebFlux AuthenticationWebFilter
import org.springframework.http.HttpMethod;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
public class LoginWebFilter extends AuthenticationWebFilter {
private final ServerCodecConfigurer serverCodecConfigurer;
/**
* Creates an instance
*
* @param authenticationManager the authentication manager to use
* @param serverCodecConfigurer
*/
public LoginWebFilter(ReactiveAuthenticationManager authenticationManager, ServerCodecConfigurer serverCodecConfigurer) {
super(authenticationManager);
this.serverCodecConfigurer = serverCodecConfigurer;
setRequiresAuthenticationMatcher(
ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/login")
);
setAuthenticationConverter(new ServerBodyAuthenticationConverter(this.serverCodecConfigurer));
setAuthenticationSuccessHandler(new StatusCodeAuthSuccessHandler());
}
}
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.function.Function;
public class ServerBodyAuthenticationConverter implements Function<ServerWebExchange, Mono<Authentication>> {
private final ResolvableType usernamePasswordType = ResolvableType.forClass(UsernamePasswordContent.class);
private ServerCodecConfigurer serverCodecConfigurer;
public ServerBodyAuthenticationConverter(ServerCodecConfigurer serverCodecConfigurer) {
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Override
public Mono<Authentication> apply(ServerWebExchange exchange) {
final ServerHttpRequest request = exchange.getRequest();
MediaType contentType = request.getHeaders().getContentType();
if (contentType.isCompatibleWith(MediaType.APPLICATION_JSON)) {
return serverCodecConfigurer.getReaders().stream()
.filter(reader -> reader.canRead(this.usernamePasswordType, MediaType.APPLICATION_JSON))
.findFirst()
.orElseThrow(() -> new IllegalStateException("No JSON reader for UsernamePasswordContent"))
.readMono(this.usernamePasswordType, request, Collections.emptyMap())
.cast(UsernamePasswordContent.class)
.map(o -> new UsernamePasswordAuthenticationToken(o.getUsername(), o.getPassword()));
}
else {
return Mono.empty();
}
}
}
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import reactor.core.publisher.Mono;
public class StatusCodeAuthSuccessHandler implements ServerAuthenticationSuccessHandler {
private HttpStatus statusCode = HttpStatus.OK;
public StatusCodeAuthSuccessHandler statusCode(HttpStatus statusCode) {
this.statusCode = statusCode;
return this;
}
@Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
webFilterExchange.getExchange().getResponse().setStatusCode(statusCode);
return Mono.empty();
}
}
import lombok.Data;
@Data
public class UsernamePasswordContent {
String username;
String password;
}
import lombok.extern.slf4j.Slf4j;
import me.itzg.taskdiary.web.LoginWebFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.util.StringUtils;
import java.util.List;
@Configuration
@Slf4j
public class WebSecurityConfig {
private SecurityProperties properties;
@Autowired
public WebSecurityConfig(SecurityProperties properties) {
this.properties = properties;
}
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
ServerCodecConfigurer serverCodecConfigurer) {
return http.addFilterAt(new LoginWebFilter(authenticationManager(), serverCodecConfigurer),
SecurityWebFiltersOrder.AUTHENTICATION)
.csrf().disable()
.build();
}
@Bean
public UserDetailsRepositoryReactiveAuthenticationManager authenticationManager() {
UserDetailsRepositoryReactiveAuthenticationManager manager =
new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService());
return manager;
}
@Bean
public MapReactiveUserDetailsService userDetailsService() {
SecurityProperties.User user = properties.getUser();
List<String> roles = user.getRoles();
String password = user.getPassword();
if (user.isPasswordGenerated()) {
log.info(String.format("%n%nUsing default security password: %s%n", user.getPassword()));
}
final UserDetails userDetails = User.withUsername(user.getName())
.password(passwordEncoder().encode(password))
.roles(StringUtils.toStringArray(roles))
.build();
return new MapReactiveUserDetailsService(userDetails);
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment