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 org.springframework.core.convert.converter.Converter;
21  import org.springframework.http.HttpStatus;
22  import org.springframework.lang.Nullable;
23  import org.springframework.security.authentication.AbstractAuthenticationToken;
24  import org.springframework.security.authentication.AuthenticationManager;
25  import org.springframework.security.authentication.AuthenticationProvider;
26  import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
27  import org.springframework.security.core.Authentication;
28  import org.springframework.security.core.AuthenticationException;
29  import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
30  import org.springframework.security.oauth2.core.OAuth2Error;
31  import org.springframework.security.oauth2.jwt.Jwt;
32  import org.springframework.security.oauth2.jwt.JwtDecoder;
33  import org.springframework.security.oauth2.jwt.JwtException;
34  import org.springframework.security.oauth2.server.resource.BearerTokenError;
35  import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
36  import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
37  
38  /**
39   * The password flow authentication manager.
40   *
41   * @author Christian Bremer
42   */
43  public class PasswordFlowAuthenticationManager
44      implements AuthenticationManager, AuthenticationProvider {
45  
46    private static final OAuth2Error DEFAULT_INVALID_TOKEN =
47        invalidToken("An error occurred while attempting to decode the Jwt: Invalid token");
48  
49    private final ClientCredentialsFlowProperties passwordFlowProperties;
50  
51    private final AccessTokenRetriever<String> accessTokenRetriever;
52  
53    private final JwtDecoder jwtDecoder;
54  
55    private final Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter;
56  
57    /**
58     * Instantiates a new password flow authentication manager.
59     *
60     * @param passwordFlowProperties the password flow properties
61     * @param jwtDecoder the jwt decoder
62     * @param jwtAuthenticationConverter the jwt authentication converter
63     * @param accessTokenRetriever the access token retriever
64     */
65    public PasswordFlowAuthenticationManager(
66        ClientCredentialsFlowProperties passwordFlowProperties,
67        JwtDecoder jwtDecoder,
68        @Nullable Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter,
69        AccessTokenRetriever<String> accessTokenRetriever) {
70  
71      this.passwordFlowProperties = passwordFlowProperties;
72      this.jwtDecoder = jwtDecoder;
73      this.jwtAuthenticationConverter = Objects.requireNonNullElseGet(
74          jwtAuthenticationConverter,
75          JwtAuthenticationConverter::new);
76      this.accessTokenRetriever = accessTokenRetriever;
77    }
78  
79    @Override
80    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
81      final PasswordFlowProperties properties = PasswordFlowProperties.builder()
82          .from(passwordFlowProperties)
83          .username(authentication.getName())
84          .password((String) authentication.getCredentials())
85          .build();
86      try {
87        return this.jwtAuthenticationConverter.convert(
88            jwtDecoder.decode(
89                accessTokenRetriever.retrieveAccessToken(properties)));
90  
91      } catch (JwtException failed) {
92        final OAuth2Error invalidToken = invalidToken(failed.getMessage());
93        throw new OAuth2AuthenticationException(invalidToken, invalidToken.getDescription(), failed);
94      }
95    }
96  
97    @Override
98    public boolean supports(Class<?> authentication) {
99      return UsernamePasswordAuthenticationToken.class
100         .isAssignableFrom(authentication);
101   }
102 
103   private static OAuth2Error invalidToken(String message) {
104     try {
105       return new BearerTokenError(
106           BearerTokenErrorCodes.INVALID_TOKEN,
107           HttpStatus.UNAUTHORIZED,
108           message,
109           "https://tools.ietf.org/html/rfc6750#section-3.1");
110     } catch (IllegalArgumentException malformed) {
111       // some third-party library error messages are not suitable for RFC 6750's error message
112       // charset
113       return DEFAULT_INVALID_TOKEN;
114     }
115   }
116 
117 }