mécanismes d'authentification multiples dans une seule application utilisant java config

actuellement, j'ai un seul mécanisme d'authentification dans mon application qui est D'utiliser LDAP pour l'authentification et l'autorisation. Ma configuration de sécurité ressemble à ceci

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable()
        .authorizeRequests()
            .anyRequest().fullyAuthenticated()
            .and()
            .httpBasic();
}

@Configuration
protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {

    @Value("${ldap-${env}.manager.dn}")
    private String managerDn;

    @Value("${ldap-${env}.manager.pass}")
    private String managerPass;

    @Value("${ldap-${env}.server.url}")
    private String url;

    @Value("${ldap.password.attribute:userPassword}")
    private String passwordAttr;

    @Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth.ldapAuthentication().userDnPatterns("uid={0},ou=people").groupSearchBase("ou=groups")
                .groupSearchFilter("(member={0})").userSearchBase("ou=people").userSearchFilter("(uid={0})")
                .userDetailsContextMapper(new CustomLdapPersonContextMapper())
                // .passwordCompare()
                // .passwordAttribute(passwordAttr)
                // .passwordEncoder(new PlaintextPasswordEncoder())
                // .and()
                .contextSource().managerDn(managerDn).managerPassword(managerPass).url(url);
    }
}
}

Il y a cependant des situations où les utilisateurs peuvent venir avec un token de session qui peut s'authentifier à partir d'un serveur clé de session et un token valide renvoie un nom d'utilisateur qui peut ensuite être utilisé pour charger des informations d'authentification à partir de LDAP pour cet utilisateur. Donc, mon deuxième mécanisme d'authentification devrait se produit d'abord lorsque Si un token de session est présent dans les en-têtes http, il doit effectuer l'authentification du token, puis la recherche ldap et si aucun token de session n'est présent, il doit tomber dans le mécanisme d'authentification actuel. Comment puis-je ajouter cette deuxième couche d'authentification.

17
demandé sur Alexander Arutinyants 2014-09-11 22:31:26

4 réponses

j'ai passé pas mal de temps à me concentrer sur la sécurité des sources en utilisant la configuration java pure. Il y a quelques étapes à franchir pour que cela fonctionne. Ce devrait être quelque chose dans ce sens. Le processus de base est la suivante:

  • créer des filtres personnalisés pour vérifier les demandes d'informations d'autorisation spécifiques

  • chaque filtre renvoie null (si aucune autorisation de ce type n'est trouvée), ou une AbstractAuthenticationToken

  • si un filtre renvoie un token, chaque méthode de support(classe) D'AuthenticationProvider sera invoquée avec ce token retournant true|false si elle doit essayer l'authentification

  • tentative d'authentification sera alors appelée Sur le fournisseur D'authentification qui supporte le token. Ici vous effectuez des appels de service pour authentifier l'utilisateur. Vous pouvez alors lancer LoginException ou l'authentification d'appel.setathenticated (true) et retourner le jeton pour une authentification réussie.

j'utilise cette configuration depuis un certain temps en supportant différentes méthodes d'authentification (demande signée, nom d'utilisateur/mot de passe, etc.) et cela fonctionne assez bien.

vous pouvez également passer les Authentifications Successhandler's et Authenticationfailuershandler's aux filtres de sécurité personnalisés pour fournir des stratégies de redirection personnalisées et la gestion des échecs.

assurez-vous également de mettre en place la fourmi matchers dans les constructeurs du filtre pour contrôler les motifs d'url que les filtres appliquent aussi. Par exemple, un filtre de requête ldap devrait probablement être vérifié avec n'importe quelle requête "/*" alors qu'un filtre de nom d'utilisateur/mot de passe peut simplement être vérifié sur POST's to /login ou quelque chose de similaire.

Exemple De Code:

1) Créez des authentifications personnalisées pour chaque type d'authentification que vous voulez prendre en charge

public class LDAPAuthorizationToken extends AbstractAuthenticationToken {
    private String token;

    public LDAPAuthorizationToken( String token ) {
        super( null );
        this.token = token;
    }

    public Object getCredentials() {
        return token;
    }

    public Object getPrincipal() {
        return null;
    }
}

public class OTPAuthorizationToken extends UsernamePasswordAuthenticationToken {
    private String otp;

    public OTPAuthorizationToken( String username, String password, String otp ) {
        super( username, password );
        this.otp = otp;
    }

    public String getOTP() {
        return otp;
    }
}

2) Créer des filtres de sécurité personnalisés pour chaque type

public class LDAPAuthorizationFilter extends AbstractAuthenticationProcessingFilter {
    @Autowired
    private UserDetailsService userDetailsService;

    public LDAPAuthorizationFilter() {
        super( "/*" ); // allow any request to contain an authorization header
    }

    public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException
    {

        if ( request.getHeader( "Authorization" ) == null ) {
            return null; // no header found, continue on to other security filters
        }

        // return a new authentication token to be processed by the authentication provider
        return new LDAPAuthorizationToken( request.getHeader( "Authorization" ) );
    }
}

public class OTPAuthorizationFilter extends AbstractAuthenticationProcessingFilter {
    @Autowired
    private UserDetailsService userDetailsService;

    public OTPAuthorizationFilter() {
        super( "/otp_login" );
    }

    public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException
    {

        if ( request.getParameter( "username" ) == null || request.getParameter( "password" ) == null || request.getParameter( "otp" ) == null ) {
            return null;
        }

        // return a new authentication token to be processed by the authentication provider
        return new OTPAuthorizationToken( request.getParameter( "username" ), request.getParameter( "password" ), request.getParameter( "otp" ) );
    }
}

3) Créer des fournisseurs d'authentification sur mesure

public class LDAPAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private MyAuthenticationService sampleService;

    @Override
    public Authentication authenticate( Authentication authentication ) throws AuthenticationException {
        LDAPAuthorizationToken auth = (LDAPAuthorizationToken)authentication;

        String username = sampleService.verifyToken( auth.getCredentials() );
        if ( username == null ) {
            throw new LoginException( "Invalid Token" );
        }

        auth.setAuthenticated( true );

        return auth;
    }

    @Override
    public boolean supports( Class<?> authentication ) {
        if ( authentication.isAssignableFrom( LDAPAuthorizationToken.class ) ) {
            return true;
        }
        return false;
    }
}

public class OTPAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private MyAuthenticationService sampleService;

    @Override
    public Authentication authenticate( Authentication authentication ) throws AuthenticationException {
        OTPAuthorizationToken auth = (OTPAuthorizationToken)authentication;

        String error = sampleService.loginWithOTP( auth.getPrincipal(), auth.getCredentials(), auth.getOTP() );
        if ( error != null ) {
            throw new LoginException( error );
        }

        auth.setAuthenticated( true );

        return auth;
    }

    @Override
    public boolean supports( Class<?> authentication ) {
        if ( authentication.isAssignableFrom( OTPAuthorizationToken.class ) ) {
            return true;
        }
        return false;
    }
}

4) Configurer la sécurité du printemps

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure( HttpSecurity http ) throws Exception {
        // configure filters
        http.addFilterBefore( new LDAPAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class );
        http.addFilterBefore( new OTPAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class );

        // configure authentication providers
        http.authenticationProvider( new LDAPAuthenticationProvider() );
        http.authenticationProvider( new OTPAuthenticationProvider() );

        // disable csrf
        http.csrf().disable();

        // setup security
        http.authorizeRequests()
            .anyRequest()
                .fullyAuthenticated()
                .and().httpBasic();
    }
}

j'Espère que ça aide!

36
répondu Matt MacLean 2017-09-29 07:33:19

autre, option pour ajouter un second fournisseur d'authentification: spécifiez simplement un autre sur le AuthenticationManagerBuilder. Parce que @EnableWebSecurity l'annotation est elle-même annotée de EnableGlobalAuthentication vous pouvez configurer l'instance globale de AuthenticationManagerBuilder. (Voir javadoc pour plus de détails.)

Par exemple, ici nous avons un fournisseur d'authentification LDAP ainsi qu'une mémoire (codé en dur) fournisseur d'authentification (c'est quelque chose que nous faisons dans le développement local aux utilisateurs de tester avec l'):

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {

      @Value("${user.role}")
      private String userRole; // i.e. ROLE_APP_USER

      @Value("${include.test.users}")
      private boolean includeTestUsers;

      @Override
      protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
          .antMatchers("/**/js/**").permitAll()
          .antMatchers("/**/images/**").permitAll()
          .antMatchers("/**/favicon.ico").permitAll()
          .antMatchers("/**/css/**").permitAll()
          .antMatchers("/**/fonts/**").permitAll()
          .antMatchers("/**").hasAnyRole(userRole)
          .and().formLogin().loginPage("/login").permitAll().and().logout().permitAll();

        http.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
      }

      @Autowired
      public void configureGlobal(AuthenticationManagerBuilder auth, LdapContextSource contextSource) throws Exception {
        auth.ldapAuthentication()
          .userSearchBase("OU=Users OU")
          .userSearchFilter("sAMAccountName={0}")
          .groupSearchBase("OU=Groups OU")
          .groupSearchFilter("member={0}")
          .contextSource(contextSource);

        if (includeTestUsers) {
          auth.inMemoryAuthentication().withUser("user").password("u").authorities(userRole);
        }
      }
    }
8
répondu Brice Roncace 2015-06-19 14:43:37

je veux juste ajouter à mclema de réponse. Vous pouvez avoir besoin d'ajouter un correcteur pour l'authentification réussie et continuer la chaîne de filtrage ou bien l'utilisateur est redirigé vers l'url par défaut ("/") au lieu de l'url d'origine (par exemple: /myrest/server/somemethod)

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
        Authentication authResult) throws IOException, ServletException {
    SecurityContext context = SecurityContextHolder.createEmptyContext();
    context.setAuthentication(authResult);
    SecurityContextHolder.setContext(context);
    chain.doFilter(request, response);
}
0
répondu Mir3 2016-11-10 21:43:15

La accepté de répondre a la question que la présente demande n'est pas accordée ie. seulement pour les demandes suivantes la session est établie! J'ai donc dû configurer au point 2

public class MyAuthorizationFilter extends AbstractAuthenticationProcessingFilter {

    public MyAuthorizationFilter() {
        super( "/*" ); // allow any request to contain an authorization header
    }

    public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException
    {

        if ( request.getHeader( "Authorization" ) == null ) {
            return null; // no header found, continue on to other security filters
        }

        // required to use the token 
        myNewToken = new MyAuthorizationToken( request.getHeader( "Authorization" ) );
        // and set in the current context ==> the current request is as well authorized
        SecurityContextHolder.getContext().setAuthentication(myNewToken);
        // return a new authentication token to be processed by the authentication provider
        return myNewToken;
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        // try to authenticate the current request
        attemptAuthentication((HttpServletRequest) req, (HttpServletResponse) res);
        super.doFilter(req, res, chain);
    }
}

sinon, la requête actuelle n'est pas encore authentifiée bien qu'une session soit déjà créée!!! (Et les fournisseurs dont je n'ai pas besoin, c'est-à-dire ajouter un filtre est suffisant.)

0
répondu LeO 2018-04-24 13:38:00