utilisation de la configuration java pour l'authentification à n facteurs [fermé]


Dans une application spring mvcutilisant spring security, je veux utiliser un AuthenticationProviderpersonnalisé pour vérifier n-number de champs supplémentaires au-delà des username et password par défaut. J'essaie d'utiliser la configuration Java. Comment dois-je le configurer?

Author: CodeMed, 2015-08-15

4 answers

Tout d'abord, quelques explications sur les interfaces avec lesquelles vous travaillez et le rôle qu'elles jouent dans le processus d'authentification:

  • Authentication - représente le résultat de l'authentification d'un utilisateur. Détient les pouvoirs accordés à cet utilisateur et tous les détails supplémentaires qui pourraient être nécessaires sur l'utilisateur. Comme il n'y a aucun moyen pour le framework de savoir quels détails seront nécessaires, l'objet d'authentification a un getDetails méthode qui peut renvoyer tout objet

  • AuthenticationProvider - objet qui peut créer une Authentication objet d'une certaine façon. Pour les rendre plus réutilisables, certains (ou la plupart) des AuthenticationProviders s'abstiennent de définir les détails de l'utilisateur sur l'objet Authentication, car chaque application peut avoir besoin de détails utilisateur spécifiques. Au lieu de cela ils délèguent le processus de résolution des détails de l'utilisateur à un settable UserDetailsService

  • UserDetailsService - unestratégie pour récupérer les détails utilisateur requis dans votre application.

Donc, si vous créez un AuthenticationProvider personnalisé, vous n'aurez peut-être même pas besoin de l'implémenter d'une manière qui nécessite un UserDetailsService. La décision est à vous et dépend, si vous prévoyez de réutiliser votre mise en œuvre dans d'autres projets.

En ce qui concerne les problèmes de compilation dans votre code, vous mélangez deux façons de fournir le UserDetailsService. Dans le CustomAuthenticationProvider vous avez annoté le champ userService avec l'annotation @Inject.Cela signifie que le conteneur (contexte d'application Spring dans votre cas) doit trouver un implémentation et l'injecter dans ce champ lors de l'exécution à l'aide de la réflexion. Le processus de définition de ce champ par le contexte est appelé injection de dépendance. Dans la classe SecurityConfig, vous essayez de fournir l'implémentation vous-même en définissant le champ via la méthode setUserDetailsService qui n'existe pas dans votre classe.

Pour résoudre ce problème, vous devez décider d'utiliser l'une des façons de fournir le service UserDetails et soit:

  • supprimer l'annotation @Inject et créer la méthode setUserDetailsService, ou
  • supprimez la ligne lorsque vous appelez la méthode inexistante et déclarez votre implémentation du UserDetailsService en tant que bean

Quant à savoir lequel des moyens à choisir, la méthode d'injection dependecy peut être mieux si vous pouvez trouver un moyen de rendre votre SecurityConfig classe réutilisable dans d'autres projets. Dans ce cas, vous pouvez simplement l'importer (en utilisant l'annotaion @Import) et déclarer une implémentation différente UserDetailsSerice en tant que bean dans votre prochaine application et l'avoir travailler.

Habituellement, des classes comme le SecurityConfig ne sont pas vraiment réutilisables, donc créer le setter et supprimer l'injection de dépendance serait probablement mon premier choix.

MODIFIER

Une implémentation fonctionnelle, quoique simpliste (basée fortement sur cette entrée de blog) serait:

public class CustomAuthenticationProvider implements AuthenticationProvider{

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String name = authentication.getName();
        String password = authentication.getCredentials().toString();
        List<GrantedAuthority> grantedAuths = new ArrayList<>();
        if (name.equals("admin") && password.equals("system")) {
            grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));  
        } 
        if(pincodeEntered(name)){
            grantedAuths.add(new SimpleGrantedAuthority("ROLE_PINCODE_USER"));  
        }
        Authentication auth = new UsernamePasswordAuthenticationToken(name, password, grantedAuths);
    }

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

    private boolean pincodeEntered(String userName){
        // do your check here
        return true;
    }
}

Ensuite, dans votre classe de configuration, changez la méthode suivante:

@Bean
AuthenticationProvider customAuthenticationProvider() {
        return new CustomAuthenticationProvider();
}
 7
Author: Apokralipsa, 2015-08-16 15:25:52

La première chose que nous devons faire est d'étendre la classe UsernamePasswordAuthenticationFilter afin qu'elle puisse gérer un deuxième champ de saisie.

public class TwoFactorAuthenticationFilter extends UsernamePasswordAuthenticationFilter
{
    private String extraParameter = "extra";
    private String delimiter = ":";
    //getters and setters

    @Override
    protected String obtainUsername(HttpServletRequest request)
    {
        String username = request.getParameter(getUsernameParameter());
        String extraInput = request.getParameter(getExtraParameter());
        String combinedUsername = username + getDelimiter() + extraInput;
        return combinedUsername;
    }

}

ObtainUsername() Cette méthode consiste à récupérer le nom d'utilisateur et le champ de saisie "supplémentaire" de l'objet HttpServletRequest transmis.

Il concatène ensuite ces deux valeurs en une seule chaîne, en les séparant par la chaîne de délimitation (un deux-points, par défaut).

Il renvoie ensuite cette chaîne combinée. Le le paramètre à partir duquel le champ de saisie" extra " est lu est extra par défaut.

UserDetailsService devrait ressembler à ceci:

@Override
public UserDetails loadUserByUsername(String input) throws UsernameNotFoundException, DataAccessException
{
    String[] split = input.split(":");
    if(split.length < 2)
    {
        throw new UsernameNotFoundException("Must specify both username and corporate domain");
    }

    String username = split[0];
    String domain = split[1];
    User user = userDao.findByUsernameAndDomain(username, domain);
    if(user == null)
    {
        throw new UsernameNotFoundException("Invalid username or corporate domain");
    }
    return user;
}

Divisez le nom d'utilisateur donné en deux composants: le nom d'utilisateur et le champ supplémentaire. Dans cet exemple, le champ supplémentaire est le domaine d'entreprise de l'utilisateur.

Une fois que nous avons le nom d'utilisateur et le domaine, nous pouvons utiliser notre DAO pour trouver l'utilisateur correspondant.

Dernier Puzzle:

TwoFactorAuthenticationFilter:

    <http use-expressions="true" auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
        <intercept-url pattern="/secured" access="isAuthenticated()" />
        <intercept-url pattern="/**" access="permitAll" />
        <custom-filter position="FORM_LOGIN_FILTER" ref="twoFactorAuthenticationFilter" />
        <logout logout-url="/logout" />
    </http>

    <authentication-manager alias="authenticationManager">
        <authentication-provider ref="authenticationProvider" />
    </authentication-manager>

    <beans:bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
        <beans:property name="passwordEncoder">
            <beans:bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" />
        </beans:property>
        <beans:property name="userDetailsService" ref="userService" />
    </beans:bean>

    <beans:bean id="userService" class="com.awnry.springexample.UserDetailsServiceImpl" />

    <beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <beans:property name="loginFormUrl" value="/login" />
    </beans:bean>

    <beans:bean id="twoFactorAuthenticationFilter" class="com.awnry.springexample.TwoFactorAuthenticationFilter">
        <beans:property name="authenticationManager" ref="authenticationManager" />
        <beans:property name="authenticationFailureHandler" ref="failureHandler" />
        <beans:property name="authenticationSuccessHandler" ref="successHandler" />
        <beans:property name="filterProcessesUrl" value="/processLogin" />
        <beans:property name="postOnly" value="true" />
        <beans:property name="extraParameter" value="domain" />
    </beans:bean>

    <beans:bean id="successHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
        <beans:property name="defaultTargetUrl" value="/login" />
    </beans:bean>

    <beans:bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <beans:property name="defaultFailureUrl" value="/login?login_error=true" />
    </beans:bean>

Dans la définition de bean twoFactorAuthenticationFilter, nous définissons la propriété extraParameter sur "domain" qui est le nom du champ de saisie à utiliser dans notre formulaire de connexion.

MODIFIER:

Jetez un œil aux constructeurs de la classe User .

Si vous ne savez pas ce qu'une autorité accordée entrer dans un regard sur ce ci-dessous lien:

Http://docs.spring.io/autorepo/docs/spring-security/3.2.1.RELEASE/apidocs/org/springframework/security/core/GrantedAuthority.html

Votre codage donne un mode différent applicable uniquement pour le nom d'utilisateur et le mot de passe normaux. Mon code fonctionne pour l'authentification à n facteurs. Essayez de passer à mon code si un problème persiste.

 6
Author: MS Ibrahim, 2015-08-31 04:07:57

Je suis très conscient que ce post a subi 28 modifications, donc j'ai peut-être manqué un peu de contexte. Je suis également conscient que vous avez amalgamé du code des autres réponses dans votre question et que le problème a été quelque peu "tourné sur la tête" de "pourquoi un utilisateur valide ne s'authentifiera-t-il pas?"à" pourquoi chaque utilisateur s'authentifie-t-il?".

Problème actuel.

Cependant, comme écrit, votre méthode CustomAuthenticationProvider.authenticate() retournera toujours un objet Authentication qui renvoie auth.isAuthenticated() == true parce que vous instanciez en utilisant cette méthode qui vous avertit de cette chose même. Même si le collection que vous avez passé en tant que troisième argument était vide, ce serait le cas. En fait, la collection contient toujours un GrantedAuthority pour "enregistré", car pincodeEntered(name) renvoie toujours true. Donc, vous devez corriger votre logique dans ces méthodes. authenticate() doit renvoyer null si l'authentification ne réussit pas.

Prochaines étapes

Vous avez indiqué dans commentaires, que ce que vous voulez, c'est un implémentation de référence de l'authentification multifacteur. C'est problématique, il n'y a pas nécessairement d'accord sur ce qui constitue une telle chose. Par exemple, certains diront que multi factor devrait inclure un facteur de possession, plutôt que n facteurs de connaissance sur une seule page de connexion. Ce n'est pas non plus vraiment adapté à une réponse SO car il faudrait un article de blog (ou une série) - aussi généreux soit-il.

Il existe des exemples pratiques d'authentification multifacteur dans spring on le web, ici et ici, par exemple. Ce dernier, je pense que vous devez avoir découvert que vous semblez utiliser une partie du code à partir de là.

Faire votre CustomAuthenticationProvider travail pourrait prendre des heures. Le débogage peut prendre encore plus de temps, car vous avez un mélange de méthodes dans votre exemple - ce n'est pas minime. En particulier, la classe TwoFactorAuthenticationFilter est censée être utilisée pour intercepter l'entrée sur une requête de la page de connexion et concaténer le nom d'utilisateur et le code PIN. Dans l'exemple du blog, c'est configuré en XML - vous pouvez ajouter l'espace de noms security à votre business-config.xml et y ajouter ces beans par exemple.

Cependant, la classe SecurityConfig et CustomAuthenticationProvider est à nouveau une méthode différente.

Ensuite, votre code de projet fait référence à une URL j_security_check, mais cette URL n'est gérée par rien. Je ne suis pas sûr de l'intention derrière cela, ou d'où il vient. Enfin, la configuration MVC pour le routage d'URL ajoute un autre élément au mix - un que je ne connais pas.

J'ai joué avec votre exemple pendant un moment. Il y a trop de méthodes mixtes et trop de complexité pour que je puisse corriger rapidement - peut-être que d'autres le peuvent.

I fortement suggérez de commencer exactement à partir de l'exemple dans le blog, puis ajoutez la configuration mvc que vous souhaitez par-dessus cela.

N.B. Configuration pour les autres essayant de faire fonctionner l'exemple

Il y avait quelques rides dans la mise en place du projet-il avait une dépendance non nécessaire et insatisfaite de javax.mail, vous devez publier les dépendances maven sur le serveur (dans project->properties->deployment assembly) et vous devez télécharger et installer des adaptateurs pour le serveur tomcat si vous ne l'avez pas déjà.

Vous devez également créer les tables et les colonnes de votre base de données.

 4
Author: J Richard Snape, 2017-05-23 12:34:09

La façon la plus simple d'utiliser java config pour l'authentification à n facteurs est de commencer par un exemple fonctionnel d'authentification à un facteur (nom d'utilisateur et mot de passe) qui utilise java config. Ensuite, il vous suffit d'apporter quelques modifications très mineures: En supposant que vous ayez une application d'authentification à facteur unique qui fonctionne à l'aide de la configuration java, les étapes sont simplement:

Tout d'abord, définissez des rôles en couches, avec un rôle pour chaque facteur. Si vous ne disposez que d'une authentification à deux facteurs, conservez votre rôle existant dans la base de données, puis créez un second rôle avec un accès complet que vous n'affectez qu'au moment de l'exécution. Ainsi, lorsque l'utilisateur se connecte, il est connecté au rôle minimal stocké dans la base de données, et ce rôle minimal n'a accès qu'à une seule vue, qui est un formulaire lui permettant d'entrer un code PIN que votre contrôleur vient de leur envoyer par texte ou par e-mail ou une autre méthode. Ces rôles en couches sont définis dans SecurityConfig.java, comme suit:

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http
        .csrf().disable()
        .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("/getpin")
            .usernameParameter("j_username")
            .passwordParameter("j_password")
            .loginProcessingUrl("/j_spring_security_check")
            .failureUrl("/login")
            .permitAll()
            .and()
        .logout()
            .logoutUrl("/logout")
            .logoutSuccessUrl("/login")
            .and()
        .authorizeRequests()
            .antMatchers("/getpin").hasAuthority("get_pin")
            .antMatchers("/securemain/**").hasAuthority("full_access")
            .antMatchers("/j_spring_security_check").permitAll()
            .and()
        .userDetailsService(userDetailsService);
    }
}

Deuxièmement, ajoutez du code qui met à niveau le rôle de l'utilisateur vers accès complet lors de la saisie réussie du code PIN correct au code du contrôleur qui gère le formulaire de saisie du code PIN POST. Le code pour attribuer manuellement un accès complet dans le contrôleur est:

Role rl2 = new Role();rl2.setRole("full-access");//Don't save this one because we will manually assign it on login.
Set<Role> rls = new HashSet<Role>();
rls.add(rl2);
CustomUserDetailsService user = new CustomUserDetailsService(appService);
Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities(rls));
SecurityContextHolder.getContext().setAuthentication(authentication);
return "redirect:/securemain";  

Vous pouvez ajouter autant de calques que vous le souhaitez après /getpin. Vous pouvez également prendre en charge plusieurs rôles d'autorisation et le rendre aussi compliqué que vous le souhaitez. Mais cette réponse donne le moyen le plus simple de le faire fonctionner avec java config.

 0
Author: CodeMed, 2015-09-09 22:42:56