web-dev-qa-db-fra.com

Cookie de sécurité Spring + authentification JWT

Je dois dire que je suis très confus à propos de l'ensemble du modèle et j'ai besoin d'aide pour coller toutes les pièces flottantes ensemble.

Je ne fais pas Spring REST, juste des contrôleurs WebMVC simples.

Ma mission: je veux un formulaire de connexion avec un nom d'utilisateur + authentification par passe. Je souhaite m'authentifier auprès d'un service tiers. En cas de succès, je veux retourner un cookie mais PAS utiliser le mécanisme de jeton de cookie par défaut. Je veux que le cookie ait un jeton JWT à la place. En tirant parti du mécanisme des cookies, chaque demande sera envoyée avec le JWT.

Donc, pour le décomposer, j'ai les modules suivants à prendre en charge:

  1. faire l'authentification contre un service tiers lors d'un utilisateur + pas de logi n
  2. remplacer le jeton de session de cookie par mon implémentation personnalisée en cas d'authentification réussie

  3. à chaque demande, analyser le JWT à partir du cookie (en utilisant un filtre)

  4. extraire les détails/données utilisateur du JWT pour être accessible aux contrôleurs

Qu'est-ce qui prête à confusion? (veuillez me corriger là où je me trompe)

Authentification tierce

pour m'authentifier contre un tiers, je devrai avoir un fournisseur personnalisé en étendant 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 );
      }

}

Des questions:

  • ce fournisseur est-il exécuté après une authentification/connexion réussie lorsque l'utilisateur soumet un formulaire utilisateur + passe? si tel est le cas, comment est-ce lié à AbstractAuthenticationProcessingFilter # réussieAuthentication?
  • dois-je retourner une instance de UsernamePasswordAuthenticationToken?
  • dois-je prendre en charge UsernamePasswordAuthenticationToken pour que l'utilisateur + passe ici?

remplacer le jeton de cookie par un JWT

Je ne sais pas comment faire cela avec élégance, je peux penser à un certain nombre de façons, mais pas à la sécurité Spring et je ne veux pas sortir du flux. Serait reconnaissant pour toutes suggestions ici!

analyser le JWT à chaque demande d'un cookie

D'après ce que je comprends, j'ai besoin d'étendre AbstractAuthenticationProcessingFilter comme ça

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

}

Des questions:

  • quand AbstractAuthenticationProcessingFilter # réussieAuthentication est-elle appelée? est-il appelé avec la connexion de l'utilisateur ou lorsque le jeton JWT a été validé avec succès?
  • existe-t-il une relation entre ce filtre et le fournisseur personnalisé que j'ai publié précédemment? Le gestionnaire appellera soi-disant le fournisseur personnalisé en fonction de l'instance de jeton qui correspond à ce que le fournisseur prend en charge via la méthode de support?

Il semble que j'ai toutes les pièces dont j'ai besoin, sauf le remplacement de la session de cookie, mais je ne peux pas les mettre dans un seul modèle cohérent et j'ai besoin de quelqu'un qui comprend assez bien la mécanique pour que je puisse coller tout cela dans un seul module .

MISE À JOUR 1

OK, je pense que j'arrive là où cela commence ... https://github.com/spring-projects/spring-security/blob/master/web/src/main/Java/org/springframework/ security/web/authentication/UsernamePasswordAuthenticationFilter.Java

Ce filtre s'enregistre auprès de POST -> "/ login" et crée une instance de UsernamePasswordAuthenticationToken et passe le contrôle au filtre suivant.

La question est de savoir où la session de cookie est définie ....

MISE À JOUR 2

Cette section du dos donne le flux de niveau supérieur qui me manquait, pour celui qui passe par là, regardez ici ... http://docs.spring.io/spring-security/site/docs/ current/reference/htmlsingle/# tech-intro-authentication

Cette section concernant AuthenticationProvider ... http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#core-services-authentication-manager

MISE À JOUR 3 - cas de travail, est-ce le meilleur moyen ??

Donc, après avoir fouillé dans les documents de Spring Security et leurs sources, j'ai réussi à faire fonctionner le modèle initial. Maintenant, en faisant cela, j'ai réalisé qu'il y avait plus d'une façon de le faire. Un conseil pour savoir pourquoi choisir de cette façon VS ce que Denys a proposé ci-dessous?

Exemple de travail ci-dessous ...

18
Assaf Moldavsky

Pour que cela fonctionne comme décrit dans le message d'origine, voici ce qui doit se produire ...

Filtre personnalisé

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

}

Fournisseur d'authentification

attachez le fournisseur au jeton UsernamePasswordAuthentication qui est généré par UsernamePasswordAuthenticationFilter, qui s'attache au POST "/ login". Pour formlogin avec POST to "/ login" générera UsernamePasswordAuthenticationToken et votre fournisseur sera déclenché

@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 );
        }
}

objet d'authentification personnalisé

Pour le JWT, nous voulons avoir notre propre objet jeton d'authentification pour transporter les données que nous voulons le long de la pile.

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

Gestionnaire de réussite d'authentification

Cela est appelé lorsque notre fournisseur personnalisé a fait son travail en authentifiant l'utilisateur par rapport à un tiers et en générant un jeton JWT.C'est l'endroit où le cookie entre dans la réponse.

@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 );
}

}

Accrocher tout cela ensemble

@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;
    }
}
12
Assaf Moldavsky

La manière la plus simple consiste à ajouter Spring Session dans votre projet et à étendre HttpSessionStrategy qui fournit des hooks pratiques pour les événements créés/détruits de session et dispose d'une méthode pour extraire la session de HttpServletRequest.

0
user4872904