Spring Security(4):自定義配置

接着上節的講,在添加了@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處理。

相關文章
相關標籤/搜索