9. Technical Overview

9.1 运行时环境

Spring Security 3.0 需要 Java 5.0 Runtime Environment 或更高版本。由于 Spring Security 旨在以独立的方式运行,因此无需在 Java 运行时环境中放置任何特殊的配置文件。特别是,无需配置特殊的 Java 身份验证和授权服务(JAAS)策略文件或将 Spring Security 放置在公共 Classpath 位置。

同样,如果您使用的是 EJB 容器或 Servlet 容器,则无需在任何地方放置任何特殊的配置文件,也无需在服务器类加载器中包含 Spring Security。所有必需的文件将包含在您的应用程序中。

这种设计提供了最大的部署时间灵 Active,因为您只需将目标工件(JAR,WAR 或 EAR)从一个系统复制到另一个系统即可立即使用。

9.2 核心组件

在 Spring Security 3.0 中,spring-security-core jar 的内容被精简到最低限度。它不再包含与 Web 应用程序安全性,LDAP 或名称空间配置有关的任何代码。我们将在这里介绍您在核心模块中找到的一些 Java 类型。它们代表了框架的构建块,因此,如果您需要超越简单的名称空间配置,那么即使实际上不需要直接与它们进行交互,也必须了解它们的含义,这一点很重要。

9.2.1 SecurityContextHolder,SecurityContext 和身份验证对象

最基本的对象是SecurityContextHolder。我们在这里存储应用程序当前安全上下文的详细信息,其中包括当前使用该应用程序的主体的详细信息。默认情况下,SecurityContextHolder使用ThreadLocal来存储这些详细信息,这意味着安全上下文始终可用于同一执行线程中的方法,即使没有将安全上下文明确地传递给这些方法的参数也是如此。如果在处理当前委托人的请求之后要清除线程,则以这种方式使用ThreadLocal是非常安全的。当然,Spring Security 会自动为您解决此问题,因此无需担心。

某些应用程序不完全适合使用ThreadLocal,因为它们使用线程的特定方式。例如,Swing Client 端可能希望 Java 虚拟机中的所有线程都使用相同的安全上下文。可以为SecurityContextHolder配置启动策略,以指定您希望如何存储上下文。对于独立应用程序,您将使用SecurityContextHolder.MODE_GLOBAL策略。其他应用程序可能希望让安全线程产生的线程也采用相同的安全身份。这是通过使用SecurityContextHolder.MODE_INHERITABLETHREADLOCAL实现的。您可以通过两种方式从默认SecurityContextHolder.MODE_THREADLOCAL更改模式。第一个是设置系统属性,第二个是在SecurityContextHolder上调用静态方法。大多数应用程序不需要更改默认值,但是如果需要更改,请查看 JavaDoc for SecurityContextHolder以了解更多信息。

获取有关当前用户的信息

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的实例作为主体。

9.2.2 UserDetailsService

上述代码片段中需要注意的另一项是,您可以从Authentication对象获取主体。主体只是Object。大多数情况下,可以将其转换为UserDetails对象。 UserDetails是 Spring Security 中的核心接口。它代表一种原理,但以一种可扩展的和特定于应用程序的方式。将UserDetails视为您自己的用户数据库和SecurityContextHolder内部的 Spring Security 所需的适配器。作为您自己的用户数据库中某些内容的表示,通常您会将UserDetails强制转换为应用程序提供的原始对象,以便可以调用特定于业务的方法(例如getEmail()getEmployeeNumber()等)。

现在,您可能想知道,那么什么时候提供UserDetails对象?我怎么做?我以为您说的这个东西是声明性的,不需要编写任何 Java 代码-有什么用?简短的答案是有一个名为UserDetailsService的特殊接口。此接口上的唯一方法接受基于String的用户名参数,并返回UserDetails

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

这是在 Spring Security 中为用户加载信息的最常用方法,只要需要有关用户的信息,就会在整个框架中使用它。

成功认证后,将使用UserDetails来构建存储在SecurityContextHolder中的Authentication对象(有关此below的更多信息)。好消息是,我们提供了许多UserDetailsService实现,包括一个使用内存 Map(InMemoryDaoImpl)和另一个使用 JDBC(JdbcDaoImpl)的实现。但是,大多数用户倾向于编写自己的应用程序,而其实现往往只是位于代表其员工,Client 或应用程序其他用户的现有数据访问对象(DAO)之上。请记住,使用上述代码段可以始终从SecurityContextHolder获得UserDetailsService返回的任何优点。

Note

关于UserDetailsService常常会有一些困惑。它纯粹是用于用户数据的 DAO,除了将数据提供给框架内的其他组件外,不执行其他功能。特别是,它不会对用户进行身份验证,这是由AuthenticationManager完成的。在许多情况下,如果您需要自定义身份验证过程,则直接使用implement AuthenticationProvider更有意义。

9.2.3 GrantedAuthority

除委托人外,Authentication提供的另一个重要方法是getAuthorities()。此方法提供了GrantedAuthority个对象的数组。毫不奇怪,GrantedAuthority是授予委托人的权限。此类权限通常是“角色”,例如ROLE_ADMINISTRATORROLE_HR_SUPERVISOR。稍后将这些角色配置为 Web 授权,方法授权和域对象授权。 Spring Security 的其他部分能够解释这些权限,并希望它们存在。 GrantedAuthority对象通常由UserDetailsService加载。

通常,GrantedAuthority对象是应用程序范围的权限。它们不特定于给定的域对象。因此,您不太可能用GrantedAuthority代表对Employee对象号 54 的许可,因为如果有成千上万个这样的权限,您很快就会用完内存(或者至少导致应用程序花费很长时间)时间来认证用户)。当然,Spring Security 是专门为满足这一通用要求而设计的,但您可以为此目的使用项目的域对象安全性功能。

9.2.4 Summary

回顾一下,到目前为止,我们已经看到了 Spring Security 的主要组成部分:

  • SecurityContextHolder,以提供对SecurityContext的访问权限。

  • SecurityContext,以保存Authentication以及可能特定于请求的安全信息。

  • Authentication,以特定于 Spring Security 的方式表示主体。

  • GrantedAuthority,以反映授予主体的应用程序范围的权限。

  • UserDetails,以提供必要的信息以从应用程序的 DAO 或其他安全数据源构建 Authentication 对象。

  • UserDetailsService,以便在传入基于String的用户名(或证书 ID 等)时创建UserDetails

现在,您已经了解了这些重复使用的组件,下面让我们仔细看看身份验证的过程。

9.3 Authentication

Spring Security 可以参与许多不同的身份验证环境。尽管我们建议人们使用 Spring Security 进行身份验证,而不是与现有的容器 Management 的身份验证集成,但是仍然支持它-与您自己的专有身份验证系统集成一样。

9.3.1 Spring Security 中的身份验证是什么?

让我们考虑一个大家都熟悉的标准身份验证方案。

  • 提示用户使用用户名和密码登录。

  • 系统(成功)验证用户名的密码正确。

  • 获取该用户的上下文信息(他们的角色列表等)。

  • 为用户构建了安全上下文

  • 用户可能会 continue 执行某些操作,该操作可能会受到访问控制机制的保护,该访问控制机制会根据当前安全上下文信息检查该操作所需的权限。

前三项构成了身份验证过程,因此我们将看看它们在 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");
}
}

在这里,我们编写了一个 Servlets,要求用户 Importing 用户名和密码并执行上述 Sequences。我们在此处实现的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.springframew[emailprotected]441d0230: \
Principal: bob; Password: [PROTECTED]; \
Authenticated: true; Details: null; \
Granted Authorities: ROLE_USER

请注意,您通常不需要编写任何此类代码。该过程通常在内部进行,例如在 Web 身份验证过滤器中。我们在此处仅包含了代码,以表明在 Spring Security 中实际上构成身份验证的问题有一个非常简单的答案。 SecurityContextHolder包含完全填充的Authentication对象时,将对用户进行身份验证。

默认情况下使用StrictHttpFirewall。此实现拒绝看起来是恶意的请求。如果对您的要求过于严格,则可以自定义拒绝哪些类型的请求。但是,重要的是要知道这样做可以使您的应用程序容易受到攻击。例如,如果您希望利用 Spring MVC 的矩阵变量,可以在 XML 中使用以下配置:

<b:bean id="httpFirewall"
      class="org.springframework.security.web.firewall.StrictHttpFirewall"
      p:allowSemicolon="true"/>

<http-firewall ref="httpFirewall"/>

使用 Java 配置,可以通过公开StrictHttpFirewall bean 实现相同的目的。

@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowSemicolon(true);
    return firewall;
}

9.3.2 直接设置 SecurityContextHolder 内容

实际上,Spring Security 并不介意如何将Authentication对象放入SecurityContextHolder。唯一关键的要求是SecurityContextHolder包含Authentication,它代表AbstractSecurityInterceptor(稍后将详细介绍)需要授权用户操作之前的主体。

您可以(而且很多用户都可以)编写自己的过滤器或 MVC 控制器,以提供与不基于 Spring Security 的身份验证系统的互操作性。例如,您可能正在使用容器 Management 的身份验证,该身份验证使当前用户可以从 ThreadLocal 或 JNDI 位置访问。或者,您可能在一家拥有遗留专有身份验证系统的公司工作,这是您无法控制的公司“标准”。在这种情况下,让 Spring Security 正常工作并仍然提供授权功能是很容易的。您所需要做的就是编写一个过滤器(或等效过滤器),该过滤器从某个位置读取第三方用户信息,构建特定于 Spring Security 的Authentication对象,并将其放入SecurityContextHolder。在这种情况下,您还需要考虑通常由内置身份验证基础结构自动处理的事情。例如,在将响应写入 Client 端脚注之前,您可能需要先创建到在请求之间缓存上下文的 HTTP 会话:[一旦提交响应,就无法创建会话。

如果您想知道AuthenticationManager在实际示例中是如何实现的,我们将在核心服务章节中进行介绍。

9.4 Web 应用程序中的身份验证

现在,让我们探讨一下在 Web 应用程序中使用 Spring Security 的情况(未启用web.xml安全性)。如何验证用户身份并构建安全上下文?

考虑典型的 Web 应用程序的身份验证过程:

  • 您访问主页,然后单击链接。

  • 向服务器发送请求,服务器确定您已请求受保护的资源。

  • 由于您目前尚未通过身份验证,因此服务器会发回响应,指示您必须进行身份验证。响应将是 HTTP 响应代码,或重定向到特定网页。

  • 根据身份验证机制,您的浏览器将重定向到特定网页,以便您可以填写表格,或者浏览器将以某种方式检索您的身份(通过 BASIC 身份验证对话框,cookie,X.509 证书等)。 )。

  • 浏览器将响应发送回服务器。这将是包含您填写的表单内容的 HTTP POST 或包含身份验证详细信息的 HTTP Headers。

  • 接下来,服务器将决定所提供的凭据是否有效。如果有效,则将进行下一步。如果它们无效,通常会要求您的浏览器再试一次(因此您返回到上面的第二步)。

  • 您尝试引起身份验证过程的原始请求将被重试。希望您已获得足够授权的身份验证,以访问受保护的资源。如果您具有足够的访问权限,则请求将成功。否则,您将收到一个 HTTP 错误代码 403,表示“禁止”。

Spring Security 有不同的类负责上述大多数步骤。主要参与者(按照使用 Sequences)是ExceptionTranslationFilterAuthenticationEntryPoint和“身份验证机制”,它们负责调用上一节中看到的AuthenticationManager

9.4.1 ExceptionTranslationFilter

ExceptionTranslationFilter是 Spring Security 过滤器,负责检测引发的任何 Spring Security 异常。此类异常通常由AbstractSecurityInterceptor引发,该AbstractSecurityInterceptor是授权服务的主要提供者。我们将在下一节中讨论AbstractSecurityInterceptor,但是现在我们只需要知道它会产生 Java 异常,并且对 HTTP 或对主体进行身份验证一无所知。相反,ExceptionTranslationFilter提供此服务,具体负责返回错误代码 403(如果主体已通过身份验证,因此仅缺少足够的访问权限-按照上述第七步),或者启动AuthenticationEntryPoint(如果主体未通过身份验证并因此我们需要开始第三步)。

9.4.2 AuthenticationEntryPoint

AuthenticationEntryPoint负责上述列表中的第三步。可以想象,每个 Web 应用程序将具有默认的身份验证策略(嗯,可以像配置 Spring Security 中的几乎所有其他功能一样配置它,但是现在让我们保持简单)。每个主要认证系统都有自己的AuthenticationEntryPoint实现,该实现通常执行步骤 3 中所述的操作之一。

9.4.3 身份验证机制

一旦浏览器提交了身份验证凭据(作为 HTTP 表单post或 HTTPHeaders),服务器上就需要有一些东西来“收集”这些身份验证详细信息。到目前为止,我们位于上述列表的第六步。在 Spring Security 中,我们有一个特殊的名称,用于从用户代理(通常是 Web 浏览器)收集身份验证详细信息的功能,将其称为“身份验证机制”。示例是基于表单的登录和基本身份验证。从用户代理收集到身份验证详细信息后,便会构建Authentication“请求”对象,然后将其呈现给AuthenticationManager

身份验证机制收到完全填充的Authentication对象后,它将认为请求有效,将Authentication放入SecurityContextHolder并导致重试原始请求(上面的第七步)。另一方面,如果AuthenticationManager拒绝了该请求,则认证机制将要求用户代理重试(上述第二步)。

9.4.4 在请求之间存储 SecurityContext

根据应用程序的类型,可能需要制定一种策略来存储用户操作之间的安全上下文。在典型的 Web 应用程序中,用户登录一次,然后通过其会话 ID 进行标识。服务器缓存持续时间会话的主体信息。在 Spring Security 中,在请求之间存储SecurityContext的责任落在SecurityContextPersistenceFilter,默认情况下,该上下文将上下文存储为 HTTP 请求之间的HttpSession属性。它为每个请求将上下文恢复到SecurityContextHolder,并且至关重要的是,在请求完成时清除SecurityContextHolder。为了安全起见,您不应直接与HttpSession进行交互。这样做根本没有道理-始终使用SecurityContextHolder代替。

许多其他类型的应用程序(例如,StatelessRESTful Web 服务)不使用 HTTP 会话,而是将对每个请求进行重新身份验证。但是,将SecurityContextPersistenceFilter包含在链中仍然很重要,以确保在每次请求后清除SecurityContextHolder

Note

在单个会话中接收并发请求的应用程序中,相同的SecurityContext实例将在线程之间共享。即使正在使用ThreadLocal,它也是从HttpSession中为每个线程检索的同一实例。如果您希望临时更改运行线程的上下文,则可能会产生影响。如果只使用SecurityContextHolder.getContext(),并在返回的上下文对象上调用setAuthentication(anAuthentication),则Authentication对象将在所有并发线程中更改,这些线程共享同一SecurityContext实例。您可以自定义SecurityContextPersistenceFilter的行为以为每个请求创建一个全新的SecurityContext,从而防止一个线程中的更改影响另一个线程。另外,您可以在临时更改上下文的位置创建一个新实例。方法SecurityContextHolder.createEmptyContext()始终返回新的上下文实例。

9.5 Spring Security 中的访问控制(授权)

负责在 Spring Security 中做出访问控制决策的主要接口是AccessDecisionManager。它具有decide方法,该方法接受代表请求访问的主体的Authentication对象,“安全对象”(请参见下文)和适用于该对象的安全元数据属性的列表(例如访问权限所必需的角色的列表)被授予)。

9.5.1 安全和 AOP 建议

如果您熟悉 AOP,您会知道有不同类型的建议可用:之前,之后,抛出和周围。环绕建议非常有用,因为顾问可以选择是否 continue 进行方法调用,是否修改响应以及是否引发异常。 Spring Security 提供了有关方法调用以及 Web 请求的全面建议。我们使用 Spring 的标准 AOP 支持来获得方法调用的通用建议,并使用标准的 Filter 来实现 Web 请求的通用建议。

对于那些不熟悉 AOP 的人来说,要了解的关键是 Spring Security 可以帮助您保护方法调用以及 Web 请求。大多数人都对在其服务层上确保方法调用感兴趣。这是因为服务层是大多数业务逻辑驻留在当前 Java EE 应用程序中的地方。如果只需要在服务层中确保方法调用的安全,那么 Spring 的标准 AOP 就足够了。如果您需要直接保护域对象,则可能会发现 AspectJ 值得考虑。

您可以选择使用 AspectJ 或 Spring AOP 执行方法授权,也可以选择使用过滤器执行 Web 请求授权。您可以同时使用零,一,二或三种方法。主流用法是执行一些 Web 请求授权,并在服务层上执行一些 Spring AOP 方法调用授权。

9.5.2 安全对象和 AbstractSecurityInterceptor

那么,什么是“安全对象”? Spring Security 使用该术语来指代任何可以对其应用安全性(例如授权决策)的对象。最常见的示例是方法调用和 Web 请求。

每个受支持的安全对象类型都有其自己的拦截器类,该类是AbstractSecurityInterceptor的子类。重要的是,到AbstractSecurityInterceptor被调用时,如果主体已通过身份验证,则SecurityContextHolder将包含有效的Authentication

AbstractSecurityInterceptor提供了一致的工作流来处理安全的对象请求,通常:

  • 查找与当前请求关联的“配置属性”

  • 将安全对象,当前Authentication和配置属性提交给AccessDecisionManager以进行授权决策

  • (可选)更改发生调用的Authentication

  • 允许进行安全对象调用(假设已授予访问权限)

  • 一旦调用返回,则调用AfterInvocationManager(如果已配置)。如果调用引发异常,则AfterInvocationManager将不会被调用。

什么是配置属性?

可以将“配置属性”视为对AbstractSecurityInterceptor使用的类具有特殊含义的字符串。它们由框架内的接口ConfigAttribute表示。它们可能是简单的角色名称,也可能具有更复杂的含义,具体取决于AccessDecisionManager实现的复杂程度。 AbstractSecurityInterceptor配置有SecurityMetadataSource,它用于查找安全对象的属性。通常,此配置对用户隐藏。配置属性将作为安全方法的 Comments 或安全 URL 的访问属性 Importing。例如,当我们在名称空间介绍中看到类似于<intercept-url pattern='/secure/**' access='ROLE_A,ROLE_B'/>的内容时,这表示配置属性ROLE_AROLE_B适用于与给定模式匹配的 Web 请求。实际上,使用默认的AccessDecisionManager配置,这意味着具有GrantedAuthority匹配这两个属性之一的任何人都将被允许访问。严格来说,它们只是属性,其解释取决于AccessDecisionManager的实现。前缀ROLE_的使用是标记,指示这些属性是角色,并且应由 Spring Security 的RoleVoter使用。仅在使用基于投票者的AccessDecisionManager时才有意义。我们将看到authorization chapter中如何实现AccessDecisionManager

RunAsManager

假设AccessDecisionManager决定允许该请求,则AbstractSecurityInterceptor通常只会 continue 处理该请求。话虽这么说,但在极少数情况下,用户可能希望将SecurityContext内的Authentication替换为另一个Authentication,这由AccessDecisionManager调用RunAsManager处理。在相当不常见的情况下,例如服务层方法需要调用远程系统并显示不同的标识时,这可能很有用。由于 Spring Security 会自动将安全身份从一台服务器传播到另一台服务器(假设您使用的是正确配置的 RMI 或 HttpInvoker 远程协议 Client 端),因此这可能很有用。

AfterInvocationManager

在安全对象调用 continue 进行之后,然后返回-这可能意味着方法调用完成或过滤器链进行了-AbstractSecurityInterceptor获得了处理调用的最后机会。在此阶段,AbstractSecurityInterceptor可能会修改返回对象。我们可能希望发生这种情况,因为无法在安全对象调用的“途中”做出授权决定。由于高度可插拔,AbstractSecurityInterceptor会将控制权传递给AfterInvocationManager,以根据需要实际修改对象。此类甚至可以完全替换对象,或者引发异常,也可以按照其选择的任何方式对其进行更改。调用后检查仅在调用成功的情况下执行。如果发生异常,将跳过其他检查。

AbstractSecurityInterceptor及其相关对象显示在图 9.1,“安全拦截器和“安全对象”模型”

图 9.1. 安全拦截器和“安全对象”模型

抽象安全拦截器

扩展安全对象模型

只有打算采用全新方法来拦截和授权请求的开发人员才需要直接使用安全对象。例如,有可能构建一个新的安全对象以保护对消息系统的呼叫。任何需要安全性并且还提供拦截呼叫的方式的东西(例如围绕建议语义的 AOP)都可以成为安全对象。话虽如此,大多数 Spring 应用程序将完全透明地使用当前支持的三种安全对象类型(AOPunionMethodInvocation,AspectJ JoinPoint和 Web 请求FilterInvocation)。

9.6 Localization

Spring Security 支持本地化最终用户可能会看到的异常消息。如果您的应用程序是为说英语的用户设计的,则您无需执行任何操作,因为默认情况下,所有安全消息都是英语的。如果您需要支持其他语言环境,则本节包含您需要了解的所有内容。

可以本地化所有异常消息,包括与身份验证失败和访问被拒绝(授权失败)有关的消息。专注于开发人员或系统部署人员的异常和日志消息(包括不正确的属性,违反接口 Contract,使用不正确的构造函数,启动时间验证,调试级别的日志记录)未本地化,而是在 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 Framework 文档以获取有关在 Spring 中使用本地化的更多详细信息。

“联系人”samples 应用程序被设置为使用本地化消息。