9.3 拦截请求

在前面的 9.1.3 小节中,我们看到一个特别简单的 Spring Security 配置,在这个默认的配置中,会要求所有请求都要经过认证。有些人可能会说,过多的安全性总比安全性太少要好。但也有一种说法就是要适量地应用安全性。

在任何应用中,并不是所有的请求都需要同等程度地保护。有些请求需要认证,而另一些可能并不需要。有些请求可能只有具备特定权限的用户才能访问,没有这些权限的用户会无法访问。

例如,考虑 Spittr 应用的请求。首页当然是公开的,不需要进行保护。类似地,因为所有的 Spittle 都是公开的,所以展现 Spittle 的页面不需要安全性。但是,创建 Spittle 的请求只有认证用户才能执行。同样,尽管用户基本信息页面是公开的,不需要认证,但是,如果要处理 /spitters/me 请求,并展现当前用户的基本信息时,那么就需要进行认证,从而确定要展现谁的信息。

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

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/spitters/me").authenticated()
.antMatchers(HttpMethod.POST, "/spittles").authenticated()
.anyRequest().permitAll();
}

configure() 方法中得到的 HttpSecurity 对象可以在多个方面配置 HTTP 的安全性。在这里,我们首先调用 authorizeRequests(),然后调用该方法所返回的对象的方法来配置请求级别的安全性细节。其中,第一次调用 antMatchers() 指定了对 /spitters/me 路径的请求需要进行认证。第二次调用 antMatchers() 更为具体,说明对 /spittles 路径的 HTTP POST 请求必须要经过认证。最后对 anyRequests() 的调用中,说明其他所有的请求都是允许的,不需要认证和任何的权限。

antMatchers() 方法中设定的路径支持 Ant 风格的通配符。在这里我们并没有这样使用,但是也可以使用通配符来指定路径,如下所示:

.antMatchers("/spitter/**").authenticated();

我们也可以在一个对 antMatchers() 方法的调用中指定多个路径:

.antMatchers("/spitter/**", "/spittles/mine").authenticated();

antMatchers() 方法所使用的路径可能会包括 Ant 风格的通配符,而 regexMatchers() 方法则能够接受正则表达式来定义请求路径。例如,如下代码片段所使用的正则表达式与 /spitters/**(Ant风格)功能是相同的:

.regexMatchers("/spitter/.*").authenticated();

除了路径选择,我们还通过 authenticated() 和 permitAll() 来定义该如何保护路径。authenticated() 要求在执行该请求时,必须已经登录了应用。如果用户没有认证的话,Spring Security 的 Filter 将会捕获该请求,并将用户重定向到应用的登录页面。同时,permitAll() 方法允许请求没有任何的安全限制。 除了 authenticated() 和 permitAll() 以外,还有其他的一些方法能够用来定义该如何保护请求。表 9.4 描述了所有可用的方案。

方法

能够做什么

access(String)

如果给定的 SpEL 表达式计算结果为 true,就允许访问

anonymous()

允许匿名用户访问 authenticated() 允许认证过的用户访问

denyAll()

无条件拒绝所有访问

fullyAuthenticated()

如果用户是完整认证的话(不是通过Remember-me 功能认证的),就允许访问

hasAnyAuthority(String...)

如果用户具备给定权限中的某一个的话,就允许访问

hasAnyRole(String...)

如果用户具备给定角色中的某一个的话,就允许访问

hasAuthority(String)

如果用户具备给定权限的话,就允许访问

hasIpAddress(String)

如果请求来自给定 IP 地址的话,就允许访问

hasRole(String)

如果用户具备给定角色的话,就允许访问

not()

对其他访问方法的结果求反

permitAll()

无条件允许访问

rememberMe()

如果用户是通过 Remember-me 功能认证的,就允许访问

通过使用表 9.4 中的方法,我们所配置的安全性能够不仅仅限于认证用户。例如,我们可以修改之前的 configure() 方法,要求用户不仅需要认证,还要具备 ROLE_SPITTER 权限:

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/spitters/me").hasAuthority("ROLE_SPITTER")
.antMatchers(HttpMethoed.POST, "/spitters").hasAuthority("ROLE_SPITTER")
.anyRequest().permitAll();
}

作为替代方案,我们还可以使用 hasRole() 方法,它会自动使用 ROLE_ 前缀:

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/spitters/me").hasRole("SPITTER")
.antMatchers(HttpMethoed.POST, "/spitters").hasRole("SPITTER")
.anyRequest().permitAll();
}

我们可以将任意数量的 antMatchers()、regexMatchers() 和 anyRequest() 连接起来,以满足 Web 应用安全规则的需要。但是,我们需要知道,这些规则会按照给定的顺序发挥作用。所以,很重要的一点就是将最为具体的请求路径放在前面,而最不具体的路径(如 anyRequest())放在最后面。如果不这样做的话,那不具体的路径配置将会覆盖掉更为具体的路径配置。