1 package cn.home1.oss.lib.security.starter;
2
3 import static cn.home1.oss.boot.autoconfigure.AppSecurity.ENABLED;
4 import static cn.home1.oss.boot.autoconfigure.AppType.MIXED;
5 import static cn.home1.oss.boot.autoconfigure.AppType.RESOURCE;
6 import static cn.home1.oss.boot.autoconfigure.AppType.RESTFUL;
7 import static cn.home1.oss.boot.autoconfigure.AppType.TEMPLATE;
8 import static cn.home1.oss.lib.errorhandle.api.ResolvedError.RESOLVED_ERROR_COOKIE;
9 import static cn.home1.oss.lib.security.api.GenericUser.GENERIC_USER_COOKIE;
10 import static cn.home1.oss.lib.security.api.GenericUser.GENERIC_USER_TOKEN;
11 import static cn.home1.oss.lib.security.internal.rest.RestfulAuthenticationEntryPoint.restfulEntryPoint;
12 import static cn.home1.oss.lib.security.internal.template.TemplateAuthenticationEntryPoint.templateEntryPoint;
13 import static cn.home1.oss.lib.security.internal.template.TemplateAuthenticationFailureHandler.templateFailureHandler;
14 import static cn.home1.oss.lib.security.internal.template.TemplateAuthenticationLogoutSuccessHandler.templateLogoutSuccessHandler;
15 import static cn.home1.oss.lib.security.internal.template.TemplateAuthenticationSuccessHandler.templateSuccessHandler;
16 import static com.google.common.base.Preconditions.checkState;
17 import static org.apache.commons.lang3.StringUtils.isBlank;
18 import static org.apache.commons.lang3.StringUtils.isNotBlank;
19 import static org.springframework.http.HttpMethod.GET;
20
21 import cn.home1.oss.boot.autoconfigure.AppProperties;
22 import cn.home1.oss.boot.autoconfigure.AppSecurityProperties;
23 import cn.home1.oss.boot.autoconfigure.AppType;
24 import cn.home1.oss.boot.autoconfigure.ConditionalOnAppSecurity;
25 import cn.home1.oss.boot.autoconfigure.ConditionalOnAppType;
26 import cn.home1.oss.boot.autoconfigure.OnAppTypeCondition;
27 import cn.home1.oss.lib.common.crypto.Cryptos;
28 import cn.home1.oss.lib.common.crypto.KeyExpression;
29 import cn.home1.oss.lib.common.crypto.Rsa;
30 import cn.home1.oss.lib.common.crypto.RsaKey;
31 import cn.home1.oss.lib.errorhandle.api.ResolvedError;
32 import cn.home1.oss.lib.errorhandle.internal.RestfulExceptionHandler;
33 import cn.home1.oss.lib.security.api.BaseUserDetailsAuthenticationProvider;
34 import cn.home1.oss.lib.security.api.GenericUser;
35 import cn.home1.oss.lib.security.crypto.ReentrantBCryptPasswordEncoder;
36 import cn.home1.oss.lib.security.internal.preauth.PreAuthTokenCookieClearingLogoutHandler;
37 import cn.home1.oss.lib.security.internal.rest.RestfulAuthenticationFailureHandler;
38 import cn.home1.oss.lib.security.internal.rest.RestfulAuthenticationSuccessHandler;
39 import cn.home1.oss.lib.security.internal.rest.RestfulLoginDisabledFilter;
40 import cn.home1.oss.lib.security.internal.rest.RestfulLoginPublicKeyFilter;
41 import cn.home1.oss.lib.security.internal.rest.RestfulLogoutDisabledFilter;
42 import cn.home1.oss.lib.security.internal.rest.RestfulLogoutSuccessHandler;
43 import cn.home1.oss.lib.webmvc.api.TypeSafeCookie;
44 import cn.home1.oss.lib.webmvc.api.TypeSafeToken;
45 import cn.home1.oss.lib.webmvc.internal.DefaultHttpEntityMethodProcessor;
46
47 import lombok.SneakyThrows;
48
49 import org.springframework.beans.factory.annotation.Autowired;
50 import org.springframework.beans.factory.annotation.Qualifier;
51 import org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint;
52 import org.springframework.boot.autoconfigure.web.ServerProperties;
53 import org.springframework.context.annotation.Bean;
54 import org.springframework.context.annotation.Configuration;
55 import org.springframework.core.annotation.Order;
56 import org.springframework.core.env.Environment;
57 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
58 import org.springframework.security.config.annotation.web.configurers.EncryptDefaultLoginPageConfigurer;
59 import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
60 import org.springframework.security.crypto.password.PasswordEncoder;
61 import org.springframework.security.web.AuthenticationEntryPoint;
62 import org.springframework.security.web.authentication.AuthenticationFailureHandler;
63 import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
64 import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
65 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
66 import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
67 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
68
69
70
71
72 @Order(FormAuthConfiguration.ORDER_FORM_AUTH)
73 @Configuration
74 public class FormAuthConfiguration extends SecurityConfigurerAdapter<FormAuthConfiguration> {
75
76 public static final int ORDER_FORM_AUTH = BasicAuthConfiguration.ORDER_BASIC_AUTH - 10;
77
78 public static final String LOGIN_CIPHER = "loginCipher";
79 public static final String FORM_AUTHENTICATION_ENTRYPOINT = "formAuthenticationEntryPoint";
80
81 @Autowired
82 private AppProperties appProperties;
83
84 @Autowired
85 private Environment environment;
86
87 @Autowired
88 private RestfulExceptionHandler exceptionHandler;
89
90 @Qualifier(RESOLVED_ERROR_COOKIE)
91 @Autowired
92 private TypeSafeCookie<ResolvedError> resolvedErrorCookie;
93
94 @Qualifier(GENERIC_USER_COOKIE)
95 @Autowired(required = false)
96 private TypeSafeCookie<GenericUser> genericUserCookie;
97
98 @Qualifier(GENERIC_USER_TOKEN)
99 @Autowired(required = false)
100 private TypeSafeToken<GenericUser> genericUserToken;
101
102 @Autowired
103 private DefaultHttpEntityMethodProcessor httpEntityMethodProcessor;
104
105 @Autowired
106 private ServerProperties serverProperties;
107
108 @Autowired(required = false)
109 @SuppressWarnings("rawtypes")
110 private BaseUserDetailsAuthenticationProvider userDetailsAuthenticationProvider;
111
112 @SneakyThrows
113 @Override
114 public void configure(final HttpSecurity http) {
115 final AuthenticationEntryPoint formAuthenticationEntryPoint = this.formAuthenticationEntryPoint();
116 if (formAuthenticationEntryPoint != null) {
117
118 http.exceptionHandling().authenticationEntryPoint(formAuthenticationEntryPoint);
119 }
120
121
122 final AppType appType = this.appProperties.getType();
123 final AppSecurityProperties appSecurityProperties = this.appProperties.getSecurity();
124 if (appSecurityProperties.getEnabled() && appType != RESOURCE) {
125 checkState(this.userDetailsAuthenticationProvider != null,
126 "must define a bean that extends "
127 + BaseUserDetailsAuthenticationProvider.class.getName()
128 + " to enable form authentication");
129
130 final KeyExpression loginKey = appSecurityProperties.getLoginKey();
131 final String publicKey;
132 if (loginKey.isPresent()) {
133 final Rsa rsa = Cryptos.cipher(loginKey);
134 this.userDetailsAuthenticationProvider.setCipher(rsa);
135 publicKey = RsaKey.extractPublicKey(rsa.getEncryptor().getKey().getKeyExpression());
136 } else {
137 publicKey = "";
138 }
139
140
141
142
143
144 final Boolean useDefaultLoginPage = appSecurityProperties.useDefaultLoginPage(this.environment);
145 http.apply(new EncryptDefaultLoginPageConfigurer<>(
146 appSecurityProperties.getLoginPage(),
147 publicKey,
148 useDefaultLoginPage,
149 this.exceptionHandler.getExceptionResolver(),
150 this.resolvedErrorCookie
151 ));
152 final FormLoginConfigurer<HttpSecurity> formLogin = http.formLogin();
153 if (!useDefaultLoginPage) {
154 formLogin.loginPage(appSecurityProperties.getLoginPage());
155
156 }
157 final String loginProcessingUrl = appSecurityProperties.getLoginProcessingUrl();
158 final String logoutUrl = appSecurityProperties.getLogoutUrl();
159 formLogin
160 .loginProcessingUrl(loginProcessingUrl)
161 .failureHandler(this.authenticationFailureHandler())
162 .successHandler(this.authenticationSuccessHandler())
163 .and()
164 .logout()
165 .logoutUrl(logoutUrl)
166 .addLogoutHandler(this.logoutHandler())
167 .logoutSuccessHandler(this.logoutSuccessHandler());
168 } else {
169 http.formLogin().disable()
170 .logout().disable()
171 ;
172
173 if (appType == MIXED || appType == RESTFUL) {
174 http
175 .addFilterBefore(this.loginDisabledFilter(), UsernamePasswordAuthenticationFilter.class)
176 .addFilterBefore(this.logoutDisabledFilter(), RestfulLoginDisabledFilter.class)
177 ;
178 }
179 }
180
181 final RestfulLoginPublicKeyFilter restfulLoginPublicKeyFilter = this.restfulLoginPublicKeyFilter();
182 if (restfulLoginPublicKeyFilter != null) {
183 http.addFilterBefore(restfulLoginPublicKeyFilter, UsernamePasswordAuthenticationFilter.class);
184 }
185 }
186
187 @Bean
188 @ConditionalOnAppSecurity(ENABLED)
189 @ConditionalOnAppType({MIXED, RESTFUL, TEMPLATE})
190 public PreAuthTokenCookieClearingLogoutHandler logoutHandler() {
191 return new PreAuthTokenCookieClearingLogoutHandler();
192 }
193
194 public LogoutSuccessHandler logoutSuccessHandler() {
195 final LogoutSuccessHandler handler;
196 if (this.appProperties.getType() == TEMPLATE) {
197 handler = templateLogoutSuccessHandler();
198 } else {
199 handler = new RestfulLogoutSuccessHandler();
200 }
201 return handler;
202 }
203
204 public RestfulLoginPublicKeyFilter restfulLoginPublicKeyFilter() {
205 final AppSecurityProperties appSecurityProperties = this.appProperties.getSecurity();
206
207 final RestfulLoginPublicKeyFilter filter;
208 if (appSecurityProperties.getEnabled() &&
209 OnAppTypeCondition.matches(this.appProperties.getType(), MIXED, RESTFUL) &&
210 appSecurityProperties.getLoginKey().isPresent()) {
211 final String loginPublicKeyUrl = appSecurityProperties.getLoginPublicKeyUrl();
212 final KeyExpression publicKey = new RsaKey(appSecurityProperties.getLoginKey()).getPublicKey();
213 filter = new RestfulLoginPublicKeyFilter(publicKey);
214 filter.setEnvironment(this.environment);
215 filter.setExceptionHandler(this.exceptionHandler);
216 filter.setHttpEntityMethodProcessor(this.httpEntityMethodProcessor);
217 filter.setRequestMatcher(new AntPathRequestMatcher(loginPublicKeyUrl, GET.name()));
218 } else {
219 filter = null;
220 }
221 return filter;
222 }
223
224 public RestfulLoginDisabledFilter loginDisabledFilter() {
225 final AppSecurityProperties appSecurityProperties = this.appProperties.getSecurity();
226
227 final RestfulLoginDisabledFilter filter;
228 if (!appSecurityProperties.getEnabled() &&
229 OnAppTypeCondition.matches(this.appProperties.getType(), MIXED, RESTFUL)) {
230 filter = new RestfulLoginDisabledFilter();
231 filter.setEnvironment(this.environment);
232 filter.setFilterProcessesUrl(appSecurityProperties.getLoginProcessingUrl());
233 filter.setPostOnly(true);
234 filter.setExceptionHandler(this.exceptionHandler);
235 } else {
236 filter = null;
237 }
238 return filter;
239 }
240
241 public RestfulLogoutDisabledFilter logoutDisabledFilter() {
242 final AppSecurityProperties appSecurityProperties = this.appProperties.getSecurity();
243
244 final RestfulLogoutDisabledFilter filter;
245 if (!appSecurityProperties.getEnabled() &&
246 OnAppTypeCondition.matches(this.appProperties.getType(), MIXED, RESTFUL)) {
247
248 filter = new RestfulLogoutDisabledFilter();
249 filter.setEnvironment(this.environment);
250 filter.setFilterProcessesUrl(appSecurityProperties.getLogoutUrl());
251 filter.setExceptionHandler(this.exceptionHandler);
252 } else {
253 filter = null;
254 }
255 return filter;
256 }
257
258 @Bean
259 @ConditionalOnAppSecurity(ENABLED)
260 @ConditionalOnAppType({MIXED, RESTFUL, TEMPLATE})
261 public AuthenticationFailureHandler authenticationFailureHandler() {
262 final AppType appType = this.appProperties.getType();
263 final AppSecurityProperties appSecurityProperties = this.appProperties.getSecurity();
264 final String authFailureHandler = appSecurityProperties.getAuthFailureHandler();
265 final String loginPage = appSecurityProperties.getLoginPage();
266
267 final AuthenticationFailureHandler failureHandler;
268 if (isBlank(authFailureHandler)) {
269 if (appType == TEMPLATE) {
270 failureHandler = templateFailureHandler(
271 loginPage, this.exceptionHandler.getExceptionResolver(), this.resolvedErrorCookie);
272 } else {
273 failureHandler = RestfulAuthenticationFailureHandler.restfulFailureHandler(this.exceptionHandler);
274 }
275 } else {
276 if ("restful".equalsIgnoreCase(authFailureHandler)) {
277 failureHandler = RestfulAuthenticationFailureHandler.restfulFailureHandler(this.exceptionHandler);
278 } else {
279 failureHandler = templateFailureHandler(
280 loginPage, this.exceptionHandler.getExceptionResolver(), this.resolvedErrorCookie);
281 }
282 }
283
284 return failureHandler;
285 }
286
287 @Bean
288 @ConditionalOnAppSecurity(ENABLED)
289 @ConditionalOnAppType({MIXED, RESTFUL, TEMPLATE})
290 public AuthenticationSuccessHandler authenticationSuccessHandler() {
291 final AppType appType = this.appProperties.getType();
292 final AppSecurityProperties appSecurityProperties = this.appProperties.getSecurity();
293 final String authSucessHandler = appSecurityProperties.getAuthSucessHandler();
294
295 final AuthenticationSuccessHandler successHandler;
296 if (isBlank(authSucessHandler)) {
297 if (appType == TEMPLATE) {
298 successHandler = templateSuccessHandler("/");
299 } else {
300 successHandler = RestfulAuthenticationSuccessHandler.restfulSuccessHandler(
301 this.genericUserCookie,
302 this.exceptionHandler,
303 this.httpEntityMethodProcessor,
304 this.genericUserToken
305 );
306 }
307 } else {
308 if ("restful".equalsIgnoreCase(authSucessHandler)) {
309 successHandler = RestfulAuthenticationSuccessHandler.restfulSuccessHandler(
310 this.genericUserCookie,
311 this.exceptionHandler,
312 this.httpEntityMethodProcessor,
313 this.genericUserToken
314 );
315 } else {
316 successHandler = templateSuccessHandler(authSucessHandler);
317 }
318 }
319
320
321 return successHandler;
322 }
323
324
325
326
327
328
329 @Bean(name = FORM_AUTHENTICATION_ENTRYPOINT)
330 public AuthenticationEntryPoint formAuthenticationEntryPoint() {
331 final AppType appType = this.appProperties.getType();
332 final AppSecurityProperties appSecurityProperties = this.appProperties.getSecurity();
333 final String authEntryPoint = appSecurityProperties.getAuthEntryPoint();
334 final String loginPage = appSecurityProperties.getLoginPage();
335
336 final AuthenticationEntryPoint entryPoint;
337 if (isBlank(authEntryPoint)) {
338 if (appType == TEMPLATE) {
339 final Boolean useDefaultLoginPage = appSecurityProperties.useDefaultLoginPage(this.environment);
340 if (useDefaultLoginPage) {
341 entryPoint = null;
342 } else {
343
344 entryPoint = templateEntryPoint(
345 loginPage, this.exceptionHandler.getExceptionResolver(), this.resolvedErrorCookie);
346 }
347 } else {
348 entryPoint = restfulEntryPoint(this.exceptionHandler);
349 }
350 } else {
351 if ("401".equals(authEntryPoint)) {
352 final String sessionCookieName = this.serverProperties.getSession().getCookie().getName();
353 final String headerValue = "Session realm=\"" +
354 (isNotBlank(sessionCookieName) ? sessionCookieName : "JSESSIONID") +
355 "\"";
356 entryPoint = new Http401AuthenticationEntryPoint(headerValue);
357 } else if ("403".equals(authEntryPoint)) {
358 entryPoint = new Http403ForbiddenEntryPoint();
359 } else if ("loginPage".equalsIgnoreCase(authEntryPoint)) {
360 entryPoint = templateEntryPoint(
361 loginPage, this.exceptionHandler.getExceptionResolver(), this.resolvedErrorCookie);
362 } else {
363 entryPoint = restfulEntryPoint(this.exceptionHandler);
364 }
365 }
366
367 return entryPoint;
368 }
369
370
371
372
373
374
375 @Bean
376 public PasswordEncoder passwordEncoder() {
377
378 return new ReentrantBCryptPasswordEncoder();
379 }
380 }