Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 737bfc2

Browse filesBrowse files
committed
Merge branch 'master' of github.com:coderbruis/JavaSourceLearning
2 parents eef2fbb + 8d4fd4b commit 737bfc2
Copy full SHA for 737bfc2

File tree

Expand file treeCollapse file tree

2 files changed

+304
-6
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+304
-6
lines changed
Open diff view settings
Collapse file

‎README.md‎

Copy file name to clipboardExpand all lines: README.md
+1-1Lines changed: 1 addition & 1 deletion
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ Java流行框架源码分析,学习以及总结。
9090
- [从零开始系统学习SpringSecurity和OAuth2(一)—— 初识SpringSecurity](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/SpringSecurity/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0SpringSecurity%E5%92%8COAuth2%EF%BC%88%E4%B8%80%EF%BC%89%E2%80%94%E2%80%94%20%E5%88%9D%E8%AF%86SpringSecurity.md)
9191
- [从零开始系统学习SpringSecurity和OAuth2(二)—— 安全过滤器FilterChainProxy](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/SpringSecurity/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0SpringSecurity%E5%92%8COAuth2%EF%BC%88%E4%BA%8C%EF%BC%89%E2%80%94%E2%80%94%20%E5%AE%89%E5%85%A8%E8%BF%87%E6%BB%A4%E5%99%A8FilterChainProxy.md)
9292
- [从零开始系统学习SpringSecurity和OAuth2(三)—— WebSecurity建造核心逻辑](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/SpringSecurity/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0SpringSecurity%E5%92%8COAuth2%EF%BC%88%E4%B8%89%EF%BC%89%E2%80%94%E2%80%94%20WebSecurity%E5%BB%BA%E9%80%A0%E6%A0%B8%E5%BF%83%E9%80%BB%E8%BE%91.md)
93-
- [从零开始系统学习SpringSecurity和OAuth2(四)—— FilterChainProxy过滤器链中的几个重要的过滤器]()
93+
- [从零开始系统学习SpringSecurity和OAuth2(四)—— FilterChainProxy过滤器链中的几个重要的过滤器](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/SpringSecurity/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0SpringSecurity%E5%92%8COAuth2%EF%BC%88%E5%9B%9B%EF%BC%89%E2%80%94%E2%80%94%20FilterChainProxy%E8%BF%87%E6%BB%A4%E5%99%A8%E9%93%BE%E4%B8%AD%E7%9A%84%E5%87%A0%E4%B8%AA%E9%87%8D%E8%A6%81%E7%9A%84%E8%BF%87%E6%BB%A4%E5%99%A8.md)
9494

9595
- Dubbo底层源码解析
9696
- Dubbo底层源码版本:2.7.8
Collapse file

‎note/SpringSecurity/从零开始系统学习SpringSecurity和OAuth2(四)—— FilterChainProxy过滤器链中的几个重要的过滤器.md‎

Copy file name to clipboardExpand all lines: note/SpringSecurity/从零开始系统学习SpringSecurity和OAuth2(四)—— FilterChainProxy过滤器链中的几个重要的过滤器.md
+303-5Lines changed: 303 additions & 5 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
============== 分割线 ==============
55

6-
本章重点介绍一下FilterChainProxy中几个重要的过滤器链
6+
本章重点介绍一下FilterChainProxy过滤器链中的SecurityContextPersistenceFilter以及ExceptionTranslationFilter
77

88
## 正文
99
在系列文章中的第一篇中,已经用一张图介绍了FilterChainProxy在整个Filter中的所处位置以及包含在FilterChainProxy的各种Filter。
@@ -27,7 +27,7 @@
2727

2828
而实现了DisposalbleBean表明了在注销bean时能进行额外的工作,实现InitializingBean表明了能在初始化时进行额外的工作。
2929

30-
> 上源码
30+
#### 1.1 源码分析
3131

3232
```
3333
public class SecurityContextPersistenceFilter extends GenericFilterBean {
@@ -92,7 +92,7 @@ public class SecurityContextPersistenceFilter extends GenericFilterBean {
9292
.getContext();
9393
// 清除SecurityContextHolder
9494
SecurityContextHolder.clearContext();
95-
// 将安全上下文存储到HttpSessionSecurityContextRepository中
95+
// 将安全上下文存储到HttpSessionSecurityContextRepository中,也就是持久化到Session中
9696
repo.saveContext(contextAfterChainExecution, holder.getRequest(),
9797
holder.getResponse());
9898
request.removeAttribute(FILTER_APPLIED);
@@ -110,9 +110,307 @@ public class SecurityContextPersistenceFilter extends GenericFilterBean {
110110
111111
```
112112

113+
从SecurityContextPersistenceFilter类的作用可以看出,它其实就是持久化SecurityContext。
114+
113115
### 2. ExceptionTranslationFilter
116+
先看下ExceptionTranslationFilter的类注释,总结为以下几点:
117+
1. 此过滤器会处理任何AccessDeniedException和AuthenticationException的异常。
118+
2. 此过滤器是必要的,因为它提供了一个桥梁用于连接Java异常和HTTP响应。它仅和维护用户界面有关,而不会执行任何的安全性强制措施。
119+
3. 如果此过滤器捕获到了AuthenticationException,该Filter会加载AuthenticationEntrypoint。它允许处理任何从AbstractSecurityInterceptor子类抛出的authentication异常,AbstractSecurityInterceptor的子类即FilterChainProxy中包含的哪些过滤器。
120+
4. 如果捕获到了AccessDeniedException,此过滤器会判断当前用户是否是一个匿名用户。如果是匿名用户,则加载authenticationEntryPoint。如果不是匿名用户,则此过滤器会将逻辑代理到AccessDeniedHandler,由其处理接下来的逻辑。
121+
5. authenticationEntryPoint指示如果检测到AuthenticationException,则通过调用authenticationEntrypoint的commence方法开始认证过程的处理。需要注意的是,在ExceptionTranslationFilter中的requestCache用于保存身份验证过程中的认证结果,一边可以在用户认证通过后即可检索以及重用,requestCache的默认实现是HttpSessionRequestCache。
122+
123+
**小结:ExceptionTranslationFilter的作用即捕获AuthenticationException和AccessDeniedException,并作出相应的处理;对于捕获AccessDeniedException时,如果是匿名用户则去调用authenticationEntryPoint去进行身份验证,如果不是匿名用户则直接抛出AccessDeniedException。**
124+
125+
#### 2.1 源码分析
126+
127+
先判断看下ExceptionTranslationFilter的成员变量
128+
```
129+
public class ExceptionTranslationFilter extends GenericFilterBean {
130+
131+
// AccessDeniedException处理器
132+
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
133+
// 用于进行身份验证的端点
134+
private AuthenticationEntryPoint authenticationEntryPoint;
135+
136+
// 身份认证信任机制,包括判断是否是匿名,判断是否是RememberMe
137+
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
138+
// 异常分析器
139+
private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
140+
141+
// 将身份认证结果存储在HttpSession中
142+
private RequestCache requestCache = new HttpSessionRequestCache();
143+
144+
// 消息源转化器
145+
private final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
146+
147+
public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint) {
148+
this(authenticationEntryPoint, new HttpSessionRequestCache());
149+
}
150+
151+
public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint,
152+
RequestCache requestCache) {
153+
Assert.notNull(authenticationEntryPoint,
154+
"authenticationEntryPoint cannot be null");
155+
Assert.notNull(requestCache, "requestCache cannot be null");
156+
this.authenticationEntryPoint = authenticationEntryPoint;
157+
this.requestCache = requestCache;
158+
}
159+
160+
// 省略
161+
}
162+
163+
```
164+
165+
#### 2.2 doFilter源码分析
166+
167+
```
168+
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
169+
throws IOException, ServletException {
170+
HttpServletRequest request = (HttpServletRequest) req;
171+
HttpServletResponse response = (HttpServletResponse) res;
172+
173+
try {
174+
// 继续调用下一个过滤器链
175+
chain.doFilter(request, response);
176+
177+
logger.debug("Chain processed normally");
178+
}
179+
catch (IOException ex) {
180+
throw ex;
181+
}
182+
catch (Exception ex) {
183+
// 尝试去获取SpringSecurityException异常
184+
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
185+
// 转化为运行时异常
186+
RuntimeException ase = (AuthenticationException) throwableAnalyzer
187+
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
188+
189+
if (ase == null) {
190+
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
191+
AccessDeniedException.class, causeChain);
192+
}
193+
194+
if (ase != null) {
195+
if (response.isCommitted()) {
196+
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
197+
}
198+
// 处理SpringSecurity异常
199+
handleSpringSecurityException(request, response, chain, ase);
200+
}
201+
else {
202+
// Rethrow ServletExceptions and RuntimeExceptions as-is
203+
if (ex instanceof ServletException) {
204+
throw (ServletException) ex;
205+
}
206+
else if (ex instanceof RuntimeException) {
207+
throw (RuntimeException) ex;
208+
}
209+
210+
// Wrap other Exceptions. This shouldn't actually happen
211+
// as we've already covered all the possibilities for doFilter
212+
throw new RuntimeException(ex);
213+
}
214+
}
215+
}
216+
217+
```
218+
219+
```
220+
// SpringSecurityException异常处理的核心逻辑
221+
private void handleSpringSecurityException(HttpServletRequest request,
222+
HttpServletResponse response, FilterChain chain, RuntimeException exception)
223+
throws IOException, ServletException {
224+
// 如果是认证异常
225+
if (exception instanceof AuthenticationException) {
226+
logger.debug(
227+
"Authentication exception occurred; redirecting to authentication entry point",
228+
exception);
229+
// 开始进行身份认证
230+
sendStartAuthentication(request, response, chain,
231+
(AuthenticationException) exception);
232+
}
233+
else if (exception instanceof AccessDeniedException) { // 如果是访问拒绝异常
234+
// 尝试从SecurityContextHolder缓存中获取认证结果
235+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
236+
// 判断认证结果是否是匿名的或者是rememberme
237+
if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
238+
logger.debug(
239+
"Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
240+
exception);
241+
// 开始进行身份认证
242+
sendStartAuthentication(
243+
request,
244+
response,
245+
chain,
246+
new InsufficientAuthenticationException(
247+
messages.getMessage(
248+
"ExceptionTranslationFilter.insufficientAuthentication",
249+
"Full authentication is required to access this resource")));
250+
}
251+
else {
252+
253+
logger.debug(
254+
"Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
255+
exception);
256+
// 如果既不是匿名用户也不是rememberme用户,则调用访问拒绝处理器
257+
accessDeniedHandler.handle(request, response,
258+
(AccessDeniedException) exception);
259+
}
260+
}
261+
}
262+
```
263+
264+
```
265+
protected void sendStartAuthentication(HttpServletRequest request,
266+
HttpServletResponse response, FilterChain chain,
267+
AuthenticationException reason) throws ServletException, IOException {
268+
// 清空缓存中的认证结果,重新进行身份验证
269+
SecurityContextHolder.getContext().setAuthentication(null);
270+
// 将认证请求request和响应response存储在session中
271+
requestCache.saveRequest(request, response);
272+
logger.debug("Calling Authentication entry point.");
273+
// 进行身份验证
274+
authenticationEntryPoint.commence(request, response, reason);
275+
}
276+
```
277+
先看下commence的实现类:
278+
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200819095038864.png#pic_center)
279+
280+
这里这么多实现类,到底调用哪一个呢?这就要看下authenticationEntryPoint注入的什么实现类了,可以将断点打在ExceptionTranslationFilter的构造方法中。
281+
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200819095634222.png#pic_center)
282+
283+
启动项目之后,进入方法调用栈,可以在图中位置看到在进行安全配置类配置时,会调用ExceptionHandlingConfigurer这个配置类的configure方法。
284+
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200819095929151.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70#pic_center)
285+
#### 2.3 ExceptionHandlingConfigurer
286+
287+
进入其configure方法查看
288+
289+
```
290+
@Override
291+
public void configure(H http) throws Exception {
292+
// 获取authenticationEntryPoint
293+
AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
294+
// 新建一个ExceptionTranslationFilter对象
295+
ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(
296+
entryPoint, getRequestCache(http));
297+
// 或获取访问拒绝处理器
298+
AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http);
299+
exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler);
300+
exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
301+
// 往FilterChainProxy中添加ExceptionTranslationFilter
302+
http.addFilter(exceptionTranslationFilter);
303+
}
304+
```
305+
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200819100503737.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70#pic_center)
306+
可以发现在实例化完ExceptionHandlingConfigurer后,依然没有注入authenticationEntryPoint。所以是在调用configure方法时,去调用getAuthenticationEntryPoint()去获取authenticationEntryPoint。
307+
308+
下面接着查看一下getAuthenticationEntryPoint()方法
309+
310+
```
311+
AuthenticationEntryPoint getAuthenticationEntryPoint(H http) {
312+
AuthenticationEntryPoint entryPoint = this.authenticationEntryPoint;
313+
// 由于entryPoint为空,所以调用createDefaultEntryPoint去创建entryPoint
314+
if (entryPoint == null) {
315+
entryPoint = createDefaultEntryPoint(http);
316+
}
317+
return entryPoint;
318+
}
319+
```
320+
321+
```
322+
private AuthenticationEntryPoint createDefaultEntryPoint(H http) {
323+
// 如果entryPointMappings为空,则返回Http403ForbiddenEntryPoint
324+
if (this.defaultEntryPointMappings.isEmpty()) {
325+
return new Http403ForbiddenEntryPoint();
326+
}
327+
if (this.defaultEntryPointMappings.size() == 1) {
328+
// 遍历defaultEntryPointMappings,获取其中存储的entrypoint
329+
return this.defaultEntryPointMappings.values().iterator().next();
330+
}
331+
// 创建DelegatingAuthenticationEntryPoint这个代理类
332+
DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint(
333+
this.defaultEntryPointMappings);
334+
entryPoint.setDefaultEntryPoint(this.defaultEntryPointMappings.values().iterator()
335+
.next());
336+
return entryPoint;
337+
}
338+
```
339+
340+
可以看出,最终返回的就是:Http403ForbiddenEntryPoint
341+
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200819101823377.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70#pic_center)
342+
343+
可以看到,HTTP403ForbiddenEntryPiont这个类代码非常少
344+
```
345+
public class Http403ForbiddenEntryPoint implements AuthenticationEntryPoint {
346+
private static final Log logger = LogFactory.getLog(Http403ForbiddenEntryPoint.class);
347+
348+
/**
349+
* Always returns a 403 error code to the client.
350+
*/
351+
public void commence(HttpServletRequest request, HttpServletResponse response,
352+
AuthenticationException arg2) throws IOException, ServletException {
353+
if (logger.isDebugEnabled()) {
354+
logger.debug("Pre-authenticated entry point called. Rejecting access");
355+
}
356+
// 在response响应中添加403 Forbidden,访问拒绝异常
357+
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
358+
}
359+
}
360+
```
361+
362+
这里,还讲解一下另外一个类LoginURLAuthenticationEntryPoint的方法commence。
363+
364+
```
365+
public void commence(HttpServletRequest request, HttpServletResponse response,
366+
AuthenticationException authException) throws IOException, ServletException {
367+
// 重定向url
368+
String redirectUrl = null;
369+
370+
if (useForward) {
371+
// 判断下请求协议是否是http
372+
if (forceHttps && "http".equals(request.getScheme())) {
373+
// 获取重定向完整的URL路径
374+
redirectUrl = buildHttpsRedirectUrlForRequest(request);
375+
}
376+
377+
if (redirectUrl == null) {
378+
// 如果重定向地址为空,则获取默认的登录form表单地址;用户可以自定义设置;
379+
String loginForm = determineUrlToUseForThisRequest(request, response,
380+
authException);
381+
382+
if (logger.isDebugEnabled()) {
383+
logger.debug("Server side forward to: " + loginForm);
384+
}
385+
386+
RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
387+
388+
dispatcher.forward(request, response);
389+
390+
return;
391+
}
392+
}
393+
else {
394+
redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
395+
396+
}
397+
// 发送重定向请求
398+
redirectStrategy.sendRedirect(request, response, redirectUrl);
399+
}
400+
```
401+
402+
LoginURLAuthenticationEntryPoint这个类其实就是重定向到login页面,如果用户不指定login页面,则重定向到默认的login页面。
403+
404+
405+
## 总结
406+
407+
本篇文章重点讲解了FilterChainProxy中的SecurityContextPersistenceFilter以及ExceptionTranslationFilter过滤器链,它们在SpringSecurity中都扮演着很重要的角色,用一句话来概括就是:
408+
**SecurityContextPersistenceFilter用于持久化SecurityContext,而ExceptionTranslationFilter则用于捕获身份认证异常(AuthenticationException)和访问异常(AccessDeniedException),并处理这些异常。**
114409

115-
### 3. FilterSecurityInterceptor
410+
然而FitlerChainProxy中还有一个非常重要的过滤器:FilterSecurityInterceptor,下一篇将重点讲解。
116411

412+
## 相关文章
117413

118-
.... 待续
414+
- [从零开始系统学习SpringSecurity和OAuth2(一)—— 初识SpringSecurity](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/SpringSecurity/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0SpringSecurity%E5%92%8COAuth2%EF%BC%88%E4%B8%80%EF%BC%89%E2%80%94%E2%80%94%20%E5%88%9D%E8%AF%86SpringSecurity.md)
415+
- [从零开始系统学习SpringSecurity和OAuth2(二)—— 安全过滤器FilterChainProxy](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/SpringSecurity/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0SpringSecurity%E5%92%8COAuth2%EF%BC%88%E4%BA%8C%EF%BC%89%E2%80%94%E2%80%94%20%E5%AE%89%E5%85%A8%E8%BF%87%E6%BB%A4%E5%99%A8FilterChainProxy.md)
416+
- [从零开始系统学习SpringSecurity和OAuth2(三)—— WebSecurity建造核心逻辑](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/SpringSecurity/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0SpringSecurity%E5%92%8COAuth2%EF%BC%88%E5%9B%9B%EF%BC%89%E2%80%94%E2%80%94%20FilterChainProxy%E8%BF%87%E6%BB%A4%E5%99%A8%E9%93%BE%E4%B8%AD%E7%9A%84%E5%87%A0%E4%B8%AA%E9%87%8D%E8%A6%81%E7%9A%84%E8%BF%87%E6%BB%A4%E5%99%A8.md)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.