通过 pattern 指定当前 intercept-url 定义应当作用于哪些 url。
可以通过 access 属性来指定 intercept-url 对应 URL 访问所应当具有的权限。access 的值是一个字符串,其可以直接是一个权限的定义,也可以是一个表达式。常用的类型有简单的角色名称定义,多个名称之间用逗号分隔,如
在上述配置中就表示 secure 路径下的所有 URL 请求都应当具有 ROLE_USER 或 ROLE_ADMIN 权限。当 access 的值是以 “ROLE_” 开头的则将会交由 RoleVoter 进行处理。
此外,其还可以是一个表达式,上述配置如果使用表达式来表示的话则应该是如下这个样子。
或者是使用 hasRole()表达式,然后中间以 or 连接,如:
需要注意的是使用表达式时需要指定 http 元素的 use-expressions=”true”。
此外,还可以指定三个比较特殊的属性值,默认情况下将使用 AuthenticatedVoter 来处理它们。IS_AUTHENTICATED_ANONYMOUSLY 表示用户不需要登录就可以访问;IS_AUTHENTICATED_REMEMBERED 表示用户需要是通过 Remember-Me 功能进行自动登录的才能访问;IS_AUTHENTICATED_FULLY 表示用户的认证类型应该是除前两者以外的,也就是用户需要是通过登录入口进行登录认证的才能访问。如我们通常会将登录地址设置为 IS_AUTHENTICATED_ANONYMOUSLY。
需求可以通过指定 intercept-url 的 requires-channel 属性来指定。requires-channel 支持三个值:http、https 和 any。any 表示 http 和 https 都可以访问。
需要注意的是当试图使用 http 请求限制了只能通过 https 访问的资源时会自动跳转到对应的 https 通道重新请求。如果所使用的 http 或者 https 协议不是监听在标准的端口上(http 默认是 80,https 默认是 443),则需要我们通过 port-mapping 元素定义好它们的对应关系。
通常我们都会要求某些 URL 只能通过 POST 请求,某些 URL 只能通过 GET 请求。这些限制 Spring Security 也已经为我们实现了,通过指定 intercept-url 的 method 属性可以限制当前 intercept-url 适用的请求方式,默认为所有的方式都可以。
method 的可选值有 GET、POST、DELETE、PUT、HEAD、OPTIONS 和 TRACE。
Spring Security 的底层是通过一系列的 Filter 来管理的,每个 Filter 都有其自身的功能,而且各个 Filter 在功能上还有关联关系,所以它们的顺序也是非常重要的。
Spring Security 已经定义了一些 Filter,不管实际应用中你用到了哪些,它们应当保持如下顺序。
接下来我们来看一下 Spring Security 给我们定义好的 FilterChain 中 Filter 对应的位置顺序、它们的别名以及将触发自动添加到 FilterChain 的元素或属性定义。下面的定义是按顺序的。
别名 | Filter 类 | 对应元素或属性 |
CHANNEL_FILTER | ChannelProcessingFilter | http/intercept-url@requires-channel |
SECURITY_CONTEXT_FILTER | SecurityContextPersistenceFilter | http |
CONCURRENT_SESSION_FILTER | ConcurrentSessionFilter | http/session-management/concurrency-control |
LOGOUT_FILTER | LogoutFilter | http/logout |
X509_FILTER | X509AuthenticationFilter | http/x509 |
PRE_AUTH_FILTER | AstractPreAuthenticatedProcessingFilter 的子类 | 无 |
CAS_FILTER | CasAuthenticationFilter | 无 |
FORM_LOGIN_FILTER | UsernamePasswordAuthenticationFilter | http/form-login |
BASIC_AUTH_FILTER | BasicAuthenticationFilter | http/http-basic |
SERVLET_API_SUPPORT_FILTER | SecurityContextHolderAwareRequestFilter | http@servlet-api-provision |
JAAS_API_SUPPORT_FILTER | JaasApiIntegrationFilter | http@jaas-api-provision |
REMEMBER_ME_FILTER | RememberMeAuthenticationFilter | http/remember-me |
ANONYMOUS_FILTER | AnonymousAuthenticationFilter | http/anonymous |
SESSION_MANAGEMENT_FILTER | SessionManagementFilter | http/session-management |
EXCEPTION_TRANSLATION_FILTER | ExceptionTranslationFilter | http |
FILTER_SECURITY_INTERCEPTOR | FilterSecurityInterceptor | http |
SWITCH_USER_FILTER | SwitchUserFilter | 无 |
DelegatingFilterProxy 是 Spring 中定义的一个 Filter 实现类,其作用是代理真正的 Filter 实现类,也就是说在调用 DelegatingFilterProxy 的 doFilter() 方法时实际上调用的是其代理 Filter 的 doFilter() 方法。其代理 Filter 必须是一个 Spring bean 对象,所以使用 DelegatingFilterProxy 的好处就是其代理 Filter 类可以使用 Spring 的依赖注入机制方便自由的使用 ApplicationContext 中的 bean。那么 DelegatingFilterProxy 如何知道其所代理的 Filter 是哪个呢?这是通过其自身的一个叫 targetBeanName 的属性来确定的,通过该名称,DelegatingFilterProxy 可以从 WebApplicationContext 中获取指定的 bean 作为代理对象。该属性可以通过在 web.xml 中定义 DelegatingFilterProxy 时通过 init-param 来指定,如果未指定的话将默认取其在 web.xml 中声明时定义的名称。
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
在上述配置中,DelegatingFilterProxy 代理的就是名为 SpringSecurityFilterChain 的 Filter。
需要注意的是被代理的 Filter 的初始化方法 init() 和销毁方法 destroy() 默认是不会被执行的。通过设置 DelegatingFilterProxy 的 targetFilterLifecycle 属性为 true,可以使被代理 Filter 与 DelegatingFilterProxy 具有同样的生命周期。
Spring Security 底层是通过一系列的 Filter 来工作的,每个 Filter 都有其各自的功能,而且各个 Filter 之间还有关联关系,所以它们的组合顺序也是非常重要的。
使用 Spring Security 时,DelegatingFilterProxy 代理的就是一个 FilterChainProxy。一个 FilterChainProxy 中可以包含有多个 FilterChain,但是某个请求只会对应一个 FilterChain,而一个 FilterChain 中又可以包含有多个 Filter。当我们使用基于 Spring Security 的 NameSpace 进行配置时,系统会自动为我们注册一个名为 springSecurityFilterChain 类型为 FilterChainProxy 的 bean(这也是为什么我们在使用 SpringSecurity 时需要在 web.xml 中声明一个 name 为 springSecurityFilterChain 类型为 DelegatingFilterProxy 的 Filter 了。),而且每一个 http 元素的定义都将拥有自己的 FilterChain,而 FilterChain 中所拥有的 Filter 则会根据定义的服务自动增减。所以我们不需要显示的再定义这些 Filter 对应的 bean 了,除非你想实现自己的逻辑,又或者你想定义的某个属性 NameSpace 没有提供对应支持等。
Spring security 允许我们在配置文件中配置多个 http 元素,以针对不同形式的 URL 使用不同的安全控制。Spring Security 将会为每一个 http 元素创建对应的 FilterChain,同时按照它们的声明顺序加入到 FilterChainProxy。所以当我们同时定义多个 http 元素时要确保将更具有特性的 URL 配置在前。
需要注意的是 http 拥有一个匹配 URL 的 pattern,未指定时表示匹配所有的请求,其下的子元素 intercept-url 也有一个匹配 URL 的 pattern,该 pattern 是在 http 元素对应 pattern 基础上的,也就是说一个请求必须先满足 http 对应的 pattern 才有可能满足其下 intercept-url 对应的 pattern。
通过前面的介绍我们知道 Spring Security 是通过 Filter 来工作的,为保证 Spring Security 的顺利运行,其内部实现了一系列的 Filter。这其中有几个是在使用 Spring Security 的 Web 应用中必定会用到的。接下来我们来简要的介绍一下 FilterSecurityInterceptor、ExceptionTranslationFilter、SecurityContextPersistenceFilter 和 UsernamePasswordAuthenticationFilter。在我们使用 http 元素时前三者会自动添加到对应的 FilterChain 中,当我们使用了 form-login 元素时 UsernamePasswordAuthenticationFilter 也会自动添加到 FilterChain 中。所以我们在利用 custom-filter 往 FilterChain 中添加自己定义的这些 Filter 时需要注意它们的位置。
FilterSecurityInterceptor 是用于保护 Http 资源的,它需要一个 AccessDecisionManager 和一个 AuthenticationManager 的引用。它会从 SecurityContextHolder 获取 Authentication,然后通过 SecurityMetadataSource 可以得知当前请求是否在请求受保护的资源。对于请求那些受保护的资源,如果 Authentication.isAuthenticated()返回 false 或者 FilterSecurityInterceptor 的 alwaysReauthenticate 属性为 true,那么将会使用其引用的 AuthenticationManager 再认证一次,认证之后再使用认证后的 Authentication 替换 SecurityContextHolder 中拥有的那个。然后就是利用 AccessDecisionManager 进行权限的检查。
我们在使用基于 NameSpace 的配置时所配置的 intercept-url 就会跟 FilterChain 内部的 FilterSecurityInterceptor 绑定。如果要自己定义 FilterSecurityInterceptor 对应的 bean,那么该 bean 定义大致如下所示:
filter-security-metadata-source 用于配置其 securityMetadataSource 属性。intercept-url 用于配置需要拦截的 URL 与对应的权限关系。
通过前面的介绍我们知道在 Spring Security 的 Filter 链表中 ExceptionTranslationFilter 就放在 FilterSecurityInterceptor 的前面。而 ExceptionTranslationFilter 是捕获来自 FilterChain 的异常,并对这些异常做处理。ExceptionTranslationFilter 能够捕获来自 FilterChain 所有的异常,但是它只会处理两类异常,AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出。如果捕获到的是 AuthenticationException,那么将会使用其对应的 AuthenticationEntryPoint 的 commence()处理。如果捕获的异常是一个 AccessDeniedException,那么将视当前访问的用户是否已经登录认证做不同的处理,如果未登录,则会使用关联的 AuthenticationEntryPoint 的 commence()方法进行处理,否则将使用关联的 AccessDeniedHandler 的 handle()方法进行处理。
AuthenticationEntryPoint 是在用户没有登录时用于引导用户进行登录认证的,在实际应用中应根据具体的认证机制选择对应的 AuthenticationEntryPoint。
AccessDeniedHandler 用于在用户已经登录了,但是访问了其自身没有权限的资源时做出对应的处理。ExceptionTranslationFilter 拥有的 AccessDeniedHandler 默认是 AccessDeniedHandlerImpl,其会返回一个 403 错误码到客户端。我们可以通过显示的配置 AccessDeniedHandlerImpl,同时给其指定一个 errorPage 使其可以返回对应的错误页面。当然我们也可以实现自己的 AccessDeniedHandler。
在上述配置中我们指定了 AccessDeniedHandler 为 AccessDeniedHandlerImpl,同时为其指定了 errorPage,这样发生 AccessDeniedException 后将转到对应的 errorPage 上。指定了 AuthenticationEntryPoint 为使用表单登录的 LoginUrlAuthenticationEntryPoint。此外,需要注意的是如果该 filter 是作为自定义 filter 加入到由 NameSpace 自动建立的 FilterChain 中时需把它放在内置的 ExceptionTranslationFilter 后面,否则异常都将被内置的 ExceptionTranslationFilter 所捕获。
在捕获到 AuthenticationException 之后,调用 AuthenticationEntryPoint 的 commence() 方法引导用户登录之前,ExceptionTranslationFilter 还做了一件事,那就是使用 RequestCache 将当前 HttpServletRequest 的信息保存起来,以至于用户成功登录后需要跳转到之前的页面时可以获取到这些信息,然后继续之前的请求,比如用户可能在未登录的情况下发表评论,待用户提交评论的时候就会将包含评论信息的当前请求保存起来,同时引导用户进行登录认证,待用户成功登录后再利用原来的 request 包含的信息继续之前的请求,即继续提交评论,所以待用户登录成功后我们通常看到的是用户成功提交了评论之后的页面。Spring Security 默认使用的 RequestCache 是 HttpSessionRequestCache,其会将 HttpServletRequest 相关信息封装为一个 SavedRequest 保存在 HttpSession 中。
SecurityContextPersistenceFilter 会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好的 SecurityContextRepository,同时清除 SecurityContextHolder 所持有的 SecurityContext。在使用 NameSpace 时,Spring Security 默认会给 SecurityContextPersistenceFilter 的 SecurityContextRepository 设置一个 HttpSessionSecurityContextRepository,其会将 SecurityContext 保存在 HttpSession 中。此外 HttpSessionSecurityContextRepository 有一个很重要的属性 allowSessionCreation,默认为 true。这样需要把 SecurityContext 保存在 session 中时,如果不存在 session,可以自动创建一个。也可以把它设置为 false,这样在请求结束后如果没有可用的 session 就不会保存 SecurityContext 到 session 了。SecurityContextRepository 还有一个空实现,NullSecurityContextRepository,如果在请求完成后不想保存 SecurityContext 也可以使用它。
这里再补充说明一点为什么 SecurityContextPersistenceFilter 在请求完成后需要清除 SecurityContextHolder 的 SecurityContext。SecurityContextHolder 在设置和保存 SecurityContext 都是使用的静态方法,具体操作是由其所持有的 SecurityContextHolderStrategy 完成的。默认使用的是基于线程变量的实现,即 SecurityContext 是存放在 ThreadLocal 里面的,这样各个独立的请求都将拥有自己的 SecurityContext。在请求完成后清除 SecurityContextHolder 中的 SucurityContext 就是清除 ThreadLocal,Servlet 容器一般都有自己的线程池,这可以避免 Servlet 容器下一次分发线程时线程中还包含 SecurityContext 变量,从而引起不必要的错误。
下面是一个 SecurityContextPersistenceFilter 的简单配置。
如果要在 http 元素定义中使用上述 AuthenticationFilter 定义,那么完整的配置应该类似于如下这样子。