接上 进阶-使用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就成功了。从新部署,测试一下吧。
请看下一篇文章。