通过令牌进行Spring Security身份验证

时间:2013-06-15 17:35:38

标签: spring-mvc spring-security

我有一台带有API的服务器。服务器受Spring Security保护。

我希望在请求参数中使用令牌从外部应用程序访问API

首先,用户将转到为其提供令牌的服务,然后使用此令牌访问API。

但我希望通过标准的Spring Security解决方案保留以前对API的访问。

那么,请你帮帮我,我该如何实现呢?

3 个答案:

答案 0 :(得分:9)

您需要像这样实现自定义AuthenticationFilter

public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

  private static final String SECURITY_TOKEN_KEY    = "token";
  private static final String SECURITY_TOKEN_HEADER = "X-Token";
  private String token = null;

  protected CustomAuthenticationFilter() {
    super("/");
  }

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

    this.token = request.getParameter(SECURITY_TOKEN_KEY);
    // or this.token = request.getHeader(SECURITY_TOKEN_HEADER);

    if (request.getAttribute(FILTER_APPLIED) != null) {
      chain.doFilter(request, response);
      return;
    }

    request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

    if(request.getParameter(actionParameter) !=null &&
        request.getParameter(actionParameter).equals("logout")) {
      SecurityContextHolder.clearContext();
      return;
    }

    if (!requiresAuthentication(request, response)) {
      chain.doFilter(request, response);
      return;
    }

    Authentication authResult;
    try {
      authResult = attemptAuthentication(request, response);
      if (authResult == null) {
        return;
      }
    } catch (AuthenticationException failed) {
      unsuccessfulAuthentication(request, response, failed);
      return;
    }

    try {
      successfulAuthentication(request, response, chain, authResult);
    } catch (NestedServletException e) {
      if(e.getCause() instanceof AccessDeniedException) {
        unsuccessfulAuthentication(request, response, new LockedException("Forbidden"));
      }
    }
  }

  @Override
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {

    AbstractAuthenticationToken userAuthenticationToken = authUserByToken(this.token);
    if(userAuthenticationToken == null)
      throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));

    return userAuthenticationToken;
  }

  private AbstractAuthenticationToken authUserByToken(String tokenRaw) {
    AbstractAuthenticationToken authToken = null;
    try {
      // check your input token, identify the user
      // if success create AbstractAuthenticationToken for user to return
      // eg:
      authToken = new UsernamePasswordAuthenticationToken(username, userHash, userAuthorities);

    } catch (Exception e) {
      logger.error("Error during authUserByToken", e);
    }
    return authToken;
  }

  @Override
  protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                          Authentication authResult) throws IOException, ServletException {
    SecurityContextHolder.getContext().setAuthentication(authResult);

    getSuccessHandler().onAuthenticationSuccess(request, response, authResult);
  }

}

和自定义SuccessHandler一样

public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

  @Override
  protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
    return request.getServletPath();
  }

  @Override
  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
    request.getRequestDispatcher(request.getServletPath()).forward(request, response);
  }
}

并在spring config

中连接它
<?xml version="1.0" encoding="UTF-8"?>
<b:beans
    xmlns="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:b="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:sec="http://www.springframework.org/schema/security"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

  <context:annotation-config/>
  <context:component-scan base-package="com.your.path" />

  <aop:aspectj-autoproxy/>

  <global-method-security pre-post-annotations="enabled" secured-annotations="enabled" proxy-target-class="true"
                          access-decision-manager-ref="accessDecisionManager"/>

  <http entry-point-ref="restAuthenticationEntryPoint" use-expressions="true"
        auto-config="true" access-decision-manager-ref="accessDecisionManager">
    <custom-filter ref="restFilter" position="PRE_AUTH_FILTER"/>
    <logout/>
  </http>

  <b:bean id="restAuthenticationEntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>

  <b:bean id="restFilter" class="com.your.path.CustomAuthenticationFilter">
    <b:property name="authenticationSuccessHandler" ref="mySuccessHandler"/>
  </b:bean>

  <b:bean id="mySuccessHandler" class="com.your.path.CustomAuthenticationSuccessHandler"/>

  <b:bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">
    <b:property name="allowIfAllAbstainDecisions" value="true"/>
    <b:property name="decisionVoters">
      <b:list>
        <b:bean class="org.springframework.security.access.vote.RoleVoter">
          <b:property name="rolePrefix" value=""/>
        </b:bean>
        <b:bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
      </b:list>
    </b:property>
  </b:bean>

</b:beans>

这应该有所帮助。

答案 1 :(得分:4)

您可以在spring security TimedKeyBasedPersistenceTokenService

中使用以下bean来执行此操作
<bean name="tokenService" class="com.digipos.security.core.token.TimedKeyBasedPersistenceTokenService">
    <property name="tokenLifeInMinutes" value="15000"/>
    <property name="serverSecret" value="1234567"/>
    <property name="serverInteger" value="15062013"/>
    <property name="pseudoRandomNumberBits" value="7"/>
    <property name="secureRandom" ref="secureRandom"/>
</bean>


<bean name="secureRandom" class="java.security.SecureRandom">
    <property name="seed" value="122"/>
</bean>

除此之外,您还需要

使用PreAuthenticatedAuthenticationProvider

entry-point-ref属性<http>Http403ForbiddenEntryPoint bean

答案 2 :(得分:3)

我找到了一种更简单的方法:

我的解决方案适用于令牌身份验证和表单身份验证,但如果需要,您可以轻松禁用其中一个。

我的过滤器类似于Roman的过滤器,但我不需要检查用户是否可以访问特定资源,也不需要注销 - &gt;传递给springSecurity。

身份验证过滤器:

public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

private static final String SECURITY_TOKEN_KEY = "token";
private static final String SECURITY_TOKEN_HEADER = "X-Token";

public TokenAuthenticationFilter() {

    super( "/" );
}

@Override
public void doFilter( ServletRequest req, ServletResponse res, FilterChain chain ) throws IOException, ServletException {

    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    String token = request.getParameter( SECURITY_TOKEN_KEY );
    // or this.token = request.getHeader(SECURITY_TOKEN_HEADER);

    if ( token != null ) {

        Authentication authResult;
        try {
            authResult = attemptAuthentication( request, response, token );
            if ( authResult == null ) {
                notAuthenticated( request, response, new LockedException( "User Not found" ) );
                return;
            }
        } catch ( AuthenticationException failed ) {
            notAuthenticated( request, response, failed );
            return;
        }

        try {
            successfulAuthentication( request, response, chain, authResult );
            return;
        } catch ( NestedServletException e ) {
            logger.error( e.getMessage( ), e );
            if ( e.getCause( ) instanceof AccessDeniedException ) {
                notAuthenticated( request, response, new LockedException( "Forbidden" ) );
                return;
            }
        }
    }
    chain.doFilter( request, response );// return to others spring security filters
}

public void notAuthenticated( HttpServletRequest request, HttpServletResponse response, AuthenticationException failed ) throws IOException {

    response.sendRedirect( "http://www.google.ro" );
    // unsuccessfulAuthentication( request, response, failed );
}

public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response, String token ) throws AuthenticationException, IOException, ServletException {

    AbstractAuthenticationToken userAuthenticationToken = authUserByToken( token );
    if ( userAuthenticationToken == null )
        throw new AuthenticationServiceException( MessageFormat.format( "Error | {0}", "Bad Token" ) );

    return userAuthenticationToken;
}

private AbstractAuthenticationToken authUserByToken( String tokenRaw ) {

    AbstractAuthenticationToken authToken = null;
    try {
        // check your input token, identify the user
        // if success create AbstractAuthenticationToken for user to return
        // eg:
        // authToken = new UsernamePasswordAuthenticationToken( username, userHash, userAuthorities );
        // authToken = new UsernamePasswordAuthenticationToken( tokenRaw, authToken, )
        logger.info( "token received = " + tokenRaw );
        // obtain user by your methods
        // if ( user != null ) {
        // SecurityUser securityUser = new SecurityUser( user );
        // return new PreAuthenticatedAuthenticationToken( securityUser, securityUser.getPassword( ), securityUser.getAuthorities( ) );
        // }
    } catch ( Exception e ) {
        logger.error( "Error during authUserByToken", e );
    }
    return authToken;
}

@Override
protected void successfulAuthentication( HttpServletRequest request, HttpServletResponse response, Authentication authResult ) throws IOException, ServletException {

    SecurityContextHolder.getContext( ).setAuthentication( authResult );

    new CustomAuthenticationSuccessHandler( ).onAuthenticationSuccess( request, response, authResult );
}

@Override
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException, IOException, ServletException {

    logger.error( "No TOKEN PROVIDED" );
    return null;
    }

}

然后你需要做的就是映射这个过滤器是在springSecurity(addFilterBefore)中配置它,这不必在servlet配置中映射。

        http.authorizeRequests( ).antMatchers( "/login*" ).permitAll( );
        http.authorizeRequests( ).antMatchers( "/register*" ).permitAll( );

        http.authorizeRequests( ).antMatchers( "/admin/**" ).hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER" );//

        http.authorizeRequests( ).and( ).formLogin( )//
                .loginPage( "/login" )//
                .successHandler( successHandler( ) )//
                .failureUrl( "/login?error" ).permitAll( )//
                .and( ).logout( )//
                .logoutUrl( "/logout" ).logoutSuccessUrl( "/login?logout" ).permitAll( )//
                .and( ).rememberMe( ).key( applicationName + "_key" ).tokenValiditySeconds( 2419200 ); // remember me for 2 weeks

        http.addFilterBefore( new TokenAuthenticationFilter( ), AnonymousAuthenticationFilter.class );