一、看下内部原理

简化后的认证过程分为7步:

  1. 用户访问网站,打开了一个链接(origin url)。

  2. 请求发送给服务器,服务器判断用户请求了受保护的资源。

  3. 由于用户没有登录,服务器重定向到登录页面

  4. 填写表单,点击登录

  5. 浏览器将用户名密码以表单形式发送给服务器

  6. 服务器验证用户名密码。成功,进入到下一步。否则要求用户重新认证(第三步)

  7. 服务器对用户拥有的权限(角色)判定: 有权限,重定向到origin url; 权限不足,返回状态码403("forbidden").

从第3步,我们可以知道,用户的请求被中断了。

用户登录成功后(第7步),会被重定向到origin url,spring security通过使用缓存的request,使得被中断的请求能够继续执行。

使用缓存

用户登录成功后,页面重定向到origin url。浏览器发出的请求优先被拦截器RequestCacheAwareFilter拦截,RequestCacheAwareFilter通过其持有的RequestCache对象实现request的恢复。

  1. public void doFilter(ServletRequest request, ServletResponse response,
  2. FilterChain chain) throws IOException, ServletException {
  3. // request匹配,则取出,该操作同时会将缓存的request从session中删除
  4. HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
  5. (HttpServletRequest) request, (HttpServletResponse) response);
  6. // 优先使用缓存的request
  7. chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
  8. response);
  9. }

何时缓存

首先,我们需要了解下RequestCache以及ExceptionTranslationFilter。

RequestCache

RequestCache接口声明了缓存与恢复操作。默认实现类是HttpSessionRequestCache。HttpSessionRequestCache的实现比较简单,这里只列出接口的声明:

  1. public interface RequestCache {
  2. // 将request缓存到session中
  3. void saveRequest(HttpServletRequest request, HttpServletResponse response);
  4. // 从session中取request
  5. SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response);
  6. // 获得与当前request匹配的缓存,并将匹配的request从session中删除
  7. HttpServletRequest getMatchingRequest(HttpServletRequest request,
  8. HttpServletResponse response);
  9. // 删除缓存的request
  10. void removeRequest(HttpServletRequest request, HttpServletResponse response);
  11. }

ExceptionTranslationFilter

ExceptionTranslationFilter 是Spring Security的核心filter之一,用来处理AuthenticationException和AccessDeniedException两种异常。

在我们的例子中,AuthenticationException指的是未登录状态下访问受保护资源,AccessDeniedException指的是登陆了但是由于权限不足(比如普通用户访问管理员界面)。

ExceptionTranslationFilter 持有两个处理类,分别是AuthenticationEntryPoint和AccessDeniedHandler。

ExceptionTranslationFilter 对异常的处理是通过这两个处理类实现的,处理规则很简单:

  1. 规则1. 如果异常是 AuthenticationException,使用 AuthenticationEntryPoint 处理
  2. 规则2. 如果异常是 AccessDeniedException 且用户是匿名用户,使用 AuthenticationEntryPoint 处理
  3. 规则3. 如果异常是 AccessDeniedException 且用户不是匿名用户,如果否则交给 AccessDeniedHandler 处理。

对应以下代码

  1. private void handleSpringSecurityException(HttpServletRequest request,
  2. HttpServletResponse response, FilterChain chain, RuntimeException exception)
  3. throws IOException, ServletException {
  4. if (exception instanceof AuthenticationException) {
  5. logger.debug(
  6. "Authentication exception occurred; redirecting to authentication entry point",
  7. exception);
  8. sendStartAuthentication(request, response, chain,
  9. (AuthenticationException) exception);
  10. }
  11. else if (exception instanceof AccessDeniedException) {
  12. if (authenticationTrustResolver.isAnonymous(SecurityContextHolder
  13. .getContext().getAuthentication())) {
  14. logger.debug(
  15. "Access is denied (user is anonymous); redirecting to authentication entry point",
  16. exception);
  17. sendStartAuthentication(
  18. request,
  19. response,
  20. chain,
  21. new InsufficientAuthenticationException(
  22. "Full authentication is required to access this resource"));
  23. }
  24. else {
  25. logger.debug(
  26. "Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
  27. exception);
  28. accessDeniedHandler.handle(request, response,
  29. (AccessDeniedException) exception);
  30. }
  31. }
  32. }

AccessDeniedHandler 默认实现是 AccessDeniedHandlerImpl。该类对异常的处理是返回403错误码。

  1. public void handle(HttpServletRequest request, HttpServletResponse response,
  2. AccessDeniedException accessDeniedException) throws IOException,
  3. ServletException {
  4. if (!response.isCommitted()) {
  5. if (errorPage != null) {  // 定义了errorPage
  6. // errorPage中可以操作该异常
  7. request.setAttribute(WebAttributes.ACCESS_DENIED_403,
  8. accessDeniedException);
  9. // 设置403状态码
  10. response.setStatus(HttpServletResponse.SC_FORBIDDEN);
  11. // 转发到errorPage
  12. RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
  13. dispatcher.forward(request, response);
  14. }
  15. else { // 没有定义errorPage,则返回403状态码(Forbidden),以及错误信息
  16. response.sendError(HttpServletResponse.SC_FORBIDDEN,
  17. accessDeniedException.getMessage());
  18. }
  19. }
  20. }

AuthenticationEntryPoint 默认实现是 LoginUrlAuthenticationEntryPoint, 该类的处理是转发或重定向到登录页面

  1. public void commence(HttpServletRequest request, HttpServletResponse response,
  2. AuthenticationException authException) throws IOException, ServletException {
  3. String redirectUrl = null;
  4. if (useForward) {
  5. if (forceHttps && "http".equals(request.getScheme())) {
  6. // First redirect the current request to HTTPS.
  7. // When that request is received, the forward to the login page will be
  8. // used.
  9. redirectUrl = buildHttpsRedirectUrlForRequest(request);
  10. }
  11. if (redirectUrl == null) {
  12. String loginForm = determineUrlToUseForThisRequest(request, response,
  13. authException);
  14. if (logger.isDebugEnabled()) {
  15. logger.debug("Server side forward to: " + loginForm);
  16. }
  17. RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
  18. // 转发
  19. dispatcher.forward(request, response);
  20. return;
  21. }
  22. }
  23. else {
  24. // redirect to login page. Use https if forceHttps true
  25. redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
  26. }
  27. // 重定向
  28. redirectStrategy.sendRedirect(request, response, redirectUrl);
  29. }

了解完这些,回到我们的例子。

第3步时,用户未登录的情况下访问受保护资源,ExceptionTranslationFilter会捕获到AuthenticationException异常(规则1)。页面需要跳转,ExceptionTranslationFilter在跳转前使用requestCache缓存request。

  1. protected void sendStartAuthentication(HttpServletRequest request,
  2. HttpServletResponse response, FilterChain chain,
  3. AuthenticationException reason) throws ServletException, IOException {
  4. // SEC-112: Clear the SecurityContextHolder's Authentication, as the
  5. // existing Authentication is no longer considered valid
  6. SecurityContextHolder.getContext().setAuthentication(null);
  7. // 缓存 request
  8. requestCache.saveRequest(request, response);
  9. logger.debug("Calling Authentication entry point.");
  10. authenticationEntryPoint.commence(request, response, reason);
  11. }

二、了解了以上原理以及上篇的forward和redirect的区别,配置实现如下,基于springsecurity4.1.3版本

配置文件:完整的

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans:beans xmlns="http://www.springframework.org/schema/security"
  3. xmlns:beans="http://www.springframework.org/schema/beans"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/security
  8. http://www.springframework.org/schema/security/spring-security.xsd">
  9. <http auto-config="true" use-expressions="true" entry-point-ref="myLoginUrlAuthenticationEntryPoint">
  10. <form-login
  11. login-page="/login"
  12. authentication-failure-url="/login?error"
  13. login-processing-url="/login"
  14. authentication-success-handler-ref="myAuthenticationSuccessHandler" />
  15. <!-- 认证成功用自定义类myAuthenticationSuccessHandler处理 -->
  16. <logout logout-url="/logout"
  17. logout-success-url="/"
  18. invalidate-session="true"
  19. delete-cookies="JSESSIONID"/>
  20. <csrf disabled="true" />
  21. <intercept-url pattern="/order/*" access="hasRole('ROLE_USER')"/>
  22. </http>
  23. <!-- 使用自定义类myUserDetailsService从数据库获取用户信息 -->
  24. <authentication-manager>
  25. <authentication-provider user-service-ref="myUserDetailsService">
  26. <!-- 加密 -->
  27. <password-encoder hash="md5">
  28. </password-encoder>
  29. </authentication-provider>
  30. </authentication-manager>
  31. <!-- 被认证请求向登录界面跳转采用forward方式 -->
  32. <beans:bean id="myLoginUrlAuthenticationEntryPoint"
  33. class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
  34. <beans:constructor-arg name="loginFormUrl" value="/login"></beans:constructor-arg>
  35. <beans:property name="useForward" value="true"/>
  36. </beans:bean>
  37. </beans:beans>

主要配置

  1. <pre code_snippet_id="1902646" snippet_file_name="blog_20160927_9_7050170" class="html" name="code"><http auto-config="true" use-expressions="true" entry-point-ref="myLoginUrlAuthenticationEntryPoint">
  2. <!-- 被认证请求向登录界面跳转采用forward方式 -->
  3. <beans:bean id="myLoginUrlAuthenticationEntryPoint"
  4. class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
  5. <beans:constructor-arg name="loginFormUrl" value="/login"></beans:constructor-arg>
  6. <beans:property name="useForward" value="true"/>
  7. </beans:bean></pre><br>
  8. <pre></pre>
  9. <p>从上面的分析可知,默认情况下采用的是redirect方式,这里通过配置从而实现了forward方式,这里还是直接利用的security自带的类LoginUrlAuthenticationEntryPoint,只不过进行了以上配置:</p>
  10. <p></p><pre code_snippet_id="1902646" snippet_file_name="blog_20161215_10_1004934" class="java" name="code">/**
  11. * Performs the redirect (or forward) to the login form URL.
  12. */
  13. public void commence(HttpServletRequest request, HttpServletResponse response,
  14. AuthenticationException authException) throws IOException, ServletException {
  15. String redirectUrl = null;
  16. if (useForward) {
  17. if (forceHttps && "http".equals(request.getScheme())) {
  18. // First redirect the current request to HTTPS.
  19. // When that request is received, the forward to the login page will be
  20. // used.
  21. redirectUrl = buildHttpsRedirectUrlForRequest(request);
  22. }
  23. if (redirectUrl == null) {
  24. String loginForm = determineUrlToUseForThisRequest(request, response,
  25. authException);
  26. if (logger.isDebugEnabled()) {
  27. logger.debug("Server side forward to: " + loginForm);
  28. }
  29. RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
  30. dispatcher.forward(request, response);
  31. return;
  32. }
  33. }
  34. else {
  35. // redirect to login page. Use https if forceHttps true
  36. redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
  37. }
  38. redirectStrategy.sendRedirect(request, response, redirectUrl);
  39. }</pre><br>
  40. <p></p>
  41. <p></p>
  42. <p></p>
  43. <p></p>
  44. <p>登录成功后的类配置,存入登录user信息后交给认证成功后的处理类MyAuthenticationSuccessHandler,该类集成了SavedRequestAwareAuthenticationSuccessHandler,他会从缓存中提取请求,从而可以恢复之前请求的数据</p>
  45. <p></p><pre code_snippet_id="1902646" snippet_file_name="blog_20161215_11_4222490" class="java" name="code">/**
  46. * 登录后操作
  47. *
  48. * @author HHL
  49. * @date
  50. *
  51. */
  52. @Component
  53. public class MyAuthenticationSuccessHandler extends
  54. SavedRequestAwareAuthenticationSuccessHandler {
  55. @Autowired
  56. private IUserService userService;
  57. @Override
  58. public void onAuthenticationSuccess(HttpServletRequest request,
  59. HttpServletResponse response, Authentication authentication)
  60. throws IOException, ServletException {
  61. // 认证成功后,获取用户信息并添加到session中
  62. UserDetails userDetails = (UserDetails) authentication.getPrincipal();
  63. MangoUser user = userService.getUserByName(userDetails.getUsername());
  64. request.getSession().setAttribute("user", user);
  65. super.onAuthenticationSuccess(request, response, authentication);
  66. }
  67. }</pre><p></p>
  68. <p></p>
  69. <p></p>
  70. <p></p>
  71. <p>SavedRequestAwareAuthenticationSuccessHandler中的onAuthenticationSuccess方法;</p>
  72. <p></p><pre code_snippet_id="1902646" snippet_file_name="blog_20161215_12_7440047" class="java" name="code">@Override
  73. public void onAuthenticationSuccess(HttpServletRequest request,
  74. HttpServletResponse response, Authentication authentication)
  75. throws ServletException, IOException {
  76. SavedRequest savedRequest = requestCache.getRequest(request, response);
  77. if (savedRequest == null) {
  78. super.onAuthenticationSuccess(request, response, authentication);
  79. return;
  80. }
  81. String targetUrlParameter = getTargetUrlParameter();
  82. if (isAlwaysUseDefaultTargetUrl()
  83. || (targetUrlParameter != null && StringUtils.hasText(request
  84. .getParameter(targetUrlParameter)))) {
  85. requestCache.removeRequest(request, response);
  86. super.onAuthenticationSuccess(request, response, authentication);
  87. return;
  88. }
  89. clearAuthenticationAttributes(request);
  90. // Use the DefaultSavedRequest URL
  91. String targetUrl = savedRequest.getRedirectUrl();
  92. logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);
  93. getRedirectStrategy().sendRedirect(request, response, targetUrl);
  94. }</pre>4.1.3中如果默认不配置的话也是采用的SavedRequestAwareAuthenticationSuccessHandler进行处理,详情可参见:<a target="_blank" href="http://blog.csdn.net/honghailiang888/article/details/53541664">Spring实战篇系列----源码解析Spring Security中的过滤器Filter初始化
  95. </a><br>
  96. <br>
  97. <br>
  98. 上述实现了跳转到登录界面采用forward方式,就是浏览器地址栏没有变化,当然也可采用redirect方式,地址栏变为登录界面地址栏,当登录完成后恢复到原先的请求页面,请求信息会从requestCache中还原回来。可参考<a target="_blank" href="http://blog.csdn.net/honghailiang888/article/details/53520557"> Spring实战篇系列----spring security4.1.3配置以及踩过的坑</a><br>
  99. <p></p>
  100. <p></p>
  101. <p></p>
  102. <p></p>
  103. <p></p>
  104. <p></p>
  105. <p><br>
  106. </p>
  107. <p><br>
  108. </p>
  109. <p>参考:</p>
  110. <p><a target="_blank" href="https://segmentfault.com/a/1190000004183264">https://segmentfault.com/a/1190000004183264</a></p>
  111. <p><a target="_blank" href="http://gtbald.iteye.com/blog/1214132">http://gtbald.iteye.com/blog/1214132</a></p>
  112. <p><br>
  113. <br>
  114. <br>
  115. </p>
  116. <div style="top:0px"></div>
  117. <div style="top:4827px"></div>
  118. <div style="top:3353px"></div>
 
 http://blog.csdn.net/honghailiang888/article/details/52679264

Spring Security4.1.3实现拦截登录后向登录页面跳转方式(redirect或forward)返回被拦截界面的更多相关文章

  1. WPF:验证登录后关闭登录窗口,显示主窗口的解决方法

    http://www.27ba.com/post/145.html WPF:验证登录后关闭登录窗口,显示主窗口的解决方法 最近想做一个基于Socket的通讯工具,想模仿QQ那样,需要先登录,登录成功后 ...

  2. Html中设置访问页面不在后进行其他页面跳转

    Html中设置访问页面不在后进行其他页面跳转 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" & ...

  3. springmvc使用路径变量后再进行页面跳转会出现路径错误问题

    学习<Servlet.JSP和SpringMVC学习指南>遇到的一个问题,记录下. 项目代码 现象 @RequestMapping(value = "/book_edit/{id ...

  4. Spring Security4实例(Java config版)——ajax登录,自定义验证

    本文源码请看这里 相关文章: Spring Security4实例(Java config 版) -- Remember-Me 首先添加起步依赖(如果不是springboot项目,自行切换为Sprin ...

  5. Python手动构造Cookie模拟登录后获取网站页面内容

    最近有个好友让我帮忙爬取个小说,这个小说是前三十章直接可读,后面章节需要充值VIP可见.所以就需要利用VIP账户登录后,构造Cookie,再用Python的获取每章节的url,得到内容后再使用 PyQ ...

  6. axios interceptors 拦截 , 页面跳转, token 验证 Vue+axios实现登陆拦截,axios封装(报错,鉴权,跳转,拦截,提示)

    Vue+axios实现登陆拦截,axios封装(报错,鉴权,跳转,拦截,提示) :https://blog.csdn.net/H1069495874/article/details/80057107 ...

  7. Dynamics CRM2013 定制你的系统登录后的首页面

    在2013中个性设置中又多了一个新的,更好的增强了用户体验,对于特定的用户而言只需要使用系统的一小块功能,所以很多用户进入 系统只需要显示跟自己业务相关的功能页面即可. 点右上角的齿轮进入选项,在常规 ...

  8. 使用QQ第三方登录 并在父页面跳转刷新

    <html> <head> <title>QQ登录跳转</title> <script src="http://lib.sinaapp. ...

  9. Spring Security4实例(Java config 版) —— Remember-Me

    本文源码请看这里 相关文章: Spring Security4实例(Java config版)--ajax登录,自定义验证 Spring Security提供了两种remember-me的实现,一种是 ...

随机推荐

  1. Android虚拟设备访问WebSocket问题

    Android虚拟设备访问WebSocket问题 最近写erlang的WebSocket网站,需要运行在RHEL6上,用Android设备访问. 可惜AVD无法访问主机 Win7上的虚拟机(RHEL6 ...

  2. linux下如何查询未知库所依赖的包

    经常会遇到linux下安装软件时提示少文件,如何知道所缺少的文件属于哪个包?用什么命令查看? 例如:/lib/ld-linux.so.2: bad ELF interpreter: 没有那个文件或目录 ...

  3. nasm预处理器(1)

    与处理器将所有以反斜杠结尾的连续行合并为一行. 单行的宏以%define来定义:当单行的宏被扩展后还含有其他宏时,会在执行时而不是定义时展开. %define a(x) 1+b(x) %define ...

  4. Mac OS X 简单的方法知道何时来电了

    最近本猫所在的小区时常停电,往往半夜或是凌晨才来电啊!早上起来本猫在想如何知道确切的来电时间,但又不费事的方法呢. 方法一是用手机录音器录音,因为来电后门禁会发出"滴"的一声,所以 ...

  5. ruby中如何调用与局部变量同名的私有方法

    如果ruby中一个局部变量名和私有方法名同名的话,默认该名称被解释为变量而不是方法: x=10; def x;puts "what?" end 当你输入x实际不能执行x方法.解释器 ...

  6. 不是标准execl转换处理方法

    不是标准execl的主要原因就是原本的html.xml.txt尾椎的文件,更改成了xls尾椎的文件 面对这种问题,最开始我用了jawin.jar,但是始终会报错,NoClassDefFoundErro ...

  7. JMM

    1.JMM简介 i.内存模型概述 Java平台自动集成了线程以及多处理器技术,这种集成程度比Java以前诞生的计算机语言要厉害很多,该语言针对多种异构平台的平台独立性而使用的多线程技术支持也是具有开拓 ...

  8. python笔记--2--字符串、正则表达式

    字符串 ASCII码采用1个字节来对字符进行编码,最多只能表示256个符号. UTF-8以3个字节表示中文 GB2312是我国制定的中文编码,使用1个字节表示英语,2个字节表示中文:GBK是GB231 ...

  9. C语言的产生

    一:C语言的产生 C语言是1972年由美国的Dennis Ritchie设计发明的,并首次在UNIX操作系统的DEC  PDP-11计算机上使用的. 它由早期的编程语言BCPL 演变而来,随着微型计算 ...

  10. 前端技术之_CSS详解第六天--完结

    前端技术之_CSS详解第六天--完结 一.复习第五天的知识 a标签的伪类4个: a:link 没有被点击过的链接 a:visited 访问过的链接 a:hover 悬停 a:active 按下鼠标不松 ...