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 lombok.AccessLevel;
20  import lombok.Getter;
21  import lombok.extern.slf4j.Slf4j;
22  import org.bremersee.security.authentication.AuthProperties.PathMatcherProperties;
23  import org.bremersee.web.CorsProperties;
24  import org.springframework.beans.factory.ObjectProvider;
25  import org.springframework.boot.autoconfigure.security.SecurityProperties;
26  import org.springframework.core.Ordered;
27  import org.springframework.core.env.Environment;
28  import org.springframework.http.HttpMethod;
29  import org.springframework.security.config.annotation.web.builders.HttpSecurity;
30  import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
31  import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
32  import org.springframework.security.crypto.password.PasswordEncoder;
33  import org.springframework.util.ClassUtils;
34  import org.springframework.util.StringUtils;
35  
36  /**
37   * The abstract resource server security auto configuration.
38   *
39   * @author Christian Bremer
40   */
41  @Slf4j
42  public abstract class AbstractResourceServerAutoConfiguration extends WebSecurityConfigurerAdapter
43      implements Ordered {
44  
45    @Getter(AccessLevel.PROTECTED)
46    private final Environment environment;
47  
48    @Getter(AccessLevel.PROTECTED)
49    private final SecurityProperties securityProperties;
50  
51    @Getter(AccessLevel.PROTECTED)
52    private final AuthProperties authProperties;
53  
54    @Getter(AccessLevel.PROTECTED)
55    private final CorsProperties corsProperties;
56  
57    @Getter(AccessLevel.PROTECTED)
58    private final ObjectProvider<JsonPathJwtConverter> jwtConverterProvider;
59  
60    @Getter(AccessLevel.PROTECTED)
61    private final ObjectProvider<PasswordEncoder> passwordEncoderProvider;
62  
63    /**
64     * Instantiates a new abstract resource server security auto configuration.
65     *
66     * @param environment the environment
67     * @param securityProperties the spring properties
68     * @param authProperties the authentication and authorization properties
69     * @param corsProperties the cors properties
70     * @param jwtConverterProvider the jwt converter provider
71     * @param passwordEncoderProvider the password encoder provider
72     */
73    protected AbstractResourceServerAutoConfiguration(
74        Environment environment,
75        SecurityProperties securityProperties,
76        AuthProperties authProperties,
77        CorsProperties corsProperties,
78        ObjectProvider<JsonPathJwtConverter> jwtConverterProvider,
79        ObjectProvider<PasswordEncoder> passwordEncoderProvider) {
80  
81      this.environment = environment;
82      this.securityProperties = securityProperties;
83      this.authProperties = authProperties;
84      this.corsProperties = corsProperties;
85      this.jwtConverterProvider = jwtConverterProvider;
86      this.passwordEncoderProvider = passwordEncoderProvider;
87    }
88  
89    @Override
90    public int getOrder() {
91      return authProperties.getResourceServerOrder();
92    }
93  
94    /**
95     * Init.
96     */
97    protected void init() {
98      final boolean hasJwkUriSet = StringUtils
99          .hasText(environment.getProperty("spring.security.oauth2.resourceserver.jwt.jwk-set-uri"));
100     log.info("\n"
101             + "*********************************************************************************\n"
102             + "* {}\n"
103             + "*********************************************************************************\n"
104             + "* enable = {}\n"
105             + "* order = {}\n"
106             + "* jwt = {}\n"
107             + "* cors = {}\n"
108             + "*********************************************************************************",
109         ClassUtils.getUserClass(getClass()).getSimpleName(),
110         authProperties.getResourceServer().name(),
111         authProperties.getResourceServerOrder(),
112         hasJwkUriSet,
113         corsProperties.isEnable());
114   }
115 
116   /**
117    * Init expression url authorization configurer . expression intercept url registry.
118    *
119    * @param httpSecurity the http security
120    * @return the expression url authorization configurer . expression intercept url registry
121    * @throws Exception the exception
122    */
123   protected abstract ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry
124   init(HttpSecurity httpSecurity) throws Exception;
125 
126   @Override
127   protected void configure(HttpSecurity httpSecurity) throws Exception {
128 
129     HttpSecurity http = httpSecurity;
130     ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry reg = init(http);
131     if (authProperties.getResourceServer() == AutoSecurityMode.NONE) {
132       http = reg
133           .anyRequest().permitAll()
134           .and()
135           .httpBasic().disable();
136     } else {
137       reg = configurePathMatchers(reg);
138       http = configureAuthenticationProvider(reg.and());
139     }
140     http = http
141         .headers().frameOptions(customizer -> {
142           switch (authProperties.getFrameOptionsMode()) {
143             case DISABLE: {
144               customizer.disable();
145               break;
146             }
147             case SAMEORIGIN: {
148               customizer.sameOrigin();
149             }
150             default:
151               customizer.deny();
152           }
153         })
154         .and()
155         .csrf().disable();
156     if (corsProperties.isEnable()) {
157       http.cors();
158     } else {
159       http.cors().disable();
160     }
161   }
162 
163   private ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry
164   configurePathMatchers(
165       ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry reg) {
166 
167     for (PathMatcherProperties props : authProperties.preparePathMatchers(corsProperties)) {
168       log.info("Securing requests to {}", props);
169       HttpMethod httpMethod = props.httpMethod();
170       if (httpMethod == null) {
171         reg = reg.antMatchers(props.getAntPattern())
172             .access(props.accessExpression(
173                 authProperties::ensureRolePrefix));
174       } else {
175         reg = reg.antMatchers(httpMethod, props.getAntPattern())
176             .access(props.accessExpression(
177                 authProperties::ensureRolePrefix));
178       }
179     }
180     return reg;
181   }
182 
183   private HttpSecurity configureAuthenticationProvider(HttpSecurity http) throws Exception {
184 
185     if (jwtConverterProvider.getIfAvailable() != null) {
186       log.info("Configure authentication provider with JWT.");
187       return http
188           .oauth2ResourceServer((rs) -> rs
189               .jwt()
190               .jwtAuthenticationConverter(jwtConverterProvider.getIfAvailable())
191               .and());
192     }
193     log.info("Configure authentication provider with basic auth and user details service.");
194     String realm = environment.getProperty("spring.application.name", "Restricted area");
195     return http
196         .formLogin().disable()
197         .httpBasic().realmName(realm)
198         .and();
199   }
200 
201 }