进阶-使用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

请看下一篇文章。

相关文章
相关标签/搜索