Created
April 22, 2018 13:10
-
-
Save itzg/dc07a711295b4c5c1d1f93322df9634b to your computer and use it in GitHub Desktop.
Custom Spring WebFlux AuthenticationWebFilter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import lombok.Data; | |
@Data | |
public class UsernamePasswordContent { | |
String username; | |
String password; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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