進階-使用Spring Security3.2搭建LDAP認證受權和Remember-me(2)

接上 進階-使用Spring Security3.2搭建LDAP認證受權和Remember-me(1) html

javaconfig

使用javaconfig,只須要生成兩個類,就能夠完成XML配置下的3個步驟。這兩個類非別是:
java

  1. 繼承於org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter的一個子類。 web

  2. 繼承於org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer的子類。ajax

原理以下:
spring

SpringServletContainerInitializer實現了servlet 3中的一個規範接口javax.servlet.ServletContainerInitializer. 一旦實現了這個接口,當web container啓動時,就會自動加載SpringServletContainerInitializer. 而SpringServletContainerInitializer會調用AbstractSecurityWebApplicationInitializer類。以上的步驟完成了至關於SpringSecurityFilterChain的配置。數據庫

接下來須要配置SpringSecurity,AbstractSecurityWebApplicationInitializer會爲Spring指定配置文件,但這個配置文件不是XML形式 ,而是java形式。而java形式的配置則爲WebSecurityConfigurerAdapter的子類。瀏覽器

以下面的小例子:tomcat

//注意java annotation的使用。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //用於配置Authentication,好比LDAP, Database鏈接,以及用戶和角色的查詢方法。
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {}
    //用於配置URL的保護形式,和login頁面。
    @Override
    public void configure(HttpSecurity http) throws Exception {}
    //用於配置相似防火牆,放行某些URL。
    @Override
    public void configure() {WebSecurity web}
}

public class SecurityWebApplicationInitializer
      extends AbstractSecurityWebApplicationInitializer {
    public SecurityWebApplicationInitializer() {
        //註冊Spring的配置文件。
        super(SecurityConfig.class);
    }
}

在3.2中,WebSecurityConfigurerAdapter使用三個configure方法,用於配置authentication, authorization和web security. 咱們能夠聲明一個WebSecurityConfigurerAdapter,也能夠聲明多個.下面用咱們項目中的實例來說解這些內容。服務器

回到firstWeb項目,咱們須要生成兩個類。能夠把這兩個類放在包com.mycompany.my.security下面。cookie

MultiHttpSecurityConfig.java

@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfig {
    
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource("ldap://127.0.0.1:389/dc=mycompany,dc=com");
        contextSource.setUserDn("cn=admin,dc=mycompany,dc=com");
        contextSource.setPassword("admin");
        contextSource.afterPropertiesSet();

        BindAuthenticator authenticator = new BindAuthenticator(contextSource);
        authenticator.setUserDnPatterns(new String[] { "uid={0},ou=people" });

        DefaultLdapAuthoritiesPopulator populator = new DefaultLdapAuthoritiesPopulator(
                contextSource, "ou=groups");
        populator.setGroupRoleAttribute("cn");
        populator.setGroupSearchFilter("uniqueMember={0}");

        AuthenticationProvider authProvider = new LdapAuthenticationProvider(
                authenticator, populator);
        auth.authenticationProvider(authProvider);
    }
    
    @Configuration
    @Order(1)
    public static class IndexSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/index.jsp").anonymous();
        }
    }
    
    @Configuration
    @Order(2)
    public static class HtmlSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/html/**")
                .authorizeRequests()
                    .antMatchers("/html/submit.jsp").hasRole("BLACK")
                    .antMatchers("/html/forbidden.html").authenticated()
                .and().formLogin()
                    .loginPage("/html/login.jsp")
                    .loginProcessingUrl("/html/login")
                    .defaultSuccessUrl("/index.jsp")
                    .permitAll()
                .and().logout().logoutUrl("/html/logout")
                .and().exceptionHandling().accessDeniedPage("/html/403.jsp");
        }

        @Override
        public void configure(WebSecurity web) {
            web.ignoring().antMatchers("/html/forbidden.html");
        }
    }
    
    @Configuration
    @Order(3)
    public static class AjaxSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
               .antMatcher("/ajax/**")
               .authorizeRequests().anyRequest().hasRole("RED")
               .and()
               .httpBasic();
        }
    }
}

SecurityWebApplicationInitializer.java

public class SecurityWebApplicationInitializer
      extends AbstractSecurityWebApplicationInitializer {

    public SecurityWebApplicationInitializer() {
        super(MultiHttpSecurityConfig.class);
    }
}

在上面的代碼中,我配置了三個service config. 他們使用共同的authentication, 關於authentication的代碼都在configureGlobal方法中。LDAP仍是使用我以前配置的OpenLDAP.

三個service config分別的解釋:

  1. IndexSecurityConfig, 配置index.jsp能夠被全部人訪問。看似多餘,由於不聲明也能夠被全部的人訪問。 可是這樣能夠對index.jsp開啓全部的Filter,好比CSRFFilter。index.jsp中有Form,因此,須要開啓CSRF。

        @Configuration
        @Order(1)
        public static class IndexSecurityConfig extends WebSecurityConfigurerAdapter {
            @Override
            public void configure(HttpSecurity http) throws Exception {
                http.antMatcher("/index.jsp").anonymous();
            }
        }
  2. HtmlSecurityConfig, 配置html路徑下的FBA。 這裏將重點講解。講解放到code的註釋中。注意,一切/xxx開始的路徑,均是指相對於context path。好比/html/login, 它真正的url應該是/context/html/login.

        @Configuration //聲明這是一個SpringSecurity config
        @Order(2) //聲明這個config使用的順序。Spring會按照順序進行匹配,一旦匹配,則越事後面。
        public static class HtmlSecurityConfig extends WebSecurityConfigurerAdapter {
            @Override
            public void configure(HttpSecurity http) throws Exception {
                http.antMatcher("/html/**")  //後面的配置適用於${context}/html/下的全部路徑。
                    .authorizeRequests() //對下面的幾個URL進行受權
                                         //${context}/html/submit.jsp只能Black role纔可訪問
                                         //${context}/html/forbidden.html認證過的人才可訪問。
                        .antMatchers("/html/submit.jsp").hasRole("BLACK") 
                        .antMatchers("/html/forbidden.html").authenticated()
                    .and().formLogin()  //配置FBA
                        .loginPage("/html/login.jsp") //指定登錄頁面,若是未認證訪問保護資源,
                                                      //則跳轉到此頁面。
                        .loginProcessingUrl("/html/login") //此爲login Form提交的URL.
                                                           //相似於j_security_check
                        .defaultSuccessUrl("/index.jsp") //登錄成功之後轉到哪個頁面。
                                                         //若是由於Get訪問受保護資源而跳轉到
                                                         //login頁面,登錄成功後會轉到受保護的資源
                                                         //默認登錄失敗url爲login page?error
                                                         //也能夠經過方法指定。
                        .permitAll() //讓login.jsp人人可訪問,不然會致使遞歸跳轉。
                    .and().logout().logoutUrl("/html/logout") //配置logout的處理URL.
                                                            //能夠配置logout成功後跳轉的頁面
                                                         //若是沒有配置,則跳轉到loginpage?logout
                    .and().exceptionHandling()  //配置exception處理頁面。
                        .accessDeniedPage("/html/403.jsp");
            }
    
            //配置放行的路徑。放行/html/forbidden.html, 即便上面聲明其爲保護資源。
            @Override
            public void configure(WebSecurity web) {
                web.ignoring().antMatchers("/html/forbidden.html");
            }
        }
  3. AjaxSecurityConfig,則是配置ajax目錄下面的Basic Authentication. 因爲看了上一個配置的詳解,這個配置就簡單多了。很少說。

到此爲止。FBA和BA均已配置完畢。將firstWeb打包,部署到tomcat上,查看每一個路徑是否符合咱們的配置。

CSRF配置

javaconfig默認會開啓CSRF,若是想關閉,能夠調用http.csrf().disable(). 若是使用XML配置,默認是關閉CSRF,須要CSRF則須要聲明<CSRF/>.

在開啓CSRF之後,網站的任何FORM POST都必須帶有CSRF的token,若是缺失token的話,則沒法提交FORM。在每一個FORM中,咱們可使用下面一段代碼來帶上CSRF的token.

<input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/>

試想,若是一個URL在GET的時候,沒有通過CSRF filter的話,就不會有_csrf.token產生,這就是爲何我在上面要對不須要保護的index.jsp也聲明瞭一個javaconfig的緣由,這使得index.jsp會受到SpringSecurityFilterChain的過濾。

未登錄的狀況下進行FORM POST

若是用戶沒有登錄,好比他在寫博客。當他寫完,提交FORM到受保護的頁面時,須要FBA認證。在SpringSecurity中,若是沒有通過特殊處理,FORM提交會轉到login頁面,當用戶登錄成功之後,會跳轉到默認頁面,以前填寫的FORM會丟失掉。 這會使人抓狂。

通常的處理方式有2種:

  1. 在用戶填寫完FORM之後,點擊提交按鈕之時,瀏覽器會先使用AJAX向服務器詢問用戶是否有權限,若是沒有,則在當前頁面動態的生成一個Warning message,告訴用戶尚未登錄。

  2. 在用戶填寫完FORM,點擊按鈕後,一樣仍是AJAX詢問服務器是否登陸,若是沒有,則彈出一個AJAX方式的用戶登錄dialog,用戶在dialog中輸入帳戶,登錄成功後,從新提交FORM.

在本實驗中,index.jsp想submit.jsp提交FORM就存在未登錄的狀況,感興趣的同窗能夠根據上面兩種方式,改進其登錄方式。我就懶的去寫了。

Remember Me功能

Remember Me的理論很簡單。服務器端將用戶名,加密事後的密碼,和一個過時時間打包在一塊兒,生成一個base64 token,寫入客戶端的cookie中。當用戶下次訪問的時候,能夠從cookie中讀取remember me的cookie,經過cookie,能夠找到用戶的名字和加密過的密碼。系統經過UserDetailsService訪問用戶的全部信息,將cookie中的信息和UserDetailsService拿到的用戶信息進行比對,其中密碼進行加密後的比對,比對成功,則完成自動登陸。

Spring Security 3中的remember me功能提供了默認實現。但它也存在幾個要求:

  1. 在login Form中寫入<input type="checkbox" name="remember-me">

  2. AuthenticationManager必須實現了UserDetailsService. 或爲RememberMeAuthenticationProvider指定UserDetailsService.

  3. XML或javaconfig中配置remember me.

接下來,我將開始配置Remember me. Token信息存儲數據庫就不配置了,Spring自帶機制。Remember me將記住帳戶14天。

  1. 在login Form中添加代碼

    <input type="checkbox" name="remember-me"></td><td>Don't ask for my password for two weeks</td>
  2. 在類HttpServiceConfig的方法configure(HttpSecurity http)中,對http的配置最後一行添加一段新的代碼。

        @Configuration
        @Order(2)
        public static class HtmlSecurityConfig extends WebSecurityConfigurerAdapter {
            @Override
            public void configure(HttpSecurity http) throws Exception {
                http.......
                    .and().exceptionHandling().accessDeniedPage("/html/403.jsp")
                    .and().rememberMe().tokenValiditySeconds(14*24*60*60);
            }
  3. 爲AuthManager添加UserDetailsService。修改以前configureGlobal方法中的代碼,添加UserDetailsService部分。

            @Autowired
    	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    		......
    		LdapAuthenticationProvider authProvider = new LdapAuthenticationProvider(
    				authenticator, populator);
    		FilterBasedLdapUserSearch userSearch = new FilterBasedLdapUserSearch("ou=people","(uid={0})",contextSource);		
    		LdapUserDetailsService userDetailsService = new LdapUserDetailsService(userSearch, populator);
    		//auth.authenticationProvider(authProvider);
    		//Will use DaoAuthenticationProvider.
    		auth.userDetailsService(userDetailsService);
    	}
  4. 到此remember me就成功了。從新部署,測試一下吧。

集羣與SSO

請看下一篇文章。

相關文章
相關標籤/搜索