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

查看:63
本文介绍了如何使用 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 分为两类:

  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 实现可能如下所示:

    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;
        ...
      }
    }
    

    Spring Security 配置针对不同 URL 的不同认证方式

    <小时>

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

    <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>
    

    注意以下几点:

    1. AuthenticationManager 不需要为 http 元素明确指定,因为配置中只有一个,它的标识符是 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天全站免登陆