接上 進階-使用Spring Security3.2搭建LDAP認證受權和Remember-me(1) html
使用javaconfig,只須要生成兩個類,就能夠完成XML配置下的3個步驟。這兩個類非別是:
java
繼承於org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter的一個子類。 web
繼承於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分別的解釋:
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(); } }
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"); } }
AjaxSecurityConfig,則是配置ajax目錄下面的Basic Authentication. 因爲看了上一個配置的詳解,這個配置就簡單多了。很少說。
到此爲止。FBA和BA均已配置完畢。將firstWeb打包,部署到tomcat上,查看每一個路徑是否符合咱們的配置。
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到受保護的頁面時,須要FBA認證。在SpringSecurity中,若是沒有通過特殊處理,FORM提交會轉到login頁面,當用戶登錄成功之後,會跳轉到默認頁面,以前填寫的FORM會丟失掉。 這會使人抓狂。
通常的處理方式有2種:
在用戶填寫完FORM之後,點擊提交按鈕之時,瀏覽器會先使用AJAX向服務器詢問用戶是否有權限,若是沒有,則在當前頁面動態的生成一個Warning message,告訴用戶尚未登錄。
在用戶填寫完FORM,點擊按鈕後,一樣仍是AJAX詢問服務器是否登陸,若是沒有,則彈出一個AJAX方式的用戶登錄dialog,用戶在dialog中輸入帳戶,登錄成功後,從新提交FORM.
在本實驗中,index.jsp想submit.jsp提交FORM就存在未登錄的狀況,感興趣的同窗能夠根據上面兩種方式,改進其登錄方式。我就懶的去寫了。
Remember Me的理論很簡單。服務器端將用戶名,加密事後的密碼,和一個過時時間打包在一塊兒,生成一個base64 token,寫入客戶端的cookie中。當用戶下次訪問的時候,能夠從cookie中讀取remember me的cookie,經過cookie,能夠找到用戶的名字和加密過的密碼。系統經過UserDetailsService訪問用戶的全部信息,將cookie中的信息和UserDetailsService拿到的用戶信息進行比對,其中密碼進行加密後的比對,比對成功,則完成自動登陸。
Spring Security 3中的remember me功能提供了默認實現。但它也存在幾個要求:
在login Form中寫入<input type="checkbox" name="remember-me">
AuthenticationManager必須實現了UserDetailsService. 或爲RememberMeAuthenticationProvider指定UserDetailsService.
XML或javaconfig中配置remember me.
接下來,我將開始配置Remember me. Token信息存儲數據庫就不配置了,Spring自帶機制。Remember me將記住帳戶14天。
在login Form中添加代碼
<input type="checkbox" name="remember-me"></td><td>Don't ask for my password for two weeks</td>
在類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); }
爲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); }
到此remember me就成功了。從新部署,測試一下吧。
請看下一篇文章。