本人现在开发的项目中用到了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.youkuaiyun.com/detail/brushli/7865697