2012-01-17 6 views
4

함께 작동 할 수 있습니까? 일부 프로젝트 샘플은 훌륭합니다.스프링 보안 3 + JCIFS ntlm

스프링 3에 웹 앱이 있습니다. 그리고 NTLM을 구현해야합니다. Spring은 3 판에서 NTLM 지원을 중단했습니다. 그것을 구현할 수있는 가능성이 있습니까?

샘플 프로젝트를 찾고 있습니다.

답변

5

함께 사용할 수 있습니다. 기본적으로 SPNEGO 프로토콜에 연결하여 클라이언트에서 NTLM 패킷을 수신하면이를 감지합니다.

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

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

NTLM에 대한 또 다른 훌륭한 자원이있다 : 프로토콜의 좋은 설명을 여기에서 찾을 수 있습니다

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

하지만 당신은 그래서 여기에 샘플 요청 간다. NTLM을 패킷을 감지하려면 디코드에게 패킷을 BASE64하고 시작하는 문자열을 검사해야합니다

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

당신은 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; 
    } 
} 

그리고 마지막으로 당신은 당신의 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> 

이 마지막으로 당신이 togeth 모두를 묶어하는 방법을 알아야 어. 첫 번째 링크 집합에 설명 된 프로토콜은 클라이언트와 서버 사이에 몇 가지 왕복이 필요하다는 것을 보여줍니다. (위)

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

스프링 필터 크게되었다

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

당신은 우리가 아니라 NTLM보다 "협상"사용 예외가 나타납니다 : 따라서 필터에 당신은 좀 더 논리를 필요 여기 JCIFS의 소스에서 찾을 수있는 jcifs.http.NtlmHttpFilter에 패턴 :

http://jcifs.samba.org/

이 전체, 당신이 requeste로 다운로드 할 수있는 프로젝트 아니다 D하지만 사회의 관심이 있다면 내 GitHub의 프로젝트에이 NTLM 코드를 추가 할 수 있습니다 :이 도움이

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

희망을!

부여

+0

나는이 구현이 작동한다는 것을 보증 할 수 있습니다. 잘 했어. – aweigold

+0

감사합니다. 이미 IE를 doFilter 메소드로 고정했습니다. 하지만 나는 주말에 spring + jscif 샘플을 틀림없이 시험해 보겠습니다. – StrekoZ