gpt4 book ai didi

java - 如何使用 Spring Security 对 Active Directory 服务器进行身份验证?

转载 作者:IT老高 更新时间:2023-10-28 13:45:31 24 4
gpt4 key购买 nike

我正在编写一个需要用户登录的 Spring Web 应用程序。我的公司有一台 Active Directory 服务器,我想将其用于此目的。但是,我在使用 Spring Security 连接到服务器时遇到了问题。

我正在使用 Spring 2.5.5 和 Spring Security 2.0.3,以及 Java 1.6。

如果我将 LDAP URL 更改为错误的 IP 地址,它不会抛出异常或任何东西,所以我想知道它是否甚至 尝试 开始连接到服务器。

虽然 Web 应用程序启动得很好,但我在登录页面中输入的任何信息都会被拒绝。我之前使用过一个 InMemoryDaoImpl,它工作得很好,所以我的应用程序的其余部分似乎配置正确。

这是我的安全相关 bean:

  <beans:bean id="ldapAuthProvider" class="org.springframework.security.providers.ldap.LdapAuthenticationProvider">
<beans:constructor-arg>
<beans:bean class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
<beans:constructor-arg ref="initialDirContextFactory" />
<beans:property name="userDnPatterns">
<beans:list>
<beans:value>CN={0},OU=SBSUsers,OU=Users,OU=MyBusiness,DC=Acme,DC=com</beans:value>
</beans:list>
</beans:property>
</beans:bean>
</beans:constructor-arg>
</beans:bean>

<beans:bean id="userDetailsService" class="org.springframework.security.userdetails.ldap.LdapUserDetailsManager">
<beans:constructor-arg ref="initialDirContextFactory" />
</beans:bean>

<beans:bean id="initialDirContextFactory" class="org.springframework.security.ldap.DefaultInitialDirContextFactory">
<beans:constructor-arg value="ldap://192.168.123.456:389/DC=Acme,DC=com" />
</beans:bean>

最佳答案

我有与你一样的撞墙体验,并最终编写了一个自定义身份验证提供程序,对 Active Directory 服务器执行 LDAP 查询。

所以我与安全相关的 bean 是:

<beans:bean id="contextSource"
class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<beans:constructor-arg value="ldap://hostname.queso.com:389/" />
</beans:bean>

<beans:bean id="ldapAuthenticationProvider"
class="org.queso.ad.service.authentication.LdapAuthenticationProvider">
<beans:property name="authenticator" ref="ldapAuthenticator" />
<custom-authentication-provider />
</beans:bean>

<beans:bean id="ldapAuthenticator"
class="org.queso.ad.service.authentication.LdapAuthenticatorImpl">
<beans:property name="contextFactory" ref="contextSource" />
<beans:property name="principalPrefix" value="QUESO\" />
</beans:bean>

然后是 LdapAuthenticationProvider 类:

/**
* Custom Spring Security authentication provider which tries to bind to an LDAP server with
* the passed-in credentials; of note, when used with the custom {@link LdapAuthenticatorImpl},
* does <strong>not</strong> require an LDAP username and password for initial binding.
*
* @author Jason
*/
public class LdapAuthenticationProvider implements AuthenticationProvider {

private LdapAuthenticator authenticator;

public Authentication authenticate(Authentication auth) throws AuthenticationException {

// Authenticate, using the passed-in credentials.
DirContextOperations authAdapter = authenticator.authenticate(auth);

// Creating an LdapAuthenticationToken (rather than using the existing Authentication
// object) allows us to add the already-created LDAP context for our app to use later.
LdapAuthenticationToken ldapAuth = new LdapAuthenticationToken(auth, "ROLE_USER");
InitialLdapContext ldapContext = (InitialLdapContext) authAdapter
.getObjectAttribute("ldapContext");
if (ldapContext != null) {
ldapAuth.setContext(ldapContext);
}

return ldapAuth;
}

public boolean supports(Class clazz) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(clazz));
}

public LdapAuthenticator getAuthenticator() {
return authenticator;
}

public void setAuthenticator(LdapAuthenticator authenticator) {
this.authenticator = authenticator;
}

}

然后是 LdapAuthenticatorImpl 类:

/**
* Custom Spring Security LDAP authenticator which tries to bind to an LDAP server using the
* passed-in credentials; does <strong>not</strong> require "master" credentials for an
* initial bind prior to searching for the passed-in username.
*
* @author Jason
*/
public class LdapAuthenticatorImpl implements LdapAuthenticator {

private DefaultSpringSecurityContextSource contextFactory;
private String principalPrefix = "";

public DirContextOperations authenticate(Authentication authentication) {

// Grab the username and password out of the authentication object.
String principal = principalPrefix + authentication.getName();
String password = "";
if (authentication.getCredentials() != null) {
password = authentication.getCredentials().toString();
}

// If we have a valid username and password, try to authenticate.
if (!("".equals(principal.trim())) && !("".equals(password.trim()))) {
InitialLdapContext ldapContext = (InitialLdapContext) contextFactory
.getReadWriteContext(principal, password);

// We need to pass the context back out, so that the auth provider can add it to the
// Authentication object.
DirContextOperations authAdapter = new DirContextAdapter();
authAdapter.addAttributeValue("ldapContext", ldapContext);

return authAdapter;
} else {
throw new BadCredentialsException("Blank username and/or password!");
}
}

/**
* Since the InitialLdapContext that's stored as a property of an LdapAuthenticationToken is
* transient (because it isn't Serializable), we need some way to recreate the
* InitialLdapContext if it's null (e.g., if the LdapAuthenticationToken has been serialized
* and deserialized). This is that mechanism.
*
* @param authenticator
* the LdapAuthenticator instance from your application's context
* @param auth
* the LdapAuthenticationToken in which to recreate the InitialLdapContext
* @return
*/
static public InitialLdapContext recreateLdapContext(LdapAuthenticator authenticator,
LdapAuthenticationToken auth) {
DirContextOperations authAdapter = authenticator.authenticate(auth);
InitialLdapContext context = (InitialLdapContext) authAdapter
.getObjectAttribute("ldapContext");
auth.setContext(context);
return context;
}

public DefaultSpringSecurityContextSource getContextFactory() {
return contextFactory;
}

/**
* Set the context factory to use for generating a new LDAP context.
*
* @param contextFactory
*/
public void setContextFactory(DefaultSpringSecurityContextSource contextFactory) {
this.contextFactory = contextFactory;
}

public String getPrincipalPrefix() {
return principalPrefix;
}

/**
* Set the string to be prepended to all principal names prior to attempting authentication
* against the LDAP server. (For example, if the Active Directory wants the domain-name-plus
* backslash prepended, use this.)
*
* @param principalPrefix
*/
public void setPrincipalPrefix(String principalPrefix) {
if (principalPrefix != null) {
this.principalPrefix = principalPrefix;
} else {
this.principalPrefix = "";
}
}

}

最后是 LdapAuthenticationToken 类:

/**
* <p>
* Authentication token to use when an app needs further access to the LDAP context used to
* authenticate the user.
* </p>
*
* <p>
* When this is the Authentication object stored in the Spring Security context, an application
* can retrieve the current LDAP context thusly:
* </p>
*
* <pre>
* LdapAuthenticationToken ldapAuth = (LdapAuthenticationToken) SecurityContextHolder
* .getContext().getAuthentication();
* InitialLdapContext ldapContext = ldapAuth.getContext();
* </pre>
*
* @author Jason
*
*/
public class LdapAuthenticationToken extends AbstractAuthenticationToken {

private static final long serialVersionUID = -5040340622950665401L;

private Authentication auth;
transient private InitialLdapContext context;
private List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

/**
* Construct a new LdapAuthenticationToken, using an existing Authentication object and
* granting all users a default authority.
*
* @param auth
* @param defaultAuthority
*/
public LdapAuthenticationToken(Authentication auth, GrantedAuthority defaultAuthority) {
this.auth = auth;
if (auth.getAuthorities() != null) {
this.authorities.addAll(Arrays.asList(auth.getAuthorities()));
}
if (defaultAuthority != null) {
this.authorities.add(defaultAuthority);
}
super.setAuthenticated(true);
}

/**
* Construct a new LdapAuthenticationToken, using an existing Authentication object and
* granting all users a default authority.
*
* @param auth
* @param defaultAuthority
*/
public LdapAuthenticationToken(Authentication auth, String defaultAuthority) {
this(auth, new GrantedAuthorityImpl(defaultAuthority));
}

public GrantedAuthority[] getAuthorities() {
GrantedAuthority[] authoritiesArray = this.authorities.toArray(new GrantedAuthority[0]);
return authoritiesArray;
}

public void addAuthority(GrantedAuthority authority) {
this.authorities.add(authority);
}

public Object getCredentials() {
return auth.getCredentials();
}

public Object getPrincipal() {
return auth.getPrincipal();
}

/**
* Retrieve the LDAP context attached to this user's authentication object.
*
* @return the LDAP context
*/
public InitialLdapContext getContext() {
return context;
}

/**
* Attach an LDAP context to this user's authentication object.
*
* @param context
* the LDAP context
*/
public void setContext(InitialLdapContext context) {
this.context = context;
}

}

您会注意到其中有一些您可能不需要的位。

例如,我的应用需要保留成功登录的 LDAP 上下文以供用户在登录后进一步使用——该应用的目的是让用户通过其 AD 凭据登录,然后执行进一步的与 AD 相关的操作功能。因此,因此,我有一个自定义身份验证 token LdapAuthenticationToken,我可以传递它(而不是 Spring 的默认身份验证 token ),它允许我附加 LDAP 上下文。在 LdapAuthenticationProvider.authenticate() 中,我创建了该 token 并将其传回;在 LdapAuthenticatorImpl.authenticate() 中,我将登录的上下文附加到返回对象,以便可以将其添加到用户的 Spring 身份验证对象中。

另外,在 LdapAuthenticationProvider.authenticate() 中,我为所有登录用户分配了 ROLE_USER 角色——这让我可以在我的拦截 url 元素中测试该角色。您需要匹配您要测试的任何角色,甚至根据 Active Directory 组或其他任何角色分配角色。

最后,由此推论,我实现 LdapAuthenticationProvider.authenticate() 的方式为所有拥有有效 AD 帐户的用户提供了相同的 ROLE_USER 角色。显然,在该方法中,您可以对用户执行进一步的测试(即,用户是否在特定的 AD 组中?)并以这种方式分配角色,甚至在授予用户访问权限之前测试某些条件 all .

关于java - 如何使用 Spring Security 对 Active Directory 服务器进行身份验证?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/84680/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com