View Javadoc
1   /*
2    * Copyright 2019 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.io.IOException;
20  import java.util.Optional;
21  import net.minidev.json.JSONObject;
22  import net.minidev.json.JSONValue;
23  import org.bremersee.exception.AccessTokenRetrieverAuthenticationException;
24  import org.springframework.http.HttpEntity;
25  import org.springframework.http.HttpHeaders;
26  import org.springframework.http.HttpMethod;
27  import org.springframework.http.HttpStatus;
28  import org.springframework.http.MediaType;
29  import org.springframework.http.client.ClientHttpResponse;
30  import org.springframework.lang.NonNull;
31  import org.springframework.util.StringUtils;
32  import org.springframework.web.client.DefaultResponseErrorHandler;
33  import org.springframework.web.client.RestTemplate;
34  
35  /**
36   * The rest template access token retriever.
37   *
38   * @author Christian Bremer
39   */
40  public class RestTemplateAccessTokenRetriever implements AccessTokenRetriever<String> {
41  
42    private final RestTemplate restTemplate;
43  
44    private final AccessTokenCache accessTokenCache;
45  
46    /**
47     * Instantiates a new rest template access token retriever.
48     *
49     * @param restTemplate the rest template
50     */
51    public RestTemplateAccessTokenRetriever(RestTemplate restTemplate) {
52      this(restTemplate, null);
53    }
54  
55    /**
56     * Instantiates a new rest template access token retriever.
57     *
58     * @param restTemplate the rest template
59     * @param accessTokenCache the access token cache
60     */
61    public RestTemplateAccessTokenRetriever(
62        RestTemplate restTemplate,
63        AccessTokenCache accessTokenCache) {
64  
65      this.restTemplate = restTemplate;
66      this.restTemplate.setErrorHandler(new ErrorHandler());
67      this.accessTokenCache = accessTokenCache;
68    }
69  
70    @Override
71    public String retrieveAccessToken(AccessTokenRetrieverProperties input) {
72      final String cacheKey = input.createCacheKeyHashed();
73      return Optional.ofNullable(accessTokenCache)
74          .flatMap(cache -> cache.findAccessToken(cacheKey))
75          .orElseGet(() -> {
76            final HttpHeaders headers = new HttpHeaders();
77            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
78            input.getBasicAuthProperties()
79                .ifPresent(basicAuthProperties -> headers.setBasicAuth(
80                    basicAuthProperties.getUsername(),
81                    basicAuthProperties.getPassword()));
82            final HttpEntity<?> request = new HttpEntity<>(input.createBody(), headers);
83            final String response = restTemplate.exchange(
84                    input.getTokenEndpoint(),
85                    HttpMethod.POST,
86                    request,
87                    String.class)
88                .getBody();
89            final JSONObject json = (JSONObject) JSONValue.parse(response);
90            final String accessToken = json.getAsString("access_token");
91            if (StringUtils.hasText(accessToken)) {
92              if (accessTokenCache != null) {
93                accessTokenCache.putAccessToken(cacheKey, accessToken);
94              }
95              return accessToken;
96            }
97            throw new AccessTokenRetrieverAuthenticationException(HttpStatus.UNAUTHORIZED,
98                "There is no access token in the response: " + accessToken);
99          });
100   }
101 
102   private static class ErrorHandler extends DefaultResponseErrorHandler {
103 
104     @Override
105     protected void handleError(final ClientHttpResponse response, @NonNull final HttpStatus statusCode)
106         throws IOException {
107       final String statusText = response.getStatusText();
108       throw new AccessTokenRetrieverAuthenticationException(statusCode, statusText);
109     }
110   }
111 
112 }