Spring Security4.1.3实现拦截登录后向登录页面跳转方式(redirect或forward)返回被拦截界面
来源:互联网 发布:js最新判断当前浏览器 编辑:程序博客网 时间:2024/06/03 17:12
一、看下内部原理
简化后的认证过程分为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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/securityhttp://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>
主要配置
<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>从上面的分析可知,默认情况下采用的是redirect方式,这里通过配置从而实现了forward方式,这里还是直接利用的security自带的类LoginUrlAuthenticationEntryPoint,只不过进行了以上配置:
/** * 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 trueredirectUrl = buildRedirectUrlToLoginPage(request, response, authException);}redirectStrategy.sendRedirect(request, response, redirectUrl);}登录成功后的类配置,存入登录user信息后交给认证成功后的处理类MyAuthenticationSuccessHandler,该类集成了SavedRequestAwareAuthenticationSuccessHandler,他会从缓存中提取请求,从而可以恢复之前请求的数据
/** * 登录后操作 * * @author HHL * @date * */@Componentpublic class MyAuthenticationSuccessHandler extendsSavedRequestAwareAuthenticationSuccessHandler {@Autowiredprivate IUserService userService;@Overridepublic 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);}}SavedRequestAwareAuthenticationSuccessHandler中的onAuthenticationSuccess方法;
@Overridepublic 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 URLString targetUrl = savedRequest.getRedirectUrl();logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);getRedirectStrategy().sendRedirect(request, response, targetUrl);}4.1.3中如果默认不配置的话也是采用的SavedRequestAwareAuthenticationSuccessHandler进行处理,详情可参见:Spring实战篇系列----源码解析Spring Security中的过滤器Filter初始化
上述实现了跳转到登录界面采用forward方式,就是浏览器地址栏没有变化,当然也可采用redirect方式,地址栏变为登录界面地址栏,当登录完成后恢复到原先的请求页面,请求信息会从requestCache中还原回来。可参考 Spring实战篇系列----spring security4.1.3配置以及踩过的坑
参考:
https://segmentfault.com/a/1190000004183264
http://gtbald.iteye.com/blog/1214132
0 0
- Spring Security4.1.3实现拦截登录后向登录页面跳转方式(redirect或forward)返回被拦截界面
- Spring Security4.1.3实现拦截登录后向登录页面跳转方式(redirect或forward)返回被拦截界面
- Spring Security4.1.3采用forward方式跳转登录界面,标签s:authorize不起作用问题
- Struts 拦截器权限控制【通过拦截器实现登录后跳转到登录前页面】
- Struts 拦截器权限控制【通过拦截器实现登录后跳转到登录前页面】
- Struts 拦截器权限控制【通过拦截器实现登录后跳转到登录前页面】
- Struts 拦截器权限控制【通过拦截器实现登录后跳转到登录前页面】
- Spring中利用拦截器控制登录及页面跳转
- 【Spring实战】----Security4.1.3实现根据请求跳转不同登录页以及登录后根据权限跳转到不同页配置
- 【Spring实战】----Security4.1.3实现根据请求跳转不同登录页以及登录后根据权限跳转到不同页配置
- spring实现拦截登录请求
- Struts 通过拦截器实现登录后跳转到登录前页面 处理普通Http请求和Ajax请求时拦截配置
- Interceptor登录拦截(Spring拦截器)
- 拦截器实现未登录跳转
- spring拦截器登录
- spring 登录拦截器
- Spring MVC实现的登录拦截器
- Spring MVC 实现登录拦截认证
- 高德地图百度地图导航坐标转换
- Comlile and Install OpenVas 8 on Ubuntu Server 14.04
- Softmax vs. Softmax-Loss: Numerical Stability
- PHP 学习任务书(一):搭建开发环境
- hashmap entry
- Spring Security4.1.3实现拦截登录后向登录页面跳转方式(redirect或forward)返回被拦截界面
- iOS Swift下dispatch_after的写法
- this指针与构造函数
- php 第一次创建文件时延迟4秒 sleep用法
- iPad开发2(高仿之路)
- 通信资料
- UI界面框架调研报告 ——Wxwidgets
- 如何正确结束驱动RunLoop的NSThread
- 把项目中常用的小工具做个总结吧,方便自己以后用到