Spring Security , authenticate use LDAP& DataBase.

本文介绍了一种结合LDAP和数据库的用户认证方案。该方案首先判断是否启用LDAP认证,若未启用,则通过数据库进行用户验证;若启用LDAP认证且认证成功,将从数据库中获取用户的权限信息。
基础部分参见:
http://suene.iteye.com/blog/1829807



<authentication-manager>
<authentication-provider ref="authenticationProvider" />
</authentication-manager>

<beans:bean id="authenticationProvider" class="org.e.simple.authtication.LdapAndDbAuthenticationProvider">
<beans:property name="authenticateByLdap" value="false" />
<beans:property name="url" value="ldap://localhost:10389/dc=example,dc=com" />
<beans:property name="userSearchBase" value="ou=Users" />
<beans:property name="hash" value="{sha}" />
</beans:bean>



/** 先根据属性 {@link #authenticateByLdap }, 是否使用 LDAP 验证 用户信息. 否则使用数据库查询验证用户.<br/>
*
* 如果验证成功,则以数据库获取用户的权限. */
public class LdapAndDbAuthenticationProvider implements AuthenticationProvider
{

private static final Logger logger = LoggerFactory.getLogger(LdapAndDbAuthenticationProvider.class);

@Autowired
private UserRepository userRepository;

/** LDAP server */
private DefaultSpringSecurityContextSource contextSource;

/** LdapAuthenticator */
private LdapAuthenticator authenticator;

/** 是否使用 LDAP 验证用户. */
private boolean authenticateByLdap;

// ldap url
private String url;

// <user-dn-pattern/>
private String userDnPattern;

// <user-search-filter/>
private String userSearchFilter = "(uid={0})";

// <user-search-base/>
private String userSearchBase = "";

// <password-compare />
// 是否验证密码.
private boolean passwordCompar = true;

// <password-attribute/>
private String passwordAttribute = "userPassword";

// password-compare : hash
// default = plaintext
// 参考 org.springframework.security.config.authentication.PasswordEncoderParser.ENCODER_CLASSES
private String hash = "plaintext";

// password-encoder : base64
private boolean useBase64;

// 获取对应的 pwEncoder
private PasswordEncoder pwEncoder;

@PostConstruct
public void init() throws Exception
{

if (passwordCompar)
{

AbstractBeanDefinition def = (AbstractBeanDefinition) PasswordEncoderParser.createPasswordEncoderBeanDefinition(hash, useBase64);
Object pwEncoderObj = def.getBeanClass().newInstance();

if (pwEncoderObj instanceof BaseDigestPasswordEncoder)
{
((BaseDigestPasswordEncoder) pwEncoderObj).setEncodeHashAsBase64(useBase64);
}

if (pwEncoderObj instanceof PasswordEncoder)
{
this.pwEncoder = (PasswordEncoder) pwEncoderObj;
}
}

if (authenticateByLdap)
{
contextSource = new DefaultSpringSecurityContextSource(url);
contextSource.afterPropertiesSet();

LdapUserSearch userSearch = new FilterBasedLdapUserSearch(this.userSearchBase, this.userSearchFilter, contextSource);

String[] userDnPatternArray = new String[0];

if (StringUtils.hasText(userDnPattern))
{
userDnPatternArray = new String[]
{ userDnPattern };
}

if (passwordCompar)
{
authenticator = new PasswordComparisonAuthenticator(contextSource);
((PasswordComparisonAuthenticator) authenticator).setPasswordAttributeName(passwordAttribute);
((PasswordComparisonAuthenticator) authenticator).setPasswordEncoder((PasswordEncoder) pwEncoder);
((PasswordComparisonAuthenticator) authenticator).setUserDnPatterns(userDnPatternArray);
((PasswordComparisonAuthenticator) authenticator).setUserSearch(userSearch);

} else
{
authenticator = new BindAuthenticator(contextSource);
((BindAuthenticator) authenticator).setUserDnPatterns(userDnPatternArray);
((BindAuthenticator) authenticator).setUserSearch(userSearch);
}
}
}

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
final UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken) authentication;

String username = userToken.getName();
String password = (String) authentication.getCredentials();

if (!StringUtils.hasLength(username))
{
throw new BadCredentialsException("Empty Username");
}

if (!StringUtils.hasLength(password))
{
throw new BadCredentialsException("Empty Password");
}

List<User> users = userRepository.findByUsername(username);

if (CollectionUtils.isEmpty(users))
{
throw new BadCredentialsException("Bad credentials");
}

UserDetails user = users.get(0);

if (authenticateByLdap)
{
doLdapAuthentication(userToken);
} else
{
if (passwordCompar)
{
String encodePassword = pwEncoder.encodePassword(password, null);
if (!encodePassword.equals(user.getPassword()))
{
throw new BadCredentialsException("Bad credentials");
}
}
}

return createSuccessfulAuthentication(userToken, user);
}

protected DirContextOperations doLdapAuthentication(UsernamePasswordAuthenticationToken authentication)
{
try
{
return authenticator.authenticate(authentication);
} catch (Exception e)
{
logger.error(e.getMessage(), e);
throw new BadCredentialsException("Bad credentials");
}
}

@Override
public boolean supports(Class<?> authentication)
{
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}

protected Authentication createSuccessfulAuthentication(UsernamePasswordAuthenticationToken authentication, UserDetails user)
{
Object password = authentication.getCredentials();

UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
result.setDetails(authentication.getDetails());

return result;
}

// set method...

}
2025-05-28 10:54:59.292 ERROR 28320 --- [nio-8080-exec-7] w.a.UsernamePasswordAuthenticationFilter : An internal error occurred while trying to authenticate the user. org.springframework.security.authentication.InternalAuthenticationServiceException: For queries with named parameters you need to use provide names for method parameters. Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters.; nested exception is java.lang.IllegalStateException: For queries with named parameters you need to use provide names for method parameters. Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters. at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:123) ~[spring-security-core-5.3.9.RELEASE.jar:5.3.9.RELEASE] at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:144) ~[spring-security-core-5.3.9.RELEASE.jar:5.3.9.RELEASE] at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:199) ~[spring-security-core-5.3.9.RELEASE.jar:5.3.9.RELEASE] at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:219) ~[spring-security-core-5.3.9.RELEASE.jar:5.3.9.RELEASE] at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:95) ~[spring-security-web-5.3.9.RELEASE.jar:5.3.9.RELEASE] at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.3.9.RELEASE.jar:5.3.9.RELEASE] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.3.9.RELEASE.jar:5.3.9.RELEASE] at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) [spring-security-web-5.3.9.R
05-29
<think>好的,我现在需要解决用户遇到的Spring Security认证错误。错误信息提到在使用命名参数的查询时,需要为方法参数提供名称,建议使用@Param注解或Java8+的-parameters编译标志。用户可能是在自定义的UserDetailsService中使用了JPA查询方法,但参数没有正确命名。首先,我要分析错误堆栈。异常发生在DaoAuthenticationProvider的retrieveUser方法,说明加载用户时出了问题。根本原因是JPA查询方法中的参数没有正确命名,导致Spring Data无法绑定参数。接下来,我需要确定用户可能哪里出错了。可能是在UserRepository中定义了一个基于方法名的查询,比如findByUsername,但参数没有用@Param注解,或者在JPQL中使用了命名参数但没有对应注解。例如,用户可能写了这样的方法: @Query("SELECT uFROM Useru WHEREu.username= :username")User findByUsername(Stringusername);这里参数名应该用@Param("username")标注,否则会抛出IllegalStateException。解决方案是检查所有自定义的JPA查询方法,确保每个命名参数都使用了@Param注解,或者在编译时启用了-parameters选项。如果用户用的是Java8以上,可以配置构建工具来添加该选项,但这可能不如显式使用注解可靠。 另外,用户可能没有正确配置UserDetailsService,导致SpringSecurity在认证时调用了存在问题的Repository方法。需要确保UserDetailsService的实现正确调用了带有正确参数绑定的查询方法。最后,建议用户检查他们的Repository接口,特别是自定义查询方法,添加必要的@Param注解,并确保编译配置正确。同时,确认UserDetailsService是否正确注入并使用这些Repository方法。</think>**问题原因**: 该错误发生在Spring Security的认证流程中,具体原因是JPA查询方法参数绑定失败。当使用命名参数(:parameter)的JPQL/HQL查询时,必须通过以下方式明确参数名称: 1. 使用`@Param`注解显式标注方法参数 2. 启用Java 8的`-parameters`编译参数(自动保留参数名称) **解决方案**: ```java // UserRepository.java的正确配置 public interface UserRepository extends JpaRepository<User, Long> { // 方式1:使用@Param注解 @Query("SELECT u FROM User u WHERE u.username = :username") User findByUsername(@Param("username") String username); // 方式2:启用-parameters编译参数(无需注解) @Query("SELECT u FROM User u WHERE u.email = :email") User findByEmail(String email); } ``` **配置优化步骤**: 1. **参数绑定修正**: ```java // 错误示例(缺少参数绑定) @Query("SELECT u FROM User u WHERE u.username = :uname") User findUser(String uname); // 参数名称不匹配 // 正确修正 @Query("SELECT u FROM User u WHERE u.username = :uname") User findUser(@Param("uname") String username); ``` 2. **编译参数配置**(Maven): ```xml <!-- pom.xml构建配置 --> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <compilerArgs> <arg>-parameters</arg> </compilerArgs> </configuration> </plugin> </plugins> </build> ``` 3. **用户服务层验证**: ```java // 正确实现的UserDetailsService @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found"); } return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), getAuthorities(user)); } } ``` **错误预防机制**: 1. **单元测试验证**: ```java @SpringBootTest public class UserRepositoryTest { @Autowired private UserRepository userRepository; @Test void shouldFindByUsername() { User user = userRepository.findByUsername("admin"); assertThat(user).isNotNull(); } } ``` 2. **静态代码分析**(使用Checkstyle): ```xml <!-- checkstyle规则配置 --> <module name="ParameterName"> <property name="format" value="^[a-z][a-zA-Z0-9]*$"/> </module> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值