前言
继续第三篇,这次增加了Spring Security的DaoAuthenticationProvider和UserDetailsService类,离源代码又会更近一步。
从一个小程序开始
使用DaoAuthenticationProvider类来代替上篇中自定义类SimpleAuthenticationProvider,代码如下:
public class AuthenticationExample {
public static List<AuthenticationProvider> providers = Arrays.asList(getDaoAuthentication());
public static AuthenticationManager am = new ProviderManager(providers);
public static void main(String[] args) throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while (true) {
System.out.println("Please enter your username:");
String name = in.readLine();
System.out.println("Please enter your password:");
String password = in.readLine();
try {
Authentication request = new UsernamePasswordAuthenticationToken(name, password);
Authentication result = am.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
break;
} catch (AuthenticationException e) {
System.out.println("Authentication failed: " + e.getMessage());
}
}
System.out.println("Successfully authenticated. Security context contains: " +
SecurityContextHolder.getContext().getAuthentication());
}
public static AuthenticationProvider getDaoAuthentication() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
UserDetails admin = User.withUsername("admin")
.password(passwordEncoder.encode("123456"))
.authorities("ROLE_USER")
.build();
UserDetailsService uds = new InMemoryUserDetailsManager(admin);
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(uds);
return provider;
}
}
在getDaoAuthentication方法中,还使用了UserDetails、UserDetailsService和PasswordEncoder用来读取用户信息。这里直接用代码添加的用户,而真实项目开发会从数据库中读取用户信息。
DaoAuthenticationProvider
DaoAuthenticationProvider类的源代码如下,retrieveUser方法是最关键的。通过username用来获取用户相关信息。此处用到了UserDetailsServiceo类的loadUserByUsername方法,请往下看。
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
...
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
...
}
UserDetailsService
UserDetailsService接口中只定义了一个loadUserByUsername方法。实现接口的类主要有InMemoryUserDetailsManager和JdbcUserDetailsManager,一个是通过HashMap存储和读取用户信息列表,一个是通过读取数据库中的用户信息列表。
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
AbstractUserDetailsAuthenticationProvider
回个头再来看看DaoAuthenticationProvider类的父类AbstractUserDetailsAuthenticationProvider。下面的只展示了关键代码。
public abstract class AbstractUserDetailsAuthenticationProvider implements
AuthenticationProvider, InitializingBean, MessageSourceAware {
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
postAuthenticationChecks.check(user);
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return createSuccessAuthentication(principalToReturn, authentication, user);
}
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
}
可以看到,这里authenticate方法,也就是我们上篇使用到的,用于用户认证。在调用authenticate方法时,就会去调用DaoAuthenticationProvider类重写的retrieveUser方法。验证成功后,createSuccessAuthentication方法就返回了一个完整的授权后的Authentication。