2014-09-11 15 views
13

Derzeit habe ich einen einzigen Authentifizierungsmechanismus in meiner Anwendung, die LDAP für Authentifizierung und Autorisierung verwenden soll. Meine Sicherheitskonfiguration sieht wie folgt ausmehrere Authentifizierungsmechanismen in einer einzigen App mit Java-Konfiguration

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

Es gibt Situationen, wenn in dem Benutzer mit einem Session-Token kommen könnte, die von einem Sitzungsschlüssel-Server und gültiges Token einen Benutzernamen Authentifizierung kann zurückgibt, die dann verwendet werden kann, laden authrization Informationen aus LDAP für diesen Benutzer Also sollte mein zweiter Authentifizierungsmechanismus zuerst passieren, wo, wenn ein Sitzungstoken in http-Headern vorhanden ist, er die Token-Authentifizierung und dann ldap-Lookup durchführen sollte und wenn kein Session-Token vorhanden ist, sollte er einfach zum aktuellen Authentifizierungsmechanismus fallen. Wie kann ich diese zweite Authentifizierungsebene hinzufügen?

Antwort

26

Ich habe ziemlich viel Zeit damit verbracht, meinen Kopf mit der Spring-Sicherheit zu verbinden, wenn ich die reine Java-Konfiguration verwende. Es gibt ein paar Schritte, um dies zum Laufen zu bringen. Es sollte etwas in dieser Richtung sein. Der grundlegende Prozess ist wie folgt:

  • Erstellen von benutzerdefinierten Filtern Anforderungen für bestimmte Berechtigungsinformationen überprüfen

  • Jeder Filter gibt null zurück (wenn keine Genehmigung dieser Art gefunden wird) oder eine benutzerdefinierte AbstractAuthenticationToken

  • wenn ein Filter ein Token zurückgibt, jeder Träger des AuthenticationProvider (Klassen-) Methode wird mit dem Token gibt true zurück, aufgerufen werden | false, wenn es die Authentifizierung versuchen sollte

  • assaultAuthentication wird dann für den AuthenticationProvider aufgerufen, der das Token unterstützt. Hier führen Sie Serviceanfragen durch, um den Benutzer zu authentifizieren. Sie können dann LoginException's auslösen oder authentication.setAuthenticated (true) aufrufen und das Token für eine erfolgreiche Authentifizierung zurückgeben.

Ich habe mit diesem Setup für eine Weile verschiedene Authentifizierungsmethoden unterstützt (schriebenen Antrag, Benutzername/Passwort, oauth etc) und es funktioniert ganz gut.

Sie können AuthenticationSuccessHandler und AuthenticationFailuersHandler auch an die benutzerdefinierten Sicherheitsfilter übergeben, um benutzerdefinierte Umleitungsstrategien und Fehlerbehandlung bereitzustellen.

Stellen Sie außerdem sicher, dass die Ant-Matcher in den Konstruktoren des Filters eingerichtet sind, um zu steuern, welche URL-Muster die Filter ebenfalls anwenden. Zum Beispiel würde ein LDAP-Anfrage-Filter wahrscheinlich mit jeder Anfrage "/ *" überprüft werden müssen, während ein Benutzername/Passwort-Filter nur auf POSTs zu/Login oder ähnlichem überprüft werden kann.

Beispielcode: Erstellen

1) individuelle AuthenticationToken die für jede Art der Authentifizierung Sie

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) Erstellen von benutzerdefinierten Sicherheitsfilter für jeden Typ

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

unterstützen möchten 3) Erstellen Sie benutzerdefinierte Authentifizierungsprovider

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) Konfigurieren Feder Sicherheit

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

Hoffnung, das hilft!

+0

Wie funktioniert es funktioniert? Was ist der Kopfzeilenschlüssel "Authorization"? Was sind Authentifizierungen? Danke – tayfun

8

Eine weitere Möglichkeit, einen zweiten Authentifizierungsanbieter hinzuzufügen: Geben Sie einfach einen anderen Authentifizierungsanbieter unter AuthenticationManagerBuilder an. Da die @EnableWebSecurity Annotation selbst mit EnableGlobalAuthentication annotiert ist, können Sie die globale Instanz AuthenticationManagerBuilder konfigurieren. (Siehe javadocs für weitere Details.)

Zum Beispiel haben wir hier einen LDAP-Authentifizierungsanbieter sowie einen (fest codierten) Authentifizierungsanbieter im Speicher (dies wird in der Entwicklung durchgeführt, damit lokale Benutzer testen können) mit):

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

Das scheint die einfachste und korrekteste Antwort zu sein. –

0

Ich möchte nur zu mclemas Antwort hinzufügen. Sie müssen möglicherweise Überschreibung für eine erfolgreiche Authentifizierung hinzufügen und die Filterkette oder auch Benutzer weiterhin wird url auf Standard umgeleitet („/“) anstelle des ursprünglichen (zB:/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); 
}