View Javadoc
1   /*
2    * Copyright 2019-2020 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * The password flow reactive authentication manager.
40   *
41   * @author Christian Bremer
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     * Instantiates a new password flow reactive authentication manager.
56     *
57     * @param passwordFlowProperties the password flow properties
58     * @param jwtDecoder the jwt decoder
59     * @param jwtConverter the jwt converter
60     * @param retriever the retriever
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     * Sets jwt authentication converter.
79     *
80     * @param jwtConverter the jwt converter
81     */
82    public void setJwtAuthenticationConverter(
83        Converter<Jwt, ? extends AbstractAuthenticationToken> jwtConverter) {
84      if (jwtConverter != null) {
85        //noinspection unchecked
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 }