Spring Security执行过程浅析
[TOC]
基础概念
无状态:
- 服务端不保存任何客户端请求者信息
- 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份
核心组件
SecurityContextPersistenceFilter
主要工作是从session中获取SecurityContext,然后放到上下文中,之后的filter大多依赖这个来获取登录信息,其主要是通过HttpSessionSecurityContextRepository来存取的。
HttpSessionSecurityContextRepository
重要流程
JWT机制
当一个前端请求到达后端,被security框架拦截后,首先进入SecurityContextPersistenceFilter,然后通过HttpSessionSecurityContextRepository来获取SpringContext上下文,如果没有使用
Session机制
当一个前端请求到达后端,被security框架拦截后,首先进入SecurityContextPersistenceFilter,然后通过HttpSessionSecurityContextRepository来获取SpringContext上下文,
SecurityContextPersistenceFilter源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.context;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
/**
* Populates the {@link SecurityContextHolder} with information obtained from the
* configured {@link SecurityContextRepository} prior to the request and stores it back in
* the repository once the request has completed and clearing the context holder. By
* default it uses an {@link HttpSessionSecurityContextRepository}. See this class for
* information <tt>HttpSession</tt> related configuration options.
* <p>
* This filter will only execute once per request, to resolve servlet container
* (specifically Weblogic) incompatibilities.
* <p>
* This filter MUST be executed BEFORE any authentication processing mechanisms.
* Authentication processing mechanisms (e.g. BASIC, CAS processing filters etc) expect
* the <code>SecurityContextHolder</code> to contain a valid <code>SecurityContext</code>
* by the time they execute.
* <p>
* This is essentially a refactoring of the old
* <tt>HttpSessionContextIntegrationFilter</tt> to delegate the storage issues to a
* separate strategy, allowing for more customization in the way the security context is
* maintained between requests.
* <p>
* The <tt>forceEagerSessionCreation</tt> property can be used to ensure that a session is
* always available before the filter chain executes (the default is <code>false</code>,
* as this is resource intensive and not recommended).
*
* @author Luke Taylor
* @since 3.0
*/
public class SecurityContextPersistenceFilter extends GenericFilterBean {
static final String FILTER_APPLIED = "__spring_security_scpf_applied";
private SecurityContextRepository repo;
private boolean forceEagerSessionCreation = false;
public SecurityContextPersistenceFilter() {
this(new HttpSessionSecurityContextRepository());
}
public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
this.repo = repo;
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) {
// ensure that filter is only applied once per request
chain.doFilter(request, response);
return;
}
final boolean debug = logger.isDebugEnabled();
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (debug && session.isNew()) {
logger.debug("Eagerly created session: " + session.getId());
}
}
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
response);
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder
.getContext();
// Crucial removal of SecurityContextHolder contents - do this before anything
// else.
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(),
holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
this.forceEagerSessionCreation = forceEagerSessionCreation;
}
}
JwtLoginFilter
- 自定义 JwtLoginFilter 继承自 AbstractAuthenticationProcessingFilter,并实现其中的三个默认方法。
- attemptAuthentication 方法中,我们从登录参数中提取出用户名密码,然后调用 AuthenticationManager.authenticate() 方法去进行自动校验。
- 第二步如果校验成功,就会来到 successfulAuthentication 回调中,在 successfulAuthentication 方法中,将用户角色遍历然后用一个
,
连接起来,然后再利用 Jwts 去生成 token,按照代码的顺序,生成过程一共配置了四个参数,分别是用户角色、主题、过期时间以及加密算法和密钥,然后将生成的 token 写出到客户端。 - 第二步如果校验失败就会来到 unsuccessfulAuthentication 方法中,在这个方法中返回一个错误提示给客户端即可。
JwtFilter
- 首先从请求头中提取出 authorization 字段,这个字段对应的 value 就是用户的 token。
- 将提取出来的 token 字符串转换为一个 Claims 对象,再从 Claims 对象中提取出当前用户名和用户角色,创建一个 UsernamePasswordAuthenticationToken 放到当前的 Context 中,然后执行过滤链使请求继续执行下去。
filter链执行顺序
疑惑解答
为什么使用JWT还要使用
禁用session的时候,是么时候使用SessionCreationPolicy.STATELESS
- 应用全生命周期不创建session
- 应用全生命周期不使用session