View Javadoc
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   * Created by zhanghaolun on 16/8/19.
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       // TODO accessDeniedHandler
118       http.exceptionHandling().authenticationEntryPoint(formAuthenticationEntryPoint);
119     }
120     // TODO RememberMeAuthenticationFilter
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       // see: http://docs.spring.io/spring-security/site/docs/4.1.3.RELEASE/guides/html5/form-javaconfig.html
141       // see: org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer
142       // see: org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer
143       // if loginPage or authenticationEntryPoint is set, there will not be loginPageGeneratingFilter
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()); // this will disable default loginPage
155         //.permitAll() // permitAll only works with HttpSecurity.authorizeRequests()
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)//.permitAll() // permitAll only works with HttpSecurity.authorizeRequests()
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("/"); // TODO redirectUrl
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    * always enabled.
326    *
327    * @return authenticationEntryPoint
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           // this will disable default loginPage
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") + // TODO is this realm ok?
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    * always enabled.
372    *
373    * @return passwordEncoder
374    */
375   @Bean
376   public PasswordEncoder passwordEncoder() {
377     //return NoOpPasswordEncoder.getInstance();
378     return new ReentrantBCryptPasswordEncoder();
379   }
380 }