接着上節的講,在添加了@EnableWebSecurity註解後,若是須要自定義一些配置,則須要和繼承WebSecurityConfigurerAdapter後,覆蓋某些方法。web
咱們來看一下WebSecurityConfigurerAdapter中哪些方法能夠重寫,須要重寫。cookie
(1)WebSecurityapp
默認是一個空方法,通常也不會再重寫。框架
public void configure(WebSecurity web) throws Exception { }
(2)HttpSecurityide
默認的父類代碼默認任何request都須要認證,使用默認的login page基於表單認證,使用HTTP基本認證。ui
protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin().and() .httpBasic(); }
下面是一些自定義寫法。this
@Override protected void configure(HttpSecurity http) throws Exception { //@formatter:off http.authorizeRequests() // all users have access to these urls .antMatchers("/resources/**", "/signup", "/about").permitAll() // Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN" .antMatchers("/admin/**").hasRole("ADMIN") // Any URL that starts with "/db/" requires the user to have both "ROLE_ADMIN" and "ROLE_DBA" .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // Any URL that starts with "/group_a/" requires the user to have both "ROLE_ADMIN" or "ROLE_GROUP_A" .antMatchers("/admin/**").hasAnyRole("ADMIN", "GROUP_A") // Any URL that has not already been matched on only requires that the user be authenticated .anyRequest().authenticated() .and().formLogin() // all users have access to custom login page .loginPage("/login").permitAll() .and().logout() // customize logout url .logoutUrl("/my/logout") // customize logout success url .logoutSuccessUrl("/my/index") // specify a custom LogoutSuccessHandler. If this is specified, logoutSuccessUrl() is ignored .logoutSuccessHandler(logoutSuccessHandler) // invalidate the HttpSession at the time of logout. This is true by default .invalidateHttpSession(true) // Adds a LogoutHandler. SecurityContextLogoutHandler is added as the last LogoutHandler by default .addLogoutHandler(logoutHandler) // Allows specifying the names of cookies to be removed on logout success .deleteCookies() .and().rememberMe() // Add remember me function and valid date. .key("uniqueAndSecret") .tokenValiditySeconds(60 * 60 * 24 * 7); //@formatter:on }
(3)AuthenticationManagerBuilder加密
默認是這樣寫的:url
protected void configure(AuthenticationManagerBuilder auth) throws Exception { this.disableLocalConfigureAuthenticationBldr = true; }
由上一節分析可知,它其實默認使用DefaultPasswordEncoderAuthenticationManagerBuilder這個Builder及自動配置的UserDetails和UserDetailsService。spa
protected AuthenticationManager authenticationManager() throws Exception { if (!authenticationManagerInitialized) {
// [1]若是覆蓋configure()方法,則disableLocalConfigureAuthenticationBldr爲false
// [2]若是是默認的configure()方法,disableLocalConfigureAuthenticationBldr仍是true configure(localConfigureAuthenticationBldr); if (disableLocalConfigureAuthenticationBldr) { authenticationManager = authenticationConfiguration .getAuthenticationManager(); // [2] } else { authenticationManager = localConfigureAuthenticationBldr.build(); // [1] } authenticationManagerInitialized = true; } return authenticationManager; }
若是被覆蓋,雖然仍是使用的DefaultPasswordEncoderAuthenticationManagerBuilder,可是咱們可使用UserDetailsManagerConfigurer(的兩個子類InMemoryUserDetailsManagerConfigurer,JdbcUserDetailsManagerConfigurer)來構建UserDetailsService及UserDetails。以InMemoryUserDetailsManagerConfigurer爲例,下面是自定義的寫法。
@Bean PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //@formatter:off // returns InMemoryUserDetailsManagerConfigurer PasswordEncoder encoder = new BCryptPasswordEncoder(); auth.inMemoryAuthentication() // create a UserDetailsBuilder and add to userBuilders .withUser("user").password("{bcrypt}" + encoder.encode("pass")).roles("USER") // returns InMemoryUserDetailsManagerConfigurer .and() // create a UserDetailsBuilder again and add to userBuilders
.withUser("admin").password("{bcrypt}" + encoder.encode("pass")).roles("USER", "ADMIN"); //@formatter:on }
[注] 框架要求密碼必須加密,因此這裏加了有關password encode的支持。
那麼這段代碼如何生成UserDetailsService及UserDetails的呢?流程以下:
[1] 調用AuthenticationManagerBuilder的inMemoryAuthentication()方法建立InMemoryUserDetailsManagerConfigurer,調用InMemoryUserDetailsManagerConfigurer的構造器時則會建立InMemoryUserDetailsManager(即UserDetailsService的實現類),最終通過層層父類(InMemoryUserDetailsManagerConfigurer -> UserDetailsManagerConfigurer -> UserDetailsServiceConfigurer -> AbstractDaoAuthenticationConfigurer)設定到AbstractDaoAuthenticationConfigurer中。
public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication() throws Exception { return apply(new InMemoryUserDetailsManagerConfigurer<>()); }
public InMemoryUserDetailsManagerConfigurer() { super(new InMemoryUserDetailsManager(new ArrayList<>())); }
protected AbstractDaoAuthenticationConfigurer(U userDetailsService) { this.userDetailsService = userDetailsService; provider.setUserDetailsService(userDetailsService); if (userDetailsService instanceof UserDetailsPasswordService) { this.provider.setUserDetailsPasswordService((UserDetailsPasswordService) userDetailsService); } }
[2] 調用AuthenticationManagerBuilder的apply()方法設定defaultUserDetailsService爲[1]的InMemoryUserDetailsManager而且把[1]的InMemoryUserDetailsManagerConfigurer加到父類AbstractConfiguredSecurityBuilder的configurers list中
private <C extends UserDetailsAwareConfigurer<AuthenticationManagerBuilder, ? extends UserDetailsService>> C apply( C configurer) throws Exception { this.defaultUserDetailsService = configurer.getUserDetailsService(); return (C) super.apply(configurer); }
[3] 調用InMemoryUserDetailsManagerConfigurer的父類UserDetailsManagerConfigurer的withUser()方法生成多個UserDetailsBuilder放在userBuilders list中
public final UserDetailsBuilder withUser(String username) { UserDetailsBuilder userBuilder = new UserDetailsBuilder((C) this); userBuilder.username(username); this.userBuilders.add(userBuilder); return userBuilder; }
[4] 當調用DefaultPasswordEncoderAuthenticationManagerBuilder的build()方法時,則會調用
[4.1] 調用UserDetailsServiceConfigurer的configure()方法
@Override public void configure(B builder) throws Exception { initUserDetailsService(); super.configure(builder); }
[4.2] 調用UserDetailsManagerConfigurer的initUserDetailsService()方法經過[3]的userBuilders建立User對象(UserDetails的實現類),而且從[1]中的AbstractDaoAuthenticationConfigurer獲取UserDetailsService,並把UserDetails放到UserDetailsService中。
@Override protected void initUserDetailsService() throws Exception { for (UserDetailsBuilder userBuilder : userBuilders) { getUserDetailsService().createUser(userBuilder.build()); } for (UserDetails userDetails : this.users) { getUserDetailsService().createUser(userDetails); } }
下面是一些自定義寫法:
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //@formatter:off // returns InMemoryUserDetailsManagerConfigurer auth.inMemoryAuthentication() // create a UserBuilder and add to userBuilders .withUser("user").password("password").roles("USER") // returns InMemoryUserDetailsManagerConfigurer .and() // create a UserBuilder again and add to userBuilders .withUser("admin").password("password").roles("USER", "ADMIN"); //@formatter:on }
(4)authenticationManagerBean()
咱們覆蓋了configure(AuthenticationManagerBuilder auth)後,咱們使用了AuthenticationManagerBuilder 的實現類DefaultPasswordEncoderAuthenticationManagerBuilder,經過InMemoryUserDetailsManagerConfigurer建立本身的UserDetailsService的實現類InMemoryUserDetailsManager及User,系統還會默認給咱們建立AuthenticationProvider的實現類DaoAuthenticationProvider。可是咱們發現,這些對象並非Spring Bean。因此咱們能夠經過覆蓋該方法而且聲明爲一個Bean,這樣就能夠在項目中注入並使用這個Bean了。
@Bean(name = "myAuthenticationManager") @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
經過父類的源碼能夠看到,實際上在調用時,建立了一個AuthenticationManager代理。
public AuthenticationManager authenticationManagerBean() throws Exception { return new AuthenticationManagerDelegator(authenticationBuilder, context); }
(5)userDetailsServiceBean()
和(4)相似,Override this method to expose a UserDetailsService created from configure(AuthenticationManagerBuilder) as a bean. In general only thefollowing override should be done of this method:
@Bean(name = "myUserDetailsService") @Override public UserDetailsService userDetailsServiceBean() throws Exception { return super.userDetailsServiceBean(); }
(6) UserDetailsService
還記得第三章的UserDetailsService實現類是如何生成的嗎?這裏作一個簡述:
[1] AuthenticationConfiguration中建立InitializeUserDetailsBeanManagerConfigurer Bean。
[2] build時調用InitializeUserDetailsBeanManagerConfigurer的內部類InitializeUserDetailsManagerConfigurer的configure()方法。
[3] 在ApplicationContext中獲取UserDetailsService(by type),若是沒有找到自定義的UserDetailsService Bean,則UserDetailsServiceAutoConfiguration生效,會lazy load一個InMemoryUserDetailsManager;反之,則使用咱們自定義的UserDetailsService Bean。
在WebSecurityConfigurerAdapter中,userDetailsServiceBean()和userDetailsService()兩個方法內容實際上都是同樣的。都是獲取當前環境中(自定義的或系統生成的InMemoryUserDetailsManager)的UserDetailsService的代理類。因此,該類通常不須要重寫,若是想自定義本身的UserDetailsService,能夠直接實現UserDetailsService接口,而且把該類聲明爲一個Spring Bean:
@Service public class MyUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // TODO Auto-generated method stub return null; } }
固然你也能夠直接覆蓋該方法並聲明爲一個Bean:
@Bean @Override public UserDetailsService userDetailsService() { return (username) -> { AppUser user = appUserRepository.findOneByUsername(username); if (user == null) { throw new UsernameNotFoundException("Incorrect username or password."); } return user; }; }
須要注意的是,咱們也要有UserDetails的實現類供UserDetailsService處理。