Spring Security 3.0 需要 Java 5.0 运行时环境或更高。由于 Spring Security 的目的是以一种自包含的方式运行,所以不需要在 Java 运行时环境中放置任何特殊的配置文件。特别是,不需要配置特殊的 Java 身份验证和授权服务(JAAS)策略文件,或者将 Spring Security 放入普通类路径位置。
类似地,如果你使用的是 EJB 容器或 Servlet 容器,则不需要在任何地方放置任何特殊的配置文件,也不需要在服务器类加载器中包含 Spring Security。所有需要的文件将包含在你的应用程序中。
这种设计提供了最大的部署时灵活性,因为你可以简单地将目标工件(无论是 JAR、WAR 还是 EAR)从一个系统复制到另一个系统,并且它将立即工作。
在 Spring Security 3.0 中,spring-security-core
jar 的内容被剥离到最小值。它不再包含与 web 应用程序安全性、LDA P或命名空间配置相关的任何代码。我们将在这里查看一些在核心模块中发现的 Java类型。它们代表框架的构建块,所以如果你需要超越简单的命名空间配置,那么理解它们是什么就很重要,即使实际上你不需要直接与它们交互。
9.2.1 SecurityContextHolder、SecurityContext 和 Authentication Objects
最基本的对象是 SecurityContextHolder
。这里存储了应用程序的当前安全上下文的详细信息,其中包括当前使用应用程序的主体的详细信息。默认情况下,SecurityContextHolder
使用 ThreadLocal
来存储这些细节,这意味着即使安全上下文没有显式地作为参数传递给这些方法,安全上下文始终可用于同一执行线程中的方法。如果在处理当前主体的请求之后小心清除线程,那么以这种方式使用 ThreadLocal
是相当安全的。当然,Spring Security 会自动为你处理这个问题,所以不必担心。
有些应用程序不完全适合使用 ThreadLocal
,因为它们使用线程的特定方式。例如,Swing 客户端可能希望 Java 虚拟机中的所有线程都使用相同的安全上下文。可以在启动时配置 SecurityContextHolder
,以指定要如何存储上下文。对于独立的应用程序,你将使用 SecurityContextHolder.MODE_GLOBAL
策略。其它应用程序可能希望由安全线程生成的线程也承担相同的安全标识。这是通过使用 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
实现的。你可以通过两种方式从默认的 SecurityContextHolder.MODE_THREADLOCAL
。第一个是设置系统属性,第二个是调用 SecurityContextHolder
的静态方法。大多数应用程序不需要从默认值进行更改,但如果需要更改,请查看 SecurityContextHolder
的 JavaDoc 以了解更多详细信息。
在 SecurityContextHolder
中,我们存储当前与应用程序交互的主体的详细信息。Spring Security 使用 Authentication
对象来表示此信息。你通常不需要自己创建 Authentication
对象,但是用户查询 Authentication
对象是相当常见的。例如,可以使用以下代码块(来自应用程序中的任何地方)来获得当前经过身份验证的用户的名称:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
调用 getContext()
返回的对象是 SecurityContext
接口的实例。这是保存在线程本地存储中的对象。正如我们将在下面看到的,Spring Security 中的大多数身份验证机制都返回 UserDetails
的实例作为主体。
从上面的代码片段中注意到的另一个项目是,你可以从 Authentication
对象中获得一个主体。principal 只是个 Object
。大多数情况下,这可以被转换成用户详细信息对象。UserDetails
是 Spring Security 中的核心接口。它代表一个主体,但具有可扩展性和特定于应用的方式。可以将 UserDetails
看作你自己的用户数据库和 Spring Security SecurityContextHolder
内部所需的适配器。作为来自你自己的用户数据库的表示,你经常会将 UserDetails
转换为应用程序提供的原始对象,因此你可以调用特定于业务的方法(如 getEmail()
、getEmployeeNumber()
等)。
到目前为止,你可能想知道,所以我什么时候提供 UserDetails
对象?我该怎么做?我以为你说这是声明性的,我不需要写任何 Java 代码-什么?简短的答案是有一个特殊的接口叫做 UserDetailsService
。此接口上的唯一方法接受基于字符串的用户名参数,并返回 UserDetails
:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
这是最常见的在 Spring Security 中为用户加载信息的方法,当需要用户的信息时,你会看到它在整个框架中使用。
成功身份验证后,使用 UserDetails
构建存储在 SecurityContextHolder
中的 Authentication
对象(下面将详细介绍)。好消息是我们提供了许多 UserDetailsService
实现,包括使用内存映射(InMemoryDaoImpl
)的实现和使用 JDBC(JdbcDaoImpl
)的实现。然而,大多数用户倾向于编写他们自己的实现,他们的实现通常仅仅位于现有的数据访问对象(DAO)之上,DAO 表示他们的雇员、客户或应用程序的其它用户。请记住,无论 UserDetailsService
返回什么,都可以使用上述代码片段从 SecurityContextHolder
获得。
![[Note]](images/note.png) |
Note |
关于 UserDetailsService 经常有些困惑。它纯粹是用于用户数据的 DAO,除了向框架中的其它组件提供数据之外,不执行其它功能。特别是,它不验证用户,这是由 AuthenticationManager 完成的。在许多情况下,如果需要自定义身份验证过程,直接实现 AuthenticationProvider 就更有意义。
|
除了主体之外,Authentication
提供的另一个重要方法是 getAuthorities()
。此方法提供了 GrantedAuthority
对象的数组。GrantedAuthority
是授权给主体的权限。这样的权限通常是 "roles",如 ROLE_ADMINISTRATOR
或 ROLE_HR_SUPERVISOR
。这些角色后来被配置为 web 授权、方法授权和域对象授权。Spring Security 的其它部分能够解释这些权限,并期望它们存在。GrantedAuthority
对象通常由 UserDetailsService
加载。
通常,GrantedAuthority
对象是应用程序范围的权限。它们不特定于给定的域对象。因此,你可能没有 GrantedAuthority
来表示对 Employee
对象编号 54 的许可,因为如果存在数千个这样的权限,那么你将很快耗尽内存(或者至少导致应用程序需要很长时间来验证用户)。当然,Spring Security 是明确地设计来处理这个公共需求的,但是你应该为此使用项目的域对象安全功能。
回顾一下,我们迄今所看到的 Spring Security 的主要组成部分是:
-
SecurityContextHolder
,提供对 SecurityContext
的访问。
-
SecurityContext
,用于保持 Authentication
和可能特定请求的安全信息。
-
Authentication
,以 Spring Security 特定的方式表示主体。
-
GrantedAuthority
,以反映授予主体的应用范围权限。
-
UserDetails
,提供从应用程序的 DAO 或其它安全数据源构建身份验证对象所需的信息。
-
UserDetailsService
,当在使用基于用户名字符串(或凭证 ID 或其它相似的)通行时用来创建 UserDetails
。
现在,你已经了解了这些重复使用的组件,让我们更深入地了解身份验证的过程。
Spring Security 可以参与许多不同的认证环境。虽然我们建议人们使用 Spring Security 进行身份验证,但不要与现有的容器管理身份验证集成,但是它仍然受到支持 - 就像与你自己的专有身份验证系统集成一样。
9.3.1 Spring Security 中的身份验证是什么?
让我们考虑一个大家都熟悉的标准身份认证方案。
-
提示用户使用用户名和密码登录。
-
系统(成功地)验证用户名的密码是否正确。
-
获得该用户的上下文信息(其角色列表等)。
-
为用户建立安全上下文
-
用户继续执行一些操作,这些操作可能受到访问控制机制的保护,访问控制机制根据当前安全上下文信息检查操作的所需权限。
前三个项目构成了身份验证过程,所以我们将研究如何在 Spring Security 中进行这些操作。
-
获取用户名和密码,并将其组合到
UsernamePasswordAuthenticationToken
的实例(我们前面看到的 Authentication
接口的实例)中。
-
令牌传递给
AuthenticationManager
的实例进行验证。
-
AuthenticationManager
在成功身份验证时返回完全填充的 Authentication
实例。
-
通过调用传入返回的身份验证对象的
SecurityContextHolder.getContext().setAuthentication(…)
来建立安全上下文。
从那时起,用户被认为是经过认证的。让我们看看一些代码作为例子。
import org.springframework.security.authentication.*;
import org.springframework.security.core.*;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
public class AuthenticationExample {
private static AuthenticationManager am = new SampleAuthenticationManager();
public static void main(String[] args) throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while(true) {
System.out.println("Please enter your username:");
String name = in.readLine();
System.out.println("Please enter your password:");
String password = in.readLine();
try {
Authentication request = new UsernamePasswordAuthenticationToken(name, password);
Authentication result = am.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
break;
} catch(AuthenticationException e) {
System.out.println("Authentication failed: " + e.getMessage());
}
}
System.out.println("Successfully authenticated. Security context contains: " +
SecurityContextHolder.getContext().getAuthentication());
}
}
class SampleAuthenticationManager implements AuthenticationManager {
static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();
static {
AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
}
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (auth.getName().equals(auth.getCredentials())) {
return new UsernamePasswordAuthenticationToken(auth.getName(),
auth.getCredentials(), AUTHORITIES);
}
throw new BadCredentialsException("Bad Credentials");
}
}
在这里,我们编写了一个小程序,要求用户输入用户名和密码,并执行上述序列。我们在这里实现的 AuthenticationManager
将对用户名和密码相同的任何用户进行身份验证。它为每个用户分配一个单独的角色。上面的输出将是:
Please enter your username:
bob
Please enter your password:
password
Authentication failed: Bad Credentials
Please enter your username:
bob
Please enter your password:
bob
Successfully authenticated. Security context contains: \
org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d0230: \
Principal: bob; Password: [PROTECTED]; \
Authenticated: true; Details: null; \
Granted Authorities: ROLE_USER
请注意,通常不需要编写像这样的任何代码。该过程通常会在内部发生,例如在 web 身份验证过滤器中。我们在这里包含的代码表明,在 Spring Security 中什么实际构成身份验证的问题有一个非常简单的答案。当 SecurityContextHolder
包含一个完全填充的 Authentication
对象时,对用户进行身份验证。
9.3.2 直接设置 SecurityContextHolder 内容
事实上,Spring Security 并不介意将 Authentication
对象放置在 SecurityContextHolder
中。惟一的关键要求是 SecurityContextHolder
包含一个 Authentication
,用来表示 AbstractSecurityInterceptor
(稍后我们将看到更多信息)需要授权用户操作之前的一个主体。
你可以(许多用户也是如此)编写自己的过滤器或 MVC 控制器来提供与非基于 Spring Security 的认证系统的互操作性。例如,你可能正在使用容器管理身份验证,这使当前用户可以从 ThreadLocal 或 JNDI 位置获得。或者,你可能为一家拥有遗留专有身份验证系统的公司工作,这是你几乎无法控制的公司标准。在这样的情况下,很容易让 Spring Security 工作,并且仍然提供授权能力。你只需要编写一个从某个位置读取第三方用户信息的过滤器(或等价物),构建特定于 Spring Security 的 Authentication
对象,并将其放入 SecurityContextHolder
。在这种情况下,你还需要考虑通常由内置身份验证基础结构自动处理的事项。例如,在将响应写入客户端之前,你可能需要先行创建一个 HTTP 会话来缓存请求之间的上下文,脚注:[一旦提交了响应,就不可能创建会话。
如果你想知道如何在真实世界示例中实现 AuthenticationManager
,我们将会在 核心服务章节 中查看。
现在让我们探究一下在 web 应用程序中使用 Spring Security(没有启用 web.xml
安全性)的情况。用户是如何认证的,安全上下文是如何建立的?
考虑一个典型的 web 应用程序的认证过程:
-
你访问主页,然后单击链接。
-
请求转到服务器,服务器决定你请求受保护的资源。
-
由于目前还没有验证,服务器会发送一个响应,指示你必须进行身份验证。该响应既可以是 HTTP 响应代码,也可以是重定向到特定网页。
-
根据身份验证机制,你的浏览器要么重定向到特定的网页,以便你可以填写表单,要么浏览器以某种方式检索你的身份(通过 BASIC 身份验证对话框、cookie、X.509 凭证等)。
-
浏览器将向服务器发送响应。这将是一个包含你填写的表单内容的 HTTP POST,或者一个包含身份验证细节的 HTTP 头。
-
接下来,服务器将决定所呈现的凭证是否有效。如果它们是有效的,下一步将会发生。如果它们是无效的,通常你的浏览器将被要求再试一次(所以你返回到上面的第二步)。
-
将重新生成导致验证过程的原始请求。希望你已经用足够授权的权限验证访问受保护的资源。如果你有足够的访问权限,请求将成功。否则,你会收到一个 HTTP 错误代码 403,这意味着禁止。
Spring Security 具有负责上述大部分步骤的不同类。主要的参与者(按照使用的顺序)是 ExceptionTranslationFilter
、AuthenticationEntryPoint
和身份验证机制,它们负责调用我们在前面部分看到的 AuthenticationManager
。
9.4.1 ExceptionTranslationFilter
ExceptionTranslationFilter
是一个 Spring Security 过滤器,负责检测抛出的任何 Spring Security 异常。此类异常通常会由 AbstractSecurityInterceptor
抛出,这是授权服务的主要提供者。我们将在下一节中讨论 AbstractSecurityInterceptor
,但现在我们只需要知道它产生 Java 异常,并且不知道 HTTP 或如何着手验证主体。相反,ExceptionTranslationFilter
提供此服务,对返回的错误代码 403(如果主体已被认证,因此仅在上面的步骤七中没有足够的访问)或发起 AuthenticationEntryPoint
(如果主体没有验证,由于未经验证,因此我们需要开始步骤三)。
9.4.2 AuthenticationEntryPoint
AuthenticationEntryPoint
负责上述列表中的步骤三。可以想象,每个 web 应用程序都有一个缺省的身份验证策略(嗯,这可以像 Spring Security 中的其它内容一样配置,但是现在让我们保持简单)。每个主要身份验证系统都有自己的 AuthenticationEntryPoint
实现,它通常执行步骤 3 中描述的动作之一。
一旦你的浏览器提交了你的身份验证凭证(作为 HTTP 表单 post 或 HTTP 报头),服务器上就需要有一些收集这些身份验证细节的内容。现在我们在上面的列表中的第六步。在 Spring Security 中,对于从用户代理(通常是 web 浏览器)收集身份验证细节的功能,我们有一个特殊的名称,称之为身份验证机制。示例是表单基础登录和基本身份验证。一旦从用户代理收集了身份验证细节,就构建一个 Authentication
请求对象,然后将其呈现给 AuthenticationManager
。
在身份验证机制接收到完全填充的 Authentication
对象之后,它将认为请求有效,将 Authentication
放入 SecurityContextHolder
,并导致重试原始请求(上面的步骤 7)。另一方面,如果 AuthenticationManager
拒绝了请求,则身份验证机制将请求用户代理重试(上面的步骤 2)。
根据应用程序的类型,可能需要适当的策略来在用户操作之间存储安全上下文。在典型的 web 应用程序中,用户登录一次,然后由他们的会话 Id 标识。服务器缓存持续时间会话的主要信息。在 Spring Security 中,在请求之间存储 SecurityContext
的责任落到了 SecurityContextPersistenceFilter
上,它默认将上下文存储为 HTTP 请求之间的 HttpSession
属性。它为每个请求将上下文还原到 SecurityContextHolder
,并且当请求完成时清除 SecurityContextHolder
。为了安全起见,你不应该直接与 HttpSession
交互。根本没有理由这么做 - 总是使用 SecurityContextHolder
替代。
许多其它类型的应用程序(例如,无状态 RESTful web 服务)不使用 HTTP 会话,并将在每个请求上重新进行身份验证。但是,在链中包含 SecurityContextPersistenceFilter
以确保在每个请求之后清除 SecurityContextHolder
,这一点仍然很重要。
![[Note]](images/note.png) |
Note |
在单个会话中接收并发请求的应用程序中,线程之间将共享相同的 SecurityContext 实例。即使使用 ThreadLocal ,它也是从每个线程的 HttpSession 中检索的相同实例。如果希望临时更改线程正在运行的上下文,这将产生影响。如果仅对返回的上下文对象使用 SecurityContextHolder.getContext() 并调用 setAuthentication(anAuthentication) ,则 Authentication 对象将在共享相同 SecurityContext 实例的所有并发线程中更改。你可以自定义 SecurityContextPersistenceFilter 的行为,以便为每个请求创建一个全新的 SecurityContext ,防止一个线程中的更改影响另一个线程。或者,你可以在临时更改上下文的位置创建一个新实例。SecurityContextHolder.createEmptyContext() 方法总是返回一个新的上下文实例。
|
9.5 Spring Security 中的访问控制(授权)
在 Spring Security 中负责访问控制决策的主要接口是 AccessDecisionManager
。它有一个 decide
方法,它采用表示请求访问的主体的 Authentication
对象、安全对象(参见下文)和适用于该对象的安全元数据属性列表(例如授予访问所需的角色列表)。
如果你熟悉 AOP,你会知道有不同类型的建议:之前,之后,抛出和环绕。环绕通知非常有用,因为 advisor 可以选择是否继续方法调用、是否修改响应以及是否抛出异常。Spring Security 为方法调用和 web 请求提供了一个全面的建议。我们使用 Spring 的标准 AOP 支持实现了方法调用的环绕通知,并使用标准 Filter 实现了 web 请求的环绕通知。
对于那些不熟悉 AOP 的人来说,要理解的关键是 Spring Security 可以帮助你保护方法调用以及 web 请求。大多数人都对在服务层上确保方法调用感兴趣。这是因为服务层是大多数业务逻辑驻留在当前 JavaEE 应用程序中的地方。如果你只需要在服务层中确保方法调用,Spring 的标准 AOP 就足够了。如果你需要直接保护域对象,你可能会发现 AspectJ 值得考虑。
你可以选择使用 AspectJ 或 Spring AOP 执行方法授权,也可以选择使用过滤器执行 web 请求授权。你可以将零、一、二、三种方法一起使用。主流使用模式是执行一些 web 请求授权,以及服务层上的一些 Spring AOP 方法调用授权。
那么,什么是安全对象呢?Spring Security 使用术语来指应用于它的具有安全性的任何对象(例如授权决策)。最常见的例子是方法调用和 web 请求。
每个支持的安全对象类型都有自己的拦截器类,它是 AbstractSecurityInterceptor
的子类。重要的是,在调用 AbstractSecurityInterceptor
的时候,如果主体已被认证,SecurityContextHolder
将包含有效的 Authentication
。
AbstractSecurityInterceptor
为处理安全对象请求提供了一个一致的工作流,通常是:
-
查找与当前请求关联的配置属性
-
向
AccessDecisionManager
提交安全对象、当前 Authentication
和配置属性以供授权决策
-
可选地更改调用发生的
Authentication
。
-
允许安全对象调用继续进行(假设访问被授予)
-
一旦调用返回,调用
AfterInvocationManager
。如果调用引发异常,则不调用 AfterInvocationManager
。
配置属性可以被认为是对 AbstractSecurityInterceptor
使用的类具有特殊意义的字符串。它们由框架内的接口 ConfigAttribute
表示。它们可能是简单的角色名,或者具有更复杂的含义,这取决于 AccessDecisionManager
实现的复杂程度。AbstractSecurityInterceptor
配置有 SecurityMetadataSource
,用于查找安全对象的属性。通常,这种配置将被用户隐藏。配置属性将作为安全方法上的注释或安全 URL 上的访问属性输入。例如,当我们在命名空间介绍中看到像 <intercept-url pattern='/secure/**' access='ROLE_A,ROLE_B'/>
之类的内容时,这就是说,配置属性 ROLE_A
和 ROLE_B
适用于匹配给定模式的 web 请求。在实践中,默认的 AccessDecisionManager
配置,意味着任何一个拥有与这两个属性中的任一个匹配的 GrantedAuthority
的人都将被允许访问。严格说来,它们只是属性,解释依赖于 AccessDecisionManager
实现。前缀 ROLE_
的使用是表示这些属性是角色的标记,应该由 Spring Security 的 RoleVoter
使用。只有在使用基于投票者的 AccessDecisionManager
时,这才是相关的。我们将在授权章节中了解 AccessDecisionManager
是如何实现的。
如果 AccessDecisionManager
决定允许这个请求,AbstractSecurityInterceptor
通常只需继续执行请求。尽管如此,在极少数情况下,用户可能希望用不同的 Authentication
来替换 SecurityContext
中的 Authentication
,该身份验证由调用 RunAsManager
的 AccessDecisionManager
处理。这对于相当不寻常的情况可能是有用的,例如如果服务层方法需要调用远程系统并呈现不同的身份。因为 Spring Security 自动将安全身份从一个服务器传播到另一个服务器(假设你正在使用正确配置的 RMI 或 HttpInvoker 远程协议客户端),所以这可能是有用的。
在安全对象调用进行并随后返回(这可能意味着方法调用完成或过滤器链进行)之后,AbstractSecurityInterceptor
得到一个处理调用的最后机会。在这个阶段,AbstractSecurityInterceptor
可能对修改返回对象感兴趣。我们可能希望这种情况发生,因为在安全对象调用中不能做出授权决定。由于具有高度可插入性,AbstractSecurityInterceptor
将把控制传递给 AfterInvocationManager
,以便在需要时实际修改对象。这个类甚至可以完全替换对象,或者抛出异常,或者不按照它选择的任何方式改变它。只有调用成功时,才会执行后调用检查。如果发生异常,将跳过额外的检查。
AbstractSecurityInterceptor
及其相关对象如 图 9.1, “安全拦截器和安全对象模型” 所示
只有考虑采用一种截获和授权请求的全新方式的开发人员才需要直接使用安全对象。例如,可以建立一个新的安全对象来确保对消息传递系统的调用。任何需要安全性并且还提供了拦截调用(如环绕通知语义的 AOP)的方法都可能成为安全对象。尽管如此,大多数 Spring 应用程序将仅以完全透明的方式使用当前支持的三种安全对象类型(AOP Alliance MethodInvocation
、AspectJ JoinPoint
和 web 请求 FilterInvocation
)。
Spring Security 支持终端用户可能看到的异常消息本地化。如果你的应用程序是为讲英语的用户设计的,那么你不需要做任何事情,因为默认情况下所有安全消息都是用英语的。如果你需要支持其它区域,你需要知道的所有内容都包含在本节中。
可以本地化所有异常消息,包括与身份验证失败和访问被拒绝(授权失败)相关的消息。关注于开发人员或系统部署人员的异常和日志消息(包括不正确的属性、违反接口契约、使用不正确的构造函数、启动时间验证、调试级别日志)没有本地化,而是在 Spring Security 中用英语硬编码的代码。
在 spring-security-core-xx.jar
中传送,你将发现 org.springframework.security
包,该包又包含 messages.properties
文件以及一些公共语言的本地化版本。这应该由 ApplicationContext
引用,因为 Spring Security 类实现 Spring 的 MessageSourceAware
接口,并期望在应用程序上下文启动时注入消息解析器。通常你需要做的就是在你的应用程序上下文中注册一个 bean 来引用消息。下面是一个例子:
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:org/springframework/security/messages"/>
</bean>
messages.properties
是根据标准资源包命名的,并且表示 Spring Security 消息支持的默认语言。此默认文件是英文的。
如果希望自定义 messages.properties
文件或支持其他语言,则应该复制该文件,相应地重命名该文件,并在上面的 bean 定义中注册该文件。在这个文件中没有大量的消息键,所以本地化不应该被认为是一个主要的主动性。如果你确实执行此文件的本地化,请考虑通过记录 JIRA 任务并附加你适当命名的本地化版本 messages.properties
来与社区共享你的工作。
Spring Security 依赖于 Spring 的本地化支持,以便实际查找适当的消息。为了实现这一点,你必须确保来自传入请求的区域设置存储在 Spring 的 org.springframework.context.i18n.LocaleContextHolder
中。Spring MVC 的 DispatcherServlet
为你的应用程序自动执行此操作,但由于 Spring Security 的过滤器在此之前被调用,因此需要在调用过滤器之前设置 LocaleContextHolder
以包含正确的 Locale
。你可以自己在过滤器中进行此操作(必须在 web.xml
的 Spring Security 过滤器之前进行),也可以使用 Spring 的 RequestContextFilter
。有关 Spring 使用本地化的详细信息,请参阅 Spring Framework 文档。
设置联系人示例应用程序以使用本地化消息。