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.dccon.config;
18  
19  import lombok.extern.slf4j.Slf4j;
20  import org.bremersee.security.authentication.AccessTokenRetriever;
21  import org.bremersee.security.authentication.AuthenticationProperties;
22  import org.bremersee.security.authentication.JsonPathJwtConverter;
23  import org.bremersee.security.authentication.PasswordFlowAuthenticationManager;
24  import org.springframework.beans.factory.ObjectProvider;
25  import org.springframework.beans.factory.annotation.Autowired;
26  import org.springframework.beans.factory.annotation.Value;
27  import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
28  import org.springframework.boot.actuate.health.HealthEndpoint;
29  import org.springframework.boot.actuate.info.InfoEndpoint;
30  import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
31  import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
32  import org.springframework.boot.context.event.ApplicationReadyEvent;
33  import org.springframework.boot.context.properties.EnableConfigurationProperties;
34  import org.springframework.context.annotation.Bean;
35  import org.springframework.context.annotation.Configuration;
36  import org.springframework.context.event.EventListener;
37  import org.springframework.core.annotation.Order;
38  import org.springframework.core.convert.converter.Converter;
39  import org.springframework.http.HttpMethod;
40  import org.springframework.security.authentication.AbstractAuthenticationToken;
41  import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
42  import org.springframework.security.config.annotation.web.builders.HttpSecurity;
43  import org.springframework.security.config.annotation.web.builders.WebSecurity;
44  import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
45  import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
46  import org.springframework.security.config.http.SessionCreationPolicy;
47  import org.springframework.security.core.userdetails.UserDetailsService;
48  import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
49  import org.springframework.security.oauth2.jwt.Jwt;
50  import org.springframework.security.oauth2.jwt.JwtValidators;
51  import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
52  import org.springframework.security.provisioning.InMemoryUserDetailsManager;
53  import org.springframework.security.web.util.matcher.AndRequestMatcher;
54  import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
55  import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
56  import org.springframework.util.Assert;
57  import org.springframework.util.StringUtils;
58  
59  /**
60   * The security configuration.
61   *
62   * @author Christian Bremer
63   */
64  @ConditionalOnWebApplication
65  @Configuration
66  @EnableWebSecurity
67  @EnableGlobalMethodSecurity(prePostEnabled = true)
68  @EnableConfigurationProperties(AuthenticationProperties.class)
69  @Slf4j
70  public class SecurityConfiguration {
71  
72    private AuthenticationProperties properties;
73  
74    /**
75     * Instantiates a new security configuration.
76     *
77     * @param properties the properties
78     */
79    public SecurityConfiguration(
80        AuthenticationProperties properties) {
81      this.properties = properties;
82    }
83  
84    /**
85     * Password flow authentication manager for the actuator endpoints that uses a different jwk uri
86     * as the resource server.
87     *
88     * @param jwkUriSet the jwk uri set
89     * @param jwsAlgorithm the jws algorithm
90     * @param issuerUri the issuer uri
91     * @param jwtAuthenticationConverter the jwt authentication converter
92     * @param accessTokenRetriever the access token retriever
93     * @return the password flow authentication manager
94     */
95    @ConditionalOnProperty(
96        prefix = "bremersee.security.authentication.actuator.jwt",
97        name = "jwk-set-uri")
98    @Bean
99    public PasswordFlowAuthenticationManager passwordFlowAuthenticationManager(
100       @Value("${bremersee.security.authentication.actuator.jwt.jwk-set-uri}")
101           String jwkUriSet,
102       @Value("${bremersee.security.authentication.actuator.jwt.jws-algorithm:RS256}")
103           String jwsAlgorithm,
104       @Value("${bremersee.security.authentication.actuator.jwt.issuer-uri:}")
105           String issuerUri,
106       ObjectProvider<Converter<Jwt, ? extends AbstractAuthenticationToken>> jwtAuthenticationConverter,
107       ObjectProvider<AccessTokenRetriever<String>> accessTokenRetriever) {
108 
109     log.info("Creating password flow authentication manager with jwk uri {}", jwkUriSet);
110     NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkUriSet)
111         .jwsAlgorithm(SignatureAlgorithm.from(jwsAlgorithm)).build();
112     if (StringUtils.hasText(issuerUri)) {
113       nimbusJwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuerUri));
114     }
115     return new PasswordFlowAuthenticationManager(
116         properties,
117         nimbusJwtDecoder,
118         jwtAuthenticationConverter.getIfAvailable(),
119         accessTokenRetriever.getIfAvailable());
120   }
121 
122   /**
123    * The swagger security configuration.
124    */
125   @ConditionalOnWebApplication
126   @Order(53)
127   @Configuration
128   static class Swagger extends WebSecurityConfigurerAdapter {
129 
130     @Override
131     public void configure(WebSecurity web) {
132       web.ignoring().antMatchers(
133           "/v2/api-docs",
134           "/v2/api-docs/**");
135     }
136   }
137 
138   /**
139    * The resource server security configuration.
140    */
141   @ConditionalOnWebApplication
142   @ConditionalOnProperty(
143       prefix = "bremersee.security.authentication",
144       name = "enable-jwt-support",
145       havingValue = "true")
146   @Order(51)
147   @Configuration
148   @Slf4j
149   static class ResourceServer extends WebSecurityConfigurerAdapter {
150 
151     private JsonPathJwtConverter jwtConverter;
152 
153     /**
154      * Instantiates a new resource server security configuration.
155      *
156      * @param jwtConverterProvider the jwt converter provider
157      */
158     @Autowired
159     public ResourceServer(ObjectProvider<JsonPathJwtConverter> jwtConverterProvider) {
160       jwtConverter = jwtConverterProvider.getIfAvailable();
161       Assert.notNull(jwtConverter, "JWT converter must be present.");
162     }
163 
164     /**
165      * Init.
166      */
167     @EventListener(ApplicationReadyEvent.class)
168     public void init() {
169       log.info("msg=[Using jwt authentication.]");
170     }
171 
172     @Override
173     public void configure(HttpSecurity http) throws Exception {
174       log.info("Authorizing requests to /api/** with OAuth2.");
175       http
176           .requestMatcher(new NegatedRequestMatcher(EndpointRequest.toAnyEndpoint()))
177           .authorizeRequests()
178           .antMatchers(HttpMethod.OPTIONS).permitAll()
179           .anyRequest().authenticated()
180           .and()
181           .oauth2ResourceServer((rs) -> rs
182               .jwt()
183               .jwtAuthenticationConverter(jwtConverter).and())
184           .csrf().disable();
185     }
186   }
187 
188   /**
189    * The actuator security configuration.
190    */
191   @ConditionalOnWebApplication
192   @ConditionalOnProperty(
193       prefix = "bremersee.security.authentication",
194       name = "enable-jwt-support",
195       havingValue = "true")
196   @Order(52)
197   @Configuration
198   @Slf4j
199   @EnableConfigurationProperties(AuthenticationProperties.class)
200   static class Actuator extends WebSecurityConfigurerAdapter {
201 
202     private final AuthenticationProperties properties;
203 
204     private final PasswordFlowAuthenticationManager passwordFlowAuthenticationManager;
205 
206     /**
207      * Instantiates a new actuator security configuration.
208      *
209      * @param properties the properties
210      * @param passwordFlowAuthenticationManager the password flow authentication manager
211      */
212     @Autowired
213     public Actuator(
214         AuthenticationProperties properties,
215         ObjectProvider<PasswordFlowAuthenticationManager> passwordFlowAuthenticationManager) {
216       this.properties = properties;
217       this.passwordFlowAuthenticationManager = passwordFlowAuthenticationManager.getIfAvailable();
218       Assert.notNull(
219           this.passwordFlowAuthenticationManager,
220           "Password flow authentication manager must be present.");
221     }
222 
223     @Override
224     protected void configure(HttpSecurity http) throws Exception {
225       log.info("Authorizing requests to /actuator/** with password flow auth.");
226       http
227           .requestMatcher(EndpointRequest.toAnyEndpoint())
228           .authorizeRequests()
229           .antMatchers(HttpMethod.OPTIONS).permitAll()
230           .requestMatchers(EndpointRequest.to(HealthEndpoint.class)).permitAll()
231           .requestMatchers(EndpointRequest.to(InfoEndpoint.class)).permitAll()
232           .requestMatchers(new AndRequestMatcher(
233               EndpointRequest.toAnyEndpoint(),
234               new AntPathRequestMatcher("/**", "GET")))
235           .access(properties.getActuator().buildAccessExpression(properties::ensureRolePrefix))
236           .anyRequest()
237           .access(properties.getActuator().buildAdminAccessExpression(properties::ensureRolePrefix))
238           .and()
239           .csrf().disable()
240           .authenticationProvider(passwordFlowAuthenticationManager)
241           .httpBasic()
242           .realmName("actuator")
243           .and()
244           .sessionManagement()
245           .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
246     }
247   }
248 
249   /**
250    * The in-memory security configuration with basic auth.
251    */
252   @ConditionalOnWebApplication
253   @ConditionalOnProperty(
254       prefix = "bremersee.security.authentication",
255       name = "enable-jwt-support",
256       havingValue = "false", matchIfMissing = true)
257   @Order(51)
258   @Configuration
259   @Slf4j
260   @EnableConfigurationProperties(AuthenticationProperties.class)
261   static class InMemorySecurityConfiguration extends WebSecurityConfigurerAdapter {
262 
263     private final AuthenticationProperties properties;
264 
265     /**
266      * Instantiates a new in-memory security configuration with basic auth.
267      *
268      * @param properties the authentication properties
269      */
270     public InMemorySecurityConfiguration(AuthenticationProperties properties) {
271       this.properties = properties;
272     }
273 
274     @Override
275     protected void configure(HttpSecurity http) throws Exception {
276       log.info("Authorizing requests to /api/** with basic auth.");
277       http
278           .authorizeRequests()
279           .antMatchers(HttpMethod.OPTIONS).permitAll()
280           .requestMatchers(EndpointRequest.to(HealthEndpoint.class)).permitAll()
281           .requestMatchers(EndpointRequest.to(InfoEndpoint.class)).permitAll()
282           .requestMatchers(new AndRequestMatcher(
283               EndpointRequest.toAnyEndpoint(),
284               new AntPathRequestMatcher("/**", "GET")))
285           .access(properties.getActuator().buildAccessExpression(properties::ensureRolePrefix))
286           .requestMatchers(EndpointRequest.toAnyEndpoint())
287           .access(properties.getActuator().buildAdminAccessExpression(properties::ensureRolePrefix))
288           .anyRequest()
289           .authenticated()
290           .and()
291           .csrf().disable()
292           .userDetailsService(userDetailsService())
293           .formLogin().disable()
294           .httpBasic().realmName("dc-con")
295           .and()
296           .sessionManagement()
297           .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
298     }
299 
300     @Override
301     protected UserDetailsService userDetailsService() {
302       return new InMemoryUserDetailsManager(properties.buildBasicAuthUserDetails());
303     }
304   }
305 
306 }