zoukankan      html  css  js  c++  java
  • Spring Security4.1.3实现拦截登录后向登录页面跳转方式(redirect或forward)返回被拦截界面

    一、看下内部原理

    简化后的认证过程分为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的恢复。

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

    何时缓存

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

    RequestCache

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

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

    ExceptionTranslationFilter

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

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

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

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

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


    对应以下代码

    [java] view plain copy
     
    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.   
    9.             sendStartAuthentication(request, response, chain,  
    10.                     (AuthenticationException) exception);  
    11.         }  
    12.         else if (exception instanceof AccessDeniedException) {  
    13.             if (authenticationTrustResolver.isAnonymous(SecurityContextHolder  
    14.                     .getContext().getAuthentication())) {  
    15.                 logger.debug(  
    16.                         "Access is denied (user is anonymous); redirecting to authentication entry point",  
    17.                         exception);  
    18.   
    19.                 sendStartAuthentication(  
    20.                         request,  
    21.                         response,  
    22.                         chain,  
    23.                         new InsufficientAuthenticationException(  
    24.                                 "Full authentication is required to access this resource"));  
    25.             }  
    26.             else {  
    27.                 logger.debug(  
    28.                         "Access is denied (user is not anonymous); delegating to AccessDeniedHandler",  
    29.                         exception);  
    30.   
    31.                 accessDeniedHandler.handle(request, response,  
    32.                         (AccessDeniedException) exception);  
    33.             }  
    34.         }  
    35.     }  

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

    [java] view plain copy
     
    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.   
    10.             // 设置403状态码  
    11.             response.setStatus(HttpServletResponse.SC_FORBIDDEN);  
    12.   
    13.             // 转发到errorPage  
    14.             RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);  
    15.             dispatcher.forward(request, response);  
    16.         }  
    17.         else { // 没有定义errorPage,则返回403状态码(Forbidden),以及错误信息  
    18.             response.sendError(HttpServletResponse.SC_FORBIDDEN,  
    19.                     accessDeniedException.getMessage());  
    20.         }  
    21.     }  
    22. }  

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

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

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

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

    [java] view plain copy
     
    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版本

    配置文件:完整的

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


    主要配置
    [html] view plain copy
     
    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.   
    3.  <!-- 被认证请求向登录界面跳转采用forward方式 -->  
    4.     <beans:bean id="myLoginUrlAuthenticationEntryPoint"   
    5.         class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">  
    6.         <beans:constructor-arg name="loginFormUrl" value="/login"></beans:constructor-arg>  
    7.         <beans:property name="useForward" value="true"/>  
    8.     </beans:bean></pre><br>  
    9. <pre></pre>  
    10. <p>从上面的分析可知,默认情况下采用的是redirect方式,这里通过配置从而实现了forward方式,这里还是直接利用的security自带的类LoginUrlAuthenticationEntryPoint,只不过进行了以上配置:</p>  
    11. <p></p><pre code_snippet_id="1902646" snippet_file_name="blog_20161215_10_1004934" class="java" name="code">/**  
    12.      * Performs the redirect (or forward) to the login form URL.  
    13.      */  
    14.     public void commence(HttpServletRequest request, HttpServletResponse response,  
    15.             AuthenticationException authException) throws IOException, ServletException {  
    16.   
    17.         String redirectUrl = null;  
    18.   
    19.         if (useForward) {  
    20.   
    21.             if (forceHttps && "http".equals(request.getScheme())) {  
    22.                 // First redirect the current request to HTTPS.  
    23.                 // When that request is received, the forward to the login page will be  
    24.                 // used.  
    25.                 redirectUrl = buildHttpsRedirectUrlForRequest(request);  
    26.             }  
    27.   
    28.             if (redirectUrl == null) {  
    29.                 String loginForm = determineUrlToUseForThisRequest(request, response,  
    30.                         authException);  
    31.   
    32.                 if (logger.isDebugEnabled()) {  
    33.                     logger.debug("Server side forward to: " + loginForm);  
    34.                 }  
    35.   
    36.                 RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);  
    37.   
    38.                 dispatcher.forward(request, response);  
    39.   
    40.                 return;  
    41.             }  
    42.         }  
    43.         else {  
    44.             // redirect to login page. Use https if forceHttps true  
    45.   
    46.             redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);  
    47.   
    48.         }  
    49.   
    50.         redirectStrategy.sendRedirect(request, response, redirectUrl);  
    51.     }</pre><br>  
    52. <p></p>  
    53. <p></p>  
    54. <p></p>  
    55. <p></p>  
    56. <p>登录成功后的类配置,存入登录user信息后交给认证成功后的处理类MyAuthenticationSuccessHandler,该类集成了SavedRequestAwareAuthenticationSuccessHandler,他会从缓存中提取请求,从而可以恢复之前请求的数据</p>  
    57. <p></p><pre code_snippet_id="1902646" snippet_file_name="blog_20161215_11_4222490" class="java" name="code">/**  
    58.  * 登录后操作  
    59.  *   
    60.  * @author HHL  
    61.  * @date  
    62.  *   
    63.  */  
    64. @Component  
    65. public class MyAuthenticationSuccessHandler extends  
    66.         SavedRequestAwareAuthenticationSuccessHandler {  
    67.   
    68.     @Autowired  
    69.     private IUserService userService;  
    70.   
    71.     @Override  
    72.     public void onAuthenticationSuccess(HttpServletRequest request,  
    73.             HttpServletResponse response, Authentication authentication)  
    74.             throws IOException, ServletException {  
    75.   
    76.         // 认证成功后,获取用户信息并添加到session中  
    77.         UserDetails userDetails = (UserDetails) authentication.getPrincipal();  
    78.         MangoUser user = userService.getUserByName(userDetails.getUsername());  
    79.         request.getSession().setAttribute("user", user);  
    80.           
    81.         super.onAuthenticationSuccess(request, response, authentication);  
    82.       
    83.     }  
    84.   
    85.   
    86. }</pre><p></p>  
    87. <p></p>  
    88. <p></p>  
    89. <p></p>  
    90. <p>SavedRequestAwareAuthenticationSuccessHandler中的onAuthenticationSuccess方法;</p>  
    91. <p></p><pre code_snippet_id="1902646" snippet_file_name="blog_20161215_12_7440047" class="java" name="code">@Override  
    92.     public void onAuthenticationSuccess(HttpServletRequest request,  
    93.             HttpServletResponse response, Authentication authentication)  
    94.             throws ServletException, IOException {  
    95.         SavedRequest savedRequest = requestCache.getRequest(request, response);  
    96.   
    97.         if (savedRequest == null) {  
    98.             super.onAuthenticationSuccess(request, response, authentication);  
    99.   
    100.             return;  
    101.         }  
    102.         String targetUrlParameter = getTargetUrlParameter();  
    103.         if (isAlwaysUseDefaultTargetUrl()  
    104.                 || (targetUrlParameter != null && StringUtils.hasText(request  
    105.                         .getParameter(targetUrlParameter)))) {  
    106.             requestCache.removeRequest(request, response);  
    107.             super.onAuthenticationSuccess(request, response, authentication);  
    108.   
    109.             return;  
    110.         }  
    111.   
    112.         clearAuthenticationAttributes(request);  
    113.   
    114.         // Use the DefaultSavedRequest URL  
    115.         String targetUrl = savedRequest.getRedirectUrl();  
    116.         logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);  
    117.         getRedirectStrategy().sendRedirect(request, response, targetUrl);  
    118.     }</pre>4.1.3中如果默认不配置的话也是采用的SavedRequestAwareAuthenticationSuccessHandler进行处理,详情可参见:<target="_blank" href="http://blog.csdn.net/honghailiang888/article/details/53541664">Spring实战篇系列----源码解析Spring Security中的过滤器Filter初始化  
    119. </a><br>  
    120. <br>  
    121. <br>  
    122. 上述实现了跳转到登录界面采用forward方式,就是浏览器地址栏没有变化,当然也可采用redirect方式,地址栏变为登录界面地址栏,当登录完成后恢复到原先的请求页面,请求信息会从requestCache中还原回来。可参考<target="_blank" href="http://blog.csdn.net/honghailiang888/article/details/53520557"> Spring实战篇系列----spring security4.1.3配置以及踩过的坑</a><br>  
    123. <p></p>  
    124. <p></p>  
    125. <p></p>  
    126. <p></p>  
    127. <p></p>  
    128. <p></p>  
    129. <p><br>  
    130. </p>  
    131. <p><br>  
    132. </p>  
    133. <p>参考:</p>  
    134. <p><target="_blank" href="https://segmentfault.com/a/1190000004183264">https://segmentfault.com/a/1190000004183264</a></p>  
    135. <p><target="_blank" href="http://gtbald.iteye.com/blog/1214132">http://gtbald.iteye.com/blog/1214132</a></p>  
    136. <p><br>  
    137. <br>  
    138. <br>  
    139. </p>  
    140. <div style="top:0px"></div>  
    141. <div style="top:4827px"></div>  
    142. <div style="top:3353px"></div>  
    143.      
     
     http://blog.csdn.net/honghailiang888/article/details/52679264
  • 相关阅读:
    lua判断字符串包含另一个字符串
    expect使用技巧
    Linux expect
    expect正则捕获返回结果
    修改alpine Linux的Docker容器的时区
    Dockerfile镜像优化,减小镜像
    Sed在匹配行前后加入一行
    scp的使用以及cp的对比
    rsync 的用法
    傅里叶系列(一)傅里叶级数的推导 (转)
  • 原文地址:https://www.cnblogs.com/softidea/p/7074838.html
Copyright © 2011-2022 走看看