如何使用Spring Security提供多种身份验证方式 [英] How to provide multiple ways of authentication with Spring Security

查看:975
本文介绍了如何使用Spring Security提供多种身份验证方式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我从各种资源中了解了Spring Security,我知道过滤器和身份验证管理器是如何分别工作的,但是我不确定请求与它们一起工作的确切顺序.如果我没看错,总之,请求首先通过过滤器,然后过滤器调用它们各自的身份验证管理器.

I have about Spring Security from various resources and I know how filters and authentication managers work separately but I am not sure of the exact sequence in which a request works with them. If I am not wrong, in short the request first goes through the filters and the filters call their respective authentication managers.

我想允许两种身份验证-一种使用JWT令牌,另一种使用用户名和密码.以下是 security.xml

I want to allow two kinds of authentication - one with JWT tokens and another with username and password. Below is the extract from security.xml

Security.xml

<http pattern="/api/**" create-session="stateless" realm="protected-apis" authentication-manager-ref="myAuthenticationManager" >
        <csrf disabled="true"/>
        <http-basic entry-point-ref="apiEntryPoint" />
        <intercept-url pattern="/api/my_api/**" requires-channel="any" access="isAuthenticated()" />  <!-- make https only. -->
        <custom-filter ref="authenticationTokenProcessingFilter" position = "FORM_LOGIN_FILTER"/>
</http>

<beans:bean id="authenticationTokenProcessingFilter"
                class="security.authentication.TokenAuthenticationFilter">
    <beans:constructor-arg value="/api/my_api/**" type="java.lang.String"/>
</beans:bean>

<authentication-manager id="myAuthenticationManager">
    <authentication-provider ref="myAuthenticationProvider" />
</authentication-manager>   

<beans:bean id="myAuthenticationProvider"
                class="security.authentication.myAuthenticationProvider" />

MyAuthenticationProvider.java

public class MyAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication)
                                      throws AuthenticationException {
        // Code
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    }
}

TokenAuthenticationFilter.java

public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter{
    protected TokenAuthenticationFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl); //defaultFilterProcessesUrl - specified in applicationContext.xml.
        super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl)); //Authentication will only be initiated for the request url matching this pattern
        setAuthenticationManager(new NoOpAuthenticationManager());
        setAuthenticationSuccessHandler(new TokenSimpleUrlAuthenticationSuccessHandler());
        setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
    }

    /**
     * Attempt to authenticate request
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException,
                                 IOException,
                                 ServletException {
        String tid = request.getHeader("authorization");
        logger.info("token found:"+tid);
        AbstractAuthenticationToken userAuthenticationToken = authUserByToken(tid,request);
        if(userAuthenticationToken == null) throw new AuthenticationServiceException("Invalid Token");
        return userAuthenticationToken;
    }

    /**
     * authenticate the user based on token
     * @return
     */
    private AbstractAuthenticationToken authUserByToken(String token,HttpServletRequest request) throws
                                                                                               JsonProcessingException {
        if(token==null) return null;

        AbstractAuthenticationToken authToken =null;

        boolean isValidToken = validate(token);
        if(isValidToken){
            List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
            authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
            authToken = new UsernamePasswordAuthenticationToken("", token, authorities);
        }
        else{
            BaseError error = new BaseError(401, "UNAUNTHORIZED");
            throw new AuthenticationServiceException(error.getStatusMessage());

        }
        return authToken;
    }

    private boolean validate(String token) {
        if(token.startsWith("TOKEN ")) return true;
        return false;
    }


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

通过myAuthenticationProvider我想要基于用户名密码的身份验证&通过自定义过滤器,我想检查JWT令牌.有人可以让我知道我是否朝着正确的方向前进吗?

Through myAuthenticationProvider I want username-password based authentication & through the custom filter I want to check for JWT tokens. Can someone let me know if I am going in the right direction?

推荐答案

解决方案概述


从广义上讲,具有多个AuthenticationProvider的要求分为两类:

Solution overview


Broadly speaking, the requirement to have multiple AuthenticationProviders fall into two categories:

  1. 使用不同的身份验证模式来身份验证对不同类型URL的请求,例如:
  1. Authenticate requests for different types of URLs with different authentication modes, for example:
  1. 使用基于表单的用户名密码身份验证来验证所有对/web/**的请求;
  2. 使用基于令牌的身份验证对/api/**的所有请求进行身份验证.
  1. Authenticate all requests for /web/** using form-based username-password authentication;
  2. Authenticate all requests for /api/** using token-based authentication.

  • 使用多种受支持的身份验证模式之一对所有请求进行身份验证.
  • 每种解决方案都略有不同,但是它们基于共同的基础.

    The solutions are slightly different for each, but they are based on a common foundation.

    Spring Security对基于表单的用户名-密码身份验证提供了开箱即用的支持,因此,无论以上两个类别,都可以轻松实现.

    Spring Security has out-of-the-box support for form-based username-password authentication, so regardless of the two categories above, this can be implemented quite easily.

    基于令牌的身份验证,因此需要自定义代码才能添加必要的支持.要添加此支持,需要以下组件:

    Token-based authentication however, is not supported out-of-the-box, so custom code is required to add the necessary support. The following components are required to add this support:

    1. 一个扩展了AbstractAuthenticationToken的POJO,它将保存用于身份验证的令牌.
    2. 扩展了AbstractAuthenticationProcessingFilter的过滤器,该过滤器将从请求中提取令牌值并填充在上面的步骤1中创建的POJO.
    3. 一个AuthenticationProvider实现,将使用令牌对请求进行身份验证.
    4. 上面的选项1或2的Spring Security配置,具体取决于需求.
    1. A POJO extending AbstractAuthenticationToken that will hold the token to use for authentication.
    2. A filter extending AbstractAuthenticationProcessingFilter that will extract the token value from the request and populate the POJO created on step 1 above.
    3. An AuthenticationProvider implementation that will authenticate requests using the token.
    4. Spring Security configuration for option 1 or 2 above, depending on the requirement.

    AbstractAuthenticationToken


    需要一个POJO来保存应用于认证请求的JWT令牌,因此,最简单的AbstractAuthenticationToken实现可能类似于:

    AbstractAuthenticationToken


    A POJO is required to hold the JWT token that should be used for authenticating the request, therefore, the simplest AbstractAuthenticationToken implementation could look like:

    public JWTAuthenticationToken extends AbstractAuthenticationToken {
      private final String token;
    
      JWTAuthenticationToken(final String token, final Object details) {
        super(new ArrayList<>());
    
        this.token = token;
    
        setAuthenticated(false);
        setDetails(details);
      }
    
      @Override
      public Object getCredentials() { return null; }
    
      @Override
      public String getPrincipal() { return token; }
    }
    

    AbstractAuthenticationProcessingFilter


    需要一个过滤器才能从请求中提取令牌.

    AbstractAuthenticationProcessingFilter


    A filter is required to extract the token from the request.

    public class JWTTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
      public JWTTokenAuthenticationFilter (String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
      }
    
      @Override
      public Authentication attemptAuthentication(final HttpServletRequest request
      , final HttpServletResponse response)
      throws AuthenticationException {
        final JWTAuthenticationToken token = new JWTAuthenticationToken(/* Get token from request */
        , authenticationDetailsSource.buildDetails(request));
    
        return getAuthenticationManager().authenticate(token);
      }
    }
    

    请注意,过滤器不会尝试执行身份验证;而是将实际的身份验证委派给AuthenticationManager,以确保也可以正确执行任何身份验证之前和之后的步骤.

    Notice that the filter does not attempt to perform the authentication; rather, it delegates the actual authentication to an AuthenticationManager, which ensures that any pre and post authentication steps are also performed correctly.

    AuthenticationProvider是负责执行身份验证的实际组件.如果配置正确,它会由AuthenticationManager自动调用.一个简单的实现如下所示:

    The AuthenticationProvider is the actual component responsible for performing the authentication. It is called by the AuthenticationManager automatically, if configured properly. A simple implementation would look like:

    public class JWTAuthenticationProvider implements AuthenticationProvider {
      @Override
      public boolean supports(final Class<?> authentication) {
        return (JWTAuthenticationToken.class.isAssignableFrom(authentication));
      }
    
      @Override
      public Authentication authenticate(final Authentication authentication)
             throws AuthenticationException {
        final JWTAuthenticationToken token = (JWTAuthenticationToken) authentication;
        ...
      }
    }
    

    针对不同URL的不同身份验证模式的Spring Security配置


    为每个URL系列使用不同的http元素,例如:

    Spring Security configuration for different authentication modes for different URLs


    Use different http elements for each of the URL families, such as:

    <bean class="com.domain.path.to.provider.FormAuthenticationProvider" "formAuthenticationProvider" />
    <bean class="com.domain.path.to.provider.JWTAuthenticationProvider" "jwtAuthenticationProvider" />
    
    <authentication-manager id="apiAuthenticationManager">
      <authentication-provider ref="jwtAuthenticationProvider" />
    </authentication-manager>
    
    <authentication-manager id="formAuthenticationManager">
      <authentication-provider ref="formAuthenticationProvider" />
    </authentication-manager>
    
    <bean class="com.domain.path.to.filter.JWTAuthenticationFilter" id="jwtAuthenticationFilter">
      <property name="authenticationManager" ref="apiAuthenticationManager" />
    </bean>
    
    <http pattern="/api/**" authentication-manager-red="apiAuthenticationManager">
      <security:custom-filter position="FORM_LOGIN_FILTER" ref="jwtAuthenticationFilter"/>
    
      ...
    </http>
    
    <http pattern="/web/**" authentication-manager-red="formAuthenticationManager">
      ...
    </http>
    

    由于不同的URL家族要求使用不同的身份验证模式,因此我们需要两个不同的AuthenticationManager和两个不同的http配置,每个URL家族一个.对于每一种,我们选择支持哪种身份验证模式.

    Since different authentication modes are required for different URL families, we need two different AuthenticationManagers and two different http configurations, one for each of the URL families. For each, we choose which mode of authentication is supported.

    使用单个http元素,如下所示:

    Use a single http element, as follows:

    <bean class="com.domain.path.to.provider.FormAuthenticationProvider" "formAuthenticationProvider" />
    <bean class="com.domain.path.to.provider.JWTAuthenticationProvider" "jwtAuthenticationProvider" />
    
    <authentication-manager id="authenticationManager">
      <authentication-provider ref="formAuthenticationProvider" />
      <authentication-provider ref="jwtAuthenticationProvider" />
    </authentication-manager>
    
    <bean class="com.domain.path.to.filter.JWTAuthenticationFilter" id="jwtAuthenticationFilter">
      <property name="authenticationManager" ref="authenticationManager" />
    </bean>
    
    <http pattern="/**">
      <security:custom-filter after="FORM_LOGIN_FILTER" ref="jwtAuthenticationFilter"/>
    
      ...
    </http>
    

    请注意以下几点:

      无需为http元素明确指定
    1. AuthenticationManager,因为配置中只有一个,并且其标识符为authenticationManager,这是默认值.
    2. 将令牌过滤器插入表单登录过滤器后 ,而不是替换它.这样可以确保表单登录和令牌登录都能正常工作.
    3. AuthenticationManager被配置为使用多个AuthenticationProvider.这样可以确保尝试多种身份验证机制,直到找到一种支持该请求的机制为止.
    1. AuthenticationManager need not be explicitly specified for the http element as there is only one in the configuration and its identifier is authenticationManager, which is the default value.
    2. The token filter is inserted after the form login filter, instead of replacing it. This ensures that both form login and token login will work.
    3. The AuthenticationManager is configured to use multiple AuthenticationProviders. This ensures that multiple authentication mechanisms are tried, until one is found that is supported for a request.

    这篇关于如何使用Spring Security提供多种身份验证方式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆