本文讨论如何在REST API的相同URI结构上设置基本身份验证和Digest身份验证。在前一篇文章中,我们讨论了另一种保护REST服务的方法 - 基于表单的身份验证,因此Basic和Digest身份验证是自然的替代方案,也是更RESTful的身份验证方法。
基本的认证配置
基于表单的身份验证不适合RESTful服务的主要原因是Spring Security将使用Sessions - 这当然是服务器上的状态,因此REST中的无状态约束几乎被忽略。
我们首先设置基本身份验证 - 首先我们从主<http>安全元素中删除旧的自定义入口点和过滤器:
<http create-session="stateless">
<intercept-url pattern="/api/admin/**" access="ROLE_ADMIN" />
<http-basic />
</http>
请注意,如何使用单个配置行添加对基本身份验证的支持 - <http-basic /> - 它处理BasicAuthenticationFilter和BasicAuthenticationEntryPoint的创建和连接。
RESTful架构风格的主要限制之一是客户端 - 服务器通信是完全无状态的,正如原始论文所示:
5.1.3 无状态:<br />接下来我们为客户端 - 服务器交互添加一个约束:通信本质上必须是无状态的,如3.4.3节(图5-3)的客户端无状态服务器(CSS)样式,这样每个请求从客户端到服务器必须包含理解请求所需的所有信息,并且不能利用服务器上任何存储的上下文。因此,会话状态完全保留在客户端上。
服务器上的Session概念在Spring Security中具有悠久的历史,并且直到现在才完全删除它,尤其是在使用命名空间完成配置时。
但是,Spring Security 使用新的无状态选项增强了命名空间配置,从而有效地保证Spring不会创建或使用任何会话。此新选项的作用是从安全筛选器链中删除所有与会话相关的筛选器,确保对每个请求执行身份验证。
从先前的配置开始,设置Digest式身份验证所需的过滤器和入口点将被定义为bean。然后,Digest入口点将覆盖由幕后的<http-basic>创建的入口点。最后,将使用安全命名空间的语义在安全过滤器链中引入自定义Digest过滤器,以在基本身份验证过滤器之后直接定位它。
<http create-session="stateless" entry-point-ref="digestEntryPoint">
<intercept-url pattern="/api/admin/**" access="ROLE_ADMIN" />
<http-basic />
<custom-filter ref="digestFilter" after="BASIC_AUTH_FILTER" />
</http>
<beans:bean id="digestFilter" class=
"org.springframework.security.web.authentication.www.DigestAuthenticationFilter">
<beans:property name="userDetailsService" ref="userService" />
<beans:property name="authenticationEntryPoint" ref="digestEntryPoint" />
</beans:bean>
<beans:bean id="digestEntryPoint" class=
"org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint">
<beans:property name="realmName" value="Contacts Realm via Digest Authentication"/>
<beans:property name="key" value="acegi" />
</beans:bean>
<authentication-manager>
<authentication-provider>
<user-service id="userService">
<user name="eparaschiv" password="eparaschiv" authorities="ROLE_ADMIN" />
<user name="user" password="user" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
遗憾的是,安全命名空间中不支持自动配置Digest式身份验证,这与使用<http-basic>配置基本身份验证的方式相同。因此,必须定义必要的bean并手动连接到安全配置中。
在同一RESTful服务中支持两种身份验证协议
仅在Spring Security中可以轻松实现基本或Digest式身份验证; 它支持同一个RESTful Web服务,在相同的URI映射上为服务的配置和测试引入了新的复杂性。
通过安全链中的基本和Digest过滤器,Spring Security处理匿名请求(不包含身份验证凭据的请求(授权HTTP标头))的方式是 - 两个身份验证过滤器将找不到凭据并将继续执行过滤链。然后,查看请求未经过身份验证的方式,抛出AccessDeniedException并将其捕获到ExceptionTranslationFilter中,该ExceptionTranslationFilter将启动Digest入口点,提示客户端提供凭据。
基本和Digest过滤器的职责非常狭窄 - 如果他们无法识别请求中的身份验证凭据类型,他们将继续执行安全过滤器链。正因为如此,Spring Security可以灵活地配置在同一URI上支持多个身份验证协议。
当请求包含正确的身份验证凭据(基本或Digest)时,将正确使用该协议。但是,对于匿名请求,将仅提示客户端提取Digest身份验证凭据。这是因为Digest入口点被配置为Spring Security链的主要和单个入口点; 因为这样的Digest认证可以被认为是默认的。
具有基本身份验证凭据的请求将由以“Basic”前缀开头的Authorization标头标识。在处理这样的请求时,凭证将在基本认证过滤器中被解码,并且该请求将被授权。同样,具有Digest式身份验证凭据的请求将使用前缀“Digest”作为其授权标头。
在使用基本或Digest进行身份验证后,测试将通过创建新资源来使用REST服务:
@Test
public void givenAuthenticatedByBasicAuth_whenAResourceIsCreated_then201IsReceived(){
// Given
// When
Response response = given()
.auth().preemptive().basic( ADMIN_USERNAME, ADMIN_PASSWORD )
.contentType( HttpConstants.MIME_JSON ).body( new Foo( randomAlphabetic( 6 ) ) )
.post( paths.getFooURL() );
// Then
assertThat( response.getStatusCode(), is( 201 ) );
}
@Test
public void givenAuthenticatedByDigestAuth_whenAResourceIsCreated_then201IsReceived(){
// Given
// When
Response response = given()
.auth().digest( ADMIN_USERNAME, ADMIN_PASSWORD )
.contentType( HttpConstants.MIME_JSON ).body( new Foo( randomAlphabetic( 6 ) ) )
.post( paths.getFooURL() );
// Then
assertThat( response.getStatusCode(), is( 201 ) );
}
请注意,使用基本身份验证的测试无论服务器是否已经过验证都会抢先向请求添加凭据。这是为了确保服务器不需要向客户端请求凭证,因为这样,才会使用摘要凭证,这是默认值。
本文介绍了RESTful服务的Basic和Digest身份验证的配置和实现,主要使用Spring Security命名空间支持以及框架中的一些新功能。