【问题标题】:Spring Security authentication via URL通过 URL 进行 Spring Security 身份验证
【发布时间】:2013-02-23 20:12:09
【问题描述】:

我有一个 Spring MVC 应用程序,它使用 Spring Security 和基于表单的登录进行授权/身份验证。

现在我想添加一个特殊的 URL,其中包含一个令牌,无需其他信息即可访问该令牌,因为该令牌对用户来说是唯一的:

http://myserver.com/special/5f6be0c0-87d7-11e2-9e96-0800200c9a66/text.pdf

我需要如何配置 Spring Security 以使用该令牌进行用户身份验证?

【问题讨论】:

  • 您想基于此令牌以编程方式验证您的用户吗?
  • 是的,我想使用自定义 UserDetailsS​​ervice 通过数据库中的此令牌加载用户,并像他使用登录名/密码登录一样对他进行完全身份验证
  • 基于 URL 对用户进行完全身份验证并不是一个好主意。如果您只是使用它来提供对该文件的访问,那可能就足够公平了,但如果它允许他们做其他通常只能通过完整登录才能做的事情,我会小心。

标签: spring spring-mvc spring-security


【解决方案1】:

您需要定义您的自定义预授权过滤器。

http 标签内的安全应用上下文中:

<custom-filter position="PRE_AUTH_FILTER" ref="preAuthTokenFilter" />

然后定义您的过滤器 bean(及其适当的属性):

<beans:bean class="com.yourcompany.PreAuthTokenFilter"
      id="preAuthTokenFilter">
    <beans:property name="authenticationDetailsSource" ref="authenticationDetailsSource" />
    <beans:property name="authenticationManager" ref="authenticationManager" />
    <beans:property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
</beans:bean>

创建从 GenericFilterBean 扩展的自定义过滤器

public class PreAuthTokenFilter extends GenericFilterBean {

private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationManager authenticationManager;
private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();

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

    String token = getTokenFromHeader(request);//your method

    if (StringUtils.isNotEmpty(token)) {
        /* get user entity from DB by token, retrieve its username and password*/

        if (isUserTokenValid(/* some args */)) {
            try {
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
                authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
                Authentication authResult = this.authenticationManager.authenticate(authRequest);
                SecurityContextHolder.getContext().setAuthentication(authResult);
            } catch (AuthenticationException e) {
            }
        }
    }

    chain.doFilter(request, response);
}

/*
other methods
*/

如果您不想或无法检索密码,您需要创建自己的AbstractAuthenticationToken,它将只接收用户名作为参数(主体)并使用它而不是UsernamePasswordAuthenticationToken

public class PreAuthToken extends AbstractAuthenticationToken {

    private final Object principal;

    public PreAuthToken(Object principal) {
        super(null);
        super.setAuthenticated(true);
        this.principal = principal;
    }

    @Override
    public Object getCredentials() {
        return "";
    }

    @Override
    public Object getPrincipal() {
        return principal;
    }
}

【讨论】:

    【解决方案2】:

    您可以提供自定义 PreAuthenticatedProcessingFilter 和 PreAuthenticatedAuthenticationProvider。有关详细信息,请参阅Pre-Authentication Scenarios 章节。

    【讨论】:

      【解决方案3】:

      我遇到了这个问题,并使用 Spring Security RembereMe 服务基础架构的自定义实现解决了这个问题。这是您需要做的。

      • 定义你自己的身份验证对象

        公共类 LinkAuthentication 扩展 AbstractAuthenticationToken { @覆盖 公共对象 getCredentials() { 返回空值; }

        @Override
        public Object getPrincipal()
        {
        
            return the prncipal that that is passed in via the constructor 
        }
        

        }

      定义

      public class LinkRememberMeService implements RememberMeServices, LogoutHandler
      {    
          /**
           * It might appear that once this method is called and returns an authentication object, that authentication should be finished and the
           * request should proceed. However, spring security does not work that way.
           * 
           * Once this method returns a non null authentication object, spring security still wants to run it through its authentication provider
           * which, is totally brain dead on the part of Spring this, is why there is also a
           * LinkAuthenticationProvider
           * 
           */
          @Override
          public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response)
          {
              String accessUrl = ServletUtils.getApplicationUrl(request, "/special/");
              String requestUrl = request.getRequestURL().toString();
              if (requestUrl.startsWith(accessUrl))
              {
                  // take appart the url extract the token, find the user details object 
                          // and return it. 
                  LinkAuthentication linkAuthentication = new LinkAuthentication(userDetailsInstance);
                  return linkAuthentication;
              } else
              {
                  return null;
              }
          }
      
          @Override
          public void loginFail(HttpServletRequest request, HttpServletResponse response)
          {
          }
      
          @Override
          public void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication)
          {
          }
      
          @Override
          public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
          {
          }
      }
      
      
      public class LinkAuthenticationProvider implements AuthenticationProvider
      {
      
          @Override
          public Authentication authenticate(Authentication authentication) throws AuthenticationException
          {
              // Spring Security is totally brain dead and over engineered
              return authentication;
          }
      
          @Override
          public boolean supports(Class<?> authentication)
          {
              return LinkAuthentication.class.isAssignableFrom(authentication);
          }
      
      }
      

      整理其余的 spring security xml 以定义自定义身份验证提供程序和自定义记住我服务。

      附:如果您在 URL 中对 GUID 进行 base64 编码,它将缩短几个字符。您可以使用 Apache commons codec base64 二进制编码器/解码器来做更安全的 url 链接。

      public static String toBase64Url(UUID uuid)
      {
          return Base64.encodeBase64URLSafeString(toBytes(uuid));
      }
      

      【讨论】:

        猜你喜欢
        • 2012-03-06
        • 2013-06-12
        • 1970-01-01
        • 2019-12-26
        • 2014-02-01
        • 2016-09-13
        • 2020-02-16
        • 2015-06-29
        • 2012-08-06
        相关资源
        最近更新 更多