Spring Security(二十七):Part II. 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中的身份验证和访问控制。
 

9. Technical Overview(技术概述)

9.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.

Spring Security 3.0需要Java 5.0 Runtime Environment或更高版本。由于Spring Security旨在以独立的方式运行,因此无需将任何特殊配置文件放入Java运行时环境中。特别是,无需配置特殊的Java身份验证和授权服务(JAAS)策略文件,也无需将Spring Security放入常见的类路径位置。
 
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.
同样,如果您使用的是EJB容器或Servlet容器,则无需在任何地方放置任何特殊配置文件,也不需要在服务器类加载器中包含Spring Security。所有必需的文件都将包含在您的应用程序中。
 
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.
这种设计提供了最大的部署时间灵活性,因为您可以简单地将目标工件(无论是JAR,WAR还是EAR)从一个系统复制到另一个系统,它将立即起作用。
 

9.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.

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

9.2.1 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.

最基本的对象是SecurityContextHolder。这是我们存储应用程序当前安全上下文的详细信息的地方,其中包括当前使用该应用程序的主体的详细信息。默认情况下,SecurityContextHolder使用ThreadLocal来存储这些详细信息,这意味着安全上下文始终可用于同一执行线程中的方法,即使安全上下文未作为这些方法的参数显式传递。如果在处理当前主体的请求之后注意清除线程,以这种方式使用ThreadLocal是非常安全的。当然,Spring Security会自动为您解决这个问题,因此无需担心。
 
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 SecurityContextHolderto learn more.
有些应用程序并不完全适合使用ThreadLocal,因为它们使用线程的特定方式。例如,Swing客户端可能希望Java虚拟机中的所有线程都使用相同的安全上下文。 SecurityContextHolder可以在启动时配置策略,以指定您希望如何存储上下文。对于独立应用程序,您将使用SecurityContextHolder.MODE_GLOBAL策略。其他应用程序可能希望安全线程生成的线程也采用相同的安全标识。这是通过使用SecurityContextHolder.MODE_INHERITABLETHREADLOCAL实现的。您可以通过两种方式从默认的SecurityContextHolder.MODE_THREADLOCAL更改模式。第一个是设置系统属性,第二个是在SecurityContextHolder上调用静态方法。大多数应用程序不需要更改默认值,但如果这样做,请查看JavaDoc for SecurityContextHolder以了解更多信息。
 

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 Authenticationobject. You can use the following code block - from anywhere in your application - to obtain the name of the currently authenticated user, for example:

在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();
}

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.

调用getContext()返回的对象是SecurityContext接口的一个实例。这是保存在线程本地存储中的对象。正如我们将在下面看到的,Spring Security中的大多数身份验证机制都会返回UserDetails的实例作为主体。
 

9.2.2 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).

上面代码片段中需要注意的另一个问题是,您可以从Authentication对象中获取主体。校长只是一个对象。大多数情况下,这可以转换为UserDetails对象。 UserDetails是Spring Security中的核心接口。它代表一个主体,但是以可扩展和特定于应用程序的方式。将UserDetails视为您自己的用户数据库与SecurityContextHolder中Spring Security所需的适配器。作为来自您自己的用户数据库的东西的表示,通常您会将UserDetails转换为您的应用程序提供的原始对象,因此您可以调用特定于业务的方法(如getEmail(),getEmployeeNumber()等)。
 
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对象?我怎么做?我以为你说这个东西是声明性的,我不需要编写任何Java代码 - 是什么给出的?简短的回答是有一个名为UserDetailsS​​ervice的特殊接口。此接口上唯一的方法接受基于String的用户名参数并返回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.

这是在Spring Security中为用户加载信息的最常用方法,只要需要有关用户的信息,您就会看到它在整个框架中使用。
 
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.
 
在成功进行身份验证后,UserDetails用于构建存储在SecurityContextHolder中的Authentication对象(详见下文)。好消息是我们提供了许多UserDetailsS​​ervice实现,包括一个使用内存映射(InMemoryDaoImpl)和另一个使用JDBC(JdbcDaoImpl)的实现。但是,大多数用户倾向于自己编写,他们的实现通常只是位于代表其员工,客户或应用程序其他用户的现有数据访问对象(DAO)之上。记住,无论你的UserDetailsS​​ervice返回什么,总是可以使用上面的代码片段从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.
关于UserDetailsS​​ervice经常会有一些混乱。它纯粹是用户数据的DAO,除了将数据提供给框架内的其他组件之外,不执行任何其他功能。特别是,它不会对用户进行身份验证,这是由AuthenticationManager完成的。在许多情况下,如果您需要自定义身份验证过程,则直接实现AuthenticationProvider会更有意义。
 

9.2.3 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.

除了主体之外,Authentication提供的另一个重要方法是getAuthorities()。此方法提供GrantedAuthority对象的数组。毫不奇怪,GrantedAuthority是授予委托人的权威。这些权限通常是“角色”,例如ROLE_ADMINISTRATOR或ROLE_HR_SUPERVISOR。稍后将为这些角色配置Web授权,方法授权和域对象授权。 Spring Security的其他部分能够解释这些权限,并期望它们存在。 GrantedAuthority对象通常由UserDetailsS​​ervice加载。
 
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.
通常,GrantedAuthority对象是应用程序范围的权限。它们不是特定于给定的域对象。因此,您可能不会有GrantedAuthority来表示对Employee对象编号54的权限,因为如果有数千个这样的权限,您将很快耗尽内存(或者,至少导致应用程序需要很长时间验证用户的时间)。当然,Spring Security专门设计用于处理这个常见需求,但您可以使用项目的域对象安全功能来实现此目的。
 

9.2.4 Summary(摘要)

Just to recap, the major building blocks of Spring Security that we’ve seen so far are:

回顾一下,我们目前看到的Spring Security的主要构建块是:
  • SecurityContextHolder, to provide access to the SecurityContext.
  • SecurityContextHolder,提供对SecurityContext的访问。
  • SecurityContext, to hold the Authentication and possibly request-specific security information.
  • SecurityContext,用于保存身份验证以及可能的特定于请求的安全信息。
  • Authentication, to represent the principal in a Spring Security-specific manner.
  • 身份验证,以Spring Security特定的方式表示主体。
  • GrantedAuthority, to reflect the application-wide permissions granted to a principal.
  • GrantedAuthority,用于反映授予主体的应用程序范围的权限。
  • UserDetails, to provide the necessary information to build an Authentication object from your application’s DAOs or other source of security data.
  • UserDetails,提供从应用程序的DAO或其他安全数据源构建Authentication对象所需的信息。
  • UserDetailsService, to create a UserDetails when passed in a String-based username (or certificate ID or the like).
  • UserDetailsS​​ervice,用于在基于字符串的用户名(或证书ID等)中传递时创建UserDetails。

Now that you’ve gained an understanding of these repeatedly-used components, let’s take a closer look at the process of authentication.

既然您已经了解了这些重复使用的组件,那么让我们仔细看看身份验证过程。

9.3 Authentication

Spring Security can participate in many different authentication environments. While we recommend people use Spring Security for authentication and not integrate with existing Container Managed Authentication, it is nevertheless supported - as is integrating with your own proprietary authentication system.

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

9.3.1 What is authentication in Spring Security?

Let’s consider a standard authentication scenario that everyone is familiar with.

让我们考虑一个每个人都熟悉的标准身份验证方案。
  1. A user is prompted to log in with a username and password.    提示用户使用用户名和密码登录。
  2. The system (successfully) verifies that the password is correct for the username.
    系统(成功)验证用户名的密码是否正确。
  3. The context information for that user is obtained (their list of roles and so on). 
    获取该用户的上下文信息(他们的角色列表等)。
  4. A security context is established for the user 
    为用户建立安全上下文
  5. The user proceeds, potentially to perform some operation which is potentially protected by an access control mechanism which checks the required permissions for the operation against the current security context information.
    用户继续进行,可能执行一些可能受访问控制机制保护的操作,该访问控制机制针对当前安全上下文信息检查操作所需的许可。

The first three items constitute the authentication process so we’ll take a look at how these take place within Spring Security.

前三项构成了身份验证过程,因此我们将了解这些内容是如何在Spring Security中进行的。
 
  1. The username and password are obtained and combined into an instance of UsernamePasswordAuthenticationToken (an instance of the Authenticationinterface, which we saw earlier).获取用户名和密码并将其组合到UsernamePasswordAuthenticationToken(Authentication接口的实例,我们之前看到)的实例中。
  2. The token is passed to an instance of AuthenticationManager for validation. 令牌传递给AuthenticationManager的实例以进行验证。
  3. The AuthenticationManager returns a fully populated Authentication instance on successful authentication.AuthenticationManager 在成功验证后返回完全填充的Authentication实例。
  4. The security context is established by calling SecurityContextHolder.getContext().setAuthentication(…​), passing in the returned authentication object. 通过调用SecurityContextHolder.getContext()。setAuthentication(...)建立安全上下文,传入返回的身份验证对象。

From that point on, the user is considered to be authenticated. Let’s look at some code as an example.

从那时起,用户被认为是经过身份验证的。我们来看一些代码作为例子。
 
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");
}
}

Here we have written a little program that asks the user to enter a username and password and performs the above sequence. The AuthenticationManager which we’ve implemented here will authenticate any user whose username and password are the same. It assigns a single role to every user. The output from the above will be something like:

在这里,我们编写了一个小程序,要求用户输入用户名和密码并执行上述顺序。我们在此实现的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

Note that you don’t normally need to write any code like this. The process will normally occur internally, in a web authentication filter for example. We’ve just included the code here to show that the question of what actually constitutes authentication in Spring Security has quite a simple answer. A user is authenticated when the SecurityContextHolder contains a fully populated Authentication object.

请注意,您通常不需要编写任何类似的代码。该过程通常在内部进行,例如在Web身份验证过滤器中。我们刚刚在这里包含了代码,以表明在Spring Security中实际构成身份验证的问题有一个非常简单的答案。当SecurityContextHolder包含完全填充的Authentication对象时,将对用户进行身份验证。
 
By default the StrictHttpFirewall is used. This implementation rejects requests that appear to be malicious. If it is too strict for your needs, then you can customize what types of requests are rejected. However, it is important that you do so knowing that this can open your application up to attacks. For example, if you wish to leverage Spring MVC’s Matrix Variables, the following configuration could be used in XML:
默认情况下使用StrictHttpFirewall。此实现拒绝看似恶意的请求。如果它对您的需求过于严格,那么您可以自定义拒绝的请求类型。但是,重要的是要知道这可以打开您的应用程序直至攻击。例如,如果您希望利用Spring MVC的Matrix变量,可以在XML中使用以下配置:
 
<b:bean id="httpFirewall"
      class="org.springframework.security.web.firewall.StrictHttpFirewall"
      p:allowSemicolon="true"/>

<http-firewall ref="httpFirewall"/>

The same thing can be achieved with Java Configuration by exposing a StrictHttpFirewall bean.

通过公开StrictHttpFirewall bean,Java Configuration可以实现同样的目的。
 
@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowSemicolon(true);
    return firewall;
}

  

9.3.2 Setting the SecurityContextHolder Contents Directly (直接设置SecurityContextHolder内容)

In fact, Spring Security doesn’t mind how you put the Authentication object inside the SecurityContextHolder. The only critical requirement is that the SecurityContextHolder contains an Authentication which represents a principal before the AbstractSecurityInterceptor (which we’ll see more about later) needs to authorize a user operation.

实际上,Spring Security并不介意如何将Authentication对象放在SecurityContextHolder中。唯一的关键要求是SecurityContextHolder包含一个Authentication,它表示在AbstractSecurityInterceptor(我们将在后面看到更多信息)需要授权用户操作之前的一个主体。
 
You can (and many users do) write their own filters or MVC controllers to provide interoperability with authentication systems that are not based on Spring Security. For example, you might be using Container-Managed Authentication which makes the current user available from a ThreadLocal or JNDI location. Or you might work for a company that has a legacy proprietary authentication system, which is a corporate "standard" over which you have little control. In situations like this it’s quite easy to get Spring Security to work, and still provide authorization capabilities. All you need to do is write a filter (or equivalent) that reads the third-party user information from a location, build a Spring Security-specific Authentication object, and put it into the SecurityContextHolder
您可以(以及许多用户)编写自己的过滤器或MVC控制器,以提供与不基于Spring Security的身份验证系统的互操作性。例如,您可能正在使用容器管理的身份验证,这使得当前用户可以从ThreadLocal或JNDI位置使用。或者您可能会为拥有传统专有身份验证系统的公司工作,这是一个您无法控制的企业“标准”。在这种情况下,很容易让Spring Security工作,并且仍然提供授权功能。您需要做的就是编写一个过滤器(或等效的),从一个位置读取第三方用户信息,构建一个特定于Spring Security的Authentication对象,并将其放入SecurityContextHolder。
 
 In this case you also need to think about things which are normally taken care of automatically by the built-in authentication infrastructure. For example, you might need to pre-emptively create an HTTP session to cache the context between requests, before you write the response to the client footnote:[It isn’t possible to create a session once the response has been committed.
 
在这种情况下,您还需要考虑内置身份验证基础结构通常会自动处理的事情。例如,在将响应写入客户端脚注之前,您可能需要先强制创建一个HTTP会话来缓存请求之间的上下文:[一旦提交响应,就无法创建会话。
 
If you’re wondering how the AuthenticationManager is implemented in a real world example, we’ll look at that in the core services chapter.
如果您想知道如何在现实世界的示例中实现AuthenticationManager,我们将在核心服务章节中查看它。
 

 

 
 
posted @ 2018-12-18 22:27  帅LOVE俊  阅读(158)  评论(0编辑  收藏  举报