1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
61
62
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
76
77
78
79 public SecurityConfiguration(
80 AuthenticationProperties properties) {
81 this.properties = properties;
82 }
83
84
85
86
87
88
89
90
91
92
93
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
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
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
155
156
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
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
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
208
209
210
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
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
267
268
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 }