springsecurity初体验(5.3.5官方文档)-1

5.1.2 密码存储
PasswordEncoder,5.0之前默认的NoOpPasswordEncoder ,框架用了DelegatingPasswordEncoder模式,方便将来更新存储方式的时候不用变动框架。
创建委托:

PasswordEncoder passwordEncoder =
    PasswordEncoderFactories.createDelegatingPasswordEncoder();

创建自定义的委托:

String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("sha256", new StandardPasswordEncoder());

PasswordEncoder passwordEncoder =
    new DelegatingPasswordEncoder(idForEncode, encoders);

DelegatingPasswordEncoder存储方式

{id}encodedPassword

举例:

{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG 
{noop}password 
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc 
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=  
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 

传递给构造函数的idForEncode确定将使用哪个PasswordEncoder编码密码。 在上面我们构造的DelegatingPasswordEncoder中,这意味着编码密码的结果将委派给BCryptPasswordEncoder并以{bcrypt}为前缀。
密码匹配:
默认的调用matches(CharSequence, String)如果不传递{id}会抛出IllegalArgumentException,需要设置默认来解决,DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder)。建议使用最新的加密算法。
简单起步,不适合生产

User user = User.withDefaultPasswordEncoder()
  .username("user")
  .password("password")
  .roles("user")
  .build();
System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

可以复用build构建多个用户,尽管仍然会对密码进行哈希,但是不是安全的。

BCryptPasswordEncoder 设置强度

// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

SS(spring security) 默认使用 DelegatingPasswordEncoder ,但是,也可以自定义PasswordEncoder 作为一个bean。如果是老项目迁移,就可以设置一个NoOpPasswordEncoder 的bean。但还是不建议,不安全。

@Bean
public static NoOpPasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}

5.2 漏洞保护
尽可能多的提供了默认保护。
5.2.1 Cross Site Request Forgery (CSRF) 跨域请求伪造
例如访问了一个银行网站,没有登出,然后访问一个恶意网站,里面包含转账脚本。这是由于尽管恶意网站看不到你的cookie,但是requet中仍然包含了银行信息。甚至访问一个正常网站时候,如果该网站是XSS攻击受害者,也会发生类似事情。
CSRF攻击的可能原因是受害者网站的HTTP请求与攻击者网站的请求完全相同。为了防御CSRF攻击,我们需要确保恶意站点无法提供请求中的某些内容,以便我们区分这两个请求。
SS提供两种方式防止CSRF攻击
同步token pattern
cookie会话中指明SameSite Attribute
为了使针对CSRF的任何一种保护都起作用,应用程序必须确保“安全” HTTP方法是幂等的。这意味着使用HTTP方法GET,HEAD,OPTIONS和TRACE的请求不应更改应用程序的状态。

最主要,最全面的防御方式就是Synchronizer Token Pattern,该解决方案是为了确保每个HTTP请求除了我们的会话cookie之外,还必须在HTTP请求中包含一个安全的,随机生成的值,称为CSRF令牌。
提交HTTP请求时,服务器必须查找预期的CSRF令牌,并将其与HTTP请求中的实际CSRF令牌进行比较。如果值不匹配,则应拒绝HTTP请求。
这项工作的关键在于,实际的CSRF令牌应该位于浏览器不会自动包含的HTTP请求的一部分中。 例如,在HTTP参数或HTTP标头中要求实际的CSRF令牌将防止CSRF攻击。 在cookie中要求实际CSRF令牌不起作用,因为浏览器会自动将cookie包含在HTTP请求中。
我们不想在HTTP GET中包含随机令牌,因为这可能导致令牌泄漏。
让我们看一下使用同步令牌模式时示例将如何变化。假设实际的CSRF令牌必须位于名为_csrf的HTTP参数中。我们应用程序的转帐表格如下:

<form method="post"
    action="/transfer">
<input type="hidden"
    name="_csrf"
    value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<input type="text"
    name="amount"/>
<input type="text"
    name="routingNumber"/>
<input type="hidden"
    name="account"/>
<input type="submit"
    value="Transfer"/>
</form>

现在,该表单包含具有CSRF令牌值的隐藏输入。外部站点无法读取CSRF令牌,因为同源策略可确保恶意站点无法读取response。相应的HTTP汇款请求如下所示:

POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721

您会注意到,HTTP请求现在包含带有安全随机值的_csrf参数。 恶意网站将无法为_csrf参数提供正确的值(必须在邪恶网站上明确提供),并且当服务器将实际CSRF令牌与预期CSRF令牌进行比较时,传输将失败。

SameSite Attribute
一种防止CSRF攻击的新兴方法是在cookie上指定SameSite属性。设置cookie时,服务器可以指定SameSite属性,以指示从外部站点发出时不应发送该cookie。
Spring Security不直接控制会话cookie的创建,因此不提供对SameSite属性的支持。 Spring Session在基于servlet的应用程序中为SameSite属性提供支持。 Spring Framework的CookieWebSessionIdResolver为基于WebFlux的应用程序中的SameSite属性提供了开箱即用的支持。
一个示例,带有SameSite属性的HTTP响应标头可能类似于:

Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax

SameSite 属性
Strict :指定后,来自同一站点的任何请求都将包含cookie。否则,cookie将不会包含在HTTP请求中。
Lax :当来自相同站点或请求来自顶级导航且方法是幂等时,将发送指定的cookie。否则,cookie将不会包含在HTTP请求中。
另一个明显的考虑因素是,为了使SameSite属性能够保护用户,浏览器必须支持SameSite属性。 大多数现代浏览器都支持SameSite属性。 但是,可能仍未使用较旧的浏览器。 因此,通常建议将SameSite属性用作深度防御,而不是针对CSRF攻击的唯一防护。

什么时候用CSRF防御
我们的建议是对普通用户可能由浏览器处理的任何请求使用CSRF保护。 如果仅创建非浏览器客户端使用的服务,则可能需要禁用CSRF保护。

CSRF防御和json
需不需要用csrf防御json请求,这得看情况。
CSRF with JSON form

产生得json结构

{ "amount": 100,
"routingNumber": "evilsRoutingNumber",
"account": "evilsAccountNumber",
"ignore_me": "=test"
}

如果应用程序未验证Content-Type,则该应用程序将被暴露。 根据设置的不同,仍然可以通过更新URL后缀以.json结尾来利用验证内容类型的Spring MVC应用程序,如下所示

CSRF和无状态浏览器应用程序
如果我的应用程序是无状态的怎么办?这并不一定意味着您受到保护。实际上,如果用户不需要针对给定请求在Web浏览器中执行任何操作,则他们可能仍然容易受到CSRF攻击。
例如,考虑一个使用自定义cookie而不是JSESSIONID的应用程序,该自定义cookie包含其中的所有状态用于身份验证。 进行CSRF攻击后,自定义cookie将与请求一起发送,其方式与在前面的示例中发送JSESSIONID cookie相同。 此应用程序容易受到CSRF攻击。
使用基本身份验证的应用程序也容易受到CSRF攻击。 该应用程序容易受到攻击,因为浏览器将在所有请求中自动包含用户名和密码,就像在上一个示例中发送JSESSIONID cookie一样。

5.2.2 Security HTTP Response Headers
有许多HTTP响应头可用于提高Web应用程序的安全性。本节专门介绍Spring Security提供显式支持的各种HTTP响应头
Spring Security提供了一组默认的与安全性相关的HTTP响应头,以提供安全的默认值。 Spring Security的默认值为包含以下头:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

Cache Control
默认关闭缓存,如果用户进行了身份验证以查看敏感信息然后注销,则我们不希望恶意用户能够单击“后退”按钮查看敏感信息。默认情况下发送的缓存控制标头是:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0

SS默认添加这些标头,但是,如果您的应用程序提供了自己的缓存控制标头,Spring Security将会退出。这允许应用程序确保可以缓存CSS和JavaScript之类的静态资源。

Content Type Options
SS默认关闭内容嗅探(一种提高用户 体验的方式),通过以下http response

X-Content-Type-Options: nosniff

HTTP Strict Transport Security (HSTS) HTTP严格传输安全性
当您输入银行的网站时,您输入的是mybank.example.com还是输入https://mybank.example.com? 如果省略https协议,则可能会受到中间人攻击。 即使网站执行重定向到https://mybank.example.com的恶意用户,也可能拦截初始HTTP请求并操纵响应(即重定向到https://mibank.example.com并窃取其凭据)。
许多用户忽略了https协议,这就是创建HTTP严格传输安全性(HSTS)的原因。 将mybank.example.com添加为HSTS主机后,浏览器可以提前知道对mybank.example.com的任何请求都应解释为https://mybank.example.com。 这大大降低了发生中间人攻击的可能性。
将站点标记为HSTS主机的一种方法是将主机预加载到浏览器中。 另一个是将Strict-Transport-Security标头添加到响应中。 例如,Spring Security的默认行为是添加以下标头,该标头指示浏览器将域视为一年的HSTS主机(一年大约31536000秒):

Strict-Transport-Security: max-age=31536000 ; includeSubDomains ; preload

X-Frame-Options
允许网站添加框架也是安全问题,现代浏览器采用以下标头来禁止
X-Frame-Options: DENY

X-XSS-Protection
一些浏览器内置了对过滤掉反射的XSS攻击的支持。这绝非万无一失,但确实有助于XSS保护。
通常默认情况下会启用过滤,因此添加标头通常只能确保已启用标头,并指示浏览器在检测到XSS攻击时应采取的措施。 例如,过滤器可能会尝试以最小侵入性的方式更改内容以仍然呈现所有内容。 有时,这种类型的替换本身可能会成为XSS漏洞。 相反,最好是阻止内容,而不要尝试对其进行修复。 默认情况下,Spring Security使用以下标头阻止内容:

X-XSS-Protection: 1; mode=block

Content Security Policy (CSP)
内容安全策略(CSP)是Web应用程序可以用来减轻诸如跨站点脚本(XSS)之类的内容注入漏洞的机制。 CSP是一种声明性策略,为Web应用程序作者提供了一种工具,可以声明该Web应用程序希望从中加载资源的来源,并最终将这些信息通知客户端(用户代理)。
内容安全策略并非旨在解决所有内容注入漏洞。 取而代之的是,可以利用CSP帮助减少内容注入攻击所造成的危害。 作为第一道防线,Web应用程序作者应验证其输入并对输出进行编码。
Web应用程序可以通过在响应中包括以下HTTP标头之一来使用CSP:
Content-Security-Policy
Content-Security-Policy-Report-Only
这些标头中的每一个都用作将安全策略传递给客户端的机制。安全策略包含一组安全策略指令,每个指令负责声明对特定资源表示形式的限制。
例如,Web应用程序可以通过在响应中包含以下标头来声明它希望从特定的受信任源中加载脚本:

Content-Security-Policy: script-src https://trustedscripts.example.com

用户代理会阻止尝试从另一个源(而不是script-src指令中声明的内容)加载脚本。 此外,如果在安全策略中声明了report-uri指令,则用户代理会将违反行为报告给声明的URL。

Content-Security-Policy: script-src https://trustedscripts.example.com; report-uri /csp-report-endpoint/

Content-Security-Policy-Report-Only标头为Web应用程序作者和管理员提供了监视安全策略而不是强制执行这些策略的功能。 该标题通常在试验/开发站点的安全策略时使用。 当某个策略被认为有效时,可以通过使用Content-Security-Policy标头字段来强制实施。

Content-Security-Policy-Report-Only: script-src 'self' https://trustedscripts.example.com; report-uri /csp-report-endpoint/

Referrer Policy

Referrer-Policy: same-origin`

Feature Policy

Feature-Policy: geolocation 'self'

Clear Site Data

Clear-Site-Data: "cache", "cookies", "storage", "executionContexts"

Custom Headers
Spring Security具有使您可以方便地将更常见的安全标头添加到应用程序中的机制。但是,它也提供了挂钩来启用添加自定义标头。

5.2.3
HTTP
所有基于HTTP的通信(包括静态资源)都应使用TLS进行保护。
作为一个框架,Spring Security不处理HTTP连接,因此不直接提供对HTTPS的支持。但是,它确实提供了许多有助于HTTPS使用的功能。
Redirect to HTTPS
当客户端使用HTTP时,Spring Security可以配置为重定向到Servlet和WebFlux环境到HTTPS。
Strict Transport Security
Spring Security提供对严格传输安全性的支持,并默认启用它。
Proxy Server Configuration
使用代理服务器时,确保已正确配置应用程序很重要。

6 Project Modules
在Spring Security 3.0中,代码库被细分为单独的jar,这些jar更清楚地区分了不同的功能区域和第三方依赖项。
Core ,Remoting,Web ,Config ,LDAP ,OAuth 2.0 Core,OAuth 2.0 Client , OAuth 2.0 JOSE,OAuth 2.0 Resource Server ,ACL ,CAS ,OpenID ,Test

8.3 启动hello spring security 项目
Spring Boot Auto Configuration

  • 用S​​pring Security的默认配置,该配置将Servlet过滤器创建为名为springSecurityFilterChain的bean。此bean负责应用程序内的所有安全性(保护应用程序URL,验证提交的用户名和密码,重定向到登录表单等)。
  • 创建一个UserDetailsS​​ervice Bean,其中包含用户名user和随机生成的密码,该密码将记录到控制台。
  • 针对每个请求,使用Servlet容器向名为springSecurityFilterChain的bean注册过滤器。

Spring Boot的配置不多,但功能很多。功能摘要如下:

  • 需要经过身份验证的用户才能与应用程序进行任何交互
  • 为您生成一个默认的登录表单
  • 让用户使用用户名user和密码登录到控制台以使用基于表单的身份验证进行身份验证
  • 使用BCrypt保护密码存储
  • 让用户注销
  • CSRF攻击预防
  • 会话固定保护
  • 安全标头集成
  • 简体)
  • 与以下Servlet API方法集成:getRemoteUser, getUserPrincipal, isUserinRole, login, logout;

9.Servlet Security: The Big Picture
本节讨论基于Servlet的应用程序中Spring Security的高级体系结构,理解 Authentication, Authorization, Protection Against Exploits。、
9.1 filters
Spring Security的Servlet支持基于Servlet过滤器,因此通常首先了解过滤器的作用会很有帮助。下图显示了单个HTTP请求的处理程序的典型分层。
springsecurity初体验(5.3.5官方文档)-1_第1张图片
由于过滤器仅影响下游过滤器和Servlet,因此每个过滤器的调用顺序非常重要。例子:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // do something before the rest of the application
    chain.doFilter(request, response); // invoke the rest of the application
    // do something after the rest of the application
}

9.2 DelegatingFilterProxy
pring提供了一个名为DelegatingFilterProxy的Filter实现,可以在Servlet容器的生命周期和Spring的ApplicationContext之间进行桥接。 Servlet容器允许使用其自己的标准注册Filters,但它不了解Spring定义的Bean。 DelegatingFilterProxy可以通过标准的Servlet容器机制进行注册,但是可以将所有工作委托给实现Filter的Spring Bean。
这是DelegatingFilterProxy如何适配Filters和FilterChain的图片。
springsecurity初体验(5.3.5官方文档)-1_第2张图片
DelegatingFilterProxy从ApplicationContext查找Bean Filter0,然后调用Bean Filter0。 DelegatingFilterProxy的伪代码如下所示。

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // Lazily get Filter that was registered as a Spring Bean
    // For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
    Filter delegate = getFilterBean(someBeanName);
    // delegate work to the Spring Bean
    delegate.doFilter(request, response);
}

DelegatingFilterProxy的另一个好处是它允许延迟查找Filter bean实例。 这很重要,因为容器需要先注册Filter实例,然后容器才能启动。 但是,Spring通常使用ContextLoaderListener来加载Spring Bean,直到需要注册Filter实例之后,它才会完成。
9.3 FilterChainProxy
Spring Security的Servlet支持包含在FilterChainProxy中。 FilterChainProxy是Spring Security提供的特殊过滤器,允许通过SecurityFilterChain委派许多过滤器实例。 由于FilterChainProxy是Bean,因此通常将其包装在DelegatingFilterProxy中。
9.4 SecurityFilterChain
FilterChainProxy使用SecurityFilterChain确定应对此请求调用哪些Spring Security过滤器。

springsecurity初体验(5.3.5官方文档)-1_第3张图片
9.5 Security Filters
安全过滤器通过SecurityFilterChain API插入到FilterChainProxy中。 过滤器的顺序很重要。 通常不必知道Spring Security的过滤器的顺序。 但是,有时知道顺序是有益的。下面是Spring Security Filter顺序的完整列表:

ChannelProcessingFilter

ConcurrentSessionFilter

WebAsyncManagerIntegrationFilter

SecurityContextPersistenceFilter

HeaderWriterFilter

CorsFilter

CsrfFilter

LogoutFilter

OAuth2AuthorizationRequestRedirectFilter

Saml2WebSsoAuthenticationRequestFilter

X509AuthenticationFilter

AbstractPreAuthenticatedProcessingFilter

CasAuthenticationFilter

OAuth2LoginAuthenticationFilter

Saml2WebSsoAuthenticationFilter

UsernamePasswordAuthenticationFilter

ConcurrentSessionFilter

OpenIDAuthenticationFilter

DefaultLoginPageGeneratingFilter

DefaultLogoutPageGeneratingFilter

DigestAuthenticationFilter

BearerTokenAuthenticationFilter

BasicAuthenticationFilter

RequestCacheAwareFilter

SecurityContextHolderAwareRequestFilter

JaasApiIntegrationFilter

RememberMeAuthenticationFilter

AnonymousAuthenticationFilter

OAuth2AuthorizationCodeGrantFilter

SessionManagementFilter

ExceptionTranslationFilter

FilterSecurityInterceptor

SwitchUserFilter

9.6 Handling Security Exceptions
ExceptionTranslationFilter允许将AccessDeniedException和AuthenticationException转换为HTTP响应。
ExceptionTranslationFilter作为filters之一插入到FilterChainProxy中。
springsecurity初体验(5.3.5官方文档)-1_第4张图片

  1. 首先,ExceptionTranslationFilter调用FilterChain.doFilter(request,response)来调用应用程序的其余部分。
  2. 如果用户未通过身份验证或它是AuthenticationException,则启动身份验证。清除SecurityContextHolder。HttpServletRequest保存在RequestCache中。用户成功进行身份验证后,将使用RequestCache重播原始请求。AuthenticationEntryPoint用于从客户端请求凭据。例如,它可能重定向到登录页面或发送WWW-Authenticate标头。
  3. 否则,如果它是AccessDeniedException,则拒绝访问。调用AccessDeniedHandler来处理被拒绝的访问。

如果应用程序未引发AccessDeniedException或AuthenticationException,则ExceptionTranslationFilter不执行任何操作。
ExceptionTranslationFilter 的伪代码:

try {
    filterChain.doFilter(request, response); 
} catch (AccessDeniedException | AuthenticationException e) {
    if (!authenticated || e instanceof AuthenticationException) {
        startAuthentication(); 
    } else {
        accessDenied(); 
    }
}

10.Authentication
SecurityContextHolder - Spring Security在SecurityContextHolder中存储通过身份验证的人员的详细信息。
SecurityContext - 从SecurityContextHolder获得,并包含当前经过身份验证的用户的身份验证。
Authentication - 用户提供的用于身份验证的凭据或来自SecurityContext的当前用户,输入到AuthenticationManager中
GrantedAuthority - 授予身份验证主体的权限(即角色,作用域等)
AuthenticationManager-定义Spring Security的过滤器如何执行身份验证的API。
ProviderManager-AuthenticationManager的最常见实现。
AuthenticationProvider-由ProviderManager用于执行特定类型的身份验证。
带AuthenticationEntryPoint的请求凭据-用于从客户端请求凭据(即,重定向到登录页面,发送WWW身份验证响应等)
AbstractAuthenticationProcessingFilter-用于验证的基本过滤器。这也为高级身份验证流程以及各个部分如何协同工作提供了一个好主意。
10.1 SecurityContextHolder
Spring Security身份验证模型的核心是SecurityContextHolder。它包含SecurityContext。
springsecurity初体验(5.3.5官方文档)-1_第5张图片
SecurityContextHolder用于存储通过身份验证的人员的详细信息。 Spring Security并不关心如何填充SecurityContextHolder。 如果它包含一个值,那么它将用作当前经过身份验证的用户。
指示用户已通过身份验证的最简单方法是直接设置SecurityContextHolder。
Setting SecurityContextHolder

SecurityContext context = SecurityContextHolder.createEmptyContext(); 
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER"); 
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context); 

我们首先创建一个空的SecurityContext。

  1. 重要的是创建新的SecurityContext实例,而不是使用SecurityContextHolder.getContext().setAuthentication(authentication)以避免多线程下的问题。
  2. Spring Security并不关心在SecurityContext上设置什么类型的Authentication实现。这里我们使用TestingAuthenticationToken,因为它非常简单。一个更常见的生产方案是UsernamePasswordAuthenticationToken(userDetails, password, authorities)
  3. 最后,我们在SecurityContextHolder上设置SecurityContext。 Spring Security将使用此信息进行授权。

如果您希望获取有关已验证主体的信息,可以通过访问SecurityContextHolder来获得。

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

默认情况下,SecurityContextHolder使用ThreadLocal存储这些详细信息,这意味着即使没有将SecurityContext作为这些方法的参数显式传递,SecurityContext始终可用于同一执行线程中的方法。 如果在处理当前委托人的请求后要清除线程,则以这种方式使用ThreadLocal是非常安全的。 Spring Security的FilterChainProxy确保始终清除SecurityContext。
10.2. SecurityContext
从SecurityContextHolder获得SecurityContext。 SecurityContext包含一个 Authentication 对象。
10.3. Authentication
Authentication在Spring Security中有两个主要用途:
AuthenticationManager的输入,用户提供的用于身份验证的凭据。在这种情况下使用时,isAuthenticated()返回false。
代表当前经过身份验证的用户。可以从SecurityContext获得当前的身份验证。
Authentication 包括:
principal - 识别用户。使用username/password进行身份验证时,这通常是UserDetails的实例。
credentials - 通常是pasword。在许多情况下,将在验证用户身份后清除此内容,以确保它不会泄漏。
authorities - GrantedAuthoritys是授予用户的高级权限,例如角色或者范围。

10.4. GrantedAuthority
GrantedAuthoritys 可以从Authentication.getAuthorities() 获得,返回GrantedAuthority 的collection,GrantedAuthority是授予主体的权限。通常是roles,比如ROLE_ADMINISTRATOR 和 ROLE_HR_SUPERVISOR。稍后将这些角色配置为Web授权,方法授权和域对象授权。pring Security的其他部分能够解释这些权限,使用基于用户名/密码的身份验证时,GrantedAuthority通常由UserDetailsS​​ervice加载。
10.5. AuthenticationManager
AuthenticationManager是用于定义Spring Security的过滤器如何执行身份验证的API。 然后由调用AuthenticationManager的控制器(即Spring Security的Filters)在SecurityContextHolder上设置返回的Authentication。 如果您不与Spring Security的过滤器集成,则可以直接设置SecurityContextHolder,并且不需要使用AuthenticationManager。 虽然AuthenticationManager的实现可以是任何东西,但最常见的实现是ProviderManager。
10.6 ProviderManager
ProviderManager是AuthenticationManager最常用的实现。 ProviderManager委托给AuthenticationProviders列表。 每个AuthenticationProvider都有机会表明身份验证应该成功,失败,或者表明它不能做出决定并允许下游AuthenticationProvider做出决定。 如果没有一个已配置的AuthenticationProviders可以进行身份验证,则身份验证将失败,并显示ProviderNotFoundException,这是一个特殊的AuthenticationException,它表示未配置ProviderManager支持传递给它的Authentication类型。
springsecurity初体验(5.3.5官方文档)-1_第6张图片
实际上,每个AuthenticationProvider都知道如何执行特定类型的身份验证。 例如,一个AuthenticationProvider可能能够验证用户名/密码,而另一个可能能够验证SAML断言。 这允许每个AuthenticationProvider进行非常特定类型的身份验证,同时支持多种身份验证,并且仅公开一个AuthenticationManager bean。
实际上,多个ProviderManager实例可能共享同一个父AuthenticationManager。 在存在多个具有相同身份验证(共享父AuthenticationManager)但又具有不同身份验证机制(不同ProviderManager实例)的多个SecurityFilterChain实例的情况下,这种情况有些常见。

10.7. AuthenticationProvider
可以将多个AuthenticationProviders注入ProviderManager。 每个AuthenticationProvider执行特定的身份验证类型。 例如,DaoAuthenticationProvider支持基于用户名/密码的身份验证,而JwtAuthenticationProvider支持对JWT令牌的身份验证。

10.8. Request Credentials with AuthenticationEntryPoint
AuthenticationEntryPoint用于发送从客户端请求凭据的http响应。
有时,客户端会主动提供凭据(例如用户名/密码)来请求资源。 在这些情况下,Spring Security无需提供HTTP响应即可从客户端请求凭证,因为它们已包含在内。
在其他情况下,客户端将对未经授权访问的资源发出未经身份验证的请求。 在这种情况下,AuthenticationEntryPoint的实现用于从客户端请求凭据。 AuthenticationEntryPoint实现可能会执行重定向到登录页面,使用WWW-Authenticate标头进行响应等。

10.9. AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter用作基础过滤器,用于验证用户的凭据。在对凭证进行身份验证之前,Spring Security通常使用AuthenticationEntryPoint请求凭证。
接下来,AbstractAuthenticationProcessingFilter可以对提交给它的任何身份验证请求进行身份验证。
springsecurity初体验(5.3.5官方文档)-1_第7张图片

  1. 当用户提交其凭据时,AbstractAuthenticationProcessingFilter将从要验证的HttpServletRequest创建一个Authentication。 创建的身份验证类型取决于AbstractAuthenticationProcessingFilter的子类。 例如,UsernamePasswordAuthenticationFilter根据在HttpServletRequest中提交的用户名和密码来创建UsernamePasswordAuthenticationToken。
  2. 接下来,将Authentication传递到AuthenticationManager进行身份验证。
  3. 如果验证失败,则Failure过程
    清除SecurityContextHolder
    RememberMeServices.loginFail被调用。
    AuthenticationFailureHandler被调用。
  4. 如果验证成功,则Success过程
    SessionAuthenticationStrategy被通知有新的登陆
    Authentication是在SecurityContextHolder上设置的。之后,SecurityContextPersistenceFilter将SecurityContext保存到HttpSession中。
    RememberMeServices.loginSuccess被调用。
    ApplicationEventPublisher发布一个InteractiveAuthenticationSuccessEvent

10.10 Username/Password Authentication
验证用户身份的最常见方法之一是验证用户名和密码。
Spring Security提供了以下内置机制,用于从HttpServletRequest中读取用户名和密码:
Form Login
Basic Authentication
Digest Authentication
用于读取用户名和密码的每种机制都可以利用任何受支持的存储机制:
Simple Storage with In-Memory Authentication
Relational Databases with JDBC Authentication
Custom data stores with UserDetailsService
LDAP storage with LDAP Authentication

10.10.1 表单登陆
让我们看看Spring Security中基于表单的登录如何工作。首先,我们了解如何将用户重定向到登录表单。
springsecurity初体验(5.3.5官方文档)-1_第8张图片

  1. 首先用户发送未经授权的资源请求/private
  2. Spring Security的FilterSecurityInterceptor通过抛出AccessDeniedException来指示未经身份验证的请求被拒绝。
  3. 由于未对用户进行身份验证,因此ExceptionTranslationFilter会启动“开始身份验证”,并使用配置的AuthenticationEntryPoint将重定向发送到登录页面。 在大多数情况下,AuthenticationEntryPoint是LoginUrlAuthenticationEntryPoint的实例。
  4. 然后,浏览器将请求将其重定向到的登录页面。
  5. 应用程序中的某些内容必须呈现登录页面。

提交用户名和密码后,UsernamePasswordAuthenticationFilter会对用户名和密码进行身份验证。 UsernamePasswordAuthenticationFilter扩展了AbstractAuthenticationProcessingFilter。
默认情况下,Spring Security表单登录处于启用状态。但是,一旦提供了任何基于servlet的配置,就必须显式提供基于表单的登录。可以在下面找到最小的显式Java配置:

protected void configure(HttpSecurity http) {
    http
        // ...
        .formLogin(withDefaults());
}

大多数生产应用程序将需要自定义登录表单。 下面的配置演示了如何提供自定义登录表单。

protected void configure(HttpSecurity http) throws Exception {
    http
        // ...
        .formLogin(form -> form
            .loginPage("/login")
            .permitAll()
        );
}

有关默认HTML表单的一些要点:
post方法 /login
该表格将需要包含一个CSRF令牌,thymeleaf下会自动包含
定义名称必须为username和password
如果找到HTTP参数错误,则表明用户未能提供有效的用户名/密码
如果找到HTTP参数注销,则表明用户已成功注销
如果您使用的是Spring MVC,则需要一个将GET / login映射到我们创建的登录模板的控制器。下面是最小的LoginController示例:

@Controller
class LoginController {
    @GetMapping("/login")
    String login() {
        return "login";
    }
}

10.10.2 Basic Authentication
本节详细介绍了Spring Security如何为基于servlet的应用程序提供对基本HTTP身份验证的支持。
和表单登陆区别是第3步,由于用户未通过身份验证,因此ExceptionTranslationFilter会启动“开始身份验证”。 配置的AuthenticationEntryPoint是BasicAuthenticationEntryPoint的实例,该实例发送WWW-Authenticate标头。 RequestCache通常是一个NullRequestCache,它不保存请求,因为客户端能够重播它最初请求的请求。当客户端收到WWW-Authenticate标头时,它知道应该使用用户名和密码重试。然后当用户提交其用户名和密码时,BasicAuthenticationFilter通过从HttpServletRequest中提取用户名和密码来创建UsernamePasswordAuthenticationToken,其他都差不多。
Spring Security的HTTP基本身份验证支持默认为启用。但是,一旦提供了任何基于servlet的配置,就必须显式提供HTTP Basic。

protected void configure(HttpSecurity http) {
    http
        // ...
        .httpBasic(withDefaults());
}

10.10.3. Digest Authentication 摘要认证
本节详细介绍了Spring Security如何提供对Digest AuthenticationFilter的 Digest Authentication 的支持。
您不应该在现代应用程序中使用摘要式身份验证,因为它不安全。 最明显的问题是您必须以纯文本,加密或MD5格式存储密码。 所有这些存储格式都被认为是不安全的。 相反,应该用bCrypt,PBKDF2,SCrypt等存储凭据。
不安全不展开细说。

10.10.4. In-Memory Authentication
Spring Security的InMemoryUserDetailsManager实现了UserDetailsS​​ervice,以支持对在内存中检索到的基于用户名/密码的身份验证。InMemoryUserDetailsManager通过实现UserDetailsManager接口来提供对UserDetails的管理。当配置为接受用户名/密码进行身份验证时,Spring Security使用基于UserDetails的身份验证。
InMemoryUserDetailsManager Java Configuration

@Bean
public UserDetailsService users() {
   UserDetails user = User.builder()
       .username("user")
       .password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
       .roles("USER")
       .build();
   UserDetails admin = User.builder()
       .username("admin")
       .password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
       .roles("USER", "ADMIN")
       .build();
   return new InMemoryUserDetailsManager(user, admin);
}

上面得例子虽然存储安全,但是对初学者不友好。
在下面的示例中,我们利用User.withDefaultPasswordEncoder来确保存储在内存中的密码受到保护。 但是,它不能防止通过反编译源代码来获取密码。 因此,User.withDefaultPasswordEncoder仅应用于“入门”,而不应用于生产。

@Bean
public UserDetailsService users() {
    // The builder will ensure the passwords are encoded before saving in memory
    UserBuilder users = User.withDefaultPasswordEncoder();
    UserDetails user = users
        .username("user")
        .password("password")
        .roles("USER")
        .build();
    UserDetails admin = users
        .username("admin")
        .password("password")
        .roles("USER", "ADMIN")
        .build();
    return new InMemoryUserDetailsManager(user, admin);
}

10.10.5. JDBC Authentication
Spring Security的JdbcDaoImpl实现了UserDetailsService,以支持使用JDBC检索的基于用户名/密码的身份验证。 JdbcUserDetailsManager扩展了JdbcDaoImpl以通过UserDetailsManager接口提供对UserDetails的管理。 当配置为接受用户名/密码进行身份验证时,Spring Security使用基于UserDetails的身份验证。
JdbcDaoImpl要求表为用户加载密码,帐户状态(启用或禁用)和权限列表(角色)。所需的默认架构可以在下面找到。
org/springframework/security/core/userdetails/jdbc/users.ddl

create table users(
    username varchar_ignorecase(50) not null primary key,
    password varchar_ignorecase(500) not null,
    enabled boolean not null
);

create table authorities (
    username varchar_ignorecase(50) not null,
    authority varchar_ignorecase(50) not null,
    constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username,authority);

在配置JdbcUserDetailsManager之前,我们必须创建一个数据源。在我们的示例中,我们将设置一个使用默认用户架构初始化的嵌入式DataSource。

@Bean
DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(H2)
        .addScript("classpath:org/springframework/security/core/userdetails/jdbc/users.ddl")
        .build();
}

在生产环境中必须设置与外部数据库连接。
JdbcUserDetailsManager Bean

@Bean
UserDetailsManager users(DataSource dataSource) {
    UserDetails user = User.builder()
        .username("user")
        .password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
        .roles("USER")
        .build();
    UserDetails admin = User.builder()
        .username("admin")
        .password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
        .roles("USER", "ADMIN")
        .build();
    JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
    users.createUser(user);
    users.createUser(admin);
}

10.10.7 UserDetailsService
UserDetails由UserDetailsS​​ervice返回。 DaoAuthenticationProvider验证UserDetails,然后返回身份验证,该身份验证的主体是已配置的UserDetailsS​​ervice返回的UserDetails。
您可以通过将自定义UserDetailsS​​ervice暴露为bean来定义自定义身份验证。例如,下面的示例将假设CustomUserDetailsS​​ervice实现UserDetailsS​​ervice来自定义身份验证
仅当尚未填充AuthenticationManagerBuilder且未定义AuthenticationProviderBean时,才使用此方法。

@Bean
CustomUserDetailsService customUserDetailsService() {
    return new CustomUserDetailsService();
}

10.10.8. PasswordEncoder
Spring Security的servlet支持通过与PasswordEncoder集成来安全地存储密码。可以通过公开一个PasswordEncoder Bean来定制Spring Security使用的PasswordEncoder实现。

10.10.9. DaoAuthenticationProvider
aoAuthenticationProvider是AuthenticationProvider实现,它利用UserDetailsS​​ervice和PasswordEncoder对用户名和密码进行身份验证。
我们看一下DaoAuthenticationProvider在Spring Security中的工作方式。该图详细说明了“读取用户名和密码”中的AuthenticationManager如何工作。
springsecurity初体验(5.3.5官方文档)-1_第9张图片

  1. 通过读取用户名和密码的authentication Filter将UsernamePasswordAuthenticationToken传递给AuthenticationManager,该身份管理器由ProviderManager实现。
  2. ProviderManager配置为使用DaoAuthenticationProvider类型的AuthenticationProvider
  3. DaoAuthenticationProvider从UserDetailsS​​ervice查找UserDetails。
  4. 然后DaoAuthenticationProvider使用PasswordEncoder来验证上一步返回的UserDetails上的密码。
  5. 身份验证成功后,返回的身份验证的类型为UsernamePasswordAuthenticationToken,其主体为配置的UserDetailsService返回的UserDetails。 最终,返回的UsernamePasswordAuthenticationToken将由身份验证筛选器在SecurityContextHolder上设置。

10.10.10. LDAP Authentication
略过

10.11 Session Management
与HTTP session相关的功能由SessionManagementFilter和SessionAuthenticationStrategy接口的组合来处理,过滤器委托该接口。 典型的用法包括防止会话固定保护攻击,检测会话超时以及限制已认证用户可以同时打开多少个会话。
10.11.1 Detecting Timeouts
可以配置Spring Security来检测提交的无效会话ID,并将用户重定向到适当的URL。这是通过会话管理元素实现的:


...


请注意,如果使用此机制来检测会话超时,则在用户注销然后重新登录而未关闭浏览器的情况下,它可能会错误地报告错误。 这是因为在使会话无效时不会清除会话cookie,即使用户已注销,会话cookie也将被重新提交。 您可能能够在注销时显式删除JSESSIONID cookie,例如通过在注销处理程序中使用以下语法:





不幸的是,不能保证它可以与每个servlet容器一起使用,因此您需要在您的环境中对其进行测试.
10.11.2 Concurrent Session Control
如果您希望限制单个用户登录到您的应用程序的能力,Spring Security可以通过以下简单的补充来支持此功能。 首先,您需要将以下侦听器添加到您的web.xml文件中,以使Spring Security更新有关会话生命周期事件的信息:



    org.springframework.security.web.session.HttpSessionEventPublisher


然后将以下行添加到您的应用程序上下文:


...

    


这将防止用户多次登录-第二次登录将使第一次登录无效。通常,您希望避免再次登录,在这种情况下,您可以使用。


...

    


第二次登录将被拒绝。

10.11.3. Session Fixation Attack Protection
会话固定保护
10.11.4. SessionManagementFilter
SessionManagementFilter根据SecurityContextHolder的当前内容检查SecurityContextRepository的内容,以确定用户是否已在当前请求期间进行了身份验证,通常是通过非交互式身份验证机制进行的,例如预身份验证或“记住我” [2]。 如果存储库包含安全上下文,则过滤器不执行任何操作。 如果不是,并且线程本地SecurityContext包含(非匿名)身份验证对象,则过滤器将假定它们已由堆栈中的先前过滤器进行了身份验证。 然后它将调用配置的SessionAuthenticationStrategy。
如果用户当前未通过身份验证,则过滤器将检查是否已请求了无效的会话ID(例如,由于超时),并且将调用已配置的InvalidSessionStrategy(如果已设置)。 最常见的行为就是重定向到固定URL,并将其封装在标准实现SimpleRedirectInvalidSessionStrategy中。 如前所述,在通过名称空间配置无效的会话URL时,也会使用后者。
10.11.5 SessionAuthenticationStrategy
用途是确保session存在或者更改sessionId防止sessison fixation攻击,被SessionManagementFilter 和 AbstractAuthenticationProcessingFilter所使用。例如你如果使用自定义表单登陆,则都需要注入这两个类中。例如







    
    ...



10.11.6 并发控制
Spring Security可以防止主体同时向同一应用程序进行身份验证超过指定次数,网络管理员喜欢此功能,因为它有助于防止人们共享登录名。例如,您可以阻止用户“蝙蝠侠”从两个不同的会话登录到Web应用程序。您可以使他们的前一次登录到期,也可以在他们尝试再次登录时报告错误,从而阻止第二次登录。
10.12 记住我
“记住我”或“持久登录”身份验证是指网站能够记住会话之间的主体身份。通常,这是通过向浏览器发送一个cookie来实现的,该cookie在以后的会话中被检测到并导致自动登录。Spring Security提供了进行这些操作所需的钩子,并具有两个具体的“记住我”实现。一种是哈希,一种是数据库等存储。
简单的基于哈希的令牌方法:
和摘要认证一样,不安全,不推荐
持久化令牌方法:
要将这种方法与命名空间配置一起使用,将提供一个数据源ref:


...


数据库应包含一个使用以下SQL(或等效SQL)创建的persistent_logins表:

create table persistent_logins (username varchar(64) not null,
                                series varchar(64) primary key,
                                token varchar(64) not null,
                                last_used timestamp not null)

10.12.4. Remember-Me Interfaces and Implementations
Remember-me和sernamePasswordAuthenticationFilter一起使用,并通过AbstractAuthenticationProcessingFilter父类中的钩子实现。它还在BasicAuthenticationFilter中使用。挂钩将在适当的时间调用具体的RememberMeServices。接口如下所示:

Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);

void loginFail(HttpServletRequest request, HttpServletResponse response);

void loginSuccess(HttpServletRequest request, HttpServletResponse response,
   Authentication successfulAuthentication);

每当SecurityContextHolder不包含Authentication时,RememberMeAuthenticationFilter就会调用autoLogin()方法。
TokenBasedRememberMeServices:
用于上面说的简单哈希方法
PersistentTokenBasedRememberMeServices:
可以使用与TokenBasedRememberMeServices相同的方式使用此类,但还需要使用PersistentTokenRepository配置该类来存储令牌。有两种标准实现。
InMemoryTokenRepositoryImpl,仅用于测试。
JdbcTokenRepositoryImpl将令牌存储在数据库中。

你可能感兴趣的:(spring)