2012-01-17 1 views
4

Peuvent-ils travailler ensemble? Un échantillon de projet serait génial.Ressort de sécurité 3 + JCIFS ntlm

J'ai une application web sur Spring3. Et j'ai besoin de mettre en œuvre NTLM. Spring a arrêté le support NTLM en 3ème version. Y a-t-il des possibilités de l'implémenter?

Vous cherchez un exemple de projet.

Répondre

5

Ils peuvent être utilisés ensemble. Essentiellement ce que vous voulez faire est de se connecter au protocole SPNEGO et de détecter quand vous recevez un paquet NTLM du client. Une bonne description du protocole se trouve ici:

http://www.innovation.ch/personal/ronald/ntlm.html

http://blogs.technet.com/b/tristank/archive/2006/08/02/negotiate-this.aspx

Une autre grande ressource pour NTLM est la suivante:

http://davenport.sourceforge.net/ntlm.html

Mais vous avez demandé un échantillon alors voici va. Pour détecter un paquet NTLM vous devez base64 décoder le paquet et vérifier une chaîne de départ:

public void doFilter(ServletRequest req, ServletResponse res, 
        FilterChain chain) throws IOException, ServletException { 
    HttpServletRequest request = (HttpServletRequest) req; 
    HttpServletResponse response = (HttpServletResponse) res; 

    String header = request.getHeader("Authorization"); 

    if ((header != null) && header.startsWith("Negotiate ")) { 
     if (logger.isDebugEnabled()) { 
      logger.debug("Received Negotiate Header for request " + request.getRequestURL() + ": " + header); 
     } 
     byte[] base64Token = header.substring(10).getBytes("UTF-8"); 
     byte[] decodedToken = Base64.decode(base64Token); 

    if (isNTLMMessage(decodedToken)) { 
     authenticationRequest = new NTLMServiceRequestToken(decodedToken); 
    } 

... 
} 

public static boolean isNTLMMessage(byte[] token) { 
    for (int i = 0; i < 8; i++) { 
     if (token[i] != NTLMSSP_SIGNATURE[i]) { 
      return false; 
     } 
    } 
    return true; 
} 

public static final byte[] NTLMSSP_SIGNATURE = new byte[]{ 
     (byte) 'N', (byte) 'T', (byte) 'L', (byte) 'M', 
     (byte) 'S', (byte) 'S', (byte) 'P', (byte) 0 
}; 

Vous aurez besoin de faire un fournisseur d'authentification qui peut gérer ce type de authenticationRequest:

import jcifs.Config; 
import jcifs.UniAddress; 
import jcifs.ntlmssp.NtlmMessage; 
import jcifs.ntlmssp.Type1Message; 
import jcifs.ntlmssp.Type2Message; 
import jcifs.ntlmssp.Type3Message; 
import jcifs.smb.NtlmPasswordAuthentication; 
import jcifs.smb.SmbSession; 
import jcifs.util.Base64; 
import org.springframework.beans.factory.InitializingBean; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.security.authentication.AccountStatusUserDetailsChecker; 
import org.springframework.security.authentication.AuthenticationProvider; 
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 
import org.springframework.security.core.Authentication; 
import org.springframework.security.core.AuthenticationException; 
import org.springframework.security.core.userdetails.UserDetailsChecker; 

import javax.annotation.PostConstruct; 
import java.io.IOException; 

/** 
* User: gcermak 
* Date: 3/15/11 
* <p/> 
*/ 
public class ActiveDirectoryNTLMAuthenticationProvider implements AuthenticationProvider, InitializingBean { 
    protected String defaultDomain; 
    protected String domainController; 

    protected UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker(); 

    public ActiveDirectoryNTLMAuthenticationProvider(){ 
     Config.setProperty("jcifs.smb.client.soTimeout", "1800000"); 
     Config.setProperty("jcifs.netbios.cachePolicy", "1200"); 
     Config.setProperty("jcifs.smb.lmCompatibility", "0"); 
     Config.setProperty("jcifs.smb.client.useExtendedSecurity", "false"); 
    } 

    @Override 
    public Authentication authenticate(Authentication authentication) throws AuthenticationException { 
     NTLMServiceRequestToken auth = (NTLMServiceRequestToken) authentication; 
     byte[] token = auth.getToken(); 

     String name = null; 
     String password = null; 

     NtlmMessage message = constructNTLMMessage(token); 

     if (message instanceof Type1Message) { 
      Type2Message type2msg = null; 
      try { 
       type2msg = new Type2Message(new Type1Message(token), getChallenge(), null); 
       throw new NtlmType2MessageException(Base64.encode(type2msg.toByteArray())); 
      } catch (IOException e) { 
       throw new NtlmAuthenticationFailure(e.getMessage()); 
      } 
     } 
     if (message instanceof Type3Message) { 
      final Type3Message type3msg; 
      try { 
       type3msg = new Type3Message(token); 
      } catch (IOException e) { 
       throw new NtlmAuthenticationFailure(e.getMessage()); 
      } 
      final byte[] lmResponse = (type3msg.getLMResponse() != null) ? type3msg.getLMResponse() : new byte[0]; 
      final byte[] ntResponse = (type3msg.getNTResponse() != null) ? type3msg.getNTResponse() : new byte[0]; 

      NtlmPasswordAuthentication ntlmPasswordAuthentication = new NtlmPasswordAuthentication(type3msg.getDomain(), type3msg.getUser(), getChallenge(), lmResponse, ntResponse); 

      String username = ntlmPasswordAuthentication.getUsername(); 
      String domain = ntlmPasswordAuthentication.getDomain(); 
      String workstation = type3msg.getWorkstation(); 

      name = ntlmPasswordAuthentication.getName(); 
      password = ntlmPasswordAuthentication.getPassword(); 
     } 

     // do custom logic here to find the user ... 
     userDetailsChecker.check(user); 

     return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities()); 
    } 

    // The Client will only ever send a Type1 or Type3 message ... try 'em both 
    protected static NtlmMessage constructNTLMMessage(byte[] token) { 
     NtlmMessage message = null; 
     try { 
      message = new Type1Message(token); 
      return message; 
     } catch (IOException e) { 
      if ("Not an NTLMSSP message.".equals(e.getMessage())) { 
       return null; 
      } 
     } 

     try { 
      message = new Type3Message(token); 
      return message; 
     } catch (IOException e) { 
      if ("Not an NTLMSSP message.".equals(e.getMessage())) { 
       return null; 
      } 
     } 

     return message; 
    } 

    protected byte[] getChallenge() { 
     UniAddress dcAddress = null; 
     try { 
      dcAddress = UniAddress.getByName(domainController, true); 
      return SmbSession.getChallenge(dcAddress); 
     } catch (IOException e) { 
      throw new NtlmAuthenticationFailure(e.getMessage()); 
     } 
    } 

    @Override 
    public boolean supports(Class<? extends Object> auth) { 
     return NTLMServiceRequestToken.class.isAssignableFrom(auth); 
    } 

    @Override 
    public void afterPropertiesSet() throws Exception { 
     // do nothing 
    } 

    public void setSmbClientUsername(String smbClientUsername) { 
     Config.setProperty("jcifs.smb.client.username", smbClientUsername); 
    } 

    public void setSmbClientPassword(String smbClientPassword) { 
     Config.setProperty("jcifs.smb.client.password", smbClientPassword); 
    } 

    public void setDefaultDomain(String defaultDomain) { 
     this.defaultDomain = defaultDomain; 
     Config.setProperty("jcifs.smb.client.domain", defaultDomain); 
    } 

    /** 
    * 0: Nothing 
    * 1: Critical [default] 
    * 2: Basic info. (Can be logged under load) 
    * 3: Detailed info. (Highest recommended level for production use) 
    * 4: Individual smb messages 
    * 6: Hex dumps 
    * @param logLevel the desired logging level 
    */ 
    public void setDebugLevel(int logLevel) throws Exception { 
     switch(logLevel) { 
      case 0: 
      case 1: 
      case 2: 
      case 3: 
      case 4: 
      case 6: 
       Config.setProperty("jcifs.util.loglevel", Integer.toString(logLevel)); 
       break; 
      default: 
       throw new Exception("Invalid Log Level specified"); 
     } 
    } 

    /** 
    * 
    * @param winsList a comma separates list of wins addresses (ex. 10.169.10.77,10.169.10.66) 
    */ 
    public void setNetBiosWins(String winsList) { 
     Config.setProperty("jcifs.netbios.wins", winsList); 
    } 

    public void setDomainController(String domainController) { 
     this.domainController = domainController; 
    } 
} 

Et enfin, vous devez attacher tous ensemble dans votre fichier spring_security.xml:

<beans:beans xmlns="http://www.springframework.org/schema/security" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xmlns:beans="http://www.springframework.org/schema/beans" 
      xmlns:jdbc="http://www.springframework.org/schema/jdbc" 
      xsi:schemaLocation=" 
       http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd 
       http://www.springframework.org/schema/jdbc 
       http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd 
       http://www.springframework.org/schema/security 
       http://www.springframework.org/schema/security/spring-security-3.1.xsd"> 

    <http auto-config="true" use-expressions="true" disable-url-rewriting="true"> 
     <form-login login-page="/auth/login" 
        login-processing-url="/auth/j_security_check"/> 
     <remember-me services-ref="rememberMeServices"/> 
     <logout invalidate-session="true" logout-success-url="/auth/logoutMessage" logout-url="/auth/logout"/> 
     <access-denied-handler error-page="/error/accessDenied"/> 
    </http> 

    <authentication-manager alias="authenticationManager"> 
     <authentication-provider user-service-ref="myUsernamePasswordUserDetailsService"> 
      <password-encoder ref="passwordEncoder"> 
       <salt-source ref="saltSource"/> 
      </password-encoder> 
     </authentication-provider> 
     <authentication-provider ref="NTLMAuthenticationProvider"/> 
    </authentication-manager> 
</beans:beans> 

enfin, vous devez savoir comment attacher tout ensem er. Le protocole tel que décrit dans le premier ensemble de liens montre qu'il y a plusieurs allers-retours que vous devez effectuer entre le client et le serveur. Ainsi, dans votre filtre, vous avez besoin d'un peu plus logique:

import jcifs.ntlmssp.Type1Message; 
import jcifs.ntlmssp.Type2Message; 
import org.springframework.security.authentication.AuthenticationManager; 
import org.springframework.security.core.Authentication; 
import org.springframework.security.core.AuthenticationException; 
import org.springframework.security.core.codec.Base64; 
import org.springframework.security.core.context.SecurityContextHolder; 
import org.springframework.security.extensions.kerberos.KerberosServiceRequestToken; 
import org.springframework.security.web.authentication.AuthenticationFailureHandler; 
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 
import org.springframework.util.Assert; 
import org.springframework.web.filter.GenericFilterBean; 

import javax.servlet.FilterChain; 
import javax.servlet.ServletException; 
import javax.servlet.ServletRequest; 
import javax.servlet.ServletResponse; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import java.io.IOException; 

/** 
* User: gcermak 
* Date: 12/5/11 
*/ 
public class SpnegoAuthenticationProcessingFilter extends GenericFilterBean { 
    private AuthenticationManager authenticationManager; 
    private AuthenticationSuccessHandler successHandler; 
    private AuthenticationFailureHandler failureHandler; 

    public void doFilter(ServletRequest req, ServletResponse res, 
         FilterChain chain) throws IOException, ServletException { 
     HttpServletRequest request = (HttpServletRequest) req; 
     HttpServletResponse response = (HttpServletResponse) res; 

     String header = request.getHeader("Authorization"); 

     if ((header != null) && header.startsWith("Negotiate ")) { 
      if (logger.isDebugEnabled()) { 
       logger.debug("Received Negotiate Header for request " + request.getRequestURL() + ": " + header); 
      } 
      byte[] base64Token = header.substring(10).getBytes("UTF-8"); 
      byte[] decodedToken = Base64.decode(base64Token); 

      // older versions of ie will sometimes do this 
      // logic cribbed from jcifs filter implementation jcifs.http.NtlmHttpFilter 
      if (request.getMethod().equalsIgnoreCase("POST")) { 
       if (decodedToken[8] == 1) { 
        logger.debug("NTLM Authorization header contains type-1 message. Sending fake response just to pass this stage..."); 
        Type1Message type1 = new Type1Message(decodedToken); 
        // respond with a type 2 message, where the challenge is null since we don't 
        // care about the server response (type-3 message) since we're already authenticated 
        // (This is just a by-pass - see method javadoc) 
        Type2Message type2 = new Type2Message(type1, new byte[8], null); 
        String msg = jcifs.util.Base64.encode(type2.toByteArray()); 
        response.setHeader("WWW-Authenticate", "Negotiate " + msg); 
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 
        response.setContentLength(0); 
        response.flushBuffer(); 
        return; 
       } 
      } 

      Authentication authenticationRequest = null; 
      if (isNTLMMessage(decodedToken)) { 
       authenticationRequest = new NTLMServiceRequestToken(decodedToken); 
      } 

      Authentication authentication; 
      try { 
       authentication = authenticationManager.authenticate(authenticationRequest); 
      } catch (NtlmBaseException e) { 
       // this happens during the normal course of action of an NTLM authentication 
       // a type 2 message is the proper response to a type 1 message from the client 
       // see: http://www.innovation.ch/personal/ronald/ntlm.html 
       response.setHeader("WWW-Authenticate", e.getMessage()); 
       response.setHeader("Connection", "Keep-Alive"); 
       response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 
       response.setContentLength(0); 
       response.flushBuffer(); 
       return; 
      } catch (AuthenticationException e) { 
       // That shouldn't happen, as it is most likely a wrong configuration on the server side 
       logger.warn("Negotiate Header was invalid: " + header, e); 
       SecurityContextHolder.clearContext(); 
       if (failureHandler != null) { 
        failureHandler.onAuthenticationFailure(request, response, e); 
       } else { 
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 
        response.flushBuffer(); 
       } 
       return; 
      } 
      if (successHandler != null) { 
       successHandler.onAuthenticationSuccess(request, response, authentication); 
      } 
      SecurityContextHolder.getContext().setAuthentication(authentication); 
     } 

     chain.doFilter(request, response); 
    } 

    public void setAuthenticationManager(AuthenticationManager authenticationManager) { 
     this.authenticationManager = authenticationManager; 
    } 

    public void setSuccessHandler(AuthenticationSuccessHandler successHandler) { 
     this.successHandler = successHandler; 
    } 

    public void setFailureHandler(AuthenticationFailureHandler failureHandler) { 
     this.failureHandler = failureHandler; 
    } 

    @Override 
    public void afterPropertiesSet() throws ServletException { 
     super.afterPropertiesSet(); 
     Assert.notNull(this.authenticationManager, "authenticationManager must be specified"); 
    } 
} 

Vous verrez que dans l'exception que nous utilisons « Negotiate » plutôt que NTLM:

/** 
* User: gcermak 
* Date: 12/5/11 
*/ 
public class NtlmType2MessageException extends NtlmBaseException { 
    private static final long serialVersionUID = 1L; 

    public NtlmType2MessageException(final String type2Msg) { 
     super("Negotiate " + type2Msg); 
    } 
} 

Le filtre à ressort (ci-dessus) a été en grande partie calqué sur jcifs.http.NtlmHttpFilter que vous pouvez trouver dans la source de jcifs ici:

http://jcifs.samba.org/

Ce n'est pas l'ensemble, le projet téléchargeable que vous requeste d mais s'il y a un intérêt de la communauté que je pourrais ajouter ce code NTLM à mon projet github:

http://git.springsource.org/~grantcermak/spring-security/activedirectory-se-security

Hope this helps!

Grant

+0

Je peux garantir que cette implémentation fonctionne. Bon travail. – aweigold

+0

Merci. Déjà IE fixe avec une méthode doFilter. Mais j'essaierai certainement vos échantillons printaniers + jscifs en weekend. – StrekoZ