之前我们一起学习了如何使用SpringSecurity搭建出一个有权限限制和登录认证的系统,但是这个系统实在是太low了,不仅满足不了我们的使用场景的实际需求,更是无法“软件工程师”和“程序员”的区别,不!那种级别的代码只能被称为“码畜”,提高逼格刻不容缓。
一、UserDetailsService
我们自定义获取用户信息的时候就需要实现这个接口,他只需要重写public UserDetails loadUserByUsername(String username)方法。
上次提到过 UsernamePasswordAuthenticationFilter会拦截相应的请求并且尝试认证登录,实际上UsernamePasswordAuthenticationFilter只负责封装登录信息,并被ProviderManager接收,但是ProviderManager并不会来处理身份验证,他维护了一个AuthenticationProvider的集合,然后他会从这个集合中选出对应的AuthenticationProvider来处理身份认证。
AuthenticationProvider中有两个方法,Authentication authenticate(Authentication authentication)用来认证身份,boolean supports(Class<?> authentication)用来识别对应的Filter,默认的authenticate方法的实现就会调用UserDetailsService的loadUserByUsername方法。
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class<?> authentication);
}
UserDetailsService的loadUserByUsername方法有一个参数username就是用户登录的时候输入的用户名,通过用户名去数据库获取相应的用户信息,同时获取到用户的角色,将角色信息存放到一个GrantedAuthority的集合中,然后将用户信息和角色信息组装成一个UserDetails,示例:
@Service
public class MyUserDetailsService implements UserDetailsService{
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AspnetusersMapper aspnetusersMapper;
@Autowired
private AspnetrolesMapper aspnetrolesMapper;
@Override
public UserDetails loadUserByUsername(String username) {
//获取用户信息
Aspnetusers aspnetusers = aspnetusersMapper.getByUserName(username);
//获取角色
List<String> roles = aspnetrolesMapper.findByUser(username);
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
//这里需要注意字符串role的格式必然是ROLE_XXX,所有的角色都需要以ROLE_开头
for (String role : roles) {
authList.add(new SimpleGrantedAuthority(role));
}
MyUserDetails userDetails= new MyUserDetails(aspnetusers);
userDetails.setAuthList(authList);
return userDetails;
}
}
二、UserDetails
UserDetails是用来存放用户详情的,并且用来判断用户登录认证是否通过。
public interface UserDetails extends Serializable {
/**
* 用户的权限信息
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
* 获取用户名密码
*/
String getPassword();
/**
* 获取用户名
*/
String getUsername();
/**
* 账号是否未过期
*/
boolean isAccountNonExpired();
/**
* 账号是否未锁定
*/
boolean isAccountNonLocked();
/**
* 凭证是否未过期
*/
boolean isCredentialsNonExpired();
/**
* 是否可用
*/
boolean isEnabled();
}
SpringSecurity会通过UserDetails的getPassword方法获取到从数据库拿出的密码,与用户输入的密码进行对比判断,两者一致,并且UserDetails中的各方法返回的都是true,用户通过验证,自定义UserDetails示例:
public class MyUserDetails implements UserDetails {
//用户详细信息
private Aspnetusers user;
//两个构造方法
publicMyUserDetails() {
}
publicMyUserDetails(Aspnetusers user) {
this.user = user;
}
//get/set
public Aspnetusers getUser() {
return user;
}
public void setUser(Aspnetusers user) {
this.user = user;
}
//用户权限
private List<GrantedAuthority> authList;
public List<GrantedAuthority> getAuthList() {
return authList;
}
public void setAuthList(List<GrantedAuthority> authList) {
this.authList = authList;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authList;
}
@Override
public String getPassword() {
return user.getPasswordhash();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
//判断当前时间是否小于账号的有效期
boolean locked = user.getLockoutenabled() && new Date().getTime() < user.getLockoutenddateutc().getTime();
return !locked;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
三、PasswordEncoder
在实际的工作当中用户密码加密是信息安全的最基本手段,SpringSecurity自然也提供了相应的接口——PasswordEncoder,这个接口一共有两个方法String encode(CharSequence rawPassword)将传入的值进行加密,boolean matches(CharSequence rawPassword, String encodedPassword)传入原始密码,和加密后的密码判断两者是否一致,示例:
public class MyPasswordEncoder implements PasswordEncoder {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public String encode(CharSequence rawPassword) {
logger.debug("加密时待加密的密码:" + rawPassword.toString());
//这里简单的加密一下 将原有的密码后面加上password
return rawPassword+"password";
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
logger.debug("校验时待加密的密码:" + rawPassword.toString());
logger.debug("校验时已加密的密码:" + encodedPassword);
return encodedPassword.equals(encode(rawPassword));
}
}
SpringSecurity拿到用户登录时输入的密码,加密后再与UserDetails中的密码进行比较,这就需要我们再存入用户信息的时候对密码加密后再存入数据库中。SpringSecurity已经为我们准备好了许多的加密方式,实际开发中可以拿来直接使用。
万事俱备只欠东风,所有的类都准备好了,下面只需要将它们接入SpringSecurity中就大功告成。
四、配置用户信息获取和密码加密
回到SpringSecurity的配置文件WebSecurityConfigurerAdapter中,重写protected void configure(AuthenticationManagerBuilder auth)方法。
@Bean
public PasswordEncoder passwordEncoder() {
return new MyPasswordEncoder();
}
@Bean
public UserDetailsService userDetails() {
return new MyUserDetailsService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetails()).passwordEncoder(passwordEncoder());
}