当前位置:   article > 正文

【SpringBoot3】Spring Security使用mybatis-plus存储用户角色权限,实现动态权限处理_mybatis-plus 3.5 数据权限配置

mybatis-plus 3.5 数据权限配置

注:本文基于Spring Boot 3.2.1 以及 Spring Security 6.2.1

相关文章

【SpringBoot3】Spring Security 核心概念
【SpringBoot3】Spring Security 常用注解
【SpringBoot3】Spring Security 详细使用实例(简单使用、JWT模式)

一、使用mybatis-plus存储用户角色权限

1)创建相关数据库表
2)从数据库中读取用户、角色、权限数据
3)登录成功返回token
4)访问相关资源时,通过自定义的过滤器解析token,判断权限

1、引入jar包依赖

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-3-starter</artifactId>
    <version>1.2.21</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.5</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2、数据配置以及创建数据表

1)增加数据库链接配置

# 允许循环引用
spring.main.allow-circular-references=true

# --- 数据库链接配置
spring.datasource.url=jdbc:mysql://localhost:3306/springsecurity?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

# --- mybatis-plus start
# 关闭MP3.0自带的banner
mybatis-plus.global-config.banner=false
# 主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
mybatis-plus.global-config.db-config.id-type=ASSIGN_ID
# 返回类型为Map,显示null对应的字段
mybatis-plus.configuration.call-setters-on-nulls=true
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2)创建数据库

创建5张表:system_menu、system_role、system_role_menu、system_user_role、system_users

这些表可作为前后端分离项目的通用权限表(如果是简单演示2张表就可以了)

CREATE TABLE `system_role` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `name` varchar(30)  NOT NULL COMMENT '角色名称',
  `code` varchar(100)  NOT NULL COMMENT '角色权限字符串',
  `sort` int NOT NULL COMMENT '显示顺序',
  `status` tinyint NOT NULL COMMENT '角色状态(0正常 1停用)',
  `remark` varchar(500)  DEFAULT NULL COMMENT '备注',
  `creator` varchar(64)  DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64)  DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=140 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色信息表';

CREATE TABLE `system_menu` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
  `name` varchar(50)  NOT NULL COMMENT '菜单名称',
  `permission` varchar(100)  NOT NULL DEFAULT '' COMMENT '权限标识',
  `type` tinyint NOT NULL COMMENT '菜单类型(1目录 2菜单 3按钮)',
  `sort` int NOT NULL DEFAULT '0' COMMENT '显示顺序',
  `parent_id` bigint NOT NULL DEFAULT '0' COMMENT '父菜单ID',
  `path` varchar(200)  DEFAULT '' COMMENT '路由地址',
  `icon` varchar(100)  DEFAULT '#' COMMENT '菜单图标',
  `component` varchar(255)  DEFAULT NULL COMMENT '组件路径',
  `component_name` varchar(255)  DEFAULT NULL COMMENT '组件名',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '菜单状态',
  `visible` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否可见',
  `keep_alive` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否缓存',
  `always_show` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否总是显示',
  `creator` varchar(64)  DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64)  DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB COMMENT='菜单权限表';

CREATE TABLE `system_role_menu` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增编号',
  `role_id` bigint NOT NULL COMMENT '角色ID',
  `menu_id` bigint NOT NULL COMMENT '菜单ID',
  `creator` varchar(64)  DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64)  DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB COMMENT='角色和菜单关联表';

CREATE TABLE `system_users` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(30)  NOT NULL COMMENT '用户账号',
  `password` varchar(100)  NOT NULL DEFAULT '' COMMENT '密码',
  `nickname` varchar(30)  NOT NULL COMMENT '用户昵称',
  `remark` varchar(500)  DEFAULT NULL COMMENT '备注',
  `email` varchar(50)  DEFAULT '' COMMENT '用户邮箱',
  `mobile` varchar(11)  DEFAULT '' COMMENT '手机号码',
  `sex` tinyint DEFAULT '0' COMMENT '用户性别',
  `avatar` varchar(512)  DEFAULT '' COMMENT '头像地址',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
  `login_ip` varchar(50)  DEFAULT '' COMMENT '最后登录IP',
  `login_date` datetime DEFAULT NULL COMMENT '最后登录时间',
  `creator` varchar(64)  DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64)  DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `idx_username` (`username`,`update_time`) USING BTREE
) ENGINE=InnoDB COMMENT='用户信息表';

CREATE TABLE `system_user_role` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增编号',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `role_id` bigint NOT NULL COMMENT '角色ID',
  `creator` varchar(64)  DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64)  DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB COMMENT='用户和角色关联表';

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

springsecurity框架提供了示例的两张表,在文件users.ddl

create table users(username varchar(50) not null primary key,password varchar(500) not null,enabled boolean not null);
create table authorities (username varchar(50) not null,authority varchar(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
  • 1
  • 2

3、增加相关Mapper类

@Mapper
public interface MenuMapper extends BaseMapper<MenuDO> {

}

@Mapper
public interface RoleMapper extends BaseMapper<RoleDO> {

}

@Mapper
public interface RoleMenuMapper extends BaseMapper<RoleMenuDO> {

    default List<RoleMenuDO> selectListByRoleId(Collection<Long> roleIds) {
        return selectList(new LambdaQueryWrapper<RoleMenuDO>().eq(RoleMenuDO::getRoleId, roleIds));
    }

}

@Mapper
public interface UserMapper extends BaseMapper<UserDO> {

    default UserDO selectByUsername(String username) {
        return selectOne(new LambdaQueryWrapper<UserDO>().eq(UserDO::getUsername, username));
    }

}

@Mapper
public interface UserRoleMapper extends BaseMapper<UserRoleDO> {

    default List<UserRoleDO> selectListByUserId(Long userId) {
        return selectList(new LambdaQueryWrapper<UserRoleDO>().eq(UserRoleDO::getUserId, userId));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

4、增加相关Service类

注意: SecurityExpressionRoot#hasAnyRole方法固定加上前缀ROLE_,因此,如果数据库的roleCode没有前缀ROLE_,则需在查询的时候拼接上去。

spring security的角色和权限是一个概念,只是字符串不一样,角色必须加前缀ROLE_,权限没有这要求。

@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    private RoleService roleService;
    @Autowired
    private PermissionService permissionService;
    @Autowired
    private MenuService menuService;
    @Resource
    private UserMapper userMapper;
    
    /**
     * 返回`UserDetails`实现类`UserEntity`
     */
    @Override
    public UserEntity loadUserByUsername(String username) {
        UserEntity entity = new UserEntity();
        UserDO user = getUserByUsername(username);
        Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(user.getId());
        List<RoleDO> roles = roleService.getRoleList(roleIds);
        Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId));
        List<MenuDO> menuList = menuService.getMenuList(menuIds);

        entity.setId(user.getId());
        entity.setUsername(user.getUsername());
        entity.setPassword(user.getPassword());
        // 注意:SecurityExpressionRoot#hasAnyRole方法固定加上前缀ROLE_,
        // 因此,如果数据库的roleCode没有前缀ROLE_,则需在查询的时候拼接上去
        Set<String> roleCodes = convertSet(roles, role -> "ROLE_" + role.getCode());
        Set<String> permCodes = convertSet(menuList, MenuDO::getPermission);
        roleCodes.addAll(permCodes);
        List<String> authorities = roleCodes.stream().filter(StrUtil::isNotBlank).toList();
        // spring security的角色和权限是一个概念,只是字符串不一样,角色必须加前缀ROLE_,权限没有这要求
        entity.setAuthorities(AuthorityUtils.createAuthorityList(authorities));
        return entity;
    }

    @Override
    public UserDO getUserByUsername(String username) {
        return userMapper.selectByUsername(username);
    }

}

/**
 * 权限 Service 实现类
 */
@Service
@Slf4j
public class PermissionServiceImpl implements PermissionService {

    @Resource
    private RoleMenuMapper roleMenuMapper;
    @Resource
    private UserRoleMapper userRoleMapper;
    @Resource
    private RoleService roleService;
    @Resource
    private MenuService menuService;

    @Override
    public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds) {
        if (CollUtil.isEmpty(roleIds)) {
            return Collections.emptySet();
        }

        // 如果是管理员的情况下,获取全部菜单编号
        if (roleService.hasAnySuperAdmin(roleIds)) {
            return convertSet(menuService.getMenuList(), MenuDO::getId);
        }
        // 如果是非管理员的情况下,获得拥有的菜单编号
        return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);
    }


    @Override
    public Set<Long> getUserRoleIdListByUserId(Long userId) {
        return convertSet(userRoleMapper.selectListByUserId(userId), UserRoleDO::getRoleId);
    }

}

/**
 * 角色 Service 实现类
 */
@Service
@Slf4j
public class RoleServiceImpl implements RoleService {
    @Resource
    private RoleMapper roleMapper;

    @Override
    public RoleDO getRole(Long id) {
        return roleMapper.selectById(id);
    }

    @Override
    public List<RoleDO> getRoleList(Collection<Long> ids) {
        if (CollectionUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return roleMapper.selectBatchIds(ids);
    }

    @Override
    public boolean hasAnySuperAdmin(Collection<Long> ids) {
        if (CollectionUtil.isEmpty(ids)) {
            return false;
        }
        return ids.stream().anyMatch(id -> {
            RoleDO role = getRole(id);
            return role != null && RoleCodeEnum.isSuperAdmin(role.getCode());
        });
    }

}

/**
 * 菜单 Service 实现
 */
@Service
@Slf4j
public class MenuServiceImpl implements MenuService {

    @Resource
    private MenuMapper menuMapper;

    @Override
    public List<MenuDO> getMenuList() {
        return menuMapper.selectList(new LambdaQueryWrapper<>());
    }

    @Override
    public List<MenuDO> getMenuList(Collection<Long> ids) {
        return menuMapper.selectBatchIds(ids);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140

5、创建自定义UserDetailsService实现类

创建 MyUserDetailsServiceImpl 实现Spring Security接口 UserDetailsService

调用userService方法,返回UserDetails实现类UserEntity

@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userService.loadUserByUsername(username);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

6、创建自定义过滤器JwtTokenFilter

@Component
public class JwtTokenFilter extends OncePerRequestFilter {

    @Autowired
    UserService userService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 验证token是否有效
        String token = request.getHeader("token");
        if (StrUtil.isNotEmpty(token)) {
            String secret = "0123456789";
            boolean verify = JWTUtil.verify(token, secret.getBytes());
            if (!verify) {
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().write("{\"code\":401,\"msg\":\"token无效\"}");
                return;
            } else {
                //认证成功,设置用户信息
                UserEntity user = JWTUtil.parseToken(token).getPayloads().toBean(UserEntity.class);
                // 模拟获取用户信息,实际情况应该是从数据库查询
                UserDetails userDetails = userService.loadUserByUsername(user.getUsername());
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails.getUsername(),
                        userDetails.getPassword(),
                        userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }
        filterChain.doFilter(request, response);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

7、配置HttpSecurity

1)使用自定义的MyUserDetailsServiceImpl
2)使用自定义过滤器JwtTokenFilter
3)登录成功,根据UserEntity生成token,返回给前端。
4)基于 token 机制,所以不需要 Session

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@Configuration
@Slf4j
public class JwtSecurityConfig {

    @Autowired
    private MyUserDetailsServiceImpl userDetailService;
    @Autowired
    private JwtTokenFilter jwtTokenFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests((authorize) -> authorize
                        .requestMatchers("/login").permitAll()
                        .anyRequest().authenticated()
                )
                .userDetailsService(userDetailService)
                .exceptionHandling(ex -> ex.authenticationEntryPoint((request, response, authException) -> {
                    log.error("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), authException);
                    ServletUtils.writeJSON(response, authException.getMessage());
                }).accessDeniedHandler((request, response, accessDeniedException) -> {
                    log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(),
                            "", accessDeniedException);
                    ServletUtils.writeJSON(response, accessDeniedException.getMessage());
                }))
                .logout(logout -> logout.invalidateHttpSession(true))
                // 配置登录页面
                .httpBasic(Customizer.withDefaults())
                .formLogin(form -> form.loginPage("/login").permitAll()
                        .loginProcessingUrl("/login")
                        .successHandler((request, response, authentication) -> {
                            // 登录成功,根据UserEntity生成token,返回给前端
                            log.info("登录成功:{}", authentication);
                            UserEntity principal = (UserEntity) authentication.getPrincipal();
                            String secret = "0123456789";
                            Map<String, Object> payload = new HashMap<>();
                            payload.put("id", principal.getId());
                            payload.put("username", principal.getUsername());
                            String token = JWTUtil.createToken(payload, secret.getBytes());
                            response.setContentType("application/json;charset=UTF-8");
                            Map<String, Object> map = new HashMap<>();
                            map.put("token", token);
                            response.getWriter().write(JSONUtil.toJsonStr(map));
                        }).failureHandler((request, response, authentication) -> {
                            log.info("登录失败", authentication);
                        }));
        // CSRF 禁用,因为不使用 Session
        http.csrf(AbstractHttpConfigurer::disable);
        // 基于 token 机制,所以不需要 Session
        http.sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
        http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

8、总结

1、使用postman请求登录接口,密码验证成功后,通过 successHandler 返回token
2、将token当作Header参数,请求 /admin 等需要权限的接口地址
3、 定义过滤器JwtTokenFilter接收到请求,解析Token校验账户权限,设置当前登录用户信息
4、权限校验完成!

二、动态权限处理

Spring Security 提供了拦截器,用于控制对安全对象的访问,这些安全对象可以是方法调用或 Web 请求。在调用之前,AuthorizationManager 实例会作出决策,判断调用是否允许继续。此外,AuthorizationManager 实例还会在调用之后作出决策,判断给定的值是否可以返回。

动态权限可以通过创建自定义类(如 MyAuthorizationManager)实现接口 AuthorizationManager,在实现类的 check() 方法中判断用户是否有请求资源的权限。

通常是结合数据库中配置的数据判断当前用户是否有当前请求URI的权限

1、创建自定义权限处理类

MyAuthorizationManager,实现接口 AuthorizationManager

@Component
@Slf4j
public class MyAuthorizationManager implements AuthorizationManager<HttpServletRequest> {

    @Autowired
    private PermissionService permissionService;
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, HttpServletRequest request) {
        log.info("Checking authorization {}", request);
        String requestURI = request.getRequestURI();
        //根据这些信息 和业务写逻辑即可 最终决定是否授权 isGrantedboolean isGranted = true;
        boolean isGranted = true;
        if (requestURI.equals("/login") || requestURI.equals("/logout")) {
            return new AuthorizationDecision(isGranted);
        }
        //当前用户的权限信息 比如角色
        Collection<? extends GrantedAuthority> authorities = authentication.get().getAuthorities();
        // 当前用户权限代码
        List<String> userPermCodes = authorities.stream().map(GrantedAuthority::getAuthority).toList();
        // 查询当前被访问的URL:需要哪些权限才能访问
        List<String> codes = permissionService.getPermissions(requestURI);
        if(codes == null || codes.isEmpty() ){
            return new AuthorizationDecision(true);
        }
        // 如果包含用户所拥有的权限,则授权通过,否则授权失败
        boolean containsed = CollectionUtil.containsAny(codes, userPermCodes);
        if(!containsed){
            isGranted = false;
        }
        return new AuthorizationDecision(isGranted);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

2、配置 HttpSecurity 添加过滤器

添加过滤器 AuthorizationFilter ,传入自定义权限处理类 MyAuthorizationManager

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@Configuration
@Slf4j
public class JwtSecurityConfig {

    @Autowired
    private MyAuthorizationManager  authorizationManager;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		// -- 省略其他代码 -- //
        http.addFilter(new AuthorizationFilter(authorizationManager));
        return http.build();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3、总结

通过以上配置,每次接口请求,都会经过MyAuthorizationManager ,用户可以自行判断是否通过授权。

通过返回值 new AuthorizationDecision(isGranted),决定是否授权成功!

参考

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/146745
推荐阅读
相关标签
  

闽ICP备14008679号