SpringBoot 集成 SpringSecurity 详解(六)-- 基于MySql实现角色授权
需求缘起
上一小节中,我们基于内存实现了角色授权,这一节我们就通过MySql数据库来操作。
本节 demo
技术要点
实现 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.创建实体类
3.1 创建用户信息实体 userinfo
@Data
@Entity
public class UserInfo {
@Id//用户id
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增
private Long id;
//角色id
private Long rid;
//用户名
private String username;
//用户密码
private String password;
}
3.2 创建角色信息实体RoleInfo
@Entity
@Data
public class RoleInfo {
@Id//角色id
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增
private Long id;
//角色名
private String name;
//该角色所拥有的权限名称
private String authority;
}
4.创建操作数据库接口层 Repository
4.1 UserInfoRepository
@Repository
public interface UserInfoRepository extends JpaRepository<UserInfo, Long> {
/**
* 通过用户名获取用户信息
* @param username
* @return
*/
UserInfo findByUsername(String username);
}
4.2 RoleInfoRepository
@Repository
public interface RoleInfoRepository extends JpaRepository<RoleInfo, Long> {
}
5. 实现业务逻辑
5.1 实现角色的创建和通过id获取角色信息
public interface RoleInfoService {
/**
* 创建角色
* @param userInfo
* @return
*/
RoleInfo create(RoleInfo userInfo);
/**
* 通过id获取角色信息
* @param id
* @return
*/
RoleInfo findById(Long id);
}
@Service
public class RoleInfoServiceImpl implements RoleInfoService {
@Autowired
private RoleInfoRepository roleInfoRepository;
/**
* 创建角色
*
* @param userInfo
* @return
*/
@Override
public RoleInfo create(RoleInfo userInfo) {
return roleInfoRepository.save(userInfo);
}
/**
* 通过id获取角色信息
*
* @param id
* @return
*/
@Override
public RoleInfo findById(Long id) {
return roleInfoRepository.findById(id).orElse(null);
}
}
5.2 实现用户信息的创建和通过用户名获取用户信息
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.添加角色信息和用户信息
6.1 添加角色信息
@Slf4j
public class RoleInfoServiceImplTest extends SpringSecurityDemoApplicationTests {
@Autowired
private RoleInfoService roleInfoService;
@Test
public void create() {
RoleInfo roleInfo1 = new RoleInfo();
roleInfo1.setId(1L);
roleInfo1.setName("管理员");
roleInfo1.setAuthority("ADMIN");
roleInfoService.create(roleInfo1);
RoleInfo roleInfo2 = new RoleInfo();
roleInfo2.setId(2L);
roleInfo2.setName("普通用户");
roleInfo2.setAuthority("USER");
roleInfoService.create(roleInfo2);
}
@Test
public void findById() {
RoleInfo result = roleInfoService.findById(1L);
log.info("result:{}", result);
}
}
6.2 添加用户信息
@Component
@Slf4j
public class UserInfoServiceImplTest extends SpringSecurityDemoApplicationTests {
@Autowired
private UserInfoService userInfoService;
@Test
public void create() {
UserInfo userInfo1 = new UserInfo();
userInfo1.setRid(1L);
userInfo1.setUsername("admin");
userInfo1.setPassword("123456");
UserInfo userInfoPO1 = userInfoService.create(userInfo1);
log.info("userInfoPO1={}", userInfoPO1);
UserInfo userInfo2 = new UserInfo();
userInfo2.setRid(2L);
userInfo2.setUsername("user");
userInfo2.setPassword("123456");
UserInfo userInfoPO2 = userInfoService.create(userInfo2);
log.info("userInfoPO2={}", userInfoPO2);
}
@Test
public void findByUsername(){
UserInfo result = userInfoService.findByUsername("admin");
log.info("result:{}", result);
}
}
7.实现认证和授权
实现认证和授权在 UserDetailsService 接口中的 loadUserByUsername() 方法中进行,
这个也是这一小节的核心,这个方法的作用是将用户名、密码和该用户的权限传递给 Spring Security,
@Component
@Slf4j
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserInfoService userInfoService;
@Autowired
private RoleInfoService roleInfoService;
/**
* 在这个方法中实现用户身份认证和授权
* @param username
* @return
* @throws UsernameNotFoundException
*/
@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<>();
RoleInfo roleInfo = roleInfoService.findById(userInfo.getRid());
log.info("roleInfo={}",roleInfo);
if (roleInfo != null) {
// 用户可以访问的资源名称(或者说用户所拥有的权限) 注意:必须"ROLE_"开头
authorities.add(new SimpleGrantedAuthority("ROLE_" + roleInfo.getAuthority()));
}
//注意要在创建用户信息的时候密码需要加密
User userDetails = new User(userInfo.getUsername(), userInfo.getPassword(), authorities);
return userDetails;
}
}
8.编写控制器
@RestController
@RequestMapping("/hello")
public class HelloSecurityController {
@GetMapping
public String hello(){
return "Hello Spring Security!";
}
/**
* 在这里例子中 用户 ADMIN和 USER 都能访问这个接口
* @return
*/
@GetMapping("/helloUser")
@PreAuthorize("hasAnyRole('ADMIN','USER')")//访问这个接口需要有 ADMIN或者USER的权限的用户才能访问
public String user() {
return "Hello,user";
}
/**
* 在这里例子中 用户 USER是不能访问这个接口的,会报权限不足异常
* @return
*/
@GetMapping("/helloAdmin")
@PreAuthorize("hasAnyRole('ADMIN')")//访问这个接口需要有 ADMIN的权限的用户才能访问
public String admin() {
return "Hello,admin";
}
}
9.Security 配置
不要忘了配置
@Configuration
@EnableWebSecurity//开启Spring Security的功能
@EnableGlobalMethodSecurity(prePostEnabled=true)//开启方法安全级别的控制
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
//SpringSecurity 提供的一种编码器,我们也可以自己实现PasswordEncoder
return new BCryptPasswordEncoder();
}
}
10.测试
- 重启应用,访问http://localhost:8080/hello/helloUser,输入用户名 “user” 和 密码 “123456”,不出意外将会顺利访问,返回 “Hello,user”;
- 此时再去访问 http://localhost:8080/hello/helloAdmin,会报403异常,因为这个接口用户"user"是没有权限访问的;
- 重启应用,访问http://localhost:8080/hello/helloUser,输入用户名 “admin” 和 密码 “123456”,不出意外将会顺利访问,返回 “Hello,user”,因为这个接口用户"admin"也有权限访问;
- 此时再去访问 http://localhost:8080/hello/helloAdminr,会发现也能顺利访问,因为这个接口用户"admin"有权限访问;