SecurityConfiguration.java
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bremersee.dccon.config;
import lombok.extern.slf4j.Slf4j;
import org.bremersee.security.authentication.AccessTokenRetriever;
import org.bremersee.security.authentication.AuthenticationProperties;
import org.bremersee.security.authentication.JsonPathJwtConverter;
import org.bremersee.security.authentication.PasswordFlowAuthenticationManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* The security configuration.
*
* @author Christian Bremer
*/
@ConditionalOnWebApplication
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableConfigurationProperties(AuthenticationProperties.class)
@Slf4j
public class SecurityConfiguration {
private AuthenticationProperties properties;
/**
* Instantiates a new security configuration.
*
* @param properties the properties
*/
public SecurityConfiguration(
AuthenticationProperties properties) {
this.properties = properties;
}
/**
* Password flow authentication manager for the actuator endpoints that uses a different jwk uri
* as the resource server.
*
* @param jwkUriSet the jwk uri set
* @param jwsAlgorithm the jws algorithm
* @param issuerUri the issuer uri
* @param jwtAuthenticationConverter the jwt authentication converter
* @param accessTokenRetriever the access token retriever
* @return the password flow authentication manager
*/
@ConditionalOnProperty(
prefix = "bremersee.security.authentication.actuator.jwt",
name = "jwk-set-uri")
@Bean
public PasswordFlowAuthenticationManager passwordFlowAuthenticationManager(
@Value("${bremersee.security.authentication.actuator.jwt.jwk-set-uri}")
String jwkUriSet,
@Value("${bremersee.security.authentication.actuator.jwt.jws-algorithm:RS256}")
String jwsAlgorithm,
@Value("${bremersee.security.authentication.actuator.jwt.issuer-uri:}")
String issuerUri,
ObjectProvider<Converter<Jwt, ? extends AbstractAuthenticationToken>> jwtAuthenticationConverter,
ObjectProvider<AccessTokenRetriever<String>> accessTokenRetriever) {
log.info("Creating password flow authentication manager with jwk uri {}", jwkUriSet);
NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkUriSet)
.jwsAlgorithm(SignatureAlgorithm.from(jwsAlgorithm)).build();
if (StringUtils.hasText(issuerUri)) {
nimbusJwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuerUri));
}
return new PasswordFlowAuthenticationManager(
properties,
nimbusJwtDecoder,
jwtAuthenticationConverter.getIfAvailable(),
accessTokenRetriever.getIfAvailable());
}
/**
* The swagger security configuration.
*/
@ConditionalOnWebApplication
@Order(53)
@Configuration
static class Swagger extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers(
"/v2/api-docs",
"/v2/api-docs/**");
}
}
/**
* The resource server security configuration.
*/
@ConditionalOnWebApplication
@ConditionalOnProperty(
prefix = "bremersee.security.authentication",
name = "enable-jwt-support",
havingValue = "true")
@Order(51)
@Configuration
@Slf4j
static class ResourceServer extends WebSecurityConfigurerAdapter {
private JsonPathJwtConverter jwtConverter;
/**
* Instantiates a new resource server security configuration.
*
* @param jwtConverterProvider the jwt converter provider
*/
@Autowired
public ResourceServer(ObjectProvider<JsonPathJwtConverter> jwtConverterProvider) {
jwtConverter = jwtConverterProvider.getIfAvailable();
Assert.notNull(jwtConverter, "JWT converter must be present.");
}
/**
* Init.
*/
@EventListener(ApplicationReadyEvent.class)
public void init() {
log.info("msg=[Using jwt authentication.]");
}
@Override
public void configure(HttpSecurity http) throws Exception {
log.info("Authorizing requests to /api/** with OAuth2.");
http
.requestMatcher(new NegatedRequestMatcher(EndpointRequest.toAnyEndpoint()))
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer((rs) -> rs
.jwt()
.jwtAuthenticationConverter(jwtConverter).and())
.csrf().disable();
}
}
/**
* The actuator security configuration.
*/
@ConditionalOnWebApplication
@ConditionalOnProperty(
prefix = "bremersee.security.authentication",
name = "enable-jwt-support",
havingValue = "true")
@Order(52)
@Configuration
@Slf4j
@EnableConfigurationProperties(AuthenticationProperties.class)
static class Actuator extends WebSecurityConfigurerAdapter {
private final AuthenticationProperties properties;
private final PasswordFlowAuthenticationManager passwordFlowAuthenticationManager;
/**
* Instantiates a new actuator security configuration.
*
* @param properties the properties
* @param passwordFlowAuthenticationManager the password flow authentication manager
*/
@Autowired
public Actuator(
AuthenticationProperties properties,
ObjectProvider<PasswordFlowAuthenticationManager> passwordFlowAuthenticationManager) {
this.properties = properties;
this.passwordFlowAuthenticationManager = passwordFlowAuthenticationManager.getIfAvailable();
Assert.notNull(
this.passwordFlowAuthenticationManager,
"Password flow authentication manager must be present.");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
log.info("Authorizing requests to /actuator/** with password flow auth.");
http
.requestMatcher(EndpointRequest.toAnyEndpoint())
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.requestMatchers(EndpointRequest.to(HealthEndpoint.class)).permitAll()
.requestMatchers(EndpointRequest.to(InfoEndpoint.class)).permitAll()
.requestMatchers(new AndRequestMatcher(
EndpointRequest.toAnyEndpoint(),
new AntPathRequestMatcher("/**", "GET")))
.access(properties.getActuator().buildAccessExpression(properties::ensureRolePrefix))
.anyRequest()
.access(properties.getActuator().buildAdminAccessExpression(properties::ensureRolePrefix))
.and()
.csrf().disable()
.authenticationProvider(passwordFlowAuthenticationManager)
.httpBasic()
.realmName("actuator")
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
/**
* The in-memory security configuration with basic auth.
*/
@ConditionalOnWebApplication
@ConditionalOnProperty(
prefix = "bremersee.security.authentication",
name = "enable-jwt-support",
havingValue = "false", matchIfMissing = true)
@Order(51)
@Configuration
@Slf4j
@EnableConfigurationProperties(AuthenticationProperties.class)
static class InMemorySecurityConfiguration extends WebSecurityConfigurerAdapter {
private final AuthenticationProperties properties;
/**
* Instantiates a new in-memory security configuration with basic auth.
*
* @param properties the authentication properties
*/
public InMemorySecurityConfiguration(AuthenticationProperties properties) {
this.properties = properties;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
log.info("Authorizing requests to /api/** with basic auth.");
http
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.requestMatchers(EndpointRequest.to(HealthEndpoint.class)).permitAll()
.requestMatchers(EndpointRequest.to(InfoEndpoint.class)).permitAll()
.requestMatchers(new AndRequestMatcher(
EndpointRequest.toAnyEndpoint(),
new AntPathRequestMatcher("/**", "GET")))
.access(properties.getActuator().buildAccessExpression(properties::ensureRolePrefix))
.requestMatchers(EndpointRequest.toAnyEndpoint())
.access(properties.getActuator().buildAdminAccessExpression(properties::ensureRolePrefix))
.anyRequest()
.authenticated()
.and()
.csrf().disable()
.userDetailsService(userDetailsService())
.formLogin().disable()
.httpBasic().realmName("dc-con")
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
protected UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(properties.buildBasicAuthUserDetails());
}
}
}