之前我们一起学习了如何使用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 自定义provider_源码分析

        万事俱备只欠东风,所有的类都准备好了,下面只需要将它们接入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());
}