在此,谢谢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 ,有问题,欢迎一起探讨。