华夫饼:如何强制kerberos?
Waffle: how to force kerberos?
我正在使用 Waffle 对 Web 应用程序进行 SingleSignOn。它工作正常,但我想知道是否可以强制 Kerberos 避免回退到 NTLM。
更新 (04.04.18):
HTTP 身份验证不支持 "Kerberos",因此无法强制执行。
https://www.chromium.org/developers/design-documents/http-authentication
Http 只知道 "Negotiate"。如果您在 windows 下使用协商,您将获得一个 SPNEGO 令牌,它可以是 Kerberos 或 NTLM。
我更改了 Waffle 设置以使用自定义 NegotiateSecurityFilterProvider。这基本上是 NegotiateSecurityFilterProvider class,但有三处更改。通过这种方式,服务将只接受 Kerberos 令牌作为身份验证。一个肮脏的解决方案,但它有效(尚未使用 Kerberos 进行测试):
构造函数(只接受协商):
public CustomSecurityFilter(final IWindowsAuthProvider newAuthProvider) {
this.auth = newAuthProvider;
this.protocols.add(CustomSecurityFilter.NEGOTIATE);
}
在 doFilter-Method 中我添加了这个:
//Custom NTLM Token Disable
if(isNTLMToken(authorizationHeader)) {
response.setHeader("Connection", "keep-alive");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.flushBuffer();
return null;
}
添加了一个 isNTLMToken 方法:
private boolean isNTLMToken(AuthorizationHeader authorizationHeader) {
String decodedToken = new String(Base64.getDecoder().decode(authorizationHeader.getToken()));
return decodedToken.contains("NTLM")
|| decodedToken.contains("ntlm");
}
这就是全部 Class:
/**
* Waffle (https://github.com/Waffle/waffle)
*
* Copyright (c) 2010-2016 Application Security, Inc.
*
* All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse
* Public License v1.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html.
*
* Contributors: Application Security, Inc.
*/
package com.example.extention;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Iterator;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.io.BaseEncoding;
import waffle.servlet.spi.SecurityFilterProvider;
import waffle.util.AuthorizationHeader;
import waffle.util.NtlmServletRequest;
import waffle.windows.auth.IWindowsAuthProvider;
import waffle.windows.auth.IWindowsIdentity;
import waffle.windows.auth.IWindowsSecurityContext;
/**
* A negotiate security filter provider.
*
* @author dblock[at]dblock[dot]org
*/
public class CustomSecurityFilter implements SecurityFilterProvider {
/** The Constant LOGGER. */
private static final Logger LOGGER = LoggerFactory.getLogger(CustomSecurityFilter.class);
/** The Constant WWW_AUTHENTICATE. */
private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
/** The Constant PROTOCOLS. */
private static final String PROTOCOLS = "protocols";
/** The Constant NEGOTIATE. */
private static final String NEGOTIATE = "Negotiate";
/** The protocols. */
private List<String> protocols = new ArrayList<>();
/** The auth. */
private final IWindowsAuthProvider auth;
/**
* Instantiates a new negotiate security filter provider.
*
* @param newAuthProvider the new auth provider
*/
public CustomSecurityFilter(final IWindowsAuthProvider newAuthProvider) {
this.auth = newAuthProvider;
this.protocols.add(CustomSecurityFilter.NEGOTIATE);
}
/**
* Gets the protocols.
*
* @return the protocols
*/
public List<String> getProtocols() {
return this.protocols;
}
/**
* Sets the protocols.
*
* @param values the new protocols
*/
public void setProtocols(final List<String> values) {
this.protocols = values;
}
/*
* (non-Javadoc)
*
* @see
* waffle.servlet.spi.SecurityFilterProvider#sendUnauthorized(javax.servlet.http
* .HttpServletResponse)
*/
@Override
public void sendUnauthorized(final HttpServletResponse response) {
final Iterator<String> protocolsIterator = this.protocols.iterator();
while (protocolsIterator.hasNext()) {
response.addHeader(WWW_AUTHENTICATE, protocolsIterator.next());
}
}
/*
* (non-Javadoc)
*
* @see
* waffle.servlet.spi.SecurityFilterProvider#isPrincipalException(javax.servlet.
* http.HttpServletRequest)
*/
@Override
public boolean isPrincipalException(final HttpServletRequest request) {
final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request);
final boolean ntlmPost = authorizationHeader.isNtlmType1PostAuthorizationHeader();
LOGGER.debug("authorization: {}, ntlm post: {}", authorizationHeader, Boolean.valueOf(ntlmPost));
return ntlmPost;
}
/*
* (non-Javadoc)
*
* @see waffle.servlet.spi.SecurityFilterProvider#doFilter(javax.servlet.http.
* HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public IWindowsIdentity doFilter(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request);
final boolean ntlmPost = authorizationHeader.isNtlmType1PostAuthorizationHeader();
//Custom NTLM Token Disable
if(isNTLMToken(authorizationHeader)) {
response.setHeader("Connection", "keep-alive");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.flushBuffer();
return null;
}
// maintain a connection-based session for NTLM tokens
final String connectionId = NtlmServletRequest.getConnectionId(request);
final String securityPackage = authorizationHeader.getSecurityPackage();
LOGGER.debug("security package: {}, connection id: {}", securityPackage, connectionId);
if (ntlmPost) {
// type 2 NTLM authentication message received
this.auth.resetSecurityToken(connectionId);
}
final byte[] tokenBuffer = authorizationHeader.getTokenBytes();
LOGGER.debug("token buffer: {} byte(s)", Integer.valueOf(tokenBuffer.length));
final IWindowsSecurityContext securityContext = this.auth.acceptSecurityToken(connectionId, tokenBuffer, securityPackage);
final byte[] continueTokenBytes = securityContext.getToken();
if (continueTokenBytes != null && continueTokenBytes.length > 0) {
final String continueToken = BaseEncoding.base64().encode(continueTokenBytes);
LOGGER.debug("continue token: {}", continueToken);
response.addHeader(WWW_AUTHENTICATE, securityPackage + " " + continueToken);
}
LOGGER.debug("continue required: {}", Boolean.valueOf(securityContext.isContinue()));
if (securityContext.isContinue() || ntlmPost) {
response.setHeader("Connection", "keep-alive");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.flushBuffer();
return null;
}
final IWindowsIdentity identity = securityContext.getIdentity();
securityContext.dispose();
return identity;
}
private boolean isNTLMToken(AuthorizationHeader authorizationHeader) {
String decodedToken = new String(Base64.getDecoder().decode(authorizationHeader.getToken()));
return decodedToken.contains("NTLM")
|| decodedToken.contains("ntlm");
}
/*
* (non-Javadoc)
*
* @see
* waffle.servlet.spi.SecurityFilterProvider#isSecurityPackageSupported(java.
* lang.String)
*/
@Override
public boolean isSecurityPackageSupported(final String securityPackage) {
for (final String protocol : this.protocols) {
if (protocol.equalsIgnoreCase(securityPackage)) {
return true;
}
}
return false;
}
/*
* (non-Javadoc)
*
* @see
* waffle.servlet.spi.SecurityFilterProvider#initParameter(java.lang.String,
* java.lang.String)
*/
@Override
public void initParameter(final String parameterName, final String parameterValue) {
if (parameterName.equals(PROTOCOLS)) {
this.protocols = new ArrayList<>();
final String[] protocolNames = parameterValue.split("\s+");
for (String protocolName : protocolNames) {
protocolName = protocolName.trim();
if (protocolName.length() > 0) {
LOGGER.debug("init protocol: {}", protocolName);
if (protocolName.equals(NEGOTIATE)) {
this.protocols.add(protocolName);
} else {
LOGGER.error("unsupported protocol: {}", protocolName);
throw new RuntimeException("Unsupported protocol: " + protocolName);
}
}
}
} else {
throw new InvalidParameterException(parameterName);
}
}
}
我正在使用 Waffle 对 Web 应用程序进行 SingleSignOn。它工作正常,但我想知道是否可以强制 Kerberos 避免回退到 NTLM。
更新 (04.04.18): HTTP 身份验证不支持 "Kerberos",因此无法强制执行。 https://www.chromium.org/developers/design-documents/http-authentication
Http 只知道 "Negotiate"。如果您在 windows 下使用协商,您将获得一个 SPNEGO 令牌,它可以是 Kerberos 或 NTLM。
我更改了 Waffle 设置以使用自定义 NegotiateSecurityFilterProvider。这基本上是 NegotiateSecurityFilterProvider class,但有三处更改。通过这种方式,服务将只接受 Kerberos 令牌作为身份验证。一个肮脏的解决方案,但它有效(尚未使用 Kerberos 进行测试):
构造函数(只接受协商):
public CustomSecurityFilter(final IWindowsAuthProvider newAuthProvider) { this.auth = newAuthProvider; this.protocols.add(CustomSecurityFilter.NEGOTIATE); }
在 doFilter-Method 中我添加了这个:
//Custom NTLM Token Disable if(isNTLMToken(authorizationHeader)) { response.setHeader("Connection", "keep-alive"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.flushBuffer(); return null; }
添加了一个 isNTLMToken 方法:
private boolean isNTLMToken(AuthorizationHeader authorizationHeader) { String decodedToken = new String(Base64.getDecoder().decode(authorizationHeader.getToken())); return decodedToken.contains("NTLM") || decodedToken.contains("ntlm"); }
这就是全部 Class:
/**
* Waffle (https://github.com/Waffle/waffle)
*
* Copyright (c) 2010-2016 Application Security, Inc.
*
* All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse
* Public License v1.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html.
*
* Contributors: Application Security, Inc.
*/
package com.example.extention;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Iterator;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.io.BaseEncoding;
import waffle.servlet.spi.SecurityFilterProvider;
import waffle.util.AuthorizationHeader;
import waffle.util.NtlmServletRequest;
import waffle.windows.auth.IWindowsAuthProvider;
import waffle.windows.auth.IWindowsIdentity;
import waffle.windows.auth.IWindowsSecurityContext;
/**
* A negotiate security filter provider.
*
* @author dblock[at]dblock[dot]org
*/
public class CustomSecurityFilter implements SecurityFilterProvider {
/** The Constant LOGGER. */
private static final Logger LOGGER = LoggerFactory.getLogger(CustomSecurityFilter.class);
/** The Constant WWW_AUTHENTICATE. */
private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
/** The Constant PROTOCOLS. */
private static final String PROTOCOLS = "protocols";
/** The Constant NEGOTIATE. */
private static final String NEGOTIATE = "Negotiate";
/** The protocols. */
private List<String> protocols = new ArrayList<>();
/** The auth. */
private final IWindowsAuthProvider auth;
/**
* Instantiates a new negotiate security filter provider.
*
* @param newAuthProvider the new auth provider
*/
public CustomSecurityFilter(final IWindowsAuthProvider newAuthProvider) {
this.auth = newAuthProvider;
this.protocols.add(CustomSecurityFilter.NEGOTIATE);
}
/**
* Gets the protocols.
*
* @return the protocols
*/
public List<String> getProtocols() {
return this.protocols;
}
/**
* Sets the protocols.
*
* @param values the new protocols
*/
public void setProtocols(final List<String> values) {
this.protocols = values;
}
/*
* (non-Javadoc)
*
* @see
* waffle.servlet.spi.SecurityFilterProvider#sendUnauthorized(javax.servlet.http
* .HttpServletResponse)
*/
@Override
public void sendUnauthorized(final HttpServletResponse response) {
final Iterator<String> protocolsIterator = this.protocols.iterator();
while (protocolsIterator.hasNext()) {
response.addHeader(WWW_AUTHENTICATE, protocolsIterator.next());
}
}
/*
* (non-Javadoc)
*
* @see
* waffle.servlet.spi.SecurityFilterProvider#isPrincipalException(javax.servlet.
* http.HttpServletRequest)
*/
@Override
public boolean isPrincipalException(final HttpServletRequest request) {
final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request);
final boolean ntlmPost = authorizationHeader.isNtlmType1PostAuthorizationHeader();
LOGGER.debug("authorization: {}, ntlm post: {}", authorizationHeader, Boolean.valueOf(ntlmPost));
return ntlmPost;
}
/*
* (non-Javadoc)
*
* @see waffle.servlet.spi.SecurityFilterProvider#doFilter(javax.servlet.http.
* HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public IWindowsIdentity doFilter(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request);
final boolean ntlmPost = authorizationHeader.isNtlmType1PostAuthorizationHeader();
//Custom NTLM Token Disable
if(isNTLMToken(authorizationHeader)) {
response.setHeader("Connection", "keep-alive");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.flushBuffer();
return null;
}
// maintain a connection-based session for NTLM tokens
final String connectionId = NtlmServletRequest.getConnectionId(request);
final String securityPackage = authorizationHeader.getSecurityPackage();
LOGGER.debug("security package: {}, connection id: {}", securityPackage, connectionId);
if (ntlmPost) {
// type 2 NTLM authentication message received
this.auth.resetSecurityToken(connectionId);
}
final byte[] tokenBuffer = authorizationHeader.getTokenBytes();
LOGGER.debug("token buffer: {} byte(s)", Integer.valueOf(tokenBuffer.length));
final IWindowsSecurityContext securityContext = this.auth.acceptSecurityToken(connectionId, tokenBuffer, securityPackage);
final byte[] continueTokenBytes = securityContext.getToken();
if (continueTokenBytes != null && continueTokenBytes.length > 0) {
final String continueToken = BaseEncoding.base64().encode(continueTokenBytes);
LOGGER.debug("continue token: {}", continueToken);
response.addHeader(WWW_AUTHENTICATE, securityPackage + " " + continueToken);
}
LOGGER.debug("continue required: {}", Boolean.valueOf(securityContext.isContinue()));
if (securityContext.isContinue() || ntlmPost) {
response.setHeader("Connection", "keep-alive");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.flushBuffer();
return null;
}
final IWindowsIdentity identity = securityContext.getIdentity();
securityContext.dispose();
return identity;
}
private boolean isNTLMToken(AuthorizationHeader authorizationHeader) {
String decodedToken = new String(Base64.getDecoder().decode(authorizationHeader.getToken()));
return decodedToken.contains("NTLM")
|| decodedToken.contains("ntlm");
}
/*
* (non-Javadoc)
*
* @see
* waffle.servlet.spi.SecurityFilterProvider#isSecurityPackageSupported(java.
* lang.String)
*/
@Override
public boolean isSecurityPackageSupported(final String securityPackage) {
for (final String protocol : this.protocols) {
if (protocol.equalsIgnoreCase(securityPackage)) {
return true;
}
}
return false;
}
/*
* (non-Javadoc)
*
* @see
* waffle.servlet.spi.SecurityFilterProvider#initParameter(java.lang.String,
* java.lang.String)
*/
@Override
public void initParameter(final String parameterName, final String parameterValue) {
if (parameterName.equals(PROTOCOLS)) {
this.protocols = new ArrayList<>();
final String[] protocolNames = parameterValue.split("\s+");
for (String protocolName : protocolNames) {
protocolName = protocolName.trim();
if (protocolName.length() > 0) {
LOGGER.debug("init protocol: {}", protocolName);
if (protocolName.equals(NEGOTIATE)) {
this.protocols.add(protocolName);
} else {
LOGGER.error("unsupported protocol: {}", protocolName);
throw new RuntimeException("Unsupported protocol: " + protocolName);
}
}
}
} else {
throw new InvalidParameterException(parameterName);
}
}
}