接着上节的讲,在添加了@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处理。