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 lombok.extern.slf4j.Slf4j;
20  import org.bremersee.context.MessageSourceProperties;
21  import org.springframework.beans.factory.ObjectProvider;
22  import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
23  import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
24  import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
25  import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
26  import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
27  import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
28  import org.springframework.boot.context.event.ApplicationReadyEvent;
29  import org.springframework.boot.context.properties.EnableConfigurationProperties;
30  import org.springframework.context.annotation.Bean;
31  import org.springframework.context.annotation.Conditional;
32  import org.springframework.context.annotation.Configuration;
33  import org.springframework.context.event.EventListener;
34  import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
35  import org.springframework.util.Assert;
36  import org.springframework.util.ClassUtils;
37  import org.springframework.web.reactive.function.client.WebClient;
38  
39  /**
40   * The reactive authentication support auto configuration.
41   *
42   * @author Christian Bremer
43   */
44  @ConditionalOnWebApplication(type = Type.REACTIVE)
45  @ConditionalOnClass({
46      JsonPathReactiveJwtConverter.class,
47      WebClientAccessTokenRetriever.class
48  })
49  @Configuration
50  @EnableConfigurationProperties({AuthProperties.class, MessageSourceProperties.class})
51  @Slf4j
52  public class ReactiveJwtSupportAutoConfiguration {
53  
54    private final AuthProperties properties;
55  
56    /**
57     * Instantiates a reactive authentication support auto configuration.
58     *
59     * @param properties the properties
60     */
61    public ReactiveJwtSupportAutoConfiguration(
62        AuthProperties properties) {
63      this.properties = properties;
64    }
65  
66    /**
67     * Init.
68     */
69    @EventListener(ApplicationReadyEvent.class)
70    public void init() {
71      log.info("\n"
72              + "*********************************************************************************\n"
73              + "* {}\n"
74              + "*********************************************************************************\n"
75              + "* rolesJsonPath = {}\n"
76              + "* rolesValueList = {}\n"
77              + "* rolesValueSeparator = {}\n"
78              + "* rolePrefix = {}\n"
79              + "* nameJsonPath = {}\n"
80              + "*********************************************************************************",
81          ClassUtils.getUserClass(getClass()).getSimpleName(),
82          properties.getRolesJsonPath(),
83          properties.isRolesValueList(),
84          properties.getRolesValueSeparator(),
85          properties.getRolePrefix(),
86          properties.getNameJsonPath());
87    }
88  
89    /**
90     * Creates authentication details bean.
91     *
92     * @param messageSourceProperties the message source properties
93     * @return the authentication details
94     */
95    @ConditionalOnMissingBean
96    @Bean
97    public AuthenticationDetails authenticationDetails(
98        MessageSourceProperties messageSourceProperties) {
99      return new JsonPathJwtAuthenticationDetails(
100         messageSourceProperties.defaultLocale(),
101         messageSourceProperties.defaultTimeZone(),
102         properties.getPreferredLanguageJsonPath(),
103         properties.getPreferredTimeZoneJsonPath());
104   }
105 
106   /**
107    * Creates a json path reactive jwt converter bean.
108    *
109    * @return the json path reactive jwt converter
110    */
111   @ConditionalOnProperty(
112       prefix = "spring.security.oauth2.resourceserver.jwt",
113       name = "jwk-set-uri")
114   @ConditionalOnMissingBean
115   @Bean
116   @SuppressWarnings("DuplicatedCode")
117   public JsonPathReactiveJwtConverter jsonPathReactiveJwtConverter() {
118 
119     log.info("Creating application {} ...", JsonPathReactiveJwtConverter.class.getSimpleName());
120     JsonPathJwtConverter converter = new JsonPathJwtConverter();
121     converter.setNameJsonPath(properties.getNameJsonPath());
122     converter.setRolePrefix(properties.getRolePrefix());
123     converter.setRolesJsonPath(properties.getRolesJsonPath());
124     converter.setRolesValueList(properties.isRolesValueList());
125     converter.setRolesValueSeparator(properties.getRolesValueSeparator());
126     return new JsonPathReactiveJwtConverter(converter);
127   }
128 
129   /**
130    * Creates access token retriever.
131    *
132    * @param accessTokenCache the access token cache
133    * @return the web client access token retriever
134    */
135   @Conditional(JwtSupportCondition.class)
136   @ConditionalOnMissingBean
137   @Bean
138   public WebClientAccessTokenRetriever webClientAccessTokenRetriever(
139       ObjectProvider<ReactiveAccessTokenCache> accessTokenCache) {
140 
141     ReactiveAccessTokenCache cache = accessTokenCache.getIfAvailable();
142     log.info("Creating common {} with cache {} ...",
143         WebClientAccessTokenRetriever.class.getSimpleName(), cache);
144     return new WebClientAccessTokenRetriever(
145         WebClient.builder().build(),
146         cache);
147   }
148 
149   /**
150    * Creates password flow reactive authentication manager.
151    *
152    * @param jwtDecoder the jwt decoder
153    * @param jwtConverter the jwt converter
154    * @param tokenRetriever the token retriever
155    * @return the password flow reactive authentication manager
156    */
157   @ConditionalOnProperty(
158       prefix = "bremersee.auth.password-flow",
159       name = {
160           "token-endpoint",
161           "client-id",
162           "client-secret"
163       })
164   @ConditionalOnBean(JsonPathReactiveJwtConverter.class)
165   @ConditionalOnMissingBean
166   @Bean
167   public PasswordFlowReactiveAuthenticationManager passwordFlowReactiveAuthenticationManager(
168       ObjectProvider<ReactiveJwtDecoder> jwtDecoder,
169       JsonPathReactiveJwtConverter jwtConverter,
170       WebClientAccessTokenRetriever tokenRetriever) {
171 
172     Assert.notNull(jwtDecoder.getIfAvailable(), "Jwt decoder must be present.");
173     log.info("Creating {} ...", PasswordFlowReactiveAuthenticationManager.class.getSimpleName());
174     return new PasswordFlowReactiveAuthenticationManager(
175         properties.getPasswordFlow(),
176         jwtDecoder.getIfAvailable(),
177         jwtConverter,
178         tokenRetriever);
179   }
180 
181 }