1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.security.authentication;
18
19 import java.util.Objects;
20 import lombok.extern.slf4j.Slf4j;
21 import org.springframework.core.convert.converter.Converter;
22 import org.springframework.http.HttpStatus;
23 import org.springframework.lang.Nullable;
24 import org.springframework.security.authentication.AbstractAuthenticationToken;
25 import org.springframework.security.authentication.ReactiveAuthenticationManager;
26 import org.springframework.security.core.Authentication;
27 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
28 import org.springframework.security.oauth2.core.OAuth2Error;
29 import org.springframework.security.oauth2.jwt.Jwt;
30 import org.springframework.security.oauth2.jwt.JwtException;
31 import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
32 import org.springframework.security.oauth2.server.resource.BearerTokenError;
33 import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
34 import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
35 import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
36 import reactor.core.publisher.Mono;
37
38
39
40
41
42
43 @Slf4j
44 public class PasswordFlowReactiveAuthenticationManager implements ReactiveAuthenticationManager {
45
46 private final ClientCredentialsFlowProperties passwordFlowProperties;
47
48 private final ReactiveJwtDecoder jwtDecoder;
49
50 private Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtConverter;
51
52 private final AccessTokenRetriever<Mono<String>> retriever;
53
54
55
56
57
58
59
60
61
62 public PasswordFlowReactiveAuthenticationManager(
63 ClientCredentialsFlowProperties passwordFlowProperties,
64 ReactiveJwtDecoder jwtDecoder,
65 @Nullable Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtConverter,
66 @Nullable AccessTokenRetriever<Mono<String>> retriever) {
67 this.passwordFlowProperties = passwordFlowProperties;
68 this.jwtDecoder = jwtDecoder;
69 this.jwtConverter = Objects.requireNonNullElseGet(
70 jwtConverter,
71 () -> new ReactiveJwtAuthenticationConverterAdapter(new JwtAuthenticationConverter()));
72 this.retriever = Objects.requireNonNullElseGet(
73 retriever,
74 WebClientAccessTokenRetriever::new);
75 }
76
77
78
79
80
81
82 public void setJwtAuthenticationConverter(
83 Converter<Jwt, ? extends AbstractAuthenticationToken> jwtConverter) {
84 if (jwtConverter != null) {
85
86 this.jwtConverter = new ReactiveJwtAuthenticationConverterAdapter(
87 (Converter<Jwt, AbstractAuthenticationToken>) jwtConverter);
88 }
89 }
90
91 @Override
92 public Mono<Authentication> authenticate(final Authentication authentication) {
93
94 final PasswordFlowProperties properties = PasswordFlowProperties.builder()
95 .from(passwordFlowProperties)
96 .username(authentication.getName())
97 .password((String) authentication.getCredentials())
98 .build();
99 return retriever.retrieveAccessToken(properties)
100 .flatMap(jwtDecoder::decode)
101 .flatMap(jwt -> Objects.requireNonNull(jwtConverter.convert(jwt)))
102 .cast(Authentication.class)
103 .onErrorMap(JwtException.class, this::onError);
104 }
105
106 private OAuth2AuthenticationException onError(JwtException e) {
107 log.error("msg=[Basic authentication with password flow failed.]", e);
108 OAuth2Error invalidRequest = invalidToken(e.getMessage());
109 return new OAuth2AuthenticationException(invalidRequest, e.getMessage());
110 }
111
112 private static OAuth2Error invalidToken(String message) {
113 return new BearerTokenError(
114 BearerTokenErrorCodes.INVALID_TOKEN,
115 HttpStatus.UNAUTHORIZED,
116 message,
117 "https://tools.ietf.org/html/rfc6750#section-3.1");
118 }
119
120 }