2014-02-24 149 views
41

我想創建一個主要提供使用Spring的REST API的webapp,並且正在嘗試配置安全性方面。春季安全 - 基於令牌的API認證和用戶/密碼認證

我想實現這種模式:https://developers.google.com/accounts/docs/MobileApps(谷歌已經完全改變了該網頁,所以不再有意義 - 看,我指的是這裏的頁面:http://web.archive.org/web/20130822184827/https://developers.google.com/accounts/docs/MobileApps

這是我需要accompish:

  • Web App有與通常彈簧用戶名/密碼認證工作的簡單登錄/註冊表單
  • REST API(與DAO /的AuthenticationManager/UserDetailsS​​ervice的等做過這種類型的東西)無狀態會話的端點和e非常請求基於與請求一起提供的令牌進行驗證

(例如,用戶登錄/簽約使用正常的形式,web應用程序提供的令牌安全cookie,然後可以在下面的API請求中使用)

我有如下一個正常的身份驗證設置:

@Override protected void configure(HttpSecurity http) throws Exception { 
    http 
     .csrf() 
      .disable() 
     .authorizeRequests() 
      .antMatchers("/resources/**").permitAll() 
      .antMatchers("/mobile/app/sign-up").permitAll() 
      .antMatchers("/v1/**").permitAll() 
      .anyRequest().authenticated() 
      .and() 
     .formLogin() 
      .loginPage("/") 
      .loginProcessingUrl("/loginprocess") 
      .failureUrl("/?loginFailure=true") 
      .permitAll(); 
} 

我想加入一個pre-auth過濾器,它檢查請求中的令牌,然後設置安全上下文(這是否意味着正常的後續認證會被跳過?),但是,超出正常用戶/密碼我沒有做太多基於令牌的安全性,但基於其他一些例子,我想出了以下內容:

Security Con圖:

@Override protected void configure(HttpSecurity http) throws Exception { 
     http 
      .csrf() 
       .disable() 
      .addFilter(restAuthenticationFilter()) 
       .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() 
       .exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint()).and() 
       .antMatcher("/v1/**") 
      .authorizeRequests() 
       .antMatchers("/resources/**").permitAll() 
       .antMatchers("/mobile/app/sign-up").permitAll() 
       .antMatchers("/v1/**").permitAll() 
       .anyRequest().authenticated() 
       .and() 
      .formLogin() 
       .loginPage("/") 
       .loginProcessingUrl("/loginprocess") 
       .failureUrl("/?loginFailure=true") 
       .permitAll(); 
    } 

我自其他過濾器:

public class RestAuthenticationFilter extends AbstractAuthenticationProcessingFilter { 

    public RestAuthenticationFilter(String defaultFilterProcessesUrl) { 
     super(defaultFilterProcessesUrl); 
    } 

    private final String HEADER_SECURITY_TOKEN = "X-Token"; 
    private String token = ""; 


    @Override 
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { 
     HttpServletRequest request = (HttpServletRequest) req; 
     HttpServletResponse response = (HttpServletResponse) res; 

     this.token = request.getHeader(HEADER_SECURITY_TOKEN); 

     //If we have already applied this filter - not sure how that would happen? - then just continue chain 
     if (request.getAttribute(FILTER_APPLIED) != null) { 
      chain.doFilter(request, response); 
      return; 
     } 

     //Now mark request as completing this filter 
     request.setAttribute(FILTER_APPLIED, Boolean.TRUE); 

     //Attempt to authenticate 
     Authentication authResult; 
     authResult = attemptAuthentication(request, response); 
     if (authResult == null) { 
      unsuccessfulAuthentication(request, response, new LockedException("Forbidden")); 
     } else { 
      successfulAuthentication(request, response, chain, authResult); 
     } 
    } 

    /** 
    * Attempt to authenticate request - basically just pass over to another method to authenticate request headers 
    */ 
    @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { 
     AbstractAuthenticationToken userAuthenticationToken = authUserByToken(); 
     if(userAuthenticationToken == null) throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token")); 
     return userAuthenticationToken; 
    } 


    /** 
    * authenticate the user based on token, mobile app secret & user agent 
    * @return 
    */ 
    private AbstractAuthenticationToken authUserByToken() { 
     AbstractAuthenticationToken authToken = null; 
     try { 
      // TODO - just return null - always fail auth just to test spring setup ok 
      return null; 
     } catch (Exception e) { 
      logger.error("Authenticate user by token error: ", e); 
     } 
     return authToken; 
    } 

上面實際上導致錯誤應用程序啓動時說:authenticationManager must be specified 誰能告訴我如何更好地做到這一點 - 是pre_auth過濾器最好的方法來做到這一點?


編輯

我寫了什麼,我發現,我如何與Spring的安全性(包括代碼)實現標準的token(沒有的OAuth)

Overview of the problem and approach/solution

做到了

Implementing the solution with Spring-security

希望它可以幫助別人..

+0

我會在自定義實現上推薦[Spring Security OAuth(2)](http://projects.spring.io/spring-security-oauth/)。恕我直言,我會盡量避免實施自定義解決方案。大多數時候它很容易出錯並且不安全。 特別是如果您使用的是Spring MVC,則可以將Spring Security和Spring Security OAuth(2) 視爲基於令牌的身份驗證流程的有效替代方案。 –

+0

我原本計劃使用OAuth2來實現安全性 - 但是質疑API計劃只被我正在構建的應用程序使用(例如沒有其他計劃的客戶端/消費者等),然後我看到上面的鏈接:https ://developers.google.com/accounts/docs/MobileApps與Google推薦了上述方法,另外對於單個客戶我不知道OAuth2是否會過度殺傷。看到我關於安全性的以前的問題:http://stackoverflow.com/q/21461223/258813 – rhinds

+0

我也看着這樣的實現:http://www.thebuzzmedia.com/designing-a-secure-rest-api- without-oauth-authentication/- 但這非常接近雙腿OAuth 1模式 – rhinds

回答

7

我相信你提到的錯誤只是因爲你正在使用的基類AbstractAuthenticationProcessingFilter需要AuthenticationManager。如果你不打算使用它,你可以將它設置爲no-op,或直接執行Filter。如果您的Filter可以驗證請求並設置SecurityContext,那麼通常會忽略下游處理(這取決於下游過濾器的實現,但我沒有在您的應用中看到任何奇怪的東西,因此它們可能都表現得如此)。

如果我是你,我可能會考慮把API端點放在一個完全獨立的過濾器鏈中(另一個WebSecurityConfigurerAdapter bean)。但這隻會讓事情變得更容易閱讀,而不一定非常重要。

您可能會發現(正如評論中所建議的那樣)您最終重新發明了方向盤,但在嘗試中沒有傷害,您可能會在此過程中瞭解更多有關Spring和Security的知識。

附加:github approach非常有趣:用戶只需在基本身份驗證中使用令牌作爲密碼,並且服務器不需要自定義過濾器(BasicAuthenticationFilter很好)。

+0

太好了 - 謝謝。我真的沒有想過有多個配置,但這是一個好主意。是否有任何理由我不能在兩個單獨的文件中配置API/webapp配置,每個文件都定義了自己的URL以進行截取以及他們自己的AuthenticationProvider?你認爲這是否合理? – rhinds

+1

它必須是2個類(2個'WebSecurityConfigurerAdapter'實例),所以它可能是我猜想的2個文件。 'AuthenticationProvider'不一定合適(YMMV),但每一個都需要一個'AuthenticationManager'。我想用'ProviderManager'來分享它,但是我沒有看到很多的價值,因爲它們是具有不同認證需求的不同資源。 –