vue3整合SpringSecurity加JWT实现权限校验
我本来是想着登录认证和权限校验放在一篇文章里的,但是上次写登录认证就写了非常多了,实在是有些写不动了,所以才分为了两篇文章。
本文适合有一定基础的人来看,如果你对springsecurity安全框架还不是很了解,建议你先去看一下我之前写过的spring security框架的快速入门:
springboot3整合SpringSecurity实现登录校验与权限认证(万字超详细讲解)
技术栈版本:vue3.3.11、springboot3.1.5、spring security6.x
之前的登录认证文章:
前后端分离,使用vue3整合SpringSecurity加JWT实现登录认证
在上次的文章中,只写到登录成功和退出之后就不写了,这次会加上权限校验。
首先,在原来数据库的基础上再新建:角色表、权限表、用户角色表、角色权限表四张表:
2、角色表
3、权限表
4、用户角色表
5、角色权限表
现在,我们的数据库中共有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("ZML_"+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; } }
Authentication 讨论了所有 Authentication 实现如何存储 GrantedAuthority 对象的列表。这些对象代表已经授予委托人(principal)的权限。GrantedAuthority 对象由 AuthenticationManager 插入到 Authentication 对象中,随后由 AccessDecisionManager 实例在做出授权决定时读取。
GrantedAuthority 接口只有一个方法。
这个方法被 AuthorizationManager 实例用来获取 GrantedAuthority 的一个精确的 String 表示。通过返回一个 String 表示,一个 GrantedAuthority 可以被大多数 AuthorizationManager 实现轻松 "读取"。如果 GrantedAuthority 不能被精确地表示为一个 String,那么该 GrantedAuthority 被认为是 "复杂的",getAuthority() 必须返回 null。
一个复杂的 GrantedAuthority 的例子是一个实现,它存储了一个适用于不同客户账号的操作和权限阈值的列表。将这种复杂的 GrantedAuthority 表示为一个 String 将是相当困难的。因此,getAuthority() 方法应该返回 null。这向任何 AuthorizationManager 表明,它需要支持特定的 GrantedAuthority 实现来理解其内容。
Spring Security 包括一个具体的 GrantedAuthority 实现。SimpleGrantedAuthority。这个实现允许任何用户指定的字符串被转换为 GrantedAuthority。安全架构中包含的所有 AuthenticationProvider 实例都使用 SimpleGrantedAuthority 来填充 Authentication 对象。
默认情况下,基于角色的授权规则包括 ROLE_ 作为前缀。这意味着,如果有一个授权规则要求 security context 的角色是 "USER",Spring Security 将默认寻找返回 "ROLE_USER" 的 GrantedAuthority#getAuthority。
你可以用 GrantedAuthorityDefaults 来定制这个。GrantedAuthorityDefaults 的存在是为了允许自定义基于角色的授权规则所使用的前缀。
你可以通过暴露一个 GrantedAuthorityDefaults Bean 来配置授权规则以使用不同的前缀,像这样:
我们需要特别注意的一点是,在spring security中。我们的角色和权限是存储在一起的,没有分开存储 如:
参考来源:授权架构 :: Spring Security Reference
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中用于表示基于用户名和密码的身份验证令牌的类。它主要有以下两个构造方法:
UsernamePasswordAuthenticationToken(Object principal, Object credentials)
- principal参数表示认证主体,通常是用户名或用户对象。在身份验证过程中,这通常是用来标识用户的信息,可以是用户名、邮箱等。
- credentials参数表示凭据,通常是用户的密码或其他凭证信息。在身份验证过程中,这用于验证用户的身份。
UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)
- 除了上述两个参数外,这个构造方法还接受一个授权权限集合(authorities参数)。这个集合表示用户所拥有的权限,通常是一个包含用户权限信息的集合。
- GrantedAuthority接口代表了用户的权限信息,可以通过该接口的实现类来表示用户具体的权限。
这两个构造方法的作用是创建一个包含用户身份信息、凭据信息和权限信息的身份验证令牌,以便在Spring Security中进行身份验证和授权操作。通过这些构造方法,可以将用户的相关信息封装成一个完整的身份验证对象,方便在安全框架中进行处理和验证。
总之,UsernamePasswordAuthenticationToken是在Spring Security中用于表示用户名密码身份验证信息的重要类,通过不同的构造方法可以满足不同场景下的需求
所以我们通过myTUserDetail.getAuthorities()方法完全可以将用户拥有的权限方法Security容器中,并供后续的拦截器获取用户信息和权限;
4、运行测试:
接下来我编写一个基于方法的权限校验,看我们编写的代码是否生效;
(基于方法的权限认证要在SecurityConfig类上加上@EnableMethodSecurity注解,表示开启了方法权限的使用;)
新建一个TestController,并在这个类中定义一个方法,用来测试:
在前端的Layout.vue页面中新增一个按钮,并绑定指定的方法用来测试;
代码如图:
现在,我们来测试看看这个方法能不能被调用到:
可以看到这个方法被正确的访问到了,这是必须的因为这个”张乔“用户有这个权限,那么我们改一下所需的权限看还能不能访问到;
点击前端按钮:
可以看到确实不能访问到了,这说明我们的代码是正确的;
我们权限校验的逻辑是:直接在登录时查询用户的权限,并放在我们自定义的实现了UserDetail的接口类中(MyUserDetail),用来表示登录用户的全部信息;
至此:我们前后端分离,使用vue3整合SpringSecurity实现登录认证和权限校验就已经全部的讲解完毕了,我还是会将前后端的源码放在码云上,有需要的童靴可以自行的下载:
码云地址:
Vue-Security: 前后端分离的Security
栏 目:JavaScript
本文标题:vue3整合SpringSecurity加JWT实现权限校验
本文地址:https://www.fushidao.cc/wangluobiancheng/23725.html
您可能感兴趣的文章
- 07-21Webpack打包速度优化方案汇总
- 07-21Vuex Actions多参数传递的解决方案
- 07-21前端JavaScript数组方法总结(非常详细!)
- 07-21使用Node.js制作图片上传服务的详细教程
- 07-21vue3整合SpringSecurity加JWT实现权限校验
- 07-21vue3中pinia的使用及持久化的实现
- 07-21vue3整合SpringSecurity加JWT实现登录认证
- 07-21一文详解如何将Javascript打包成exe可执行文件
- 07-21JavaScript中if、else if、else和switch的语法、用法及注意事项
- 07-21Vue 3 中 vue-router 的 router.resolve () API详解


阅读排行
- 1Webpack打包速度优化方案汇总
- 2Vuex Actions多参数传递的解决方案
- 3前端JavaScript数组方法总结(非常详细!)
- 4使用Node.js制作图片上传服务的详细教程
- 5vue3整合SpringSecurity加JWT实现权限校验
- 6vue3中pinia的使用及持久化的实现
- 7vue3整合SpringSecurity加JWT实现登录认证
- 8一文详解如何将Javascript打包成exe可执行文件
- 9JavaScript中if、else if、else和switch的语法、用法及注意事项
- 10Vue 3 中 vue-router 的 router.resolve () API详解
推荐教程
- 04-23JavaScript Array实例方法flat的实现
- 04-23Vue3使用v-if指令进行条件渲染的实例代码
- 04-23THREE.JS使用TransformControls对模型拖拽的代码实例
- 07-21JavaScript判断数据类型的四种方式总结
- 04-23vue3+ts项目搭建的实现示例
- 07-21JavaScript检查变量类型的常用方法
- 07-21基于vue3与supabase系统认证机制详解
- 07-21JavaScript双问号操作符(??)的惊人用法总结大全
- 07-21JavaScript中if、else if、else和switch的语法、用法及注意事项
- 07-21Vue中使用vue-plugin-hiprint插件进行打印的功能实现