- 浏览: 3522 次
- 性别:
- 来自: 长春
文章分类
最新评论
在此,谢谢http://blog.csdn.net/k10509806/article/details/6436987这篇文章的作者,我参考了其中Filter部分的内容,再次感谢!
先说两句没用的话,本想在CSDN上发表这篇文章了,谁曾想第一天开通博客,居然不让用,要等三天时间,要知道,对于程序员来说,时间宝贵,况且又有个什么密码泄漏事件,想来想去还是来iteye吧。
第一次在iteye上面写东西,不知道最终会是什么效果,期待中......
最近想在闲暇时间,写个CMS玩玩, 需要用到前后台页面登录和登出,因为使用SpringSecurity的时间也不长,相对来说对与这种需求还是有一点点复杂的,所以用了几个小时的研究,终于满足了这种需求。
其实原理很简单,就是重写实现类,根据访问的路径判断页面是前台还是后台,实现跳转到不同登录页面和登出成功页面。
我用的环境是Spring3.0和SpringSecurity3.0,MyEclipse自动引包就行了。
需要注意的是,这里的大部分Java代码,请看springsecurity3的源码,我是在源码的基础之上,进行重构的,我所写的部分,大多都写有注释,对于spring源码,因为很忙,我也没有细看,不明白的,留言好了。
先看一下配置的http节点。
<http auto-config="false" entry-point-ref="loginUrlEntryPoint"> <intercept-url pattern="/admin/**" access="ROLE_ADMIN" /> <intercept-url pattern="/admin/jsp/login/login.jsp* filters="none" /> <intercept-url pattern="/login.jsp*" filters="none" /> <intercept-url pattern="/**" access="ROLE_ADMIN,ROLE_USER" /> <custom-filter position="FORM_LOGIN_FILTER" ref="loginFilter" /> <custom-filter position="LOGOUT_FILTER" ref="logoutFilter" /> </http>
很简单,关键部分我用红色标明了,其中的 loginUrlEntryPoint 是实现的AuthenticationEntryPoint接口,用来判断是前台登录还是后台登录, loginFilter 是继承的AbstractAuthenticationProcessingFilter类,实现登录验证的功能。
下面看 loginUrlEntryPoint 的说明。
配置如下:
<beans:bean id="loginUrlEntryPoint" class="service.LoginUrlEntryPoint" />
代码如下:
package service;
import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; public class LoginUrlEntryPoint implements AuthenticationEntryPoint { public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { String targetUrl = null; String url = request.getRequestURI(); if (url.indexOf("admin") != -1) { //判断url中是否包含admin,如果包含,则判断为后台,否则为前台 targetUrl = "/admin/jsp/login/login.jsp"; request.getSession().setAttribute("loginType", "back"); //前登录状态保存到session中,在后面这个值会频繁用到 } else { targetUrl = "/login.jsp"; request.getSession().setAttribute("loginType", "front");//同理 } targetUrl = request.getContextPath() + targetUrl; response.sendRedirect(targetUrl); } }
下面再看一下loginFilter的配置:
<beans:bean id="loginFilter" class="service.LoginFilter"> <beans:constructor-arg name="validateUrl" type="java.lang.String" value="/j_spring_security_check" /> <beans:property name="usernameParameter" value="username"></beans:property> <beans:property name="passwordParameter" value="password"></beans:property> <beans:property name="authenticationManager" ref="authenticationManager" /> <beans:property name="authenticationFailureHandler" ref="failureHandler" /> <beans:property name="authenticationSuccessHandler" ref="successHandler" /> </beans:bean>
作一下简要解释,这里的validateUrl参数,是定义执行提交身份验证的URL,提供自定义,和登录页面的action对应即可。usernameParameter是登录页面的用户输入框的id,passwordParameter是密码输入框的id,重点是authenticationFailureHandler和authenticationSuccessHandler,分别是验证成功和失败。
先看一下loginFilter的代码:
package service; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.util.Assert; public class LoginFilter extends AbstractAuthenticationProcessingFilter { public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "j_username"; public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "j_password"; @Deprecated public static final String SPRING_SECURITY_LAST_USERNAME_KEY = "SPRING_SECURITY_LAST_USERNAME"; private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; private boolean postOnly = true; /** * 构造方法,参数为执行验证的url,在spring配置中进后构造注入 */ public LoginFilter(String validateUrl) { super(validateUrl); } public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } protected String obtainPassword(HttpServletRequest request) { return request.getParameter(passwordParameter); } protected String obtainUsername(HttpServletRequest request) { return request.getParameter(usernameParameter); } protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource .buildDetails(request)); } public void setUsernameParameter(String usernameParameter) { Assert.hasText(usernameParameter, "Username parameter must not be empty or null"); this.usernameParameter = usernameParameter; } public void setPasswordParameter(String passwordParameter) { Assert.hasText(passwordParameter, "Password parameter must not be empty or null"); this.passwordParameter = passwordParameter; } public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; } public final String getUsernameParameter() { return usernameParameter; } public final String getPasswordParameter() { return passwordParameter; } }
上面这段代码主要实现了验证url和用户名id、密码id的设置。
再看一下failureHandler的xml配置:
<beans:bean id="failureHandler" class="service.LoginFailureHandler"> <beans:property name="defaultBackTargetUrl" value="/admin/jsp/login/login.jsp?error=1" /> <beans:property name="defaultFrontTargetUrl" value="/login.jsp?error=1" /> </beans:bean>
两个属性分别是前台登录失败和后台登录失败的跳转url,我写的是到登录页面,通过error参数判断是验证失败。
下面看一下failureHandler的代码:
package service; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.WebAttributes; import org.springframework.security.web.DefaultRedirectStrategy; import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.util.UrlUtils; import org.springframework.util.Assert; public class LoginFailureHandler implements AuthenticationFailureHandler { protected final Log logger = LogFactory.getLog(getClass()); private String defaultFailureUrl; private boolean forwardToDestination = false; private boolean allowSessionCreation = true; private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); private String defaultBackTargetUrl; //定义后台登录验证失败跳转地址 private String defaultFrontTargetUrl; //定义前台登录验证失败跳转地址 //定义set方法 public void setDefaultBackTargetUrl(String defaultBackTargetUrl) { this.defaultBackTargetUrl = defaultBackTargetUrl; } public void setDefaultFrontTargetUrl(String defaultFrontTargetUrl) { this.defaultFrontTargetUrl = defaultFrontTargetUrl; } public LoginFailureHandler() { } public LoginFailureHandler(String defaultFailureUrl) { setDefaultFailureUrl(defaultFailureUrl); } public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { saveException(request, exception); setFailureTarget(request); //调用前后台判断方法 if (forwardToDestination) { logger.debug("Forwarding to " + defaultFailureUrl); request.getRequestDispatcher(defaultFailureUrl).forward(request, response); } else { logger.debug("Redirecting to " + defaultFailureUrl); redirectStrategy.sendRedirect(request, response, defaultFailureUrl); } } protected final void setFailureTarget(HttpServletRequest request) { String loginType = (String) request.getSession().getAttribute( "loginType"); //获取登录状态,这个值是在前面判断登录入口放入session的 if (loginType.equals("back")) { setDefaultFailureUrl(this.defaultBackTargetUrl); //如果是后台,前后台跳转地址设置到defaultFailureUrl } else if (loginType.equals("front")) { setDefaultFailureUrl(this.defaultFrontTargetUrl); //如果是前台,与后台类似 } } protected final void saveException(HttpServletRequest request, AuthenticationException exception) { if (forwardToDestination) { request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception); } else { HttpSession session = request.getSession(false); if (session != null || allowSessionCreation) { request.getSession().setAttribute( WebAttributes.AUTHENTICATION_EXCEPTION, exception); } } } public void setDefaultFailureUrl(String defaultFailureUrl) { Assert.isTrue(UrlUtils.isValidRedirectUrl(defaultFailureUrl), "'" + defaultFailureUrl + "' is not a valid redirect URL"); this.defaultFailureUrl = defaultFailureUrl; } protected boolean isUseForward() { return forwardToDestination; } public void setUseForward(boolean forwardToDestination) { this.forwardToDestination = forwardToDestination; } public void setRedirectStrategy(RedirectStrategy redirectStrategy) { this.redirectStrategy = redirectStrategy; } protected RedirectStrategy getRedirectStrategy() { return redirectStrategy; } protected boolean isAllowSessionCreation() { return allowSessionCreation; } public void setAllowSessionCreation(boolean allowSessionCreation) { this.allowSessionCreation = allowSessionCreation; } }
这里的关键代码已经给了明确注释,很简单,容易理解。这一步骤能够实现登录失败分别跳转到前台、后台的失败页面。
再看一下successHandler的配置:
<beans:bean id="successHandler" class="service.LoginSuccessHandler"> <beans:property name="defaultBackTargetUrl" value="/admin/index.jsp"></beans:property> <beans:property name="defaultFrontTargetUrl" value="/index.jsp"></beans:property> <beans:property name="alwaysUseDefaultTargetUrl" value="true"></beans:property> </beans:bean>
和failureHandler含义一样,不解释了。
下面再看一下successHandler的代码:
package service; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.security.core.Authentication; import org.springframework.security.web.WebAttributes; import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; public class LoginSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler implements AuthenticationSuccessHandler { private String defaultFrontTargetUrl; // 前台登录成功的跳转地址 private String defaultBackTargetUrl; // 后台登录成功的跳转地址 // set方法 spring调用 public void setDefaultFrontTargetUrl(String defaultFrontTargetUrl) { this.defaultFrontTargetUrl = defaultFrontTargetUrl; } public void setDefaultBackTargetUrl(String defaultBackTargetUrl) { this.defaultBackTargetUrl = defaultBackTargetUrl; } public LoginSuccessHandler() { } public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { String loginType = (String) request.getSession().getAttribute( "loginType"); // 获取登录状态,这个值是在前面判断登录入口放入session的 if (loginType.equals("back")) { setDefaultTargetUrl(this.defaultBackTargetUrl); // 如果是后台,前后台跳转地址设置到defaultFailureUrl } else if (loginType.equals("front")) { setDefaultTargetUrl(this.defaultFrontTargetUrl); // 如果是前台,与后台类似 } handle(request, response, authentication); clearAuthenticationAttributes(request); } protected final void clearAuthenticationAttributes( HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) { return; } session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); } }
同样,不解释了,看简单的几条注释。
下面实现登出功能,logoutFilter的配置:
<beans:bean id="logoutFilter" class="service.LogoutFilter"> <beans:constructor-arg name="frontLogoutSuccessUrl" value="/index.jsp"></beans:constructor-arg> <beans:constructor-arg name="backLogoutSuccessUrl" value="/admin/index.jsp"></beans:constructor-arg> <beans:constructor-arg> <beans:list> <beans:bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" /> </beans:list> </beans:constructor-arg> <beans:property name="filterProcessesUrl" value="/j_spring_security_logout" /> </beans:bean>
这里通过构造注入,分别注入前后台登出成功的url和SecurityContextLogoutHandler,filterProcessesUrl的意思是执行登出的url,默认就是/j_spring_security_logout,不管是前台还是后台,只要执行filterProcessesUrl所对应的url,就会分别跳转到所配置的登出成功页面,下面看一下logoutFilter的代码部分:
package service; import java.io.IOException; import java.util.Arrays; import java.util.List; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.security.web.util.UrlUtils; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.filter.GenericFilterBean; public class LogoutFilter extends GenericFilterBean { private String filterProcessesUrl = "/j_spring_security_logout"; private final List<LogoutHandler> handlers; private LogoutSuccessHandler logoutSuccessHandler; private SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler; //作临时变量用 private String frontLogoutSuccessUrl; //前台登出成功跳转页面 private String backLogoutSuccessUrl; //后台登出成功跳转页面 public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler... handlers) { Assert.notEmpty(handlers, "LogoutHandlers are required"); this.handlers = Arrays.asList(handlers); Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null"); this.logoutSuccessHandler = logoutSuccessHandler; } public LogoutFilter(String frontLogoutSuccessUrl,String backLogoutSuccessUrl, LogoutHandler... handlers) { Assert.notEmpty(handlers, "LogoutHandlers are required"); this.handlers = Arrays.asList(handlers); Assert.isTrue( !StringUtils.hasLength(frontLogoutSuccessUrl) || UrlUtils.isValidRedirectUrl(frontLogoutSuccessUrl), frontLogoutSuccessUrl + " isn't a valid redirect URL"); Assert.isTrue( !StringUtils.hasLength(backLogoutSuccessUrl) || UrlUtils.isValidRedirectUrl(backLogoutSuccessUrl), backLogoutSuccessUrl + " isn't a valid redirect URL"); SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler(); if (StringUtils.hasText(frontLogoutSuccessUrl) && StringUtils.hasText(backLogoutSuccessUrl)) { this.frontLogoutSuccessUrl = frontLogoutSuccessUrl; this.backLogoutSuccessUrl = backLogoutSuccessUrl; } this.urlLogoutSuccessHandler = urlLogoutSuccessHandler;//赋值给临时变量,以便于后面判断是前台登录还是后台登录 } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String loginType = (String) request.getSession().getAttribute("loginType");//获取登录类型 if(loginType!=null) { //这句必须有,否则报空指针 if (loginType.equals("back")) { urlLogoutSuccessHandler.setDefaultTargetUrl(this.backLogoutSuccessUrl); } else if (loginType.equals("front")) { urlLogoutSuccessHandler.setDefaultTargetUrl(this.frontLogoutSuccessUrl); } } logoutSuccessHandler = urlLogoutSuccessHandler; //前临时变量赋值给LogoutSuccessHandler,供后面代码调用 if (requiresLogout(request, response)) { Authentication auth = SecurityContextHolder.getContext() .getAuthentication(); if (logger.isDebugEnabled()) { logger.debug("Logging out user '" + auth + "' and transferring to logout destination"); } for (LogoutHandler handler : handlers) { handler.logout(request, response, auth); } logoutSuccessHandler.onLogoutSuccess(request, response, auth); return; } chain.doFilter(request, response); } protected boolean requiresLogout(HttpServletRequest request, HttpServletResponse response) { String uri = request.getRequestURI(); int pathParamIndex = uri.indexOf(';'); if (pathParamIndex > 0) { uri = uri.substring(0, pathParamIndex); } int queryParamIndex = uri.indexOf('?'); if (queryParamIndex > 0) { uri = uri.substring(0, queryParamIndex); } if ("".equals(request.getContextPath())) { return uri.endsWith(filterProcessesUrl); } return uri.endsWith(request.getContextPath() + filterProcessesUrl); } public void setFilterProcessesUrl(String filterProcessesUrl) { Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid value for" + " 'filterProcessesUrl'"); this.filterProcessesUrl = filterProcessesUrl; } protected String getFilterProcessesUrl() { return filterProcessesUrl; } }
这里也不解释了,真的没什么好说的,说多了都是废话。
可以看出来,几乎所有的代码都有一个共性,就是从session中取出loginType判断前后台,再根据这个值,将跳转地 址赋值给springsecurity,最终实现了所给的需求。
写的有点混乱,对于写这种文章实在没什么经验,也是一种锻炼。
最后,希望这些能帮助到朋友们。
我的邮箱是:zhbf5156@163.com ,有问题,欢迎一起探讨。
相关推荐
教程将涵盖SpringBoot的配置管理、RESTful API设计、数据库集成(如MyBatis或JPA)以及Spring Security的使用等内容。 Vue3则是Vue.js框架的最新迭代,带来了许多性能优化和设计上的改进。Vue3引入了Composition ...
开发这样一个系统,开发者可能使用了诸如Hibernate或MyBatis这样的ORM框架来处理数据库操作,Spring Security或Apache Shiro进行权限控制,使用了Bootstrap或Materialize CSS来美化前端界面,并且可能利用了RESTful ...
【标题】"基于Spring Boot+Vue的后台管理系统"是一个现代Web应用程序开发的综合实例,它结合了Spring Boot的后端强大功能与Vue.js的前端高效渲染能力,为开发者提供了一个全面的解决方案,用于构建功能丰富的管理...
使用Traefik进行反向代理,监听后台变化自动化应用新的配置文件。极简封装了多租户底层,用更少的代码换来拓展性更强的SaaS多租户系统。借鉴OAuth2,实现了多终端认证系统,可控制子系统的token权限互相隔离。借鉴...
通过深入研究这个项目,开发者不仅可以了解到如何结合SpringBoot、Spring Security和JWT构建安全的后台系统,还能学习到如何运用Vue.js和Element UI打造高效的前端界面,同时也能体验到Vue3带来的新特性和性能提升。
ThinkItCMS 采用 SpringBoot + Mybatis Plus + Redis+ Spring Security + OAuth2 + Freemark 搭建的一套cms 系统,数据库采用 mysql 数据库,文件服务器采用 Fastdfs 全文检索采用 Solr 。 前端架构采用ant design ...
Eladmin是一个采用最新技术栈构建的,基于Spring Boot 2.1.0、Spring Boot JPA、JWT、Spring Security、Redis、Vue.js和Element-UI的现代化后台管理系统。其设计理念是提高开发效率,实现功能模块化,权限管理采用...
SpringBoot 是一个基于 Java 的框架,它简化了创建和配置基于 Spring 的应用程序的过程。这篇学习笔记将引导我们深入了解 SpringBoot 的基本用法,包括项目的创建、返回视图、数据库交互、前端与后端数据传递、日志...
JooLun微信管理平台是一个 Java EE 企业级微信快速开发平台,基于经典技术组合(Spring Boot、Spring Security、MyBatis、Jwt、Vue),内置模块如:公众号管理、部门管理、角色用户、菜单及按钮授权、数据权限、系统...
6. 安装weshop-admin-ui模块,运行mvn install和mvn build命令,运行命令前需要安装nodeJs 7. 运行weshop-eureka-server、weshop-config-server、weshop-api-gateway这几个基础服务 8. 运行weshop-user、weshop-...