解决用户权限问题

解决授权

然后关于权限部分,也是security的重要功能,当用户认证成功之后,我们就知道谁在访问系统接口,这是又有一个问题,就是这个用户有没有权限来访问我们这个接口呢,要解决这个问题,我们需要知道用户有哪些权限,哪些角色,这样security才能我们做权限判断。

之前我们已经定义及几张表,用户、角色、菜单、以及一些关联表,一般当权限粒度比较细的时候,我们都通过判断用户有没有此菜单或操作的权限,而不是通过角色判断,而用户和菜单是不直接做关联的,是通过用户拥有哪些角色,然后角色拥有哪些菜单权限这样来获得的。

问题1:我们是在哪里赋予用户权限的?有两个地方:

  • 1、用户登录,调用调用UserDetailsService.loadUserByUsername()方法时候可以返回用户的权限信息。
  • 2、接口调用进行身份认证过滤器时候JWTAuthenticationFilter,需要返回用户权限信息

问题2:在哪里决定什么接口需要什么权限?

Security内置的权限注解:

可以在Controller的方法前添加这些注解表示接口需要什么权限。

比如需要Admin角色权限:

@PreAuthorize("hasRole('admin')")

比如需要添加管理员的操作权限

@PreAuthorize("hasAuthority('sys:user:save')")

ok,我们再来整体梳理一下授权、验证权限的流程:

  1. 用户登录或者调用接口时候识别到用户,并获取到用户的权限信息
  2. 注解标识Controller中的方法需要的权限或角色
  3. Security通过FilterSecurityInterceptor匹配URI和权限是否匹配
  4. 有权限则可以访问接口,当无权限的时候返回异常交给AccessDeniedHandler操作类处理

ok,流程清晰之后我们就开始我们的编码:

  • UserDetailsServiceImpl
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
   ...   
   return new AccountUser(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(), getUserAuthority(sysUser.getId()));
}
public List<GrantedAuthority> getUserAuthority(Long userId) {
   // 通过内置的工具类,把权限字符串封装成GrantedAuthority列表
   return  AuthorityUtils.commaSeparatedStringToAuthorityList(
         sysUserService.getUserAuthorityInfo(userId)
   );
}
SysUser sysUser = sysUserService.getByUsername(username);
List<GrantedAuthority> grantedAuthorities = userDetailsService.getUserAuthority(sysUser.getId());
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
      = new UsernamePasswordAuthenticationToken(username, null, grantedAuthorities);

代码中的com.service.impl.SysUserServiceImpl#getUserAuthorityInfo是重点:

@Slf4j
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
   ... 
   @Override
   public String getUserAuthorityInfo(Long userId) {
      SysUser sysUser = this.getById(userId);
      String authority = null;

      if (redisUtil.hasKey("GrantedAuthority:" + sysUser.getUsername())) {
         // 优先从缓存获取
         authority = (String)redisUtil.get("GrantedAuthority:" + sysUser.getUsername());

      } else {

         List<SysRole> roles = sysRoleService.list(new QueryWrapper<SysRole>()
               .inSql("id", "select role_id from sys_user_role where user_id = " + userId));
         List<Long> menuIds = sysUserMapper.getNavMenuIds(userId);
         List<SysMenu> menus = sysMenuService.listByIds(menuIds);

         String roleNames = roles.stream().map(r -> "ROLE_" + r.getCode()).collect(Collectors.joining(","));
         String permNames = menus.stream().map(m -> m.getPerms()).collect(Collectors.joining(","));

         authority = roleNames.concat(",").concat(permNames);
         log.info("用户ID - {} ---拥有的权限:{}", userId, authority);

         redisUtil.set("GrantedAuthority:" + sysUser.getUsername(), authority, 60*60);

      }
      return authority;
   }
}

可以看到,我通过用户id分别获取到用户的角色信息和菜单信息,然后通过逗号链接起来,因为角色信息我们需要这样“ROLE_”+角色,所以才有了上面的写法:
比如用户拥有Admin角色和添加用户权限,则最后的字符串是:ROLE_admin,syssave

同时为了避免多次查库,我做了一层缓存,这里理解应该不难。

然后sysUserMapper.getNavMenuIds(userId)因为要查询数据库,具体SQL如下:

<select id="getNavMenuIds" resultType="java.lang.Long">
    SELECT
        DISTINCT rm.menu_id
    FROM
        sys_user_role ur
    LEFT JOIN `sys_role_menu` rm ON rm.role_id = ur.role_id
    WHERE
        ur.user_id = #{userId};
</select>

上面表示通过用户ID获取用户关联的菜单的id,因此需要用到两个中间表的关联了。
ok,这样我们就赋予了用户角色和操作权限了。后面我们只需要在Controller添加上具体注解表示需要的权限,Security就会自动帮我们自动完成权限校验了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值