Spring实战第九章:保护Web应用

一、Spring Security简介

1、一种基于 Spring AOP 和 Servlet 规范中的 Filter 实现的安全框架,能够在Web请求级别和方法调用级别处理身份认证和授权。

Spring Security充分利用了依赖注入和面向切面技术。

2、Spring Security(3.2) 从两个角度来解决安全性问题:

  • 使用 Servlet 规范中的Filter保护Web请求并限制URL级别的访问;
  • 使用 Spring AOP 保护方法调用 —— 借助于对象代理和使用通知,能够确保只有具备适当权限的用户才能访问安全保护的方法;

二、理解Spring Security的模块

Security 3.2分为11个模块,如下表:

模块描述
ACL支持通过访问控制列表(access control list, ACL)为域对象提供安全性
切面(Aspects)一个很小的模块,当使用Spring Security注解时,会使用基于AspectJ的切面,而不是使用标准的Spring AOP
CAS客户端(CAS Client)提供与Jasig的中心认证服务(Central Authentication Service, CAS)进行集成的功能
配置(Configuration)包含通过XML和Java配置Spring Security的功能支持
核心(Core)提供Spring Security基本库
加密(Cryptography)提供了加密和密码编码的功能
LDAP支持基于LDAP进行认证
OpenID支持使用OpenID进行集中式认证
Remoting提供了对Spring Remoting的支持
标签库(Tag Library)Spring Security的JSP标签库
Web提供了Spring Security基于Filter的Web安全性支持

应用的类路径下至少要包含Core和Configuration两个模块,如应用到Web应用中,还需要添加Web模块

 

三、代码编写

1、配置过滤器

Spring Security借助一系列Servlet Filter来提供各种安全性功能。此处只需配置DelegatingFilterProxy;

DelegatingFilterProxy是一个特殊的Servlet Filter,它本身所做的工作并不多。只是将工作委托给一个javax.servlet.Filter实现类,这个实现类作为一个<bean>注册在Spring应用的上下文中。

(1)xml方式配置

<!-- 在web.xml中进行如下配置-->
<filter>
     <filter-name>springSecurityFilterChain</filter-name>
     <filter-class>
          org.springframework.web.filter.DelegatingFilterProxy
     </filter-class>
</filter>

(2)java方式

/*
AbstractSecurityWebApplicationInitializer实现了WebApplicationInitializer,
因此Spring会发现它,并用它在Web容器中注册DelegatingFilterProxy。
尽管我们可以重载它的appendFilters()或insertFilters()方法来注册自己选择的Filter,
但是要注册DelegatingFilterProxy的话,我们并不需要重载任何方法。
*/
public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer {
}

不管我们通过web.xml还是通过AbstractSecurityWebApplicationInitializer的子类来配置DelegatingFilterProxy,它都会拦截发往应用中的请求,并将请求委托给ID为springSecurityFilterChain bean。

 

2、启用安全性配置

/*
Spring Security必须配置在一个实现了WebSecurityConfigurer的bean中,或者扩展WebSecurityConfigurerAdapter。
在Spring应用上下文中,任何实现了WebSecurityConfigurer的bean都可以用来配置Spring Security;
*/
@Configuration
@EnableWebSecurity   // 启用任意Web的安全功能
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}

如果应用是使用SpringMVC开发的,使用@EnableWebMvcSecurity代替; 

  • 能配置一个Spring MVC参数解析器。那样处理器方法就能够通过带有@AuthenticationPrincipal注解的参数获取得认证用户的principal。
  • 同时还配置一个bean,在使用Spring表单绑定标签库来定义表单时,这个bean会自动添加一个隐藏的跨站请求伪造(CSRF)token的输入域。

以上@EnableWebSecurity和@EnableWebMvcSecurity的配置都会将应用严格锁定,导致没有人能够进入系统。(没有重载configure方法定义安全细节)

为了指定Web安全的细节,可以通过重载WebSecurityConfigurerAdapter中的方法来实现。 

方法描述
configure(WebSecurity)通过重载,配置Spring Security的Filter链
configure(HttpSecurity)通过重载,配置如何通过拦截器保护请求
configure(AuthenticationManagerBuilder)通过重载,配置user-detail服务

  默认的configure(HttpSecurity)实际上等同于如下所示:

/*
此默认配置指定了该如何保护HTTP请求,以及客户端认证用户的方案;
authorizeRequests()和anyRequest().authenticated()就会要求所有进入应用的HTTP请求都要进行认证。
*/
protected void configure(HttpSecurity http) throws Exception {
   http.authorizeRequests()
       .anyRequest().authenticated()
       .and()
       .formLogin() // 设置基于表单的登录
       .and()
       .httpBasic(); // HTTP Basic方式认证
}
/*
因为没有重载configure(AuthenticationManagerBuilder)方法,所以没有用户存储支撑认证过程。
没有用户存储,实际上就等于没有用户。所以在这里所有的请求都需要认证,但是没有人能够登录成功。
*/

还需要添加配置:

  • 配置用户储存;
  • 指定哪些请求需要认证,哪些请求不需要认证,以及所需要的权限;
  • 提供一个自定义的登录页面,替代原来简单的默认登录页。

 

3、配置用户存储

(1)基于内存的用户存储

配置用户存储的最简单方式就是重载configure()方法,并以AuthenticationManagerBuilder作为传入参数。AuthenticationManagerBuilder有多个方法可以用来配置Spring Security对认证的支持。通过inMemoryAuthentication()方法,我们可以启动、配置并任意填充基于内存的用户存储。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()  // 启动内存用户存储
                .withUser("user").password("password").roles("USER").and()
                .withUser("admin").password("password").roles("USER","ADMIN");
        // roles("USER")等同于authorities("ROLE_USER")
    }
}
方法描述
accountExpired(boolean)定义账号是否已经过期
accountLocked(boolean)定义账号是否已经锁定
and()用来连接配置
authorities(GrantedAuthority...)授予某个用户一项或多项权限
authorities(List<? extends GrantedAuthority>)授予某个用户一项或多项权限
authorities(String...)授予某个用户一项或多项权限
credentialsExpired(boolean)定义凭证是否已经过期
disabled(boolean)定义账号是否已经被禁用
password(String)定义用户的密码
roles(String...)授予某个用户一项或多项角色

  (2)基于数据库表进行认证

当用户数据存储在DB中,可以使用jdbcAuthentication()方法进行配置;

/* 这种方式预期数据库中存在某些存储用户数据的表,如下是Spring Security内部当查询用户信息时的sql
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = 
        "select username,password,enabled " +
        "from users " + 
        "where username = ?";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = 
         "select username,authority " + 
         "from authorities " + 
         "where username = ?";
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY = 
         "select g.id, g.group_name, ga.authority " + 
         "from group g, group_members gm, ga.authorities ga " + 
         "where gm.username = ?"
         "and g.id = ga.group_id " + 
         "and g.id = gm.group_id";
*/
    @Autowired
    private DataSource dataSource;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
                .dataSource(dataSource);
    }

以下是改成自定义查询方式:

// 自定义查询需要注意所有查询都将用户名作为唯一的参数
@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
                .dataSource(dataSource)
                .usersByUsernameQuery(
                        "select username, password,  true  from Spitter where username=?")
                .authoritiesByUsernameQuery(
                        "select username, 'ROLE_USER' from Spitter where username=?");
    }

关于密码转码器见 P261

 

(3)其他方式见 P262

 

4、拦截请求

对每个请求进行细粒度安全性控制的关键在于重载configure(HttpSecurity)方法。如下的代码片段展现了重载的configure(HttpSecurity)方法,它为不同的URL路径有选择地应用安全性:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/spitter/me").authenticated() // 指定请求路径需要认证
                .antMatchers(HttpMethod.POST, "/spittles").authenticated() //对"/spittles"路径的HTTP POST请求必须经过认证
                .anyRequest().permitAll();// anyRequest()说明其他请求都是允许的,不需要认证和任何的权限
    }

antMatchers() 方法中设定的路径:

  • 支持Ant风格的通配符:antMatchers("/spitters/**")
  • 支持指定多个路径:antMatchers("/spitters/**", "spittles/mine")

regexMatchers()方法支持正则定义请求路径:regexMatchers("/spitters/.*")

authenticated():要求在执行该请求时,必须已经登录了应用。如果用户没有认证的话,Spring Security的Filter将会捕获该请求,并将用户重定向到应用的登录页面。

permitAll():允许请求没有任何的安全限制。

(1)使用Spring表达式进行安全保护

借助access()方法,将SpEL作为声明访问限制的一种方式:

具有“ROLE_SPITTER”角色才能访问“/spitter/me”URL:.antMatchers("/spitter/me").access("hasRole('ROLE_SPITTER')")

(2)强制通道的安全性

通过configure() 方法中的HttpSecurity对象,借助对象的requiresChannel() 方法,能够为各种为各种URL模式声明所要求的通道。

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/spitter/me").hasRole("SPITTER")
                .antMatchers(HttpMethod.POST, "/spittles").hasRole("SPITTER")
                .anyRequest().permitAll()
                .and() 
                .requiresChannel()
                .antMatchers("/spitter/form").requiresSecure();   // 需要 HTTPS
    }

requiresInsecure():声明通过HTTP通道

(3)防止跨站请求伪造

跨站请求伪造:一个站点欺骗用户提交请求到其他服务器的话,就会发生CSRF攻击,这可能会带来消极的后果

 

5、认证用户

(1)启用HTTP Basic认证

HTTP Basic认证(HTTP Basic Authentication)会直接通过 HTTP请求本身,对要访问应用程序的用户进行认证。

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/login")// 如果不设置,则默认为/login
                .and()
                .httpBasic() // 启用HTTP Basic认证
                .realmName("Spittr") // 执行域
                .and()
        ...
    }

(2)启用Remember-me功能

只要登录过一次,应用就会记住你,当再次回到应用的时候你就不需要登录了。

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
            .loginPage("/login")// 如果不设置,则默认为/login
            .and()
            .rememberMe() // 启用remember me认证(通过在cookie中存储一个token完成)
            .tokenValiditySeconds(2419200) // 指定token有效期
            .key("spittrKey"); // token在写入cookie前会MD5哈希,token中的私钥默认名称为”SpringSecured“,此处自定义为spittrKey
    }

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值