无极,无际,无迹

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  3 Posts :: 8 Stories :: 10 Comments :: 0 Trackbacks
研究了好长时间,不知道从哪里下手。新的版本,很多东西在网上找不到,只能看他们的文档,当然这些文档相当不错,就看是否耐心的研究了!总是有急躁的心理作祟,不能专心研读,却处处碰壁,效率上反而未达预期效果!
终于,在无数次的沮丧下,稍微看到了点光亮!前面的文章太过皮毛,接下来的一些,希望能更加实际的,更加深入的分析每一个过程!

一直通过默认配置进行设置:
namespace(是security 3.0,网上也看到一些兄弟描述的是3.0,但是总是不符合我这里的namespace配置):
<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"
           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时,就相当于
  <http>
    <form-login />
    <http-basic />
    <logout />
  </http>
也就是使用了默认的过滤器。
我最开始的想法是能够把本地的login信息(不是调用spring security的login方法),传入到spring security的验证过滤器里面。
这里有一个比较关键的问题,就是封装他们的过滤器(或者仅仅是知道他们到底是哪些过滤器在起作用):
表1
AliasFilter ClassNamespace Element or Attribute
CHANNEL_FILTER ChannelProcessingFilter http/intercept-url@requires-channel
CONCURRENT_SESSION_FILTER ConcurrentSessionFilter session-management/concurrency-control
SECURITY_CONTEXT_FILTER SecurityContextPersistenceFilter http
LOGOUT_FILTER LogoutFilter http/logout
X509_FILTER X509AuthenticationFilter http/x509
PRE_AUTH_FILTER AstractPreAuthenticatedProcessingFilter Subclasses N/A
CAS_FILTER CasAuthenticationFilter N/A
FORM_LOGIN_FILTER UsernamePasswordAuthenticationFilter http/form-login
BASIC_AUTH_FILTER BasicAuthenticationFilter http/http-basic
SERVLET_API_SUPPORT_FILTER SecurityContextHolderAwareFilter http/@servlet-api-provision
REMEMBER_ME_FILTER RememberMeAuthenticationFilter http/remember-me
ANONYMOUS_FILTER AnonymousAuthenticationFilter http/anonymous
SESSION_MANAGEMENT_FILTER SessionManagementFilter session-management
EXCEPTION_TRANSLATION_FILTER ExceptionTranslationFilter http
FILTER_SECURITY_INTERCEPTOR FilterSecurityInterceptor http
SWITCH_USER_FILTER SwitchUserFilter N/A


(最开始看的时候,把这个表格忽略了,现在看来这些就是我们想要的!)
我们的验证过程,就是按照这样的顺序进行的。自上而下进行。

如果我们要自己定制相应的验证处理方法(在过滤器里面),我们就可以对照上面的过滤器,覆盖相应的接口方法。
根据我的处理过程,主要是用户名密码的验证过程,我大体描述一下自己的配置和处理过程:
1.配置namespace的标签:
  <http  use-expressions="true" ><!-- This is not the default value -->
       <custom-filter position="FORM_LOGIN_FILTER" ref="myFilter"/> <!--This is my own filter which just extends AbstractAuthenticationProcessingFilter as what UsernamePasswordAuthenticationFilter does.-->
       <intercept-url pattern="/test/**"  access="hasRole('ROLE_MY')"/><!-- I tested that what is role,and how I can use it.So ROLE_MY is just a role name I defined.-->
       <intercept-url pattern="/login.jsp*" access="permitAll" />
       <logout />
       <anonymous />
       <http-basic />
  </http>
这里的问题是,要定制自己的过滤器,就要通过<custom-filter/>,然后对照 表1 中指定的position,覆盖默认的filter。
2.我的form-login  filter配置(这些配置都是在application-security.xml文件中)为:
<beans:bean id="myFilter"
      class="com.saveworld.authentication.filters.MyUsernamePasswordAuthenticationFilter">
    <beans:property name="defaultTargetUrl"  value="/default.jsp" />
    <beans:property name="defaultFailureUrl"  value="/error.jsp" />
    <beans:property name="authenticationManager" ref="authenticationManager" />
    <beans:property name="filterProcessesUrl" value="/j_spring_security_check" />
    <beans:property name="continueChainBeforeSuccessfulAuthentication" value="false" />
  </beans:bean>

NOTE:
在这里有个问题就是: filter position conflicts!
如果使用这样的配置
<http auto-config='true'>
   <custom-filter position="FORM_LOGIN_FILTER" ref="myFilter"/>
</http>
自定义的filter对应的position是FORM_LOGIN_FILTER
但是因为使用了auto-config='true',所以默认有<form-login />,which is on the position FORM_LOGIN_FILTER!
这时就会出现position conflicts问题了。当然,如果你没有设置auto-config='true',但是却自己设置了<form-login />,呵呵,这个情况就是自己大意了,还是有了position conflicts的异常,所以,好好看看上面的表格是相当必要的,看清楚每个position默认都对应那些namespace,都是对应的哪些filter!

接着:
我的类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.<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);
    }

    /**
     * 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 getDefaultTargetUrl() {
        return defaultTargetUrl;
    }

    public void setDefaultTargetUrl(String defaultTargetUrl) {
        this.defaultTargetUrl = defaultTargetUrl;
    }

    public String getDefaultFailureUrl() {
        return defaultFailureUrl;
    }

    public void setDefaultFailureUrl(String defaultFailureUrl) {
        this.defaultFailureUrl = defaultFailureUrl;
    }
   
   
}

这里要关注的就是几个字段:
    <beans:property name="defaultTargetUrl"  value="/default.jsp" />
    <beans:property name="defaultFailureUrl"  value="/error.jsp" />
这两个字段是指定验证成功或失败后转向的页面,这里要注意是以“/”开头,否则在AbstractAuthenticationTargetUrlRequestHandler中调用setDefaultTargetUrl方法时会抛出"defaultTarget must start with '/' or with 'http(s)'"的异常!
默认情况下,FORM_LOGIN_FILTER对应的target url和failure url都是通过 <form-login />中的default-target-url,authentication-failure-url指定,也可以通过指定authentication-success-handler-ref和authentication-failure-handler-ref来实现认证成功和失败之后的处理方式.在我的filter中,是自定义了两个handler分别对应成功的和失败的验证。

3.用户信息获取和验证:
  <authentication-manager alias="authenticationManager">
    <authentication-provider user-service-ref='myUserDetailsService'/>
  </authentication-manager>
  <beans:bean id="myUserDetailsService" class="com.saveworld.userdetails.MyUserDetailsService"></beans:bean>
这个指定的authentication-manager是使用默认的ProviderManager,这个manager是在哪里使用的呢?
看看MyUsernamePasswordAuthenticationFilter中的attemptAuthentication方法的最后一行,这里是获取指定的authentication-manager。getAuthenticationManager是从父类AbstractAuthenticationProcessingFilter继承过来的。所以,我们的<authentication-manager alias="authenticationManager">中就指定了一个别名authenticationManager,在myfilter中设置属性的引用<beans:property name="authenticationManager" ref="authenticationManager" />,然后我们就可以通过Provider引用我们自己的用户信息验证service了(eg:用户信息获取和验证)!这里实际是使用了Method Template模式(AbstractAuthenticationManager中设定模板函数doAuthentication,ProviderManager中做了实现)。
这里难免要说明一下,我们的Service是如何被调用的,我们做了配置<authentication-provider user-service-ref='myUserDetailsService'/>
指定了我们的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<String, User> userDetails = new HashMap<String, User>();
    private HashMap<String, List<GrantedAuthority>> userAuthorities = new HashMap<String, List<GrantedAuthority>>();
   
    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<GrantedAuthority> rodsAuthsList = new ArrayList<GrantedAuthority>();
        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中。
这一切就这么有条不紊的进行了!呵呵,总算是有点成果了!有了一点点感性的认识了!上面的描述中难免会有些混乱,但是尽量是哪里需要,哪里就做说明!

下篇中说明entrypoint的用处吧!
posted on 2010-01-19 14:19 taochen 阅读(18699) 评论(3)  编辑  收藏 所属分类: java软件架构

Feedback

# re: SpringSecurity使用记录(五)-- 配置 2010-09-01 14:37 杨彩
这是真好文!thankyou  回复  更多评论
  

# re: SpringSecurity使用记录(五)-- 配置 2010-10-08 19:26 南方不下雪
能將完整的配制發表出來嗎  回复  更多评论
  

# re: SpringSecurity使用记录(五)-- 配置 2013-08-21 18:57 ximlos
首先非常感谢楼主把这篇帖子发表出来,让我们一头雾水的人可以有解决问题的思路了,但是我有个地方不太清楚,我把这句粘进去的时候提示有错,是怎么回事呢?需要配置entrypoint吗?
<http use-expressions="true" >
<custom-filter position="FORM_LOGIN_FILTER" ref="myFilter"/>


错误:
Multiple annotations found at this line:
- Method 'setAuthenticationEntryPoint' is marked deprecated [config set: dncr-telco-web/web-context]
- Method 'setSecurityContextRepository' is marked deprecated [config set: dncr-telco-web/web-context]
- Method 'setUserAttribute' is marked deprecated [config set: dncr-telco-web/web-context]
- Method 'setSessionAuthenticationStrategy' is marked deprecated [config set: dncr-telco-web/web-context]
- Method 'setRequestCache' is marked deprecated [config set: dncr-telco-web/web-context]
- No AuthenticationEntryPoint could be established. Please make sure you have a login mechanism configured through the namespace (such
as form-login) or specify a custom AuthenticationEntryPoint with the 'entry-point-ref' attribute
- Method 'setKey' is marked deprecated [config set: dncr-telco-web/web-context]

谢谢楼主了  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: