如何骗过Spring Security——直接添加Authentication对象
1.需求
在最近的项目中,出现了这样的需求。我需要在后台使用spring security,但是android端显然不能使用像web端登录那样的处理方式,所以如何"骗过"spring security直接在它的认证流程中插入我自己的对象,这成为了我急切的问题。
2.Spring Security的核心组件
SecurityContextHolder
SecurityContextHolder用于获取当前用户的信息,SecurityContextHolder默认使用ThreadLocal来存储认证信息,这是一种与线程绑定的策略。只要在同一个线程中进行,即使不在各个方法之间以参数的形式传递,各个方法也能通过SecurityContextHolder工具获取到安全上下文。
获取当前的用户信息
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
Authentication
源码
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();//密码信息
Object getDetails();//细节信息,记录了ip地址和sessionId的值
Object getPrincipal();//大部分情况下返回UserDetais的实现类
boolean isAuthenticated();//是否认证
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
AuthenticationManager
正如他的名字一样,认证的管理者。他的作用不是直接亲自上场进行认证,而是委派其他有能力的AuthenticationProvider(实际上是实现此接口的实现类进行认证)进行认证,而AuthenticationProvider又由ProviderManager提供。在实际的需求中,我们登陆的方式不同,认证的方式就会不同,所以设计成这样后,如果我们需要自己定义登录方式,则只需要提供相应的AuthenticationProvider就可以了。
DaoAuthenticationProvider
提交的用户名和密码,被封装成了UsernamePasswordAuthenticationToken,而根据用户名加载用户的任务则是交给了UserDetailsService,在DaoAuthenticationProvider中,对应的方法便是retrieveUser,虽然有两个参数,但是retrieveUser只有第一个参数起主要作用,返回一个UserDetails。还需要完成UsernamePasswordAuthenticationToken和UserDetails密码的比对,这便是交给additionalAuthenticationChecks方法完成的,如果这个void方法没有抛异常,则认为比对成功。比对密码的过程,用到了PasswordEncoder和SaltSource。
UserDetails与UserDetailsService
UserDetais源码
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
UserDetails是与UsernamePasswordAuthenticationToken比对的对象,由UserDetailsService获得。UserDetailsService是我们组装UserDetails的地方,我们也可以实现这个接口来完成自定义的组装。
3.Spring Security身份认证流程
- 用户名和密码被过滤器获取到,封装成Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
- AuthenticationManager 身份管理器负责验证这个Authentication
- 认证成功后,AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。
- SecurityContextHolder安全上下文容器将第3步填充了信息的Authentication,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。
4.将自己需要的Authentication对象放入Spring Security中
@RequestMapping("/doLogin")
public int doLogin(@RequestParam("userName") String userName,@RequestParam("password") String password,HttpServletRequest request){
User user = simpleUserService.getUserByName(userName);
//如果用户不存在则抛出异常
if(user==null){
throw new UsernameNotFoundException("没有当前用户");
}
else {
//如果用户存在且用户的密码相同,则在SecurityContextHolder.getContext().setAuthentication()放入authentication
if(user.getPassword().equals(password)){
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, password, AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole()));
authRequest.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authRequest);
return 1;
}
}
return 0;
}