13. 用户登录-准备工作
在开发注册功能时,在SecurityConfig类中配置以如下代码:
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); }
以上代码的作用是关闭跨域攻击,如果没有以上代码,则执行异步请求时就会出错!
一旦添加了以上代码,却没有添加更多详细配置之前,Spring Security的登录拦截将不生效!为了便于开发登录功能,先暂时将以上代码去除(删除,或添加为注释)。
另外,在SecurityConfig中还有:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
以上代码的作用是创建密码加密器对象并交给Spring容器进行管理,以至于需要执行密码加密时,直接自动装配密码加密器即可!
目前,为了保证能够正确登录,需要将以上密码加密器去除,因为,开发完注册功能后,用户注册成功后的密码已经使用密文的形式存储在数据库中了,并且添加了{bcrypt}前缀用于声明加密时使用的算法,Spring Security会自动使用以上代码装配的PasswordEncoder执行1次加密,还会再因为{bcrypt}前缀再执行1次加密,就会导致登录验证失败!
【小结】密文使用${bcrypt}前缀,和让Spring容器管理BcryptPasswordEncoder这2个做法只能二选一!
一旦去除以上代码,就会导致Spring容器中没有PasswordEncoder对象了,但是,在UserServiceImpl中还需要使用到它,则应该将其调整为自行创建的模式,即:
// @Autowired // 需要去除自动装配注解
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
14. 用户登录-基于内存验证的模拟登录
先将application.properties中配置的Spring Security的用户名和密码去除!
然后,在SecurityConfig类(继承自WebSecurityConfigurerAdapter的配置类)中重写protected void configure(AuthenticationManagerBuilder auth)方法,并在这个方法中配置允许使用的用户名、密码及该账号的权限:
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("java") .password("{bcrypt}$2a$10$tsM03ULkiifEpSCWtQ5Mq.yrLZIPKVr5vHwU1FGjtT9B1vPlswa.C") .authorities("/test"); }
以上密文密码的原文是1234。
注意:配置以上代码时,必须调用authorities()以配置授权范围,如果没有配置,将会启动失败,由于当前尚未配置各请求所需要具备的权限,所以,关于以上范围,可以暂时使用任意字符串表示。
15. 用户登录-UserDetailsService接口
Spring Security定义了UserDetailsService接口,在接口存在抽象方法:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
该方法的作用是:给定用户名,需要返回用户详情(UserDetails类型的对象),Spring Security获取到该用户详情后,会自动完成用户身份的验证,包括验证成功之后的用户权限信息,都是由框架处理的,作为开发人员,只需要解决“根据用户名获取用户详情”的问题即可!
可以在cn.tedu.straw.portal.security包中创建UserDetailsServiceImpl类,实现以上接口,模拟实现获取用户数据:
@Component public class UserDetailsServiceImpl implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 假设正确的用户名是security // 假设用户名是正确值:security if ("security".equals(username)) { // 通过Spring-Security提供的User类来构建UserDetails对象 UserDetails userDetails = User.builder() .username("security") .password("{bcrypt}$2a$10$tsM03ULkiifEpSCWtQ5Mq.yrLZIPKVr5vHwU1FGjtT9B1vPlswa.C") .authorities("test") .build(); return userDetails; } return null; } }
注意:以上类必须在组件扫描的包中,并添加@Component注解,则Spring框架会自动创建以上类的对象并管理,后续就可以直接装配这个类的对象了!
然后,回到SecurityConfig类,应用以上类的对象:
@Autowired UserDetailsServiceImpl userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); }
注意:以上全局属性声明为UserDetailsServiceImpl类型,不可以声明为其接口类型,因为接口类型的对象不只1个。
16. 用户登录-查询数据库验证登录
先在IUserService接口中添加抽象方法:
UserDetails login(String username);
严格意义上来说,以上方法并不是“登录”方法,只是一个“获取用户详情”的方法,甚至都不知道登录成功与否,所以,在参数列表中也没有密码,后续,将由Spring Security获取以上方法返回的对象,并验证密码是否正确等。
然后,在UserServiceImpl实现类中重写以上抽象方法:
@Override public UserDetails login(String username) { // 根据参数username查询用户信息 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("username", username); User user = userMapper.selectOne(queryWrapper); // 判断查询结果是否为null,即:有没有这个用户 // 注意:后续的验证和最终的界面是由Spring-Security显示的,此处不要抛出异常 if (user == null) { return null; } // 组织“用户详情”对象 // TODO 未完 UserDetails userDetails = org.springframework.security.core.userdetails.User .builder() .username(user.getUsername()) .password(user.getPassword()) .authorities("test") .build(); return userDetails; }