Skip to content

开启 Security 权限组

  1. 权限组 是 权限的集合
  2. 权限 小于 角色
  3. 在 Spring Security 中,没有明确划分 权限 与 角色 表
    1. 在 Spring Security 将 权限 和 角色 放在相同的表和字段中
    2. 如果要区分 权限 和 角色,可以使用前缀 ROLE_ 区分,详情见: 使用 @PreAuthorize 注解

配置

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);
    }

}