开启 Security 权限组
- 权限组 是 权限的集合
- 权限 小于 角色
- 在 Spring Security 中,没有明确划分 权限 与 角色 表
- 在 Spring Security 将 权限 和 角色 放在相同的表和字段中
配置
mysql
-- ----------------------------
-- Table structure for group_authorities
-- ----------------------------
CREATE TABLE group_authorities
(
authority varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
group_id int(11) NULL DEFAULT NULL,
UNIQUE INDEX uk_group_authority (authority, group_id) USING BTREE
) ENGINE = InnoDB
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_general_ci
ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for group_members
-- ----------------------------
CREATE TABLE group_members
(
username varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
group_id int(11) NULL DEFAULT NULL,
INDEX uk_group_members (username, group_id) USING BTREE
) ENGINE = InnoDB
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_general_ci
ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for groups
-- ----------------------------
CREATE TABLE `groups`
(
id int(11) NOT NULL AUTO_INCREMENT,
group_name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (id) USING BTREE,
UNIQUE INDEX uk_groups_name (group_name) USING BTREE
) ENGINE = InnoDB
AUTO_INCREMENT = 1
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_general_ci
ROW_FORMAT = Dynamic;
java
package cloud.xuxiaowei.passport.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import javax.sql.DataSource;
/**
* @author xuxiaowei
* @since 0.1.0
*/
@Configuration
public class ResourceServerConfig {
/**
* 此处返回值需要使用 {@link UserDetailsService} 接口的实现
* <p>
* 此处使用 {@link JdbcUserDetailsManager} 作为 {@link Bean} 的类型是因为在测试类中需要使用
* {@link JdbcUserDetailsManager} 的 {@link Bean}
* <p>
* @see JdbcDaoImpl#DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY 权限组查询语句
* @see JdbcUserDetailsManager#DEF_FIND_GROUPS_SQL 查询所有群组
* @see JdbcUserDetailsManager#DEF_FIND_USERS_IN_GROUP_SQL 根据群组名称查询关联的用户
* @see JdbcUserDetailsManager#DEF_INSERT_GROUP_SQL 创建权限组
* @see JdbcUserDetailsManager#DEF_FIND_GROUP_ID_SQL 根据权限组名称查询权限组ID
* @see JdbcUserDetailsManager#DEF_DELETE_GROUP_SQL 根据群组ID删除群组
* @see JdbcUserDetailsManager#DEF_RENAME_GROUP_SQL 重命名群组
* @see JdbcUserDetailsManager#DEF_GROUP_AUTHORITIES_QUERY_SQL 根据群组名称查询权限
*/
@Bean
public JdbcUserDetailsManager userDetailsService(DataSource dataSource) {
JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource);
// 是否开启权限组
jdbcUserDetailsManager.setEnableGroups(true);
// 转译:在开启权限组时,转译关键字 groups(在 Linux MySQL 时会出现关键字问题)
// @formatter:off
// 使用用户名查询权限组
jdbcUserDetailsManager.setGroupAuthoritiesByUsernameQuery(JdbcDaoImpl.DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY.replace("groups", "`groups`"));
// 查询所有群组
jdbcUserDetailsManager.setFindAllGroupsSql(JdbcUserDetailsManager.DEF_FIND_GROUPS_SQL.replace("groups", "`groups`"));
// 根据群组名称查询关联的用户
jdbcUserDetailsManager.setFindUsersInGroupSql(JdbcUserDetailsManager.DEF_FIND_USERS_IN_GROUP_SQL.replace("groups", "`groups`"));
// 创建权限组
jdbcUserDetailsManager.setInsertGroupSql(JdbcUserDetailsManager.DEF_INSERT_GROUP_SQL.replace("groups", "`groups`"));
// 根据权限组名称查询权限组ID
jdbcUserDetailsManager.setFindGroupIdSql(JdbcUserDetailsManager.DEF_FIND_GROUP_ID_SQL.replace("groups", "`groups`"));
// 根据群组ID删除群组
jdbcUserDetailsManager.setDeleteGroupSql(JdbcUserDetailsManager.DEF_DELETE_GROUP_SQL.replace("groups", "`groups`"));
// 重命名群组
jdbcUserDetailsManager.setRenameGroupSql(JdbcUserDetailsManager.DEF_RENAME_GROUP_SQL.replace("groups", "`groups`"));
// 根据群组名称查询权限
jdbcUserDetailsManager.setGroupAuthoritiesSql(JdbcUserDetailsManager.DEF_GROUP_AUTHORITIES_QUERY_SQL.replace("groups", "`groups`"));
// @formatter:on
return jdbcUserDetailsManager;
}
}
测试
java
package cloud.xuxiaowei.passport.oauth;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import java.util.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* OAuth 2.1 权限组 测试类
*
* @author xuxiaowei
* @since 0.0.1
*/
@Slf4j
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class EnableGroupsTests {
@Autowired
private JdbcUserDetailsManager jdbcUserDetailsManager;
@Test
void createGroup() {
// 生成一个随机用户名
String username = UUID.randomUUID().toString();
String password = UUID.randomUUID().toString();
log.info("username = {}", username);
log.info("password = {}", password);
// 生成随机权限
// @formatter:off
String[] authorities = Arrays
.asList("A-" + UUID.randomUUID().toString().substring(0, 4),
"A-" + UUID.randomUUID().toString().substring(0, 4),
"A-" + UUID.randomUUID().toString().substring(0, 4))
.toArray(new String[0]);
// @formatter:on
log.info("authorities = {}", Arrays.toString(authorities));
// 创建用户
UserDetails user = User.builder().username(username).password(password).passwordEncoder(encoder -> {
// 密码加密储存
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
return passwordEncoder.encode(encoder);
}).authorities(authorities).build();
// 在数据库中创建用户
jdbcUserDetailsManager.createUser(user);
// 查询数据库中已创建的用户
UserDetails userDetails = jdbcUserDetailsManager.loadUserByUsername(username);
// 断言
assertNotNull(userDetails);
assertNotNull(userDetails.getAuthorities());
assertEquals(authorities.length, userDetails.getAuthorities().size());
List<String> authorityList = Arrays.asList(authorities);
// 排序
Collections.sort(authorityList);
// 数据库中查询的权限
List<String> databaseAuthorityList = Arrays
.asList(userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).toArray(String[]::new));
// 排序
Collections.sort(databaseAuthorityList);
// 比较权限:创建的权限 与 数据库中的权限 对比
assertEquals(authorityList, databaseAuthorityList);
String groupName = UUID.randomUUID().toString();
log.info("groupName = {}", groupName);
// 创建权限组
List<GrantedAuthority> groupAuthorities = Arrays.asList(
new SimpleGrantedAuthority("AG-" + UUID.randomUUID().toString().substring(0, 4)),
new SimpleGrantedAuthority("AG-" + UUID.randomUUID().toString().substring(0, 4)),
new SimpleGrantedAuthority("AG-" + UUID.randomUUID().toString().substring(0, 4)));
log.info("groupAuthorities = {}", groupAuthorities);
// 插入权限组
jdbcUserDetailsManager.createGroup(groupName, groupAuthorities);
// 给用户添加权限组
jdbcUserDetailsManager.addUserToGroup(username, groupName);
// 再次查询用户
userDetails = jdbcUserDetailsManager.loadUserByUsername(username);
// 断言
assertNotNull(userDetails);
assertNotNull(userDetails.getAuthorities());
// 生成随机权限 + 创建权限组 的权限数据 与 数据库中用户的权限 数量相同
assertEquals(authorities.length + groupAuthorities.size(), userDetails.getAuthorities().size());
// 权限:从数据库中查询:包含权限组
databaseAuthorityList = Arrays
.asList(userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).toArray(String[]::new));
Collections.sort(databaseAuthorityList);
// 权限:生成随机权限 + 创建权限组
List<String> groupAuthorityList = Arrays
.asList(groupAuthorities.stream().map(GrantedAuthority::getAuthority).toArray(String[]::new));
authorityList = new ArrayList<>(Arrays.asList(authorities));
authorityList.addAll(groupAuthorityList);
Collections.sort(authorityList);
// 比较权限:生成随机权限 + 创建权限组 的权限数据 与 数据库中用户的权限 内容相同
assertEquals(authorityList, databaseAuthorityList);
}
}