Spring Security框架完成用户登录认证的核心就在与org.springframework.security.core.userdetails
包下的UserDetailsService
接口
UserDetailsService接口只有一个抽象方法就是loadUserByUsername(String username),
UserDetailsService接口的返回值是UserDetails
接口, 这又是一个接口,Spring Security框架提供了它的实现类org.springframework.security.core.userdetails
包下的User
类对象
我们再看看UserDetailsService接口提供的默认的实现类
我们在分别看看UserDetailsService的实现类JdbcDaoImpl
和InMemoryUserDetailsManager
都是怎么实现这个loadUserByUsername(String username)方法的
我们在分别看看UserDetailsService的实现类JdbcDaoImpl
中loadUserByUsername(String username)方法的实现过程
下图主要就是验证一下createuserDetails方法的返回值是不是一个User对象,没有什么具体含义。
看看InMemoryUserDetailsManager
类的loadUserByUsername(String username)方法的具体实现
通过以上的源码解读, 我们可以大胆猜测一下一个是不是只要我们在我们自己的项目中定义一个自定义的实现类实现这个UserDetailsService,就可以完成我们自定义的登录认证逻辑, 接下来我们自己写一个实现类来尝试一下。
自定义实现类MyUserDetailsServiceImpl
package com.yige.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
private static final String USERNAME = "test";
private static final String PASSWORD = "test";
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
if(!USERNAME.equals(userName)){
throw new UsernameNotFoundException("用户名不存在");
}
UserDetails userDetails = new User(USERNAME,
PASSWORD,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,common"));
return userDetails;
}
}
重启项目看看,能不能生效。
登录输入用户名"test", 密码"test",发现页面刷新了一下又回到登录页面了, 怎么回事?
检查了一下后台程序,发现后台报错了,没有使用任何的PasswordEncoder,我们输入的密码没有用加密工具进行加密
Spring Security其实已经给我们提供了很多的PasswordEncoder, 我们来看一下
在org.springframework.security.crypto.password
包下有一个PasswordEncoder
接口,看看他的实现类
所有这里就很简单了啊,两种方式可以搞定:
这里我们采用第一种方式,人家已经实现了, 不需要重复造轮子,当然如果你确信你自己造的轮子更好就可以用你自己的。
package com.yige.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
修改MyUserDetailsServiceImpl
类如下:
package com.yige.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
/**
* @Author: karma
* @Date: 2022/3/21 0021 - 03 - 21 - 11:20
* @Description: com.yige.service.impl
* @version: 1.0
*/
@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
private static final String USERNAME = "test";
private static final String PASSWORD = "test";
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
if(!USERNAME.equals(userName)){
throw new UsernameNotFoundException("用户名不存在");
}
return new User(userName, passwordEncoder.encode(PASSWORD), AuthorityUtils.commaSeparatedStringToAuthorityList("admin,common"));
}
}
重启项目再次测试。
登录输入用户名"test", 密码"test",确认登录,
页面跳转到http://localhost:8080,说明我们的登录认证成功了。以上测试说明我们自定义的登录认证逻辑生效了,
由以上测试我们可以得出结论,当我们自定义实现了UserDetailsService
逻辑并注入bean到容器之后, 项目再次启动之后,在进行登录认证的时候就会使用我们自定义的逻辑来进行处理, 不会在使用Spring Security框架默认的逻辑进行认证处理。
当前我们的实现都是把用户名、密码、权限都是硬编码在我们自己的代码中, 这样肯定是不行的, 下一节我们着重说说怎么让我们自定义的登录认证跟我们的数据库搭配使用,从数据库中获取用户的数据信息来完成登录认证的过程。