web-dev-qa-db-ja.com

Spring Security Cookie + JWT認証

私はモデル全体に​​ついて非常に混乱していると言わざるを得ず、すべてのフローティングピースを接着するのに助けが必要です。

Spring RESTではなく、単なるWebMVCコントローラーを実行しています。

私の使命:ユーザー名と認証に合格したフォームログインが必要です。サードパーティのサービスに対して認証したい。成功したら、Cookieを返しますが、デフォルトのCookieトークンメカニズムは使用しません。代わりに、CookieにJWTトークンが必要です。 Cookieメカニズムを活用することにより、すべてのリクエストがJWTで送信されます。

分解するために、次のモジュールを用意します。

  1. ユーザー+ pas logi nを実行するときに、サードパーティサービスに対して認証を行う
  2. 認証が成功したら、Cookieセッショントークンをカスタム実装に置き換えます

  3. リクエストごとにCookieからJWTを解析します(フィルターを使用)

  4. jWTからユーザーの詳細/データを抽出して、コントローラーにアクセスできるようにします

混乱しているのは何ですか? (私が間違っている箇所を修正してください)

サードパーティ認証

サードパーティに対して認証するには、AuthenticationProviderを拡張してカスタムプロバイダーが必要です。

public class JWTTokenAuthenticationProvider implements AuthenticationProvider { 

      @Override
      public Authentication authenticate( Authentication authentication ) throws AuthenticationException {

          // auth against 3rd party

          // return Authentication
          return new UsernamePasswordAuthenticationToken( name, password, new ArrayList<>() );

      }

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

}

質問:

  • ユーザーがフォームユーザー+パスを送信すると、認証/ログインが成功したときにこのプロバイダーが実行されますか?もしそうなら、AbstractAuthenticationProcessingFilter#successfulAuthenticationにどのように関連していますか?
  • usernamePasswordAuthenticationTokenのインスタンスを返す必要がありますか?
  • ここでユーザー+パスを取得するには、UsernamePasswordAuthenticationTokenをサポートする必要がありますか?

CookieトークンをJWTに置き換えます

これを上品に行う方法はわかりませんが、いくつかの方法を考えることができますが、Spring Securityの方法ではなく、フローから抜け出したくありません。ここでの提案に感謝します!

CookieからのすべてのリクエストでJWTを解析します

私が理解していることから、AbstractAuthenticationProcessingFilterをそのように拡張する必要があります

public class CookieAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

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

        String token = "";

        // get token from a Cookie

        // create an instance to Authentication
        TokenAuthentication authentication = new TokenAuthentication(null, null);

        return getAuthenticationManager().authenticate(tokenAuthentication);

    }

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

}

質問:

  • abstractAuthenticationProcessingFilter#successfulAuthenticationはいつ呼び出されますか?ユーザーのログイン時に、またはJWTトークンが正常に検証されたときに呼び出されますか?
  • このフィルターと以前に投稿したカスタムプロバイダーの間に関係はありますか?マネージャーは、サポートメソッドを介してプロバイダーがサポートするものと一致するトークンインスタンスに基づいてカスタムプロバイダーを呼び出すと思われますか?

Cookieセッションの交換を除いて、必要なものはすべて揃っているようですが、単一の一貫したモデルに入れることはできず、メカニズムを十分に理解している人から必要なため、すべてを単一のモジュールに接着できます。

更新1

OK、私はこれがどこから始まっているのかを得ていると思います... https://github.com/spring-projects/spring-security/blob/master/web/src/main/Java/org/springframework/ security/web/authentication/UsernamePasswordAuthenticationFilter.Java

このフィルターは、自身をPOST-> "/ login"に登録し、UsernamePasswordAuthenticationTokenのインスタンスを作成して、次のフィルターに制御を渡します。

質問は、Cookieセッションが設定される場所です。

更新2

DOSのこのセクションは、私が行方不明だったトップレベルのフローを提供します。これを経験している人は誰でもここを見てください... http://docs.spring.io/spring-security/site/docs/ current/reference/htmlsingle /#tech-intro-authentication

AuthenticationProviderに関するこのセクション... http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#core-services-authentication-manager

更新3-作業ケース、これが最良の方法ですか?

そこで、Spring Securityのドキュメントとそのソースを掘り下げた後、最初のモデルが機能するようになりました。今、これを行うと、複数の方法があることに気付きました。この方法を選ぶ理由と、Denysが以下で提案したことについてのアドバイスはありますか?

以下の作業例...

15
Assaf Moldavsky

これを元の投稿で説明した方法で機能させるには、これが起こる必要があります...

カスタムフィルター

public class CookieAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

        public CookieAuthenticationFilter( RequestMatcher requestMatcher ) {

            super( requestMatcher );
            setAuthenticationManager( super.getAuthenticationManager() );

        }

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

            String token = "";

            // get token from a Cookie
            Cookie[] cookies = request.getCookies();

            if( cookies == null || cookies.length < 1 ) {
                throw new AuthenticationServiceException( "Invalid Token" );
            }

            Cookie sessionCookie = null;
            for( Cookie cookie : cookies ) {
                if( ( "someSessionId" ).equals( cookie.getName() ) ) {
                sessionCookie = cookie;
                break;
                }
            }

            // TODO: move the cookie validation into a private method
            if( sessionCookie == null || StringUtils.isEmpty( sessionCookie.getValue() ) ) {
                throw new AuthenticationServiceException( "Invalid Token" );
            }

            JWTAuthenticationToken jwtAuthentication = new JWTAuthenticationToken( sessionCookie.getValue(), null, null );

            return jwtAuthentication;

        }


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

}

認証プロバイダー

usernamePasswordAuthenticationFilterによって生成されるUsernamePasswordAuthenticationTokenにプロバイダーを添付します。UsernamePasswordAuthenticationFilterは、「/ login」POSTに自身を添付します。 POST to "/ login"を含むformloginの場合、UsernamePasswordAuthenticationTokenが生成され、プロバイダーがトリガーされます。

@Component
public class ApiAuthenticationProvider implements AuthenticationProvider {

        @Autowired
        TokenAuthenticationService tokenAuthService;

        @Override
        public Authentication authenticate( Authentication authentication ) throws AuthenticationException {

            String login = authentication.getName();
            String password = authentication.getCredentials().toString();

            // perform API call to auth against a 3rd party

            // get User data
            User user = new User();

            // create a JWT token
            String jwtToken = "some-token-123"

            return new JWTAuthenticationToken( jwtToken, user, new ArrayList<>() );

        }

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

カスタム認証オブジェクト

JWTの場合、スタックに沿って必要なデータを運ぶための独自の認証トークンオブジェクトが必要です。

public class JWTAuthenticationToken extends AbstractAuthenticationToken {

        User principal;
        String token;

        public JWTAuthenticationToken( String token, User principal, Collection<? extends GrantedAuthority> authorities ) {
            super( authorities );
            this.token = token;
            this.principal = principal;
        }

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

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

        public void setToken( String token ) {
            this.token = token;
        }

        public String getToken() {
            return token;
        }
}

認証成功ハンドラー

これは、カスタムプロバイダーがサードパーティに対してユーザーを認証し、JWTトークンを生成することでその役割を果たしたときに呼び出されます。これは、Cookieが応答に入る場所です。

@Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(
                HttpServletRequest request, 
                HttpServletResponse response, 
                Authentication authentication) throws IOException, ServletException {

        if( !(authentication instanceof JWTAuthenticationToken) ) {
            return;
        }

        JWTAuthenticationToken jwtAuthenticaton =    (JWTAuthenticationToken) authentication;

        // Add a session cookie
        Cookie sessionCookie = new Cookie( "someSessionId", jwtAuthenticaton.getToken() );
        response.addCookie( sessionCookie );

        //clearAuthenticationAttributes(request);

        // call the original impl
        super.onAuthenticationSuccess( request, response, authentication );
}

}

これをまとめてフックする

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired @Required
    ApiAuthenticationProvider apiAuthProvider;

    @Autowired @Required
    AuthenticationSuccessHandler authSuccessHandler;

    @Autowired @Required
    SimpleUrlAuthenticationFailureHandler authFailureHandler;

    @Override
    protected void configure( AuthenticationManagerBuilder auth ) throws Exception {
    auth.authenticationProvider( apiAuthProvider );
    }

    @Override
    protected void configure( HttpSecurity httpSecurity ) throws Exception {

            httpSecurity

            // don't create session
            .sessionManagement()
                .sessionCreationPolicy( SessionCreationPolicy.STATELESS )
                .and()

            .authorizeRequests()
                .antMatchers( "/", "/login", "/register" ).permitAll()
                .antMatchers( "/js/**", "/css/**", "/img/**" ).permitAll()
                .anyRequest().authenticated()
                .and()

            // login
            .formLogin()
                .failureHandler( authFailureHandler )
                //.failureUrl( "/login" )
                .loginPage("/login")
                .successHandler( authSuccessHandler )
                        .and()

            // JWT cookie filter
            .addFilterAfter( getCookieAuthenticationFilter(
                    new AndRequestMatcher( new AntPathRequestMatcher( "/account" ) )
            ) , UsernamePasswordAuthenticationFilter.class );
    }


    @Bean
    SimpleUrlAuthenticationFailureHandler getAuthFailureHandler() {

            SimpleUrlAuthenticationFailureHandler handler = new SimpleUrlAuthenticationFailureHandler( "/login" );
            handler.setDefaultFailureUrl( "/login" );
            //handler.setUseForward( true );

            return handler;

    }

    CookieAuthenticationFilter getCookieAuthenticationFilter( RequestMatcher requestMatcher ) {

            CookieAuthenticationFilter filter = new CookieAuthenticationFilter( requestMatcher );
            filter.setAuthenticationFailureHandler( authFailureHandler );
            return filter;
    }
}
11
Assaf Moldavsky

最も簡単な方法は、Spring Sessionをプロジェクトに追加し、 HttpSessionStrategy を拡張することです。これは、セッション作成/破棄イベントに便利なフックを提供し、HttpServletRequestからセッションを抽出するメソッドを備えています。

0
user4872904