無極,無際,無跡

            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


          (最開始看的時候,把這個表格忽略了,現在看來這些就是我們想要的?。?br /> 我們的驗證過程,就是按照這樣的順序進行的。自上而下進行。

          如果我們要自己定制相應的驗證處理方法(在過濾器里面),我們就可以對照上面的過濾器,覆蓋相應的接口方法。
          根據我的處理過程,主要是用戶名密碼的驗證過程,我大體描述一下自己的配置和處理過程:
          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 閱讀(18831) 評論(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]

          謝謝樓主了  回復  更多評論
            

          主站蜘蛛池模板: 安宁市| 贡觉县| 定西市| 清徐县| 黔江区| 拜泉县| 平利县| 辽阳县| 盈江县| 南京市| 广东省| 南川市| 宜兰市| 乳源| 宣化县| 镇安县| 云浮市| 佛冈县| 临澧县| 黄浦区| 汪清县| 新民市| 合水县| 莆田市| 临潭县| 海南省| 葫芦岛市| 逊克县| 宜君县| 台前县| 怀柔区| 平邑县| 徐水县| 秭归县| 晋州市| 巴楚县| 东乌珠穆沁旗| 桦川县| 周口市| 瑞金市| 东源县|