2014-02-24 151 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'来分享它,但是我没有看到很多的价值,因为它们是具有不同认证需求的不同资源。 –