8 Architecture and Implementation
Once you are familiar with setting up and running some namespace-configuration based applications, you may wish to develop more of an understanding of how the framework actually works behind the namespace facade. Like most software, Spring Security has certain central interfaces, classes and conceptual abstractions that are commonly used throughout the framework. In this part of the reference guide we will look at some of these and see how they work together to support authentication and access-control within Spring Security.
架构和实现
一旦您熟悉了设置和运行一些基于名称空间配置的应用程序,您可能希望进一步了解框架在名称空间背后的实际工作方式。与大多数软件一样,Spring Security具有某些在整个框架中普遍使用的中央接口、类和概念抽象。在参考指南的这一部分中,我们将研究其中的一些,并了解它们如何协同工作来支持Spring Security中的身份验证和访问控制。
8.1 Technical Overview
8.1.1 Runtime Environment
Spring Security 3.0 requires a Java 5.0 Runtime Environment or higher. As Spring Security aims to operate in a self-contained manner, there is no need to place any special configuration files into your Java Runtime Environment. In particular, there is no need to configure a special Java Authentication and Authorization Service (JAAS) policy file or place Spring Security into common classpath locations.
Similarly, if you are using an EJB Container or Servlet Container there is no need to put any special configuration files anywhere, nor include Spring Security in a server classloader. All the required files will be contained within your application.
This design offers maximum deployment time flexibility, as you can simply copy your target artifact (be it a JAR, WAR or EAR) from one system to another and it will immediately work.
8.1.1 运行时环境
Spring Security 3.0需要Java 5.0或更高的运行时环境。由于Spring Security旨在以自包含的方式进行操作,因此不需要在Java运行时环境中放置任何特殊的配置文件。特别是,不需要配置特殊的Java身份验证和授权服务(JAAS)策略文件,也不需要将Spring安全性放到公共类路径位置。
类似地,如果您使用EJB容器或Servlet容器,则不需要在任何地方放置任何特殊配置文件,也不需要在服务器类加载器中包含Spring安全性。所有必需的文件都将包含在应用程序中。
这种设计提供了最大的部署时间灵活性,因为您可以简单地将目标工件(不管是JAR、WAR还是EAR)从一个系统复制到另一个系统,并且它可以立即工作。
8.1.2 Core Components
In Spring Security 3.0, the contents of the spring-security-core
jar were stripped down to the bare minimum. It no longer contains any code related to web-application security, LDAP or namespace configuration. We’ll take a look here at some of the Java types that you’ll find in the core module. They represent the building blocks of the framework, so if you ever need to go beyond a simple namespace configuration then it’s important that you understand what they are, even if you don’t actually need to interact with them directly.
8.1.2 核心组件
在Spring Security 3.0中,Spring-Security-core
jar 的内容被精简到最少。它不再包含任何与web应用程序安全性、LDAP或名称空间配置相关的代码。我们将在这里查看在核心模块中找到的一些Java类型。它们表示框架的构建块,因此,如果您需要超越简单的名称空间配置,那么理解它们是什么非常重要,即使您实际上不需要直接与它们交互。
SecurityContextHolder, SecurityContext and Authentication Objects
The most fundamental object is SecurityContextHolder
. This is where we store details of the present security context of the application, which includes details of the principal currently using the application. By default the SecurityContextHolder
uses a ThreadLocal
to store these details, which means that the security context is always available to methods in the same thread of execution, even if the security context is not explicitly passed around as an argument to those methods. Using a ThreadLocal in this way is quite safe if care is taken to clear the thread after the present principal’s request is processed. Of course, Spring Security takes care of this for you automatically so there is no need to worry about it.
Some applications aren’t entirely suitable for using a ThreadLocal
, because of the specific way they work with threads. For example, a Swing client might want all threads in a Java Virtual Machine to use the same security context. SecurityContextHolder
can be configured with a strategy on startup to specify how you would like the context to be stored. For a standalone application you would use the SecurityContextHolder.MODE_GLOBAL
strategy. Other applications might want to have threads spawned by the secure thread also assume the same security identity. This is achieved by using SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
. You can change the mode from the default SecurityContextHolder.MODE_THREADLOCAL
in two ways. The first is to set a system property, the second is to call a static method on SecurityContextHolder
. Most applications won’t need to change from the default, but if you do, take a look at the JavaDoc for SecurityContextHolder
to learn more.
最基本的对象是 SecurityContextHolder
。这里存储应用程序当前安全上下文的详细信息,其中包括当前使用该应用程序的主体的详细信息。默认情况下,SecurityContextHolder
使用一个ThreadLocal
来存储这些细节,这意味着安全上下文对于同一执行线程中的方法总是可用的,即使安全上下文没有作为参数显式地传递给这些方法。如果在处理当前主体的请求之后小心地清除线程,那么以这种方式使用ThreadLocal
是非常安全的。当然,Spring Security会自动为您处理这些问题,因此无需担心。
有些应用程序并不完全适合使用ThreadLocal
,因为它们使用线程的特定方式。例如,Swing客户机可能希望Java虚拟机中的所有线程使用相同的安全上下文。SecurityContextHolder
可以在启动时配置策略,以指定希望如何存储上下文。对于独立的应用程序,您将使用SecurityContextHolder.MODE_GLOBAL
策略。其他应用程序可能希望由安全线程派生的线程也具有相同的安全标识。这是通过使用SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
实现的。您可以从默认的SecurityContextHolder.MODE_THREADLOCAL
通过两种方式去改变。第一个是设置系统属性,第二个是调用SecurityContextHolder
上的静态方法。大多数应用程序不需要更改默认值,但是如果需要更改,请查看SecurityContextHolder
的JavaDoc以了解更多信息。
Obtaining information about the current user
Inside the SecurityContextHolder
we store details of the principal currently interacting with the application. Spring Security uses an Authentication
object to represent this information. You won’t normally need to create an Authentication
object yourself, but it is fairly common for users to query the Authentication
object. You can use the following code block – from anywhere in your application – to obtain the name of the currently authenticated user, for example:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
The object returned by the call to getContext()
is an instance of the SecurityContext
interface. This is the object that is kept in thread-local storage. As we’ll see below, most authentication mechanisms within Spring Security return an instance of UserDetails
as the principal.
获取关于当前用户的信息
在SecurityContextHolder
中,我们存储当前与应用程序交互的主体的详细信息。Spring Security使用身份验证对象表示此信息。您通常不需要自己创建身份验证对象,但是用户查询Authentication object
是相当常见的。您可以从应用程序中的任何位置使用以下代码块获取当前已验证用户的名称,例如:
调用getContext()
返回的对象是SecurityContext
接口的一个实例。这是保存在线程本地存储中的对象。正如我们将在下面看到的,Spring Security中的大多数身份验证机制都返回一个UserDetails
实例作为主体。
The UserDetailsService
Another item to note from the above code fragment is that you can obtain a principal from the Authentication
object. The principal is just an Object
. Most of the time this can be cast into a UserDetails
object. UserDetails
is a core interface in Spring Security. It represents a principal, but in an extensible and application-specific way. Think of UserDetails
as the adapter between your own user database and what Spring Security needs inside the SecurityContextHolder
. Being a representation of something from your own user database, quite often you will cast the UserDetails
to the original object that your application provided, so you can call business-specific methods (like getEmail()
, getEmployeeNumber()
and so on).
By now you’re probably wondering, so when do I provide a UserDetails
object? How do I do that? I thought you said this thing was declarative and I didn’t need to write any Java code – what gives? The short answer is that there is a special interface called UserDetailsService
. The only method on this interface accepts a String
-based username argument and returns a UserDetails
:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
This is the most common approach to loading information for a user within Spring Security and you will see it used throughout the framework whenever information on a user is required.
On successful authentication, UserDetails
is used to build the Authentication
object that is stored in the SecurityContextHolder
(more on this below). The good news is that we provide a number of UserDetailsService
implementations, including one that uses an in-memory map (InMemoryDaoImpl
) and another that uses JDBC (JdbcDaoImpl
). Most users tend to write their own, though, with their implementations often simply sitting on top of an existing Data Access Object (DAO) that represents their employees, customers, or other users of the application. Remember the advantage that whatever your UserDetailsService
returns can always be obtained from the SecurityContextHolder
using the above code fragment.
从上面的代码片段中需要注意的另一项是,您可以从Authentication
对象中获取主体。主体只是一个对象。大多数情况下,这可以转换为UserDetails
对象。UserDetails
是Spring Security中的一个核心接口。它表示主体,但是是以可扩展的和特定于应用程序的方式。将UserDetails
视为您自己的用户数据库和Spring Security需要的SecurityContextHolder
之间的适配器。作为来自您自己的用户数据库的内容的表示,您经常会将UserDetails
转换为您的应用程序提供的原始对象,因此您可以调用特定于业务的方法(如getEmail()、getEmployeeNumber()等)。
现在您可能想知道,我什么时候提供UserDetails对象?我该怎么做呢?我记得你说过这个东西是声明性的,我不需要编写任何Java代码——这是怎么回事?简单地说,有一个名为UserDetailsService的特殊接口。这个接口上唯一的方法接受一个基于字符串的username参数,并返回一个UserDetails
:
这是在Spring Security中为用户加载信息的最常见方法,在需要用户信息时,您将看到它在整个框架中使用。
在成功进行身份验证时,UserDetails用于构建存储在SecurityContextHolder
中的身份验证对象(下面将对此进行详细介绍)。好消息是我们提供了许多UserDetailsService实现,包括一个使用内存映射(InMemoryDaoImpl)的实现和另一个使用JDBC (JdbcDaoImpl)的实现。但是,大多数用户倾向于编写自己的实现,他们的实现通常只是位于代表他们的雇员、客户或应用程序的其他用户的现有数据访问对象(DAO)之上。记住,无论UserDetailsService返回什么,都可以使用上面的代码片段从SecurityContextHolder
获得。
There is often some confusion about
UserDetailsService
. It is purely a DAO for user data and performs no other function other than to supply that data to other components within the framework. In particular, it does not authenticate the user, which is done by the
AuthenticationManager
. In many cases it makes more sense to
implement AuthenticationProvider directly if you require a custom authentication process.关于
UserDetailsService
经常会有一些混淆。它纯粹是一个用于用户数据的DAO,除了向框架内的其他组件提供数据之外,不执行任何其他功能。特别是,它不验证用户,这是由AuthenticationManager
完成的。在许多情况下,如果需要自定义身份验证过程,则直接实现AuthenticationProvider
更有意义。
GrantedAuthority
Besides the principal, another important method provided by Authentication
is getAuthorities()
. This method provides an array of GrantedAuthority
objects. A GrantedAuthority
is, not surprisingly, an authority that is granted to the principal. Such authorities are usually “roles”, such as ROLE_ADMINISTRATOR
or ROLE_HR_SUPERVISOR
. These roles are later on configured for web authorization, method authorization and domain object authorization. Other parts of Spring Security are capable of interpreting these authorities, and expect them to be present. GrantedAuthority
objects are usually loaded by the UserDetailsService
.
Usually the GrantedAuthority
objects are application-wide permissions. They are not specific to a given domain object. Thus, you wouldn’t likely have a GrantedAuthority
to represent a permission to Employee
object number 54, because if there are thousands of such authorities you would quickly run out of memory (or, at the very least, cause the application to take a long time to authenticate a user). Of course, Spring Security is expressly designed to handle this common requirement, but you’d instead use the project’s domain object security capabilities for this purpose.
除了主体之外,Authentication
提供的另一个重要方法是getAuthorities()
。该方法提供了一个GrantedAuthority对象数组。授予的权限是授予主体的权限,这并不奇怪。这些权限通常是“角色”,例如ROLE_ADMINISTRATOR
或ROLE_HR_SUPERVISOR
。稍后将为web授权、方法授权和域对象授权配置这些角色。Spring Security的其他部分能够解释这些权限,并希望它们能够出现。GrantedAuthority
对象通常由UserDetailsService
加载。
通常GrantedAuthority
的对象是应用程序范围的权限。它们并不特定于给定的域对象。因此,您不太可能获得GrantedAuthority
来表示Employee
对象54的权限,因为如果有数千个这样的权限,您将很快耗尽内存(或者,至少会导致应用程序花很长时间来验证用户)。当然,Spring Security是专门为处理这种常见需求而设计的,但是您可以为此使用项目的域对象安全功能。
Summary
Just to recap, the major building blocks of Spring Security that we’ve seen so far are:
-
SecurityContextHolder
, to provide access to theSecurityContext
. -
SecurityContext
, to hold theAuthentication
and possibly request-specific security information. -
Authentication
, to represent the principal in a Spring Security-specific manner. -
GrantedAuthority
, to reflect the application-wide permissions granted to a principal. -
UserDetails
, to provide the necessary information to build an Authentication object from your application’s DAOs or other source of security data. -
UserDetailsService
, to create aUserDetails
when passed in aString
-based username (or certificate ID or the like).
Now that you’ve gained an understanding of these repeatedly-used components, let’s take a closer look at the process of authentication.
简单回顾一下,到目前为止我们所看到的Spring安全的主要构建块是:
-
SecurityContextHolder
提供对SecurityContext
的接入。 -
SecurityContext
,以保存Authentication
和可能是特定于请求的安全信息。 -
Authentication
,以特定于Spring安全的方式表示主体。 -
GrantedAuthority
,以反映授予主体的应用程序范围的权限。 -
UserDetails
,以提供从应用程序的dao或其他安全数据源构建身份验证对象所需的信息。 -
UserDetailsService
,用于在传入基于字符串的用户名(或证书ID或类似的名称)时创建用户详细信息。
现在您已经了解了这些重复使用的组件,接下来让我们进一步了解身份验证的过程。