【Spring Security系列】基于Spring Security实现权限动态分配之用户-角色分配

本文将介绍基于Spring Security实现安全框架中角色动态分配的功能。

作者:后端小肥肠

目录

1. 前言

2. Spring Security授权原理

3. 成果界面展示

4. 开发环境搭建

4.1. 所需版本工具

4.2. pom文件编写

5. 表结构关系

6. 核心代码讲解

6.1. 重写UserDetails

6.2. 重写UserDetailsService

6.3. 编写角色-用户绑定方法

6.4. 编写SysRoleAndPermissionVo实体类

7. 结语

8.参考链接


1. 前言

        欢迎来到【Spring Security系列】!在当今复杂多变的网络环境下,实现灵活而安全的权限管理是每个开发者不可回避的挑战。本文将以 Spring Security授权原理为基础背景,以成果界面展示、核心代码说明及表结构关系的讲解来告诉大家怎么实现用户-角色的动态分配以达到动态授权的目的。

2. Spring Security授权原理

        Spring Security 是一个强大的身份验证和访问控制框架,它允许在Java应用程序中实现安全性。Spring Security 的角色分配是通过授权(Authorization)来实现的,授权主要通过配置权限和角色来完成。Spring Security的授权流程如下图所示:

由上图可看出Spring Security授权原理主要可以囊括为以下几点:

1. 用户认证(Authentication)

        用户身份的验证是通过 Authentication 对象来表示的。Authentication 对象包含了用户的身份信息,如用户名和密码。Spring Security 提供了不同的认证方式,包括基于表单登录、HTTP 基本认证、LDAP 等。

2. 访问控制(Authorization)

        一旦用户被认证,Spring Security 就通过授权来判断用户是否有访问资源的权限。授权是通过 GrantedAuthority 接口的实现类表示的,而其中的一种实现类就是角色(SimpleGrantedAuthority)。

3. 角色和权限的关系

        在 Spring Security 中,角色和权限是相互关联的。一个角色可以包含多个权限,而一个用户可以拥有多个角色。角色和权限的关系是通过配置来定义的。以下是一个简单的 Spring Security 配置类的示例,通过注解配置了两个角色(ROLE_USERROLE_ADMIN)和相应的权限:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("{noop}password").roles("USER")
                .and()
                .withUser("admin").password("{noop}password").roles("ADMIN");
    }
}

         在这个配置中,.hasRole("USER").hasRole("ADMIN") 表示访问对应路径的用户需要具备相应的角色。在 configureGlobal 方法中,使用了 inMemoryAuthentication 来配置了两个用户,一个拥有 USER 角色,另一个拥有 ADMIN 角色。采用这种硬编码的方式分配角色在实际项目中是不被允许的,因为系统管理员在需要变更用户权限时,可能需要修改代码并重新部署应用程序,这不仅繁琐,而且容易引入错误。

3. 成果界面展示

        用户-角色动态分配涉及到三个大功能模块,分别为用户管理,角色管理,用户-角色管理,通过这三个功能模块能实现角色全部由界面交互动态分配,无需改动后端代码。

用户管理界面(真实项目数据厚码,着重关注字段就行

        上述界面为用户管理界面,主要实现用户列表的展示及搜索、用户禁用(禁用的用户无法登录系统)、 用户的新增、编辑和删除功能。

角色管理界面: 

        上述界面为角色管理界面,主要实现角色的列表展示及搜索、角色数据的新增、编辑和删除功能。 

 用户-角色分配界面(真实项目数据厚码

        在上述界面中点击 【保存当前页配置】按钮,即可实现用户与角色的绑定配置。

4. 开发环境搭建

4.1. 所需版本工具

依赖版本
Spring Boot2.6.14
java1.8

4.2. pom文件编写

1. 引入SpringBoot依赖

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.14</version>
        <relativePath/>
</parent>


2. 引入Spring Security依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

5. 表结构关系

        角色动态分配所涉及的表为用户表,角色表,用户和角色的桥接表,我们仅需要在桥接表中新增数据,即可实现用户和角色的双向绑定,从下图可以看出用户和角色之间的关系为多对多(一个用户可以拥有多个角色,一个角色对应多个用户)。

        在上图中所涉及的表我已经上传到了我的资源中,需要的同学可以自行下载:

https://download.csdn.net/download/c18213590220/88670443?spm=1001.2014.3001.5503

6. 核心代码讲解

        我在这里只写核心代码,不涉及增删改查基础代码,如用户列表和角色列表的CRUD。

6.1. 重写UserDetails

@Component
public class AuthUser implements UserDetails {

    private String username;

    private String password;

    private Integer state;

    private Collection<? extends GrantedAuthority> authorities;

    public AuthUser() {
    }

    public AuthUser(String username, String password, Integer state, Collection<? extends GrantedAuthority> authorities) {
        this.username = username;
        this.password = password;
        this.state = state;
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    // 账户是否未过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // 账户是否未被锁
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public Integer getState() {
        return state;
    }

    public void setState(Integer state) {
        this.state = state;
    }

    @Override
    public String toString() {
        return "JwtUser{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", state=" + state +
                ", authorities=" + authorities +
                '}';
    }

6.2. 重写UserDetailsService

@Service
public class AuthUserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private ISysUserService userService;

    @Autowired
    private ISysRoleService roleService;

    /**
     * 通过账号查找用户、角色的信息
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = userService.getUserByUserName(username);
        if (user == null) {
            throw new UsernameNotFoundException(String.format("%s.这个用户不存在", username));
        }else {
            //查找角色
            List<String> roles =  roleService.getRolesByUserName(username);
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            for (String role : roles) {
                authorities.add(new SimpleGrantedAuthority(role));
            }
            return new AuthUser(user.getUsername(), user.getPassword(), user.getIsEnabled(), authorities);
        }
    }
}

        要实现角色的动态配置,首先应当重写UserDetailsService,从数据库中获取某用户所匹配的角色。

6.3. 编写角色-用户绑定方法

    @Transactional
    public   boolean saveRoleUser(String roleId, SysRoleAndPermissionVo... sysRoleAndPermissionVos){
        //先删除数据
        this.delRoleId(roleId);
        //
        if(sysRoleAndPermissionVos !=null) {
            Set<SysRoleUserTable> set = new HashSet<>();
            SysRoleUserTable roleUser = null;
            for (SysRoleAndPermissionVo roleVo : sysRoleAndPermissionVos) {
                roleUser = new SysRoleUserTable();
                //存储roleID和userID到多对对的中间表
                roleUser.setRoleId(roleVo.getRoleId());
                roleUser.setUserId(roleVo.getId());
                set.add(roleUser);
            }
            System.out.println("set = " + set);
            //再批量保存
            return this.saveBatch(set);
        }
        return  false;
    }

 上述方法就是将用户和角色id存储到sys_user_role桥接表中,实现用户和角色的绑定。

6.4. 编写SysRoleAndPermissionVo实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SysRoleAndPermissionVo {
    String  id;
    String  name;
    String  roleId;
    String  pid;
}

7. 结语

        本文讲解了基于Spring Security实现角色动态分配的方法,下期将继续讲解怎么实现菜单的动态配置,针对本文的问题如您有更好的实现或解决方式,欢迎在评论区留言探讨~~

8.参考链接

 SpringSecurity认证流程-CSDN博客

  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个简单的示例代码,演示如何使用Spring Security实现动态分配菜单权限和数据权限。在本示例中,我们假设您已经创建了一个名为“menu”和一个名为“role”的数据库表。 首先,您需要定义菜单和角色的实体类: ```java @Entity @Table(name = "menu") public class Menu { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String url; private String icon; private Long parentId; // getter and setter } @Entity @Table(name = "role") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String description; private String menus; // 存储菜单ID的字符串,以逗号分隔 // getter and setter } ``` 然后,您需要为这些实体类创建相应的Repository接口: ```java public interface MenuRepository extends JpaRepository<Menu, Long> { } public interface RoleRepository extends JpaRepository<Role, Long> { } ``` 接下来,您需要在Spring Security中配置角色权限: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasAnyRole("ADMIN", "USER") .anyRequest().authenticated() .and().formLogin().loginPage("/login").permitAll() .and().logout().logoutUrl("/logout").permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ``` 在上述配置中,我们定义了两个角色:“ADMIN”和“USER”,并将其与不同的资源进行关联。例如,只有拥有“ADMIN”角色用户才能访问“/admin/**”路径。 接下来,您需要实现自定义的UserDetailsService类,以根据用户角色动态生成菜单: ```java @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Autowired private RoleRepository roleRepository; @Autowired private MenuRepository menuRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found with username: " + username); } List<GrantedAuthority> authorities = new ArrayList<>(); String[] roles = user.getRoles().split(","); for (String roleName : roles) { Role role = roleRepository.findByName(roleName); if (role != null) { authorities.add(new SimpleGrantedAuthority(role.getName())); } String[] menuIds = role.getMenus().split(","); for (String menuId : menuIds) { Menu menu = menuRepository.findById(Long.parseLong(menuId)).orElse(null); if (menu != null) { // 生成菜单 } } } return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities); } } ``` 在上述代码中,我们首先获取用户所拥有的角色,然后根据角色获取对应的菜单。最后,我们将这些菜单生成为动态菜单,以供用户访问。 最后,您可以使用Spring Data JPA和Thymeleaf等技术实现动态生成菜单的功能。例如: ```html <ul class="sidebar-menu"> <li th:each="menu : ${menus}" th:class="${menu.parentId == null ? 'treeview' : ''}"> <a th:href="@{${menu.url}}"> <i th:class="${menu.icon}"></i> <span th:text="${menu.name}"></span> <i th:if="${menu.children.size() > 0}" class="fa fa-angle-left pull-right"></i> </a> <ul th:if="${menu.children.size() > 0}" class="treeview-menu"> <li th:each="child : ${menu.children}"> <a th:href="@{${child.url}}"> <i th:class="${child.icon}"></i> <span th:text="${child.name}"></span> </a> </li> </ul> </li> </ul> ``` 上述代码将根据用户所拥有的菜单动态生成侧边栏菜单。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后端小肥肠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值