spring ldap
由于一般的时间限制,我在这篇文章中没有过多地介绍“ fluff”,只是使用多个XSD和LDAP安全性来创建和保护Spring 3和Spring WS 2 Web服务。
代码:
服务端点:ExampleServiceEndpoint
这是将在后面的配置中使用Web服务公开的类。
package javaitzen.spring.ws;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
import javax.annotation.Resource;
@Endpoint
public class ExampleServiceEndpoint {
private static final String NAMESPACE_URI = "http://www.briandupreez.net";
/**
* Autowire a POJO to handle the business logic
@Resource(name = "businessComponent")
private ComponentInterface businessComponent;
*/
public ExampleServiceEndpoint() {
System.out.println(">> javaitzen.spring.ws.ExampleServiceEndpoint loaded.");
}
@PayloadRoot(localPart = "ProcessExample1Request", namespace = NAMESPACE_URI + "/example1")
@ResponsePayload
public Example1Response processExample1Request(@RequestPayload final Example1 request) {
System.out.println(">> process example request1 ran.");
return new Example1Response();
}
@PayloadRoot(localPart = "ProcessExample2Request", namespace = NAMESPACE_URI + "/example2")
@ResponsePayload
public Example2Response processExample2Request(@RequestPayload final Example2 request) {
System.out.println(">> process example request2 ran.");
return new Example2Response();
}
}
代码:CustomValidationCallbackHandler
这是我编写的用于扩展AbstactCallbackHandler的自定义代码,它允许我们使用LDAP。
根据下面的CallbackHandler中的注释,最好有一个缓存管理器,例如Hazelcast或Ehcache来缓存经过身份验证的用户,这取决于安全/性能方面的考虑。
下面的Digest Validator可以直接在Sun库中使用,我只是想了解它是如何工作的。
package javaitzen.spring.ws;
import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;
import com.sun.xml.wss.impl.callback.PasswordValidationCallback;
import com.sun.xml.wss.impl.misc.Base64;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
import org.springframework.ws.soap.security.callback.AbstractCallbackHandler;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.util.Properties;
public class CustomValidationCallbackHandler extends AbstractCallbackHandler implements InitializingBean {
private Properties users = new Properties();
private AuthenticationManager ldapAuthenticationManager;
@Override
protected void handleInternal(final Callback callback) throws IOException, UnsupportedCallbackException {
if (callback instanceof PasswordValidationCallback) {
final PasswordValidationCallback passwordCallback = (PasswordValidationCallback) callback;
if (passwordCallback.getRequest() instanceof PasswordValidationCallback.DigestPasswordRequest) {
final PasswordValidationCallback.DigestPasswordRequest digestPasswordRequest =
(PasswordValidationCallback.DigestPasswordRequest) passwordCallback.getRequest();
final String password = users
.getProperty(digestPasswordRequest
.getUsername());
digestPasswordRequest.setPassword(password);
passwordCallback
.setValidator(new CustomDigestPasswordValidator());
}
if (passwordCallback.getRequest() instanceof PasswordValidationCallback.PlainTextPasswordRequest) {
passwordCallback
.setValidator(new LDAPPlainTextPasswordValidator());
}
} else {
throw new UnsupportedCallbackException(callback);
}
}
/**
* Digest Validator.
* This code is directly from the sun class, I was just curious how it worked.
*/
private class CustomDigestPasswordValidator implements PasswordValidationCallback.PasswordValidator {
public boolean validate(final PasswordValidationCallback.Request request) throws PasswordValidationCallback.PasswordValidationException {
final PasswordValidationCallback.DigestPasswordRequest req = (PasswordValidationCallback.DigestPasswordRequest) request;
final String passwd = req.getPassword();
final String nonce = req.getNonce();
final String created = req.getCreated();
final String passwordDigest = req.getDigest();
final String username = req.getUsername();
if (null == passwd)
return false;
byte[] decodedNonce = null;
if (null != nonce) {
try {
decodedNonce = Base64.decode(nonce);
} catch (final Base64DecodingException bde) {
throw new PasswordValidationCallback.PasswordValidationException(bde);
}
}
String utf8String = "";
if (created != null) {
utf8String += created;
}
utf8String += passwd;
final byte[] utf8Bytes;
try {
utf8Bytes = utf8String.getBytes("utf-8");
} catch (final UnsupportedEncodingException uee) {
throw new PasswordValidationCallback.PasswordValidationException(uee);
}
final byte[] bytesToHash;
if (decodedNonce != null) {
bytesToHash = new byte[utf8Bytes.length + decodedNonce.length];
for (int i = 0; i < decodedNonce.length; i++)
bytesToHash[i] = decodedNonce[i];
for (int i = decodedNonce.length;
i < utf8Bytes.length + decodedNonce.length;
i++)
bytesToHash[i] = utf8Bytes[i - decodedNonce.length];
} else {
bytesToHash = utf8Bytes;
}
final byte[] hash;
try {
final MessageDigest sha = MessageDigest.getInstance("SHA-1");
hash = sha.digest(bytesToHash);
} catch (final Exception e) {
throw new PasswordValidationCallback.PasswordValidationException(
"Password Digest could not be created" + e);
}
return (passwordDigest.equals(Base64.encode(hash)));
}
}
/**
* LDAP Plain Text validator.
*/
private class LDAPPlainTextPasswordValidator implements
PasswordValidationCallback.PasswordValidator {
/**
* Validate the callback against the injected LDAP server.
* Probably a good idea to have a cache manager - ehcache / hazelcast injected to cache authenticated users.
*
* @param request the callback request
* @return true if login successful
* @throws PasswordValidationCallback.PasswordValidationException
*
*/
public boolean validate(final PasswordValidationCallback.Request request) throws PasswordValidationCallback.PasswordValidationException {
final PasswordValidationCallback.PlainTextPasswordRequest plainTextPasswordRequest =
(PasswordValidationCallback.PlainTextPasswordRequest) request;
final String username = plainTextPasswordRequest.getUsername();
final Authentication authentication;
final Authentication userPassAuth = new UsernamePasswordAuthenticationToken(username, plainTextPasswordRequest.getPassword());
authentication = ldapAuthenticationManager.authenticate(userPassAuth);
return authentication.isAuthenticated();
}
}
/**
* Assert users.
*
* @throws Exception error
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(users, "Users is required.");
Assert.notNull(this.ldapAuthenticationManager, "A LDAP Authentication manager is required.");
}
/**
* Sets the users to validate against. Property names are usernames, property values are passwords.
*
* @param users the users
*/
public void setUsers(final Properties users) {
this.users = users;
}
/**
* The the authentication manager.
*
* @param ldapAuthenticationManager the provider
*/
public void setLdapAuthenticationManager(final AuthenticationManager ldapAuthenticationManager) {
this.ldapAuthenticationManager = ldapAuthenticationManager;
}
}
服务配置:
端点,CallbackHandler和LDAP身份验证管理器的配置。
应用程序上下文–服务器端:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:sws="http://www.springframework.org/schema/web-services"
xmlns:s="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/web-services
http://www.springframework.org/schema/web-services/web-services-2.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<sws:annotation-driven/>
<context:component-scan base-package="javaitzen.spring.ws"/>
<sws:dynamic-wsdl id="exampleService"
portTypeName="javaitzen.spring.ws.ExampleServiceEndpoint"
locationUri="/exampleService/"
targetNamespace="http://www.briandupreez.net/exampleService">
<sws:xsd location="classpath:/xsd/Example1Request.xsd"/>
<sws:xsd location="classpath:/xsd/Example1Response.xsd"/>
<sws:xsd location="classpath:/xsd/Example2Request.xsd"/>
<sws:xsd location="classpath:/xsd/Example2Response.xsd"/>
</sws:dynamic-wsdl>
<sws:interceptors>
<bean id="validatingInterceptor"
class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
<property name="schema" value="classpath:/xsd/Example1Request.xsd"/>
<property name="validateRequest" value="true"/>
<property name="validateResponse" value="true"/>
</bean>
<bean id="loggingInterceptor"
class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>
<bean class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
<property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml"/>
<property name="callbackHandlers">
<list>
<ref bean="callbackHandler"/>
</list>
</property>
</bean>
</sws:interceptors>
<bean id="callbackHandler" class="javaitzen.spring.ws.CustomValidationCallbackHandler">
<property name="ldapAuthenticationManager" ref="authManager" />
</bean>
<s:authentication-manager alias="authManager">
<s:ldap-authentication-provider
user-search-filter="(uid={0})"
user-search-base="ou=users"
group-role-attribute="cn"
role-prefix="ROLE_">
</s:ldap-authentication-provider>
</s:authentication-manager>
<!-- Example... (inmemory apache ldap service) -->
<s:ldap-server id="contextSource" root="o=example" ldif="classpath:example.ldif"/>
<!--
If you want to connect to a real LDAP server it would look more like:
<s:ldap-server id="contextSource" url="ldap://localhost:7001/o=example" manager-dn="uid=admin,ou=system" manager-password="secret">
</s:ldap-server>-->
<bean id="marshallingPayloadMethodProcessor"
class="org.springframework.ws.server.endpoint.adapter.method.MarshallingPayloadMethodProcessor">
<constructor-arg ref="serviceMarshaller"/>
<constructor-arg ref="serviceMarshaller"/>
</bean>
<bean id="defaultMethodEndpointAdapter"
class="org.springframework.ws.server.endpoint.adapter.DefaultMethodEndpointAdapter">
<property name="methodArgumentResolvers">
<list>
<ref bean="marshallingPayloadMethodProcessor"/>
</list>
</property>
<property name="methodReturnValueHandlers">
<list>
<ref bean="marshallingPayloadMethodProcessor"/>
</list>
</property>
</bean>
<bean id="serviceMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>javaitzen.spring.ws.Example1</value>
<value>javaitzen.spring.ws.Example1Response</value>
<value>javaitzen.spring.ws.Example2</value>
<value>javaitzen.spring.ws.Example2Response</value>
</list>
</property>
<property name="marshallerProperties">
<map>
<entry key="jaxb.formatted.output">
<value type="java.lang.Boolean">true</value>
</entry>
</map>
</property>
</bean>
</beans>
安全上下文–服务器端:
xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
<xwss:RequireTimestamp maxClockSkew="60" timestampFreshnessLimit="300"/>
<!-- Expect plain text tokens from the client -->
<xwss:RequireUsernameToken passwordDigestRequired="false" nonceRequired="false"/>
<xwss:Timestamp/>
<!-- server side reply token -->
<xwss:UsernameToken name="server" password="server1" digestPassword="false" useNonce="false"/>
</xwss:SecurityConfiguration>
Web XML:
这里没有什么特别的,只是Spring WS MessageDispatcherServlet。
spring-ws
org.springframework.ws.transport.http.MessageDispatcherServlet
transformWsdlLocationstrue
1
spring-ws
/*
客户端配置:
要测试或使用该服务,您需要:
应用程序上下文–客户端测试:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
<constructor-arg ref="messageFactory"/>
<property name="marshaller" ref="serviceMarshaller"/>
<property name="unmarshaller" ref="serviceMarshaller"/>
<property name="defaultUri" value="http://localhost:7001/example/spring-ws/exampleService"/>
<property name="interceptors">
<list>
<ref local="xwsSecurityInterceptor"/>
</list>
</property>
</bean>
<bean id="xwsSecurityInterceptor"
class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
<property name="policyConfiguration" value="testSecurityPolicy.xml"/>
<property name="callbackHandlers">
<list>
<ref bean="callbackHandler"/>
</list>
</property>
</bean>
<!-- As a client the username and password generated by the server must match with the client! -->
<!-- a simple callback handler to configure users and passwords with an in-memory Properties object. -->
<bean id="callbackHandler"
class="org.springframework.ws.soap.security.xwss.callback.SimplePasswordValidationCallbackHandler">
<property name="users">
<props>
<prop key="server">server1</prop>
</props>
</property>
</bean>
<bean id="serviceMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>javaitzen.spring.ws.Example1</value>
<value>javaitzen.spring.ws.Example1Response</value>
<value>javaitzen.spring.ws.Example2</value>
<value>javaitzen.spring.ws.Example2Response</value>
</list>
</property>
<property name="marshallerProperties">
<map>
<entry key="jaxb.formatted.output">
<value type="java.lang.Boolean">true</value>
</entry>
</map>
</property>
</bean>
安全上下文–客户端:
<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
<xwss:RequireTimestamp maxClockSkew="60" timestampFreshnessLimit="300"/>
<!-- Expect a plain text reply from the server -->
<xwss:RequireUsernameToken passwordDigestRequired="false" nonceRequired="false"/>
<xwss:Timestamp/>
<!-- Client sending to server -->
<xwss:UsernameToken name="example" password="pass" digestPassword="false" useNonce="false"/>
</xwss:SecurityConfiguration>
与Java一样,在jar和版本方面可能会有一些细微差别,因此下面是我使用的pom的一部分。
依赖关系:
3.0.6.RELEASE
2.0.2.RELEASE
org.apache.directory.server
apacheds-all
1.5.5
jar
compile
org.springframework.ws
spring-ws-core
${spring-ws-version}
org.springframework
spring-webmvc
${spring-version}
org.springframework
spring-web
${spring-version}
org.springframework
spring-context
${spring-version}
org.springframework
spring-core
${spring-version}
org.springframework
spring-beans
${spring-version}
org.springframework
spring-oxm
${spring-version}
org.springframework.ws
spring-ws-security
${spring-ws-version}
org.springframework.security
spring-security-core
${spring-version}
org.springframework.security
spring-security-ldap
${spring-version}
org.springframework.ldap
spring-ldap-core
1.3.0.RELEASE
org.apache.ws.security
wss4j
1.5.12
com.sun.xml.wss
xws-security
3.0
org.apache.ws.commons.schema
XmlSchema
1.4.2
</project>
参考: Spring 3,Spring Web Services 2和LDAP安全。 来自我们的JCG合作伙伴 Zen博客中的Zen领域的 Brian Du Preez。
翻译自: https://www.javacodegeeks.com/2012/02/spring-3-spring-web-services-2-ldap.html
spring ldap