前后端分离,使用vue3整合SpringSecurity加JWT实现权限校验

书接上回,之前写了vue3整合SpringSecurity实现登录认证。现在,接着之前写的那两个项目实现权限校验。

我本来是想着登录认证和权限校验放在一篇文章里的,但是上次写登录认证就写了非常多了,实在是有些写不动了,所以才分为了两篇文章。

本文适合有一定基础的人来看,如果你对springsecurity安全框架还不是很了解,建议你先去看一下我之前写过的spring security框架的快速入门:

springboot3整合SpringSecurity实现登录校验与权限认证(万字超详细讲解)_springboot3 springsecurity-CSDN博客

技术栈版本:vue3.3.11、springboot3.1.5、spring security6.x

之前的登录认证文章:
前后端分离,使用vue3整合SpringSecurity加JWT实现登录认证_springsecurity整合vue3-CSDN博客

在上次的文章中,只写到登录成功和退出之后就不写了,这次会加上权限校验。

首先,在原来数据库的基础上再新建:角色表、权限表、用户角色表、角色权限表四张表:

2、角色表

CREATE TABLE roles (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50) NOT NULL UNIQUE,
  description VARCHAR(255),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);


3、权限表

CREATE TABLE permissions (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50) NOT NULL UNIQUE,
  description VARCHAR(255),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);


4、用户角色表

CREATE TABLE user_roles (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  role_id INT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id),
  FOREIGN KEY (role_id) REFERENCES roles(id)
);


5、角色权限表

CREATE TABLE role_permissions (
  id INT AUTO_INCREMENT PRIMARY KEY,
  role_id INT NOT NULL,
  permission_id INT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (role_id) REFERENCES roles(id),
  FOREIGN KEY (permission_id) REFERENCES permissions(id)
);

现在,我们的数据库中共有5张表,分别创建相应的server、mapper和controller层。

接下来,再原来的登录认证的代码的基础上就可以来实现我们的权限校验了;

权限校验这方面主要体现在后端代码上,所以前端我只是进行一些简单的演示即可;

1、在我们的MyTUserDetail类中定义角色和权限的属性集合,并添加到UserDetails类的getAuthorities方法中(角色和权限我都使用Set定义,这样能够去重)

代码如下:

@Data
public class MyUserDetail implements Serializable, UserDetails {

    private static final long serialVersionUID = 1L;

    private Users Users;

//    角色
    private Set<String> roles;

//    权限
    private  Set<String> permissions;

    @JsonIgnore  //json忽略
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        List<GrantedAuthority> list =  new ArrayList<>();

//        如果角色不用空,则将角色添加到list中
        if (!ObjectUtils.isEmpty(roles)){
            roles.forEach(role->list.add(new SimpleGrantedAuthority(role)));
        }

//                如果权限不用空,则将权限添加到list中
        if (!ObjectUtils.isEmpty(permissions)){
            permissions.forEach(permission->list.add(new SimpleGrantedAuthority(permission)));
        }
        return list;
        
    }

    @JsonIgnore
    @Override
    public String getPassword() {
        return this.getUsers().getPassword();
    }
    @JsonIgnore
    @Override
    public String getUsername() {
        return this.getUsers().getUsername();
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return this.getUsers().getStatus()==0;
    }
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return this.getUsers().getStatus()==0;
    }
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return this.getUsers().getStatus()==0;
    }
    @JsonIgnore
    @Override
    public boolean isEnabled() {
        return this.getUsers().getStatus()==0;
    }
}

2、在MyUserDetailServerImpl类的loadUserByUsername方法中查出登录用户的权限集合:

代码如下:

@Service
@Slf4j
public class MyUserDetailServerImpl implements MyUserDetailServer {
    @Autowired
    UsersMapper userService;

    /**
     * 返回一个账号所拥有的权限码集合
     */

//    角色权限表
    @Autowired
    IRolePermissionsService rolePermissionsService;
    //    用户角色表
    @Autowired
    IUserRolesService userRolesService;
    //权限表
    @Autowired
    IPermissionsService permissionsService;

    //    角色表
    @Autowired
    IRolesService rolesService;



    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Users users = userService.selectOne(new LambdaQueryWrapper<Users>().
                eq(username != null, Users::getUsername, username));
        if (users == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
log.info("UserDetailServer中的user:=========>"+users);
        MyUserDetail myTUserDetail=new MyUserDetail();
myTUserDetail.setUsers(users);


        // 查询用户权限


//        根据用户id从用户角色表中获取角色id
        List<UserRoles> roleIds = userRolesService.list(new LambdaQueryWrapper<UserRoles>()
                .eq(UserRoles::getUserId,users.getId()));
        List<Integer> rolesList = roleIds.stream().map(UserRoles::getRoleId).toList();

        if (!(roleIds.size() >0)){
//            用户没有分配角色
            return myTUserDetail;
        }
        
        Set<String> listPermission = new HashSet<>();

        rolesList.forEach(roleId ->{
            // 根据角色id从角色权限表中获取权限id
            List<RolePermissions> rolePermissions = rolePermissionsService.list(new LambdaQueryWrapper<RolePermissions>().
                    eq(RolePermissions::getRoleId, roleId));
            // 根据权限id从权限表中获取权限名称
            rolePermissions.forEach(permissionsId->{
                Permissions permissions = permissionsService.getById(permissionsId.getPermissionId());
                listPermission.add(permissions.getName());
            });
        });

myTUserDetail.setPermissions( listPermission);

        // 查询角色角色

        Set<String> listRole = new HashSet<>();

     
        
        roleIds.forEach(roleId ->{
            Roles byId = rolesService.getById(roleId.getRoleId());
            listRole.add(byId.getName());
        });
        myTUserDetail.setRoles(listRole);
log.info("UserDetailServer中的查完权限的myTUserDetail:=========>"+myTUserDetail);
        return myTUserDetail;
    }

}

我所实现的是标准的RBAC(基于用户、角色、权限的访问控制模型)。所以,在得到用户id的情况下、先根据用户角色表查出角色id(如果角色id的集合为空,说明用户没有分配任何角色,直接返回用户信息)、在根据角色权限表查询权限id,在根据权限表查出具体权限名称。

上面使用了Mybatis-plus的条件构造器和stream流的形式进行查询。

3、在JwtAuthenticationTokenFilter拦截器中,在查询到用户信息时,将用户的标识和用户拥有的权限一起放到SecurityContextHolder中,这样后面的过滤器在获取到用户信息的同时也能获取到用户所拥有的权限;

代码如下:

@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取请求头中的token
        String token = request.getHeader("token");
        System.out.println("前端的token信息=======>"+token);
        //如果token为空直接放行,由于用户信息没有存放在SecurityContextHolder.getContext()中所以后面的过滤器依旧认证失败符合要求
        if(!StringUtils.hasText(token)){
            filterChain.doFilter(request,response);
            return;
        }

//        解析Jwt中的用户id
        Integer userId = jwtUtil.getUsernameFromToken(token);
        //从redis中获取用户信息
        String redisUser = redisTemplate.opsForValue().get(String.valueOf(userId));
        if(!StringUtils.hasText(redisUser)){
            filterChain.doFilter(request,response);
            return;
        }

        MyUserDetail myTUserDetail= JSON.parseObject(redisUser, MyUserDetail.class);
log.info("Jwt过滤器中MyUserDetail的值============>"+myTUserDetail.toString());

        //将用户信息存放在SecurityContextHolder.getContext(),后面的过滤器就可以获得用户信息了。这表明当前这个用户是登录过的,后续的拦截器就不用再拦截了
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(myTUserDetail,null,myTUserDetail.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        filterChain.doFilter(request,response);
    }
}

在这里解释一下UsernamePasswordAuthenticationToken类:

UsernamePasswordAuthenticationToken是Spring Security中用于表示基于用户名和密码的身份验证令牌的类。它主要有以下两个构造方法:

  1. UsernamePasswordAuthenticationToken(Object principal, Object credentials)

    • principal参数表示认证主体,通常是用户名或用户对象。在身份验证过程中,这通常是用来标识用户的信息,可以是用户名、邮箱等。
    • credentials参数表示凭据,通常是用户的密码或其他凭证信息。在身份验证过程中,这用于验证用户的身份。
  2. UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)

    • 除了上述两个参数外,这个构造方法还接受一个授权权限集合(authorities参数)。这个集合表示用户所拥有的权限,通常是一个包含用户权限信息的集合。
    • GrantedAuthority接口代表了用户的权限信息,可以通过该接口的实现类来表示用户具体的权限。

这两个构造方法的作用是创建一个包含用户身份信息、凭据信息和权限信息的身份验证令牌,以便在Spring Security中进行身份验证和授权操作。通过这些构造方法,可以将用户的相关信息封装成一个完整的身份验证对象,方便在安全框架中进行处理和验证。

总之,UsernamePasswordAuthenticationToken是在Spring Security中用于表示用户名密码身份验证信息的重要类,通过不同的构造方法可以满足不同场景下的需求

所以我们通过myTUserDetail.getAuthorities()方法完全可以将用户拥有的权限方法Security容器中,并供后续的拦截器获取用户信息和权限;

4、运行测试:
接下来我编写一个基于方法的权限校验,看我们编写的代码是否生效;

(基于方法的权限认证要在SecurityConfig类上加上@EnableMethodSecurity注解,表示开启了方法权限的使用;)

新建一个TestController,并在这个类中定义一个方法,用来测试:

@RestController
@RequestMapping("/test")
public class TestController {
    @PreAuthorize("hasAnyAuthority('所有权限')")
 @GetMapping("/hello")
    public Result hello(){
        System.out.println("test接口中的hello方法调用========================");
        return Result.successData("hello");
    }

}

在前端的Layout.vue页面中新增一个按钮,并绑定指定的方法用来测试;

代码如图:

const testHello = async() => {
  let data:any= await api.get("/test/hello")
if(data.code===200){
  ElMessage('有权限')
}
else{
  ElMessage.error('没有权限')
}
}

现在,我们来测试看看这个方法能不能被调用到:
 

可以看到这个方法被正确的访问到了,这是必须的因为这个”张乔“用户有这个权限,那么我们改一下所需的权限看还能不能访问到;

点击前端按钮:

可以看到确实不能访问到了,这说明我们的代码是正确的;

我们权限校验的逻辑是:直接在登录时查询用户的权限,并放在我们自定义的实现了UserDetail的接口类中(MyUserDetail),用来表示登录用户的全部信息;

至此:我们前后端分离,使用vue3整合SpringSecurity实现登录认证和权限校验就已经全部的讲解完毕了,我还是会将前后端的源码放在码云上,有需要的童靴可以自行的下载:
码云地址:
Vue-Security: 前后端分离的Security

有什么疑问可以在评论区说,我看到了会回复的

  • 30
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Spring Boot和Vue.js是两个非常流行的技术栈,可以非常好地实现前后端分离的开发模式。SecurityJWT是两个很好的工具,可以帮助我们实现安全的登录和授权机制。 以下是实现Spring Boot和Vue.js前后端分离的步骤: 1.创建Spring Boot工程 首先,我们需要创建一个Spring Boot工程,可以使用Spring Initializr来生成一个基本的Maven项目,添所需的依赖项,包括Spring SecurityJWT。 2.配置Spring SecuritySpring Security中,我们需要定义一个安全配置类,该类将定义我们的安全策略和JWT的配置。在这里,我们可以使用注解来定义我们的安全策略,如@PreAuthorize和@Secured。 3.实现JWT JWT是一种基于令牌的身份验证机制,它使用JSON Web Token来传递安全信息。在我们的应用程序中,我们需要实现JWT的生成和验证机制,以便我们可以安全地登录和授权。 4.配置Vue.js 在Vue.js中,我们需要创建一个Vue.js项目,并使用Vue CLI来安装和配置我们的项目。我们需要使用Vue Router来定义我们的路由,并使用Axios来发送HTTP请求。 5.实现登录和授权 最后,我们需要实现登录和授权机制,以便用户可以安全地登录和访问我们的应用程序。在Vue.js中,我们可以使用Vue Router和Axios来发送HTTP请求,并在Spring Boot中使用JWT来验证用户身份。 总结 以上是实现Spring Boot和Vue.js前后端分离的步骤,我们可以使用SecurityJWT实现安全的登录和授权机制。这种开发模式可以让我们更好地实现前后端分离,提高我们的开发效率和应用程序的安全性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

张乔24

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

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

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

打赏作者

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

抵扣说明:

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

余额充值