Spring Security4.1.3实现拦截登录后向登录页面跳转方式(redirect或forward)返回被拦截界面
一、看下内部原理
简化后的认证过程分为7步:
用户访问网站,打开了一个链接(origin url)。
请求发送给服务器,服务器判断用户请求了受保护的资源。
由于用户没有登录,服务器重定向到登录页面
填写表单,点击登录
浏览器将用户名密码以表单形式发送给服务器
服务器验证用户名密码。成功,进入到下一步。否则要求用户重新认证(第三步)
服务器对用户拥有的权限(角色)判定: 有权限,重定向到origin url; 权限不足,返回状态码403("forbidden").
从第3步,我们可以知道,用户的请求被中断了。
用户登录成功后(第7步),会被重定向到origin url,spring security通过使用缓存的request,使得被中断的请求能够继续执行。
使用缓存
用户登录成功后,页面重定向到origin url。浏览器发出的请求优先被拦截器RequestCacheAwareFilter拦截,RequestCacheAwareFilter通过其持有的RequestCache对象实现request的恢复。
- public void doFilter(ServletRequest request, ServletResponse response,
- FilterChain chain) throws IOException, ServletException {
- // request匹配,则取出,该操作同时会将缓存的request从session中删除
- HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
- (HttpServletRequest) request, (HttpServletResponse) response);
- // 优先使用缓存的request
- chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
- response);
- }
何时缓存
首先,我们需要了解下RequestCache以及ExceptionTranslationFilter。
RequestCache
RequestCache接口声明了缓存与恢复操作。默认实现类是HttpSessionRequestCache。HttpSessionRequestCache的实现比较简单,这里只列出接口的声明:
- public interface RequestCache {
- // 将request缓存到session中
- void saveRequest(HttpServletRequest request, HttpServletResponse response);
- // 从session中取request
- SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response);
- // 获得与当前request匹配的缓存,并将匹配的request从session中删除
- HttpServletRequest getMatchingRequest(HttpServletRequest request,
- HttpServletResponse response);
- // 删除缓存的request
- void removeRequest(HttpServletRequest request, HttpServletResponse response);
- }
ExceptionTranslationFilter
ExceptionTranslationFilter 是Spring Security的核心filter之一,用来处理AuthenticationException和AccessDeniedException两种异常。
在我们的例子中,AuthenticationException指的是未登录状态下访问受保护资源,AccessDeniedException指的是登陆了但是由于权限不足(比如普通用户访问管理员界面)。
ExceptionTranslationFilter 持有两个处理类,分别是AuthenticationEntryPoint和AccessDeniedHandler。
ExceptionTranslationFilter 对异常的处理是通过这两个处理类实现的,处理规则很简单:
- 规则1. 如果异常是 AuthenticationException,使用 AuthenticationEntryPoint 处理
- 规则2. 如果异常是 AccessDeniedException 且用户是匿名用户,使用 AuthenticationEntryPoint 处理
- 规则3. 如果异常是 AccessDeniedException 且用户不是匿名用户,如果否则交给 AccessDeniedHandler 处理。
对应以下代码
- private void handleSpringSecurityException(HttpServletRequest request,
- HttpServletResponse response, FilterChain chain, RuntimeException exception)
- throws IOException, ServletException {
- if (exception instanceof AuthenticationException) {
- logger.debug(
- "Authentication exception occurred; redirecting to authentication entry point",
- exception);
- sendStartAuthentication(request, response, chain,
- (AuthenticationException) exception);
- }
- else if (exception instanceof AccessDeniedException) {
- if (authenticationTrustResolver.isAnonymous(SecurityContextHolder
- .getContext().getAuthentication())) {
- logger.debug(
- "Access is denied (user is anonymous); redirecting to authentication entry point",
- exception);
- sendStartAuthentication(
- request,
- response,
- chain,
- new InsufficientAuthenticationException(
- "Full authentication is required to access this resource"));
- }
- else {
- logger.debug(
- "Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
- exception);
- accessDeniedHandler.handle(request, response,
- (AccessDeniedException) exception);
- }
- }
- }
AccessDeniedHandler 默认实现是 AccessDeniedHandlerImpl。该类对异常的处理是返回403错误码。
- public void handle(HttpServletRequest request, HttpServletResponse response,
- AccessDeniedException accessDeniedException) throws IOException,
- ServletException {
- if (!response.isCommitted()) {
- if (errorPage != null) { // 定义了errorPage
- // errorPage中可以操作该异常
- request.setAttribute(WebAttributes.ACCESS_DENIED_403,
- accessDeniedException);
- // 设置403状态码
- response.setStatus(HttpServletResponse.SC_FORBIDDEN);
- // 转发到errorPage
- RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
- dispatcher.forward(request, response);
- }
- else { // 没有定义errorPage,则返回403状态码(Forbidden),以及错误信息
- response.sendError(HttpServletResponse.SC_FORBIDDEN,
- accessDeniedException.getMessage());
- }
- }
- }
AuthenticationEntryPoint 默认实现是 LoginUrlAuthenticationEntryPoint, 该类的处理是转发或重定向到登录页面
- public void commence(HttpServletRequest request, HttpServletResponse response,
- AuthenticationException authException) throws IOException, ServletException {
- String redirectUrl = null;
- if (useForward) {
- if (forceHttps && "http".equals(request.getScheme())) {
- // First redirect the current request to HTTPS.
- // When that request is received, the forward to the login page will be
- // used.
- redirectUrl = buildHttpsRedirectUrlForRequest(request);
- }
- if (redirectUrl == null) {
- String loginForm = determineUrlToUseForThisRequest(request, response,
- authException);
- if (logger.isDebugEnabled()) {
- logger.debug("Server side forward to: " + loginForm);
- }
- RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
- // 转发
- dispatcher.forward(request, response);
- return;
- }
- }
- else {
- // redirect to login page. Use https if forceHttps true
- redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
- }
- // 重定向
- redirectStrategy.sendRedirect(request, response, redirectUrl);
- }
了解完这些,回到我们的例子。
第3步时,用户未登录的情况下访问受保护资源,ExceptionTranslationFilter会捕获到AuthenticationException异常(规则1)。页面需要跳转,ExceptionTranslationFilter在跳转前使用requestCache缓存request。
- protected void sendStartAuthentication(HttpServletRequest request,
- HttpServletResponse response, FilterChain chain,
- AuthenticationException reason) throws ServletException, IOException {
- // SEC-112: Clear the SecurityContextHolder's Authentication, as the
- // existing Authentication is no longer considered valid
- SecurityContextHolder.getContext().setAuthentication(null);
- // 缓存 request
- requestCache.saveRequest(request, response);
- logger.debug("Calling Authentication entry point.");
- authenticationEntryPoint.commence(request, response, reason);
- }
二、了解了以上原理以及上篇的forward和redirect的区别,配置实现如下,基于springsecurity4.1.3版本
配置文件:完整的
- <?xml version="1.0" encoding="UTF-8"?>
- <beans:beans xmlns="http://www.springframework.org/schema/security"
- xmlns:beans="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/security
- http://www.springframework.org/schema/security/spring-security.xsd">
- <http auto-config="true" use-expressions="true" entry-point-ref="myLoginUrlAuthenticationEntryPoint">
- <form-login
- login-page="/login"
- authentication-failure-url="/login?error"
- login-processing-url="/login"
- authentication-success-handler-ref="myAuthenticationSuccessHandler" />
- <!-- 认证成功用自定义类myAuthenticationSuccessHandler处理 -->
- <logout logout-url="/logout"
- logout-success-url="/"
- invalidate-session="true"
- delete-cookies="JSESSIONID"/>
- <csrf disabled="true" />
- <intercept-url pattern="/order/*" access="hasRole('ROLE_USER')"/>
- </http>
- <!-- 使用自定义类myUserDetailsService从数据库获取用户信息 -->
- <authentication-manager>
- <authentication-provider user-service-ref="myUserDetailsService">
- <!-- 加密 -->
- <password-encoder hash="md5">
- </password-encoder>
- </authentication-provider>
- </authentication-manager>
- <!-- 被认证请求向登录界面跳转采用forward方式 -->
- <beans:bean id="myLoginUrlAuthenticationEntryPoint"
- class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
- <beans:constructor-arg name="loginFormUrl" value="/login"></beans:constructor-arg>
- <beans:property name="useForward" value="true"/>
- </beans:bean>
- </beans:beans>
主要配置
- <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">
- <!-- 被认证请求向登录界面跳转采用forward方式 -->
- <beans:bean id="myLoginUrlAuthenticationEntryPoint"
- class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
- <beans:constructor-arg name="loginFormUrl" value="/login"></beans:constructor-arg>
- <beans:property name="useForward" value="true"/>
- </beans:bean></pre><br>
- <pre></pre>
- <p>从上面的分析可知,默认情况下采用的是redirect方式,这里通过配置从而实现了forward方式,这里还是直接利用的security自带的类LoginUrlAuthenticationEntryPoint,只不过进行了以上配置:</p>
- <p></p><pre code_snippet_id="1902646" snippet_file_name="blog_20161215_10_1004934" class="java" name="code">/**
- * Performs the redirect (or forward) to the login form URL.
- */
- public void commence(HttpServletRequest request, HttpServletResponse response,
- AuthenticationException authException) throws IOException, ServletException {
- String redirectUrl = null;
- if (useForward) {
- if (forceHttps && "http".equals(request.getScheme())) {
- // First redirect the current request to HTTPS.
- // When that request is received, the forward to the login page will be
- // used.
- redirectUrl = buildHttpsRedirectUrlForRequest(request);
- }
- if (redirectUrl == null) {
- String loginForm = determineUrlToUseForThisRequest(request, response,
- authException);
- if (logger.isDebugEnabled()) {
- logger.debug("Server side forward to: " + loginForm);
- }
- RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
- dispatcher.forward(request, response);
- return;
- }
- }
- else {
- // redirect to login page. Use https if forceHttps true
- redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
- }
- redirectStrategy.sendRedirect(request, response, redirectUrl);
- }</pre><br>
- <p></p>
- <p></p>
- <p></p>
- <p></p>
- <p>登录成功后的类配置,存入登录user信息后交给认证成功后的处理类MyAuthenticationSuccessHandler,该类集成了SavedRequestAwareAuthenticationSuccessHandler,他会从缓存中提取请求,从而可以恢复之前请求的数据</p>
- <p></p><pre code_snippet_id="1902646" snippet_file_name="blog_20161215_11_4222490" class="java" name="code">/**
- * 登录后操作
- *
- * @author HHL
- * @date
- *
- */
- @Component
- public class MyAuthenticationSuccessHandler extends
- SavedRequestAwareAuthenticationSuccessHandler {
- @Autowired
- private IUserService userService;
- @Override
- public void onAuthenticationSuccess(HttpServletRequest request,
- HttpServletResponse response, Authentication authentication)
- throws IOException, ServletException {
- // 认证成功后,获取用户信息并添加到session中
- UserDetails userDetails = (UserDetails) authentication.getPrincipal();
- MangoUser user = userService.getUserByName(userDetails.getUsername());
- request.getSession().setAttribute("user", user);
- super.onAuthenticationSuccess(request, response, authentication);
- }
- }</pre><p></p>
- <p></p>
- <p></p>
- <p></p>
- <p>SavedRequestAwareAuthenticationSuccessHandler中的onAuthenticationSuccess方法;</p>
- <p></p><pre code_snippet_id="1902646" snippet_file_name="blog_20161215_12_7440047" class="java" name="code">@Override
- public void onAuthenticationSuccess(HttpServletRequest request,
- HttpServletResponse response, Authentication authentication)
- throws ServletException, IOException {
- SavedRequest savedRequest = requestCache.getRequest(request, response);
- if (savedRequest == null) {
- super.onAuthenticationSuccess(request, response, authentication);
- return;
- }
- String targetUrlParameter = getTargetUrlParameter();
- if (isAlwaysUseDefaultTargetUrl()
- || (targetUrlParameter != null && StringUtils.hasText(request
- .getParameter(targetUrlParameter)))) {
- requestCache.removeRequest(request, response);
- super.onAuthenticationSuccess(request, response, authentication);
- return;
- }
- clearAuthenticationAttributes(request);
- // Use the DefaultSavedRequest URL
- String targetUrl = savedRequest.getRedirectUrl();
- logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);
- getRedirectStrategy().sendRedirect(request, response, targetUrl);
- }</pre>4.1.3中如果默认不配置的话也是采用的SavedRequestAwareAuthenticationSuccessHandler进行处理,详情可参见:<a target="_blank" href="http://blog.csdn.net/honghailiang888/article/details/53541664">Spring实战篇系列----源码解析Spring Security中的过滤器Filter初始化
- </a><br>
- <br>
- <br>
- 上述实现了跳转到登录界面采用forward方式,就是浏览器地址栏没有变化,当然也可采用redirect方式,地址栏变为登录界面地址栏,当登录完成后恢复到原先的请求页面,请求信息会从requestCache中还原回来。可参考<a target="_blank" href="http://blog.csdn.net/honghailiang888/article/details/53520557"> Spring实战篇系列----spring security4.1.3配置以及踩过的坑</a><br>
- <p></p>
- <p></p>
- <p></p>
- <p></p>
- <p></p>
- <p></p>
- <p><br>
- </p>
- <p><br>
- </p>
- <p>参考:</p>
- <p><a target="_blank" href="https://segmentfault.com/a/1190000004183264">https://segmentfault.com/a/1190000004183264</a></p>
- <p><a target="_blank" href="http://gtbald.iteye.com/blog/1214132">http://gtbald.iteye.com/blog/1214132</a></p>
- <p><br>
- <br>
- <br>
- </p>
- <div style="top:0px"></div>
- <div style="top:4827px"></div>
- <div style="top:3353px"></div>
Spring Security4.1.3实现拦截登录后向登录页面跳转方式(redirect或forward)返回被拦截界面的更多相关文章
- WPF:验证登录后关闭登录窗口,显示主窗口的解决方法
http://www.27ba.com/post/145.html WPF:验证登录后关闭登录窗口,显示主窗口的解决方法 最近想做一个基于Socket的通讯工具,想模仿QQ那样,需要先登录,登录成功后 ...
- Html中设置访问页面不在后进行其他页面跳转
Html中设置访问页面不在后进行其他页面跳转 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" & ...
- springmvc使用路径变量后再进行页面跳转会出现路径错误问题
学习<Servlet.JSP和SpringMVC学习指南>遇到的一个问题,记录下. 项目代码 现象 @RequestMapping(value = "/book_edit/{id ...
- Spring Security4实例(Java config版)——ajax登录,自定义验证
本文源码请看这里 相关文章: Spring Security4实例(Java config 版) -- Remember-Me 首先添加起步依赖(如果不是springboot项目,自行切换为Sprin ...
- Python手动构造Cookie模拟登录后获取网站页面内容
最近有个好友让我帮忙爬取个小说,这个小说是前三十章直接可读,后面章节需要充值VIP可见.所以就需要利用VIP账户登录后,构造Cookie,再用Python的获取每章节的url,得到内容后再使用 PyQ ...
- axios interceptors 拦截 , 页面跳转, token 验证 Vue+axios实现登陆拦截,axios封装(报错,鉴权,跳转,拦截,提示)
Vue+axios实现登陆拦截,axios封装(报错,鉴权,跳转,拦截,提示) :https://blog.csdn.net/H1069495874/article/details/80057107 ...
- Dynamics CRM2013 定制你的系统登录后的首页面
在2013中个性设置中又多了一个新的,更好的增强了用户体验,对于特定的用户而言只需要使用系统的一小块功能,所以很多用户进入 系统只需要显示跟自己业务相关的功能页面即可. 点右上角的齿轮进入选项,在常规 ...
- 使用QQ第三方登录 并在父页面跳转刷新
<html> <head> <title>QQ登录跳转</title> <script src="http://lib.sinaapp. ...
- Spring Security4实例(Java config 版) —— Remember-Me
本文源码请看这里 相关文章: Spring Security4实例(Java config版)--ajax登录,自定义验证 Spring Security提供了两种remember-me的实现,一种是 ...
随机推荐
- 如何上传代码到git上
windows环境下上传代码到git仓库 1,https://github.com/new 2,创建成功后是这样子的 3,远程添加github上的Blog仓库. 1)进入本地文件夹下-右击鼠标-Git ...
- 坚持自己的追求,迎来 “中国系统开发网” (CSDN)的专访
坚持自己的追求,迎来 "中国系统开发网" (CSDN)的专访: 专访马根峰:海量数据处理与分析大师的中国本土程序员" http://www.csdn.net/articl ...
- Nginx的内部(进程)模型
nginx是以多进程的方式来工作的,当然nginx也是支持多线程的方式的,只是我们主流的方式还是多进程的方式,也是nginx的默认方式.nginx采用多进程的方式有诸多好处. (1)nginx在启动后 ...
- 循环赛日程编排c代码
#include <stdio.h> int m,s,n; int i,j; int a[100][100]; int sf(int n) { if(n%2!=0) printf(&quo ...
- javascript算术运算溢出
js中的算术在溢出overflow,下溢underflow或被0整除时不会报错. 当数字结果超过了数字上限时,结果为一个特殊的无穷大(infinity)值,在js中以Infinity表示:同理,当负数 ...
- mysql-proxy中的admin-lua-script
[root@ecs-7b55 lua]# cat admin.lua --[[ $%BEGINLICENSE%$ Copyright (c) 2008, 2012, Oracle and/or its ...
- AngularJS学习笔记之directive——scope选项与绑定策略
开门见山地说,scope:{}使指令与外界隔离开来,使其模板(template)处于non-inheriting(无继承)的状态,当然除非你在其中使用了transclude嵌入,这点之后的笔记会再详细 ...
- IT轮子系列(六)——Excel上传与解析,一套代码解决所有Excel业务上传,你Get到了吗
前言 在日常开发当中,excel的上传与解析是很常见的.根据业务不同,解析的数据模型也都不一样.不同的数据模型也就需要不同的校验逻辑,这往往需要写多套的代码进行字段的检验,如必填项,数据格式.为了避免 ...
- 什么才是java的基础知识?
近日里,很多人邀请我回答各种j2ee开发的初级问题,我无一都强调java初学者要先扎实自己的基础知识,那什么才是java的基础知识?又怎么样才算掌握了java的基础知识呢?这个问题还真值得仔细思考. ...
- Java编程语言下Selenium 对于下拉框,单选,多选等选择器的操作
WebElement selector = driver.findElement(By.id("Selector")); Select select = new Select(se ...