Spring MVC在进行认证时,是由 AuthenticationManager 来管理的,但是真正进行认证的是 AuthenticationManager 中定义的 AuthenticationProvider。
AuthenticationManager 中可以定义有多个 AuthenticationProvider。当我们使用 authentication-provider 元素来定义一个 AuthenticationProvider 时,如果没有指定对应关联的 AuthenticationProvider 对象,Spring Security 默认会使用 DaoAuthenticationProvider。
DaoAuthenticationProvider 在进行认证的时候需要一个 UserDetailsService 来获取用户的信息 UserDetails,其中包括用户名、密码和所拥有的权限等。
主要的认证流程是:
- Spring MVC会触发AuthenticationProvider的authenticate(Authentication authentication)方法,其中Authentication包含了前段页面输入的账号和密码。
- authenticate(Authentication authentication)根据authentication.getName()获取用户名,然后根据UserDetailsService的loadUserByUsername(String username)方法,获取用户的UserDetails,其中返回的UserDetails包含了服务器已经保存好的用户名、密码和对应的权限。
- 将用户输入的用户名和密码(包含在authentication里)和服务器已经保存好的UserDetails进行对比,如果匹配成功,返回一个UsernamePasswordAuthenticationToken(),否则爆出异常或者返回null。
举例子说明Spring MVC Security自定义认证机制的实现方法。
步骤1.自定义一个CustomerAuthenticationProvider继承AuthenticationProvider,代码如下:
public class CustomerAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = LoggerFactory.getLogger(CustomerAuthenticationProvider.class);
CustomerUserService userDetailsService;
// 该方法为了直接或者xml配置的key-value
public void setUserDetailsService(CustomerUserService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
logger.info("用户输入的用户名是:" + authentication.getName());
logger.info("用户输入的密码是:" + authentication.getCredentials());
// 根据用户输入的用户名获取该用户名已经在服务器上存在的用户详情,如果没有则返回null
UserDetails userDetails = this.userDetailsService.loadUserByUsername(authentication.getName());
try{
logger.info("服务器上已经保存的用户名是:" + userDetails.getUsername());
logger.info("服务器上保存的该用户名对应的密码是: " + userDetails.getPassword());
logger.info("服务器上保存的该用户对应的权限是:" + userDetails.getAuthorities());
//判断用户输入的密码和服务器上已经保存的密码是否一致
if(authentication.getCredentials().equals(userDetails.getPassword())){
logger.info("author success");
//如果验证通过,将返回一个UsernamePasswordAuthenticaionToken对象
return new UsernamePasswordAuthenticationToken(userDetails,
authentication.getCredentials(), userDetails.getAuthorities());
}
}catch (Exception e){
logger.error("author failed, the error message is: " + e);
throw e;
}
//如果验证不通过将抛出异常或者返回null
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
步骤2.自定义一个CustomerUserService继承UserDetailsService,代码如下:
public class CustomerUserService implements UserDetailsService {
private static final Logger logger = LoggerFactory.getLogger(CustomerUserService.class);
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User detail = null;
//判断username是否为null
if(username != null){
ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
authorities.add(new SimpleGrantedAuthority("ROLE_MODELER"));
authorities.add(new SimpleGrantedAuthority("ROLE_ANALYST"));
//可以加入其他判断逻辑,以及根据username获取密码的方法。
//由于是Demo,就直接将密码写死为"TEST",权限直接设置成"ROLE_ADMIN"、"ROLE_MODELER"和"ROLE_ANALYST"
detail = new User(username, "TEST", authorities);
}
return detail;
}
}
步骤3.修改xml,代码如下:
<bean id="userDetailsService" class="CustomerUserService"/>
<bean id ="TCustomerAuthenticationProvider" class="CustomerAuthenticationProvider">
<property name="userDetailsService" ref="userDetailsService"></property>
</bean>
<scr:authentication-manager alias="testingAuthenticationManager">
<scr:authentication-provider ref="TCustomerAuthenticationProvider" />
</scr:authentication-manager>