SpringBoot 集成 SpringSecurity 详解(四)-- 基于MySQL数据库实现多用户认证信息
需求缘起
上一小节中,我们基于内存实现了多用户登录,但是很明显我们不可能为每一个用户这样配置,我们需要对用户进行持久化操作,一般都是通过数据库进行操作的。这一节我们就通过MySql数据库来操作。
技术要点
实现 UserDetailsService 接口中的 loadUserByUsername() 方法,在该方法中的主要逻辑是,将数据库中的用户名,密码和权限信息(现在先不考虑权限相关的知识点)传给 SpringSecurity ,由SpringSecurity 进一步处理。
Spring Data JPA进行操作数据库,关于 Spring JPA 的使用可以参考我的另一篇博文一起学Springboot – 第四节 Spring-data-jpa 操作MySQL数据库(一)
1.添加依赖
添加MySQL数据库依赖,我们在创建项目的时候已经添加过了,没添加过的再添加即可
<!--MySql 依赖,要同步本地安装的数据库版本,我这里是8.0.11-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.11</version>
</dependency>
<!Spring data jpa 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--这里使用阿里巴巴数据连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
2. 添加配置
在application.yml 中添加数据库相关配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/security?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username: root
password: 123456
jpa:
show-sql: true
hibernate:
ddl-auto: update
database: mysql
3.创建实体类 userinfo
@Data
@Entity
public class UserInfo {
@Id//用户id
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增
private Long id;
//用户名
private String username;
//用户密码
private String password;
}
4.创建Repository
JPA 与数据库交互 UserInfoRepository 接口
@Repository
public interface UserInfoRepository extends JpaRepository<UserInfo, Long> {
/**
* 通过用户名获取用户信息
* @param username
* @return
*/
UserInfo findByUsername(String username);
}
5. UserInfo 实现业务逻辑
我们在这实现两个业务逻辑,添加用户信息和通过用户名获取用户信息。
UserInfoService 接口
public interface UserInfoService {
/**
* 创建新用户
* @param userInfo
* @return
*/
UserInfo create(UserInfo userInfo);
/**
* 通过用户名查找用户信息
* @param username
* @return
*/
UserInfo findByUsername(String username);
}
UserInfoService 接口实现类
package com.security.demo.service.impl;
import com.security.demo.entity.UserInfo;
import com.security.demo.repository.UserInfoRepository;
import com.security.demo.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private UserInfoRepository userInfoRepository;
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 创建新用户
*
* @param userInfo
* @return
*/
@Override
public UserInfo create(UserInfo userInfo) {
//密码加密
userInfo.setPassword(passwordEncoder.encode(userInfo.getPassword()));
return userInfoRepository.save(userInfo);
}
/**
* 通过用户名查找用户信息
*
* @param username
* @return
*/
@Override
public UserInfo findByUsername(String username) {
return userInfoRepository.findByUsername(username);
}
}
在springSecurity 5.x之后密码需要加密,当创建用户信息的时候加密,还需要在配置,其实在上一节中我们使用过了,代码如下
@Configuration
@EnableWebSecurity//开启Spring Security的功能
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
//SpringSecurity 提供的一种编码器,我们也可以自己实现PasswordEncoder
return new BCryptPasswordEncoder();
}
}
6.添加用户信息
创建服务测试类 UserInfoServiceImplTest ,在类中添加用户信息
@Component
@Slf4j
public class UserInfoServiceImplTest extends SpringSecurityDemoApplicationTests {
@Autowired
private UserInfoService userInfoService;
@Test
public void create() {
UserInfo userInfo1 = new UserInfo();
userInfo1.setId(1L);
userInfo1.setUsername("admin");
userInfo1.setPassword("123456");
UserInfo userInfoPO1 = userInfoService.create(userInfo1);
log.info("userInfoPO1={}", userInfoPO1);
UserInfo userInfo2 = new UserInfo();
userInfo2.setId(2L);
userInfo2.setUsername("user");
userInfo2.setPassword("123456");
UserInfo userInfoPO2 = userInfoService.create(userInfo2);
log.info("userInfoPO2={}", userInfoPO2);
}
}
执行 create() 方法后如果不出意外数据库中应该有两条数据了,可通过可视化工具或者 mysql 指令查看。
7.实现 UserDetailsService 接口中的 loadUserByUsername() 方法
这个也是这一小节的核心,这个方法的作用是将用户名、密码和权限传递给 Spring Security,
@Component
@Slf4j
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserInfoService userInfoService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("loadUserByUsername->username={}", username);
UserInfo userInfo = userInfoService.findByUsername(username);
if (userInfo == null) {
throw new UsernameNotFoundException("用户不存在");
}
//暂时不考虑权限
List<GrantedAuthority> authorities = new ArrayList<>();
//如果在创建用户信息的时候没有加密,这里就需要做加密处理否则会报错
// User userDetails = new User(userInfo.getUsername(), passwordEncoder.encode(userInfo.getPassword()), authorities);
//已经在创建用户信息的时候做了加密处理,这里直接使用即可
User userDetails = new User(userInfo.getUsername(), userInfo.getPassword(), authorities);
return userDetails;
}
}
8.启动测试即可
具体测试这里不写了,参考上一篇,不出意外,项目将可以正常运行,本节Demo代码