首頁 > 軟體

Spring Security設定保姆級教學

2023-02-16 06:02:24

背景

筆者使用 Spring Security 5.8 時,發現網上很多教學所教的 Spring Security 設定類 SecurityConfig.java 的設定風格還是停留在繼承 WebSecurityConfigurerAdapter 的風格。然而, WebSecurityConfigurerAdapter 在 Spring Security 5.7.0-M2 版本中已經被 deprecated 了。因此在本文中分享 Spring 官方最新推薦的 Spring Security 設定風格。

一、前言

在 Spring Security 5.7.0 (2022 年 2 月 21 日更新) 中,官方棄用了 WebSecurityConfigurerAdapter 。因為Spring 官方鼓勵開發者朝著元件化安全設定遷移。為了幫助開發者順利過渡到這種設定風格,Spring 官方準備了一系列常見的使用案例和建議的替代方案。

在下面的例子中,我們將遵循最佳實踐,使用 Spring Security lambda DSL 和 HttpSecurity.java 中的 authorizeHttpRequests() 方法來定義授權規則。如果您對 lambda DSL 感到陌生,可以參考我的這篇文章進行學習:《如何使用Lambda DSL設定Spring Security》。

二、設定HttpSecurity

在 Spring Security 5.4 中,新增了通過建立一個 SecurityFilterChain 的 Bean 來設定 HttpSecurity 的功能。

首先來看看沒有棄用 WebSecurityConfigurerAdapter 的範例,下面是使用 WebSecurityConfigurerAdapter 的設定範例,該設定使用 httpBasic() 方法來保護所有的介面。

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authz) -> authz
                .anyRequest().authenticated()
            )
            .httpBasic(withDefaults());
    }
}

但今後, WebSecurityConfigurerAdapter 就被 Spring 官方棄用了,取而代之的是通過註冊一個 SecurityFilterChain 的 Bean 來設定 HttpSecurity

@Configuration
public class SecurityConfiguration {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authz) -> authz
                .anyRequest().authenticated()
            )
            .httpBasic(withDefaults());
        return http.build();
    }
}

三、設定WebSecurity

在 Spring Security 5.4 中,Spring 官方新增了 WebSecurityCustomizer

WebSecurityCustomizer 是一個回撥介面,可以用來設定 WebSecurity

首先來看看先前的舊設定風格的程式碼範例:使用 WebSecurityConfigurerAdapter 來忽略與 /ignore1/ignore2 匹配的請求 (即不攔截這兩個請求進行認證授權) 。

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/ignore1", "/ignore2");
    }
}

但今後,Spring 官方不再推薦上面的設定風格,取而代之的是向 Spring 容器中注入一個 WebSecurityCustomizer 的 Bean 。

@Configuration
public class SecurityConfiguration {
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
    	return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
    }
}

注意:如果你想通過設定 WebSecurity 來忽略請求,建議優先考慮通過設定 HttpSecurityauthorizeHttpRequests() 方法,使用 .permitAll() 來實現。

四、設定LDAP認證

LDAP (Light Directory Access Protocol) ,是基於 X.500 標準的輕量級目錄存取協定。有興趣的同學可以自行谷歌搜尋學習,LDAP 這種協定常用於授權認證的情景。

在 Spring Security 5.7 中,Spring 官方新增了 EmbeddedLdapServerContextSourceFactoryBeanLdapBindAuthenticationManagerFactoryLdapPasswordComparisonAuthenticationManagerFactory ,用來建立一個嵌入式 LDAP 伺服器和一個 AuthenticationManager 物件,來執行 LDAP 認證。

同樣的,先來看先前舊的設定風格的範例,繼承 WebSecurityConfigurerAdapter ,建立一個嵌入式 LDAP 伺服器,建立一個 AuthenticationManager ,使用繫結認證 (Bind Authentication) 來執行 LDAP 認證。

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .ladpAuthentication()
            .userDetailsContextMapper(new PersonContextMapper())
            .userDnPatterns("uid={0}, ou=people")
            .contextSource()
            .port(0);
    }
}

但今後,Spring 官方不再推薦上面的設定風格,取而代之的是使用新的 LDAP 類。

@Configuration
public class SecurityConfiguration {
    @Bean
    public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
        EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean =
            EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
        contextSourceFactoryBean.setPort(0);
        return contextSourceFactoryBean;
    }
    @Bean
    AuthenticationManager ldapAuthenticationManager(
        	BaseLdapPathContextSource contextSource) {
        LdapBindAuthenticationManagerFactory factory = 
            new LdapBindAuthenticationManagerFactory(contextSource);
        factory.setUserDnPatterns("uid={0}, ou=people");
        factory.setUserDetailsContextMapper(new PersonContextMapper());
        return factory.createAuthenticationManager();
    }
}

五、設定JDBC認證

同樣的,先來看先前舊的設定風格的範例,繼承 WebSecurityConfigurerAdapter ,使用一個以預設方式初始化且具有一個使用者的嵌入式 DataSource

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();
        auth.jdbcAuthentication()
            .withDefaultSchema()
            .dataSource(dataSource())
            .withUser(user);
    }
}

但今後,Spring 官方不再推薦上面的設定風格,取而代之的是向 Spring 容器注入一個 JdbcUserDetailsManager 的 Bean 。

@Configuration
public class SecurityConfiguration {
    @Bean
    public DataSource datasource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript(JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATION)
            .build();
    }
    @Bean
    public UserDetailsManager users(DataSource dataSource) {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();
        JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
        users.createUser(user);
        return users;
    }
}

注意:上面的程式碼範例第 14 行中,為了密碼的可讀性才使用方法 User.withDefaultPasswordEncoder() 。在實際的生產環境中並不會使用預設的密碼編碼器,因為這樣儲存的密碼是明文,十分不安全。這裡推薦使用 BCryptPasswordEncoder 作為密碼編碼器來對密碼進行加密成暗文儲存。

六、In-Memory Authentication

與上面儲存在資料庫的不同,In-Memory 是把使用者資訊儲存在記憶體中。

同樣的,先來看先前舊的設定風格的範例,繼承 WebSecurityConfigurerAdapter 來設定儲存在記憶體中的一個使用者資訊。

@Configuration
public class SecurityConfiguration extends WebSecutiryConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();
        auth.inMemoryAuthentication()
            .withUser(user);
    }
}

但今後,Spring 官方不再推薦上面的設定風格,取而代之的是向 Spring 容器注入一個 InMemoryUserDetailsManager 的 Bean 。

@Configuration
public class SecurityConfiguration {
    @Bean
    public InMemoryUserDetailsManager userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();
        return new InMemoryUserDetailsManager(user);
    }
}

注意:上面的程式碼範例第 6 行中,為了密碼的可讀性才使用方法 User.withDefaultPasswordEncoder() 。在實際的生產環境中並不會使用預設的密碼編碼器,因為這樣儲存的密碼是明文,十分不安全。這裡推薦使用 BCryptPasswordEncoder 作為密碼編碼器來對密碼進行加密成暗文儲存。

七、設定全域性AuthenticationManager

如果你想要建立整個應用都可以呼叫的 AuthenticationManager 物件,只需要簡單地使用註解 @Bean 來注入 Spring 容器。

設定風格範例與 LDAP 的設定範例相同。

八、設定區域性AuthenticationManager

在 Spring Security 5.6 中,Spring 官方在 HttpSecurity 中新增了方法 authenticationManager() ,該方法重寫具體的 SecurityFilterChain 的預設 AuthenticationManager

下面是設定自定義區域性 AuthenticationManager 的程式碼範例。

@Configuration
public class SecurityConfiguration {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authz) -> authz
               	.anyRequest().authticated())
            .httpBasic(withDefaults())
            .authenticationManager(new CustomAuthenticationManager());
        return http.build();
    }
}

九、呼叫區域性AuthenticationManager

可以使用 custom DSL 來呼叫區域性 AuthenticationManager 。這實際上正是 Spring Security 內部實現諸如 HttpSecurity.authorizeRequests() 方法的方式。

public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
        http.addFilter(new CustomFilter(authenticationManager));
    }
    public static MyCustomDsl customDsl() {
        return new MyCustomDsl();
    }
}

當Spring Security開始構建 SecurityFilterChain 時,custom DSL 就會被自動呼叫。

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // ...
    http.apply(customDsl());
    return http.build();
}

到此這篇關於Spring Security設定保姆級教學的文章就介紹到這了,更多相關Spring Security設定內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com