View Javadoc
1   /*
2    * Copyright 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.Optional;
20  import lombok.extern.slf4j.Slf4j;
21  import org.bremersee.core.OrderedProxy;
22  import org.bremersee.security.FrameOptionsMode;
23  import org.bremersee.security.authentication.AuthProperties.PathMatcherProperties;
24  import org.bremersee.web.CorsProperties;
25  import org.springframework.beans.factory.ObjectProvider;
26  import org.springframework.core.env.Environment;
27  import org.springframework.security.authentication.ReactiveAuthenticationManager;
28  import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
29  import org.springframework.security.config.web.server.ServerHttpSecurity;
30  import org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec;
31  import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
32  import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
33  import org.springframework.security.core.userdetails.UserDetails;
34  import org.springframework.security.crypto.password.PasswordEncoder;
35  import org.springframework.security.web.server.SecurityWebFilterChain;
36  import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter.Mode;
37  import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
38  import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
39  import org.springframework.util.Assert;
40  import org.springframework.util.ClassUtils;
41  import org.springframework.util.StringUtils;
42  
43  /**
44   * The abstract reactive resource server security auto configuration.
45   *
46   * @author Christian Bremer
47   */
48  @Slf4j
49  public abstract class AbstractReactiveResourceServerAutoConfiguration {
50  
51    private final Environment environment;
52  
53    private final CorsProperties corsProperties;
54  
55    private final AuthProperties authProperties;
56  
57    private final ObjectProvider<JsonPathReactiveJwtConverter> jwtConverterProvider;
58  
59    private final ObjectProvider<ReactiveUserDetailsService> userDetailsServiceProvider;
60  
61    private final ObjectProvider<PasswordEncoder> passwordEncoderProvider;
62  
63    /**
64     * Instantiates a new abstract reactive resource server security auto configuration.
65     *
66     * @param environment the environment
67     * @param corsProperties the cors properties
68     * @param authProperties the authentication nad authorization properties
69     * @param jwtConverterProvider the jwt converter provider
70     * @param userDetailsServiceProvider the user details service provider
71     * @param passwordEncoderProvider the password encoder provider
72     */
73    protected AbstractReactiveResourceServerAutoConfiguration(
74        Environment environment,
75        CorsProperties corsProperties,
76        AuthProperties authProperties,
77        ObjectProvider<JsonPathReactiveJwtConverter> jwtConverterProvider,
78        ObjectProvider<ReactiveUserDetailsService> userDetailsServiceProvider,
79        ObjectProvider<PasswordEncoder> passwordEncoderProvider) {
80      this.environment = environment;
81      this.corsProperties = corsProperties;
82      this.authProperties = authProperties;
83      this.jwtConverterProvider = jwtConverterProvider;
84      this.userDetailsServiceProvider = userDetailsServiceProvider;
85      this.passwordEncoderProvider = passwordEncoderProvider;
86    }
87  
88    /**
89     * Init.
90     */
91    protected void init() {
92      final boolean hasJwkUriSet = StringUtils
93          .hasText(environment.getProperty("spring.security.oauth2.resourceserver.jwt.jwk-set-uri"));
94      log.info("\n"
95              + "*********************************************************************************\n"
96              + "* {}\n"
97              + "*********************************************************************************\n"
98              + "* enable = {}\n"
99              + "* order = {}\n"
100             + "* jwt = {}\n"
101             + "* cors = {}\n"
102             + "*********************************************************************************",
103         ClassUtils.getUserClass(getClass()).getSimpleName(),
104         authProperties.getResourceServer().name(),
105         authProperties.getResourceServerOrder(),
106         hasJwkUriSet,
107         corsProperties.isEnable());
108   }
109 
110   /**
111    * Init authorize exchange.
112    *
113    * @param http the http
114    * @return the authorize exchange spec
115    */
116   protected abstract AuthorizeExchangeSpec init(ServerHttpSecurity http);
117 
118   /**
119    * Resource server filter chain.
120    *
121    * @param serverHttpSecurity the server http security
122    * @return the security web filter chain
123    */
124   protected SecurityWebFilterChain resourceServerFilterChain(
125       ServerHttpSecurity serverHttpSecurity) {
126 
127     ServerHttpSecurity http = serverHttpSecurity;
128     http.headers().frameOptions(conf -> conf.mode(Mode.SAMEORIGIN));
129     Assert.notNull(http, "Server http security must be present.");
130     AuthorizeExchangeSpec spec = init(http);
131     if (authProperties.getResourceServer() == AutoSecurityMode.NONE) {
132       http = spec
133           .anyExchange().permitAll()
134           .and()
135           .httpBasic().disable();
136     } else {
137       spec = configurePathMatchers(spec);
138       http = configureAuthenticationManager(spec.and());
139     }
140     http = http
141         .headers().frameOptions(customizer -> {
142           if (authProperties.getFrameOptionsMode() == FrameOptionsMode.DISABLE) {
143             customizer.disable();
144           } else {
145             customizer.mode(authProperties.getFrameOptionsMode().getMode());
146           }
147         })
148         .and()
149         .csrf().disable();
150     if (corsProperties.isEnable()) {
151       http = http.cors().and();
152     } else {
153       http = http.cors().disable();
154     }
155     return OrderedProxy.create(
156         http.build(),
157         authProperties.getResourceServerOrder());
158   }
159 
160   private AuthorizeExchangeSpec configurePathMatchers(AuthorizeExchangeSpec spec) {
161     for (PathMatcherProperties props : authProperties.preparePathMatchers(corsProperties)) {
162       log.info("Securing requests to {}", props);
163       switch (props.getAccessMode()) {
164         case DENY_ALL:
165           spec = spec.matchers(matcher(props)).denyAll();
166           break;
167         case PERMIT_ALL:
168           spec = spec.matchers(matcher(props)).permitAll();
169           break;
170         default:
171           spec = spec.matchers(matcher(props)).access(new RoleOrIpBasedAuthorizationManager(
172               props.roles(authProperties::ensureRolePrefix),
173               props.getIpAddresses()));
174       }
175     }
176     return spec;
177   }
178 
179   private ServerWebExchangeMatcher matcher(PathMatcherProperties props) {
180     return Optional.ofNullable(props.httpMethod())
181         .map(method -> ServerWebExchangeMatchers.pathMatchers(method, props.getAntPattern()))
182         .orElseGet(() -> ServerWebExchangeMatchers.pathMatchers(props.getAntPattern()));
183   }
184 
185   private ServerHttpSecurity configureAuthenticationManager(ServerHttpSecurity http) {
186     return Optional.ofNullable(jwtConverterProvider.getIfAvailable())
187         .map(jwtConverter -> http
188             .oauth2ResourceServer((rs) -> rs
189                 .jwt()
190                 .jwtAuthenticationConverter(jwtConverter)
191                 .and()))
192         .orElseGet(() -> http
193             .authenticationManager(userDetailsAuthenticationManager())
194             .httpBasic()
195             .and()
196             .formLogin().disable());
197   }
198 
199   private ReactiveAuthenticationManager userDetailsAuthenticationManager() {
200     ReactiveUserDetailsService userDetailsService = userDetailsServiceProvider
201         .getIfAvailable(this::defaultReactiveUserDetailsService);
202     log.info("Creating ReactiveAuthenticationManager with {}",
203         ClassUtils.getUserClass(userDetailsService).getSimpleName());
204     UserDetailsRepositoryReactiveAuthenticationManager manager
205         = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
206     passwordEncoderProvider.ifAvailable(passwordEncoder -> {
207       log.info("Setting {} to ReactiveAuthenticationManager", ClassUtils.getUserClass(passwordEncoder).getSimpleName());
208       manager.setPasswordEncoder(passwordEncoder);
209     });
210     return manager;
211   }
212 
213   private ReactiveUserDetailsService defaultReactiveUserDetailsService() {
214     UserDetails[] userDetails = authProperties.buildBasicAuthUserDetails(passwordEncoderProvider.getIfAvailable());
215     return new MapReactiveUserDetailsService(userDetails);
216   }
217 
218 }