本人现在开发的项目中用到了spring security3.1.2来实现登陆验证,但默认的过滤器UsernamePasswordAuthenticationFilter只能传递两个参数j_username和j_password,如果要传递验证码或首次打开的页面URL参数,只能重写UsernamePasswordAuthenticationFilter。
对于不了解spring security3.1的童鞋,请先阅读本人写的另一篇博客Spring Security权限管理,下面将在这篇博客的基础上,添加一个功能:传递登陆之前的页面URL。公司现在做的项目,如果没有登陆或会话超时,都要求重新登陆,登陆之后会直接跳转到首页,而不是跳转到原来打开的页面,这很恶心。于是本人决心对项目进行改造,使得可以传递最后打开页面的URL,登陆之后直接跳转到这个URL去。
1.重写UsernamePasswordAuthenticationFilter
package com.web.filter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; 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; /** * 验证登陆用户名和密码的拦截器,记录最后登陆页面 * @author brushli * @date 2014-10-30 */ public class MyUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "j_username"; public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "j_password"; public static final String SPRING_SECURITY_FORM_LAST_LOGIN_URL_KEY = "lastLoginUrl"; public static final String SPRING_SECURITY_SPLIT = ","; /** * @deprecated If you want to retain the username, cache it in a customized {@code AuthenticationFailureHandler} */ @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 String lastLoginUrlParameter = SPRING_SECURITY_FORM_LAST_LOGIN_URL_KEY; private String split = SPRING_SECURITY_SPLIT; private boolean postOnly = true; //~ Constructors =================================================================================================== public MyUsernamePasswordAuthenticationFilter() { super("/j_spring_security_check"); } //~ Methods ======================================================================================================== 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); String lastLoginUrl = obtainLastLoginUrl(request); if (username == null) { username = ""; } if (password == null) { password = ""; } if (lastLoginUrl == null) { lastLoginUrl = ""; } username = username.trim(); username = username + SPRING_SECURITY_SPLIT + lastLoginUrl; UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } /** * Enables subclasses to override the composition of the password, such as by including additional values * and a separator.<p>This might be used for example if a postcode/zipcode was required in addition to the * password. A delimiter such as a pipe (|) should be used to separate the password and extended value(s). The * <code>AuthenticationDao</code> will need to generate the expected password in a corresponding manner.</p> * * @param request so that request attributes can be retrieved * * @return the password that will be presented in the <code>Authentication</code> request token to the * <code>AuthenticationManager</code> */ protected String obtainPassword(HttpServletRequest request) { return request.getParameter(passwordParameter); } /** * Enables subclasses to override the composition of the username, such as by including additional values * and a separator. * * @param request so that request attributes can be retrieved * * @return the username that will be presented in the <code>Authentication</code> request token to the * <code>AuthenticationManager</code> */ protected String obtainUsername(HttpServletRequest request) { return request.getParameter(usernameParameter); } protected String obtainLastLoginUrl(HttpServletRequest request) { return request.getParameter(lastLoginUrlParameter); } /** * Provided so that subclasses may configure what is put into the authentication request's details * property. * * @param request that an authentication request is being created for * @param authRequest the authentication request object that should have its details set */ protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); } /** * Sets the parameter name which will be used to obtain the username from the login request. * * @param usernameParameter the parameter name. Defaults to "j_username". */ public void setUsernameParameter(String usernameParameter) { Assert.hasText(usernameParameter, "Username parameter must not be empty or null"); this.usernameParameter = usernameParameter; } /** * Sets the parameter name which will be used to obtain the password from the login request.. * * @param passwordParameter the parameter name. Defaults to "j_password". */ public void setPasswordParameter(String passwordParameter) { Assert.hasText(passwordParameter, "Password parameter must not be empty or null"); this.passwordParameter = passwordParameter; } /** * Defines whether only HTTP POST requests will be allowed by this filter. * If set to true, and an authentication request is received which is not a POST request, an exception will * be raised immediately and authentication will not be attempted. The <tt>unsuccessfulAuthentication()</tt> method * will be called as if handling a failed authentication. * <p> * Defaults to <tt>true</tt> but may be overridden by subclasses. */ public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; } public final String getUsernameParameter() { return usernameParameter; } public final String getPasswordParameter() { return passwordParameter; } public String getLastLoginUrlParameter() { return lastLoginUrlParameter; } public void setLastLoginUrlParameter(String lastLoginUrlParameter) { Assert.hasText(lastLoginUrlParameter, "lastLoginUrl Parameter must not be empty or null"); this.lastLoginUrlParameter = lastLoginUrlParameter; } public String getSplit() { return split; } /** * The string to separate username and lastLoginUrl * @param split */ public void setSplit(String split) { this.split = split; } }
2.修改AuthenticationFailHandlerImpl
package com.service.impl; 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.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; public class AuthenticationFailHandlerImpl extends SimpleUrlAuthenticationFailureHandler implements AuthenticationFailureHandler { /** * 登录时密码或账号不正确时的操作 */ @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { String lastLoginUrl = request.getParameter("lastLoginUrl"); if(lastLoginUrl == null){ lastLoginUrl = ""; } lastLoginUrl = lastLoginUrl.replace("?", "%3F"); lastLoginUrl = lastLoginUrl.replace("&", "%26"); logger.info("lastLoginUrl=" + lastLoginUrl); response.sendRedirect(request.getContextPath() + "/login.jsp?result=0&lastLoginUrl="+lastLoginUrl); return; } }
3.修改AuthenticationSuccessHandlerImpl
package com.service.impl; 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.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import com.bean.UserLoginDetails; /** * Authentication Handler for redirect the original inputed URL after login system. */ public class AuthenticationSuccessHandlerImpl extends SimpleUrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler { /** * 登录时通过密码验证后进行的操作 */ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { //查询当前登录用户有权限的功能 if(!SecurityContextHolder.getContext().getAuthentication().getPrincipal().equals("anonymousUser")){ UserLoginDetails userDetails = (UserLoginDetails) authentication.getPrincipal(); HttpSession session = request.getSession(); session.setAttribute("userDetails", userDetails); logger.info("lastLoginUrl=" + userDetails.getLastLoginUrl()); if(userDetails.getLastLoginUrl() != null && !"".equals(userDetails.getLastLoginUrl().trim())){ response.sendRedirect(userDetails.getLastLoginUrl()); }else{ response.sendRedirect(request.getContextPath() + "/myPage/myPage.jsp"); } } return; } }
4.修改WebUserDetailsService
package com.service.impl; import java.util.HashSet; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import com.bean.Role; import com.bean.User; import com.bean.UserLoginDetails; import com.service.UserService; import com.web.filter.MyUsernamePasswordAuthenticationFilter; /** * 用户登录是准备阶段的业务逻辑 */ public class WebUserDetailsService implements UserDetailsService { protected static final Logger logger = LoggerFactory.getLogger(WebUserDetailsService.class); private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { boolean accountNonExpired = true; boolean credentialsNonExpired = true; boolean accountNonLocked = true; boolean enabled = true; String[] usernameLastLoginUrl = username.split(MyUsernamePasswordAuthenticationFilter.SPRING_SECURITY_SPLIT); String lastLoginUrl = ""; if(usernameLastLoginUrl.length == 1){ username = usernameLastLoginUrl[0]; }else if(usernameLastLoginUrl.length == 2){ username = usernameLastLoginUrl[0]; lastLoginUrl = usernameLastLoginUrl[1]; } User user = null; try { user = userService.getUserByUsername(username); } catch (Exception e) { e.printStackTrace(); } if(user == null){ throw new UsernameNotFoundException("User account : " + username + " not found!"); } //封装该用户具有什么角色 Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>(); if(user.getRoles() != null && !user.getRoles().isEmpty()){ for(Role role : user.getRoles()){ GrantedAuthority ga = new SimpleGrantedAuthority(role.getName()); authorities.add(ga); } } UserLoginDetails userLoginDetails = new UserLoginDetails(user.getUsername(), user.getPassword(), authorities, accountNonExpired, accountNonLocked, credentialsNonExpired, enabled, lastLoginUrl); return userLoginDetails; } }
5.修改UserLoginDetails
package com.bean; import java.util.Collection; import java.util.Set; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; public class UserLoginDetails implements UserDetails { private static final long serialVersionUID = 1L; private String username; private String password; private Set<GrantedAuthority> authorities = null; private boolean accountNonExpired = false; //账号未过期 private boolean accountNonLocked = false; //账号未锁定 private boolean credentialsNonExpired = false; //证书未过期 private boolean enabled = false; //是否可用 private final String lastLoginUrl; //最后登陆的页面 public UserLoginDetails(String username, String password, Set<GrantedAuthority> authorities, boolean accountNonExpired, boolean accountNonLocked, boolean credentialsNonExpired, boolean enabled, String lastLoginUrl){ this.username = username; this.password = password; this.authorities = authorities; this.accountNonExpired = accountNonExpired; this.accountNonLocked = accountNonLocked; this.credentialsNonExpired = credentialsNonExpired; this.enabled = enabled; this.lastLoginUrl = lastLoginUrl; } public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } public String getPassword() { return password; } public String getUsername() { return username; } public boolean isAccountNonExpired() { return accountNonExpired; } public boolean isAccountNonLocked() { return accountNonLocked; } public boolean isCredentialsNonExpired() { return credentialsNonExpired; } public boolean isEnabled() { return enabled; } public String getLastLoginUrl() { return lastLoginUrl; } }
6.修改配置文件
<?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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- 访问login.jsp页面不需要权限验证 --> <http pattern="/login.jsp" security="none"/> <!-- This is where we configure Spring-Security --> <!-- <http auto-config="true" use-expressions="true"> --> <http auto-config="false" use-expressions="true" access-denied-page="/deny.jsp" entry-point-ref="authenticationEntryPoint"> <intercept-url pattern="/admin/**" access="hasRole('admin')"/><!-- 用户只有拥有角色admin才能访问/admin目录下面的资源 --> <intercept-url pattern="/myPage/**" access="hasAnyRole('admin', 'myRole')"/><!-- 用户拥有角色admin,myRole中的任意一个就能访问/myPage目录下面的资源 --> <!-- <form-login login-page="/login.jsp" authentication-failure-url="/deny.jsp" default-target-url="/index.jsp" authentication-success-handler-ref="authenticationSuccessHandlerImpl" authentication-failure-handler-ref="authenticationFailHandlerImpl" /> --> <!-- 配置自定义的Filter,并且将其放在FORM_LOGIN_FILTER节点,就会替换掉原来的FORM_LOGIN_FILTER节点 设置auto-config="false",不然会报已经存在同样的过滤器错误,同时还要删除<form-login> --> <custom-filter ref="loginProcessFilter" position ="FORM_LOGIN_FILTER" /> <logout invalidate-session="true" logout-success-url="/login.jsp" /> <!-- 增加一个自定义的filter,放在FILTER_SECURITY_INTERCEPTOR之前, 实现用户、角色、权限、资源的数据库管理。 --> <custom-filter ref="securityFilter" before="FILTER_SECURITY_INTERCEPTOR"/> </http> <beans:bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <beans:property name="loginFormUrl" value="/login.jsp"></beans:property> </beans:bean> <beans:bean id="loginProcessFilter" class="com.web.filter.MyUsernamePasswordAuthenticationFilter"> <beans:property name="authenticationManager" ref="authenticationManagerService"></beans:property> <beans:property name="authenticationSuccessHandler" ref="authenticationSuccessHandlerImpl"></beans:property> <beans:property name="authenticationFailureHandler" ref="authenticationFailHandlerImpl"></beans:property> </beans:bean> <beans:bean id="securityFilter" class="com.filter.SecurityInterceptorFilter"> <beans:property name="authenticationManager" ref="authenticationManagerService"/> <beans:property name="accessDecisionManager" ref="accessDecisionManagerService"/> <beans:property name="securityMetadataSource" ref="securityMetadataSourceService"/> </beans:bean> <beans:bean id="securityMetadataSourceService" class="com.service.impl.SecurityMetadataSourceService" init-method="loadAllResources"> <beans:property name="userService" ref="userService"/> </beans:bean> <!-- Declare an authentication-manager to use a custom userDetailsService --> <authentication-manager alias="authenticationManagerService"> <authentication-provider user-service-ref="webUserDetailsService"> <password-encoder hash="md5" /> </authentication-provider> </authentication-manager> <beans:bean id="accessDecisionManagerService" class="com.service.impl.AccessDecisionManagerService" /> </beans:beans>
7.程序运行结果
我们打开首页:http://localhost:8082/springSecurity/index.jsp
程序会自动跳转到登陆页面:http://localhost:8082/springSecurity/login.jsp?lastLoginUrl=/springSecurity/index.jsp
后面会附带首页的URL,输入正确的用户名和密码后,就会自动跳转到首页
7.项目的代码结构
8.项目源代码下载
包括整个项目的源代码和MYSQL建库脚本
http://download.csdn.net/detail/brushli/7865697