真实项目中权限设计的案例

我们看一下真实项目的权限设计案例,典型的错误用例。

首先我们从Controller的Mapping看起。


    /**
     * 新增
     *
     * @param entity
     * @return
     */
    @PostMapping
    @Transactional
    @PreAuthorize("@ps.check(@CONTRACTOR.INSERT)")
    public Object create(@RequestBody Contractor entity) {
        ......
    }

这里使用@PreAuthorize注解来对API进行授权。

用户权限加载

@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account user = userService.getByPhoneNumber(username);
        if (user == null) {
            throw new UsernameNotFoundException("没有找到该用户!");
        }
        Role role = roleService.getById(user.getRoleId());
        if (role == null) {
            throw new AccessDeniedException("该用户没有被授权,或授权角色已被删除。");
        }
        return new FioUserDetails(user, role);
    }

public class FioUserDetails implements UserDetails, Serializable {
    Account account;
    Role role;

    public FioUserDetails(Account account, Role role) {
        this.account = account;
        this.role = role;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (String role : RoleUtils.getRoles(this.role)) {
            authorities.add(new SimpleGrantedAuthority(role));
        }

        if (this.account.getId() <= 10) {
            authorities.add(new SimpleGrantedAuthority("ADMIN"));
        }

        return authorities;
    }

......
    }
}


/**
 * 权限 util
 *
 * @author liangzhuowei
 * @date 2021/11/27
 * @since open-jdk 11
 */
public class RoleUtils {
    /**
     * 扫描被权限注解的类包,返回相应需要的权限
     *
     * @return
     */
    public static List<String> getRoles(Role role) {
        List<String> roles = new ArrayList<>();
        // 现场操作者权限
        ClassUtil.scanPackageByAnnotation("包名...", FioOperateRole.class)
                .forEach(item -> {
                    String className = item.getSimpleName().toUpperCase(Locale.ROOT);
                    FioOperateRole operateRole = item.getAnnotation(FioOperateRole.class);
                    RolePrefix prefix = FioOperateRole.class.getAnnotation(RolePrefix.class);
                    RoleTypes types = FioOperateRole.class.getAnnotation(RoleTypes.class);
                    int idx = operateRole.value();
                    String fix = prefix.value().name();
                    var ts = Arrays.asList(types.value());
                    if (ts.contains(RoleTypes.Type.INSERT) && (role.getOperateInsert() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.INSERT.name())));
                    }
                    if (ts.contains(RoleTypes.Type.DELETE) && (role.getOperateDelete() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.DELETE.name())));
                    }
                    if (ts.contains(RoleTypes.Type.UPDATE) && (role.getOperateModify() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.UPDATE.name())));
                    }
                    if (ts.contains(RoleTypes.Type.SELECT) && (role.getOperateSelect() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.SELECT.name())));
                    }
                });


        // 其他附属权限
        ClassUtil.scanPackageByAnnotation("包名...", FioExtraRole.class)
                .forEach(item -> {
                    String className = item.getSimpleName().toUpperCase(Locale.ROOT);
                    FioExtraRole extraRole = item.getAnnotation(FioExtraRole.class);
                    RolePrefix prefix = FioExtraRole.class.getAnnotation(RolePrefix.class);
                    RoleTypes types = FioExtraRole.class.getAnnotation(RoleTypes.class);
                    int idx = extraRole.value();
                    String fix = prefix.value().name();
                    var ts = Arrays.asList(types.value());
                    if (ts.contains(RoleTypes.Type.INSERT) && (role.getExtraInsert() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.INSERT.name())));
                    }
                    if (ts.contains(RoleTypes.Type.DELETE) && (role.getExtraDelete() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.DELETE.name())));
                    }
                    if (ts.contains(RoleTypes.Type.UPDATE) && (role.getExtraModify() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.UPDATE.name())));
                    }
                    if (ts.contains(RoleTypes.Type.SELECT) && (role.getExtraSelect() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.SELECT.name())));
                    }
                });

        // 办公权限
        ClassUtil.scanPackageByAnnotation("包名...", FioOfficeRole.class)
                .forEach(item -> {
                    String className = item.getSimpleName().toUpperCase(Locale.ROOT);
                    FioOfficeRole officeRole = item.getAnnotation(FioOfficeRole.class);
                    RolePrefix prefix = FioOfficeRole.class.getAnnotation(RolePrefix.class);
                    RoleTypes types = FioOfficeRole.class.getAnnotation(RoleTypes.class);
                    int idx = officeRole.value();
                    String fix = prefix.value().name();
                    var ts = Arrays.asList(types.value());
                    if (ts.contains(RoleTypes.Type.INSERT) && (role.getOfficeInsert() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.INSERT.name())));
                    }
                    if (ts.contains(RoleTypes.Type.DELETE) && (role.getOfficeDelete() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.DELETE.name())));
                    }
                    if (ts.contains(RoleTypes.Type.UPDATE) && (role.getOfficeModify() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.UPDATE.name())));
                    }
                    if (ts.contains(RoleTypes.Type.SELECT) && (role.getOfficeSelect() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.SELECT.name())));
                    }
                });
        // 管理权限
        ClassUtil.scanPackageByAnnotation("包名...", FioManageRole.class)
                .forEach(item -> {
                    String className = item.getSimpleName().toUpperCase(Locale.ROOT);
                    FioManageRole manageRole = item.getAnnotation(FioManageRole.class);
                    RolePrefix prefix = FioOfficeRole.class.getAnnotation(RolePrefix.class);
                    RoleTypes types = FioOfficeRole.class.getAnnotation(RoleTypes.class);
                    int idx = manageRole.value();
                    String fix = prefix.value().name();
                    var ts = Arrays.asList(types.value());
                    if (ts.contains(RoleTypes.Type.INSERT) && (role.getOfficeInsert() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.INSERT.name())));
                    }
                    if (ts.contains(RoleTypes.Type.DELETE) && (role.getOfficeDelete() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.DELETE.name())));
                    }
                    if (ts.contains(RoleTypes.Type.UPDATE) && (role.getOfficeModify() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.UPDATE.name())));
                    }
                    if (ts.contains(RoleTypes.Type.SELECT) && (role.getOfficeSelect() >> idx & 0x1) == 1) {
                        roles.add(String.join("_", List.of(fix, className, RoleTypes.Type.SELECT.name())));
                    }
                });
        return roles;
    }

    /**
     * 检查 权限值 是否唯一
     */
    public static void check() {
        ClassUtil.scanPackageByAnnotation("包名...", FioOfficeRole.class)
                .stream()
                .collect(Collectors.groupingBy(clazz -> clazz.getAnnotation(FioOfficeRole.class).value(), Collectors.toList()))
                .forEach((k, v) -> {
                    if (v.size() > 1) {
                        Class<?> aClass = v.get(0);
                        throw new RuntimeException(aClass.getSimpleName() + "的" + FioOfficeRole.class.getSimpleName() + "值重复,值:" + k);
                    }
                });

        ClassUtil.scanPackageByAnnotation("com.gason.backend.entity", FioOperateRole.class)
                .stream()
                .collect(Collectors.groupingBy(clazz -> clazz.getAnnotation(FioOperateRole.class).value(), Collectors.toList()))
                .forEach((k, v) -> {
                    if (v.size() > 1) {
                        Class<?> aClass = v.get(0);
                        throw new RuntimeException(aClass.getSimpleName() + "的" + FioOperateRole.class.getSimpleName() + "值重复,值:" + k);
                    }
                });

        ClassUtil.scanPackageByAnnotation("包名...", FioExtraRole.class)
                .stream()
                .collect(Collectors.groupingBy(clazz -> clazz.getAnnotation(FioExtraRole.class).value(), Collectors.toList()))
                .forEach((k, v) -> {
                    if (v.size() > 1) {
                        Class<?> aClass = v.get(0);
                        throw new RuntimeException(aClass.getSimpleName() + "的" + FioExtraRole.class.getSimpleName() + "值重复,值:" + k);
                    }
                });

        ClassUtil.scanPackageByAnnotation("包名...", FioManageRole.class)
                .stream()
                .collect(Collectors.groupingBy(clazz -> clazz.getAnnotation(FioManageRole.class).value(), Collectors.toList()))
                .forEach((k, v) -> {
                    if (v.size() > 1) {
                        Class<?> aClass = v.get(0);
                        throw new RuntimeException(aClass.getSimpleName() + "的" + FioManageRole.class.getSimpleName() + "值重复,值:" + k);
                    }
                });
    }


}

说明:Account表保存用户的账号信息,唯一的username,唯一的roleId

  1. FioUserDetails 根据用户的唯一角色roleId(包含了 Office, Operate, Extra, Manage), 查找所有注解对应的实体类,  将相应的增删改查权限装入用户的Authorities

      格式为: OFFCIE_CONTRACTOR_INSERT

      此格式为 OfficeAuthority 类加载:

/**
 * 权限
 *
 * @author liangzhuowei
 * @date 2021/12/10
 * @since open-jdk 11
 */
public class OfficeAuthority {

    /**
     * 0
     * 角色管理权限
     */
    @Service("ROLE")
    public static class ROLE {
        public final String INSERT = "OFFICE_ROLE_INSERT";
        public final String DELETE = "OFFICE_ROLE_DELETE";
        public final String UPDATE = "OFFICE_ROLE_UPDATE";
        public final String SELECT = "OFFICE_ROLE_SELECT";
    }

    /**
     * 1
     * 用户管理权限
     */
    @Service("ACCOUNT")
    public static class ACCOUNT {
        public final String INSERT = "OFFICE_ACCOUNT_INSERT";
        public final String DELETE = "OFFICE_ACCOUNT_DELETE";
        public final String UPDATE = "OFFICE_ACCOUNT_UPDATE";
        public final String SELECT = "OFFICE_ACCOUNT_SELECT";
    }
...
}

实体类和权限绑定

/**
 * <p>
 * 公司档案字段表
 * </p>
 *
 * @author MyBatisPlusGenerator
 * @since 2023-08-22
 */
@Data
@FioOfficeRole(52)
@TableName("fio_contractor")
@ApiModel(value = "Contractor对象", description = "公司档案字段表")
public class Contractor extends BasicCustomEntity {
......
}

权限检查

/**
 * 权限检验
 *
 * @author liangzhuowei
 * @date 2021/11/29
 * @since open-jdk 11
 */
@Service("ps")
public class PermissionService {
....
}

============================================================================

以上为真实项目的权限设计,点评如下:

  1. 授权的资源对象应该是Controller里的UrlMapping对应的API,这里的设计直接改为了实体类,并且每个实体类对应的权限固定为4个:增,删,改,查

  2. 授权操作应该是创建角色并将角色与资源对象关联,这里的设计没有角色的创建,直接创建了角色与资源对象操作的关联,请注意,这里的角色并没有与资源对象关联,而是直接关联资源对象的操作(增,删,改,查)

  3. 这里的设计是将资源对象分为4大类:OFFICE, OPERATE,MANAGE,EXTRA,授权操作不能直接作用的资源对象

  4. 每个用户的角色只有一个...

正确的使用方式请参考SpringSecurity的官方文档, 正确区分权限框架的使用场景

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值