转载:http://www.blogjava.net/taochen1984/articles/310072.html
研究了好长时间,不知道从哪里下手。新的版本,很多东西在网上找不到,只能看他们的文档,当然这些文档相当不错,就看是否耐心的研究了!总是有急躁的心理作祟,不能专心研读,却处处碰壁,效率上反而未达预期效果!
终于,在无数次的沮丧下,稍微看到了点光亮!前面的文章太过皮毛,接下来的一些,希望能更加实际的,更加深入的分析每一个过程!
一直通过默认配置进行设置:
namespace(是security 3.0,网上也看到一些兄弟描述的是3.0,但是总是不符合我这里的namespace配置):
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
按照默认配置的http(这是用来根据namespace设置的基本的security过滤器chain):
auto-config=true时,就相当于
也就是使用了默认的过滤器。
我最开始的想法是能够把本地的login信息(不是调用spring security的login方法),传入到spring security的验证过滤器里面。
这里有一个比较关键的问题,就是封装他们的过滤器(或者仅仅是知道他们到底是哪些过滤器在起作用):
表1
|
(最开始看的时候,把这个表格忽略了,现在看来这些就是我们想要的!)
我们的验证过程,就是按照这样的顺序进行的。自上而下进行。
如果我们要自己定制相应的验证处理方法(在过滤器里面),我们就可以对照上面的过滤器,覆盖相应的接口方法。
根据我的处理过程,主要是用户名密码的验证过程,我大体描述一下自己的配置和处理过程:
1.配置namespace的标签:
这里的问题是,要定制自己的过滤器,就要通过
2.我的form-login filter配置(这些配置都是在application-security.xml文件中)为:
NOTE:
在这里有个问题就是: filter position conflicts!
如果使用这样的配置
自定义的filter对应的position是FORM_LOGIN_FILTER
但是因为使用了auto-config='true',所以默认有
这时就会出现position conflicts问题了。当然,如果你没有设置auto-config='true',但是却自己设置了
接着:
我的类MyUsernamePasswordAuthenticationFilter实现(我的说明方式就按照哪里需要,哪里加入的方式了):
package com.saveworld.authentication.filters;
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.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.NullRememberMeServices;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.util.TextEscapeUtils;
import org.springframework.util.Assert;
import com.saveworld.authentication.handlers.MySavedRequestAwareAuthenticationSuccessHandler;
import com.saveworld.authentication.handlers.MySimpleUrlAuthenticationFailureHandler;
public class MyUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter{
//~ Static fields/initializers =====================================================================================
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
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;
private boolean allowSessionCreation = true;
private String defaultTargetUrl = "/";
private String defaultFailureUrl = "/login.jsp";
private AuthenticationSuccessHandler successHandler = null;
private AuthenticationFailureHandler failureHandler = null;
private RememberMeServices rememberMeServices = null;
//~ Constructors ===================================================================================================
public MyUsernamePasswordAuthenticationFilter() {
//初始化
super("/j_spring_security_check");
this.rememberMeServices = (super.getRememberMeServices() == null)
? new NullRememberMeServices():super.getRememberMeServices();
}
//~ 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);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Place the last username attempted into HttpSession for views
HttpSession session = request.getSession(false);
if (session != null || getAllowSessionCreation()) {
request.getSession().setAttribute(SPRING_SECURITY_LAST_USERNAME_KEY, TextEscapeUtils.escapeEntities(username));
}
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authResult) throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
if(successHandler == null){
successHandler = new MySavedRequestAwareAuthenticationSuccessHandler(getDefaultTargetUrl());
}
successHandler.onAuthenticationSuccess(request, response, authResult);
}
public void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
if(failureHandler == null){
failureHandler = new MySimpleUrlAuthenticationFailureHandler(getDefaultFailureUrl());
}
if (logger.isDebugEnabled()) {
logger.debug("Authentication request failed: " + failed.toString());
logger.debug("Updated SecurityContextHolder to contain null Authentication");
logger.debug("Delegating to authentication failure handler" + failureHandler);
}
HttpSession session = request.getSession(false);
if (session != null || allowSessionCreation) {
request.getSession().setAttribute(SPRING_SECURITY_LAST_EXCEPTION_KEY, failed);
}
rememberMeServices.loginFail(request, response);
failureHandler.onAuthenticationFailure(request, response, failed);
}
/**
* Enables subclasses to override the composition of the password, such as by including additional values
* and a separator.
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
* AuthenticationDao
will need to generate the expected password in a corresponding manner.
Authentication
request token to theAuthenticationManager
Authentication
request token to theAuthenticationManager
* Defaults to true 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 getDefaultTargetUrl() {
return defaultTargetUrl;
}
public void setDefaultTargetUrl(String defaultTargetUrl) {
this.defaultTargetUrl = defaultTargetUrl;
}
public String getDefaultFailureUrl() {
return defaultFailureUrl;
}
public void setDefaultFailureUrl(String defaultFailureUrl) {
this.defaultFailureUrl = defaultFailureUrl;
}
}
这里要关注的就是几个字段:
这两个字段是指定验证成功或失败后转向的页面,这里要注意是以“/”开头,否则在AbstractAuthenticationTargetUrlRequestHandler中调用setDefaultTargetUrl方法时会抛出"defaultTarget must start with '/' or with 'http(s)'"的异常!
默认情况下,FORM_LOGIN_FILTER对应的target url和failure url都是通过
3.用户信息获取和验证:
这个指定的authentication-manager是使用默认的ProviderManager,这个manager是在哪里使用的呢?
看看MyUsernamePasswordAuthenticationFilter中的attemptAuthentication方法的最后一行,这里是获取指定的authentication-manager。getAuthenticationManager是从父类AbstractAuthenticationProcessingFilter继承过来的。所以,我们的
这里难免要说明一下,我们的Service是如何被调用的,我们做了配置
指定了我们的UserDetailsService,类实现(为了测试和理解,The easier the better!):
package com.saveworld.userdetails;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public class MyUserDetailsService implements UserDetailsService{
private HashMap
private HashMap
public MyUserDetailsService(){
//Make up a user named 'rod' with 'rod' as his password!
//
String username = "rod";
String password = "1";
boolean enabled = true;
//purview for rod
GrantedAuthority specAuth = new GrantedAuthorityImpl("ROLE_MY");
List
rodsAuthsList.add(specAuth);
// userAuthorities.put("rod", rodsAuthsList);
userDetails.put("rod", new User(username, password, enabled, true, true, true, rodsAuthsList));
}
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
System.out.println("验证从此地过了一遭");
return userDetails.get(username);
}
}
通过DaoAuthenticationProvider中的userDetailsService关联我们的UserDetailsService(不得不提的是,AbstractUserDetailsAuthenticationProvider中有设定了模板函数retrieveUser,DaoAuthenticationProvider进行了实现,通过retrieveUser方法调用UserDetailsService.loadUserByUsername,然后在AbstractUserDetailsAuthenticationProvider.authenticate方法进行验证)。
接下来就是看验证的结果了,是否成功,进入filter chain中。
这一切就这么有条不紊的进行了!呵呵,总算是有点成果了!有了一点点感性的认识了!上面的描述中难免会有些混乱,但是尽量是哪里需要,哪里就做说明!