1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
45
46
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
65
66
67
68
69
70
71
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
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
112
113
114
115
116 protected abstract AuthorizeExchangeSpec init(ServerHttpSecurity http);
117
118
119
120
121
122
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 }