Apollo 源码解析 —— Portal 认证与授权(二)之授权

apollo配置中心apollo-portal权限的介绍分享 一、前言 本文是采用apollo配置中心 1.5.0 进行介绍 配置中心进行容器化部署,从官方下载的apollo-portal.jar包,解压出来,发现spring.profiles.active=github,auth 上面的意思,就是采用 auth 进行鉴权,采用该鉴权,就是走apollo默认用户权限关系,采用前后端不分离的session方式进行鉴权。 如果从github下载apollo配置中心源码,spring.profiles.active的参数是没有默认值的 spring: applicat 阅读详情

点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 8:55 更新文章,每天掉亿点点头发...

源码精品专栏

 

摘要: 原创出处 http://www.iocoder.cn/Apollo/portal-auth-2/ 「芋道源码」欢迎转载,保留摘要,谢谢!

  • 1. 概述

  • 2. 权限模型

  • 3. RolePermissionService

  • 4. RoleInitializationService

  • 5. PermissionValidator

  • 6. PermissionController

  • 666. 彩蛋


1. 概述

老艿艿:本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》 ,特别是 《Portal 实现用户登录功能》 。

本文接 《Apollo 源码解析 —— Portal 认证与授权(一)之认证》 ,侧重在授权部分。在上一文中,我们提到:

具体每个 URL 的权限校验,通过在对应的方法上,添加 @PreAuthorize 方法注解,配合具体的方法参数,一起校验功能 + 数据级的权限校验。

2. 权限模型

常见的权限模型,有两种:RBAC 和 ACL 。如果不了解的胖友,可以看下 《基于AOP实现权限管理:访问控制模型 RBAC 和 ACL 》 。

笔者一开始看到 Role + UserRole + Permission + RolePermission 四张表,认为是 RBAC 权限模型。但是看了 Permission 的数据结构,以及 PermissionValidator 的权限判断方式,又感受到几分 ACL 权限模型的味道。

所以,很难完全说,Apollo  属于 RBAC 还是 ACL 权限模型。或者说,权限模型,本身会根据实际业务场景的业务需要,做一些变种和改造。权限模型,提供给我们的是指导和借鉴,不需要过于拘泥。

关系如下图:

2.1 Role

Role 表,角色表,对应实体 com.ctrip.framework.apollo.portal.entity.po.Role ,代码如下:

@Entity
@Table(name = "Role")
@SQLDelete(sql = "Update Role set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Role extends BaseEntity {

    /**
     * 角色名
     */
    @Column(name = "RoleName", nullable = false)
    private String roleName;

}
  • roleName 字段,角色名,通过系统自动生成。目前有三种类型( 不是三个 )角色:

    • App 管理员,格式为 "Master + AppId" ,例如:"Master+100004458" 。

    • Namespace 修改管理员,格式为 "ModifyNamespace + AppId + NamespaceName" ,例如:"ModifyNamespace+100004458+application" 。

    • Namespace 发布管理员,格式为 "ReleaseNamespace + AppId + NamespaceName" ,例如:"ReleaseNamespace+100004458+application" 。

  • 例子如下图:

2.2 UserRole

UserRole 表,用户与角色的关联表,对应实体 com.ctrip.framework.apollo.portal.entity.po.UserRole ,代码如下:

@Entity
@Table(name = "UserRole")
@SQLDelete(sql = "Update UserRole set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class UserRole extends BaseEntity {

    /**
     * 账号 {@link UserPO#username}
     */
    @Column(name = "UserId", nullable = false)
    private String userId;
    /**
     * 角色编号 {@link Role#id}
     */
    @Column(name = "RoleId", nullable = false)
    private long roleId;

}
  • userId 字段,用户编号,指向对应的 User 。目前使用 UserPO.username 。当然,我们自己的业务系统里,推荐使用 UserPO.id 。

  • roleId 字段,角色编号,指向对应的 Role 。

  • 例子如下图:

2.3 Permission

Permission 表,权限表,对应实体 com.ctrip.framework.apollo.portal.entity.po.Permission ,代码如下:

@Entity
@Table(name = "Permission")
@SQLDelete(sql = "Update Permission set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Permission extends BaseEntity {

    /**
     * 权限类型
     */
    @Column(name = "PermissionType", nullable = false)
    private String permissionType;
    /**
     * 目标编号
     */
    @Column(name = "TargetId", nullable = false)
    private String targetId;
    
}
  • permissionType 字段,权限类型。在 com.ctrip.framework.apollo.portal.constant.PermissionType 中枚举,代码如下:

    public interface PermissionType {
    
        // ========== APP level permission ==========
        String CREATE_NAMESPACE = "CreateNamespace"; // 创建 Namespace
        String CREATE_CLUSTER = "CreateCluster"; // 创建 Cluster
        String ASSIGN_ROLE = "AssignRole"; // 分配用户权限的权限
    
        // ========== namespace level permission =========
        String MODIFY_NAMESPACE = "ModifyNamespace"; // 修改 Namespace
        String RELEASE_NAMESPACE = "ReleaseNamespace"; // 发布 Namespace
    
    }
    
    • 分成 App 和 Namespace 两种级别的权限类型。

  • targetId 字段,目标编号。

  • 例子如下图:

    • 为什么不是 Namespace 的编号?Namespace 级别,是所有环境 + 所有集群都有权限,所以不能具体某个 Namespace 。

    • App 级别时,targetId 指向 "App 编号"。

    • Namespace 级别时,targetId 指向 "App 编号 + Namespace 名字"。

2.4 RolePermission

RolePermission 表,角色与权限的关联表,对应实体 com.ctrip.framework.apollo.portal.entity.po.RolePermission ,代码如下:

@Entity
@Table(name = "RolePermission")
@SQLDelete(sql = "Update RolePermission set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class RolePermission extends BaseEntity {

    /**
     * 角色编号 {@link Role#id}
     */
    @Column(name = "RoleId", nullable = false)
    private long roleId;
    /**
     * 权限编号 {@link Permission#id}
     */
    @Column(name = "PermissionId", nullable = false)
    private long permissionId;

}
  • roleId 字段,角色编号,指向对应的 Role 。

  • permissionId 字段,权限编号,指向对应的 Permission 。

  • 例子如下图:

3. RolePermissionService

com.ctrip.framework.apollo.portal.service.RolePermissionService ,提供 Role、UserRole、Permission、UserPermission 相关的操作。代码如下:

public interface RolePermissionService {

    // ========== Role 相关 ==========
    /**
     * Create role with permissions, note that role name should be unique
     */
    Role createRoleWithPermissions(Role role, Set<Long> permissionIds);
    /**
     * Find role by role name, note that roleName should be unique
     */
    Role findRoleByRoleName(String roleName);

    // ========== UserRole 相关 ==========
    /**
     * Assign role to users
     *
     * @return the users assigned roles
     */
    Set<String> assignRoleToUsers(String roleName, Set<String> userIds, String operatorUserId);
    /**
     * Remove role from users
     */
    void removeRoleFromUsers(String roleName, Set<String> userIds, String operatorUserId);
    /**
     * Query users with role
     */
    Set<UserInfo> queryUsersWithRole(String roleName);

    // ========== UserPermission 相关 ==========
    /**
     * Check whether user has the permission
     */
    boolean userHasPermission(String userId, String permissionType, String targetId);
    /**
     * 校验是否为超级管理员
     */
    boolean isSuperAdmin(String userId);

    // ========== Permission 相关 ==========
    /**
     * Create permission, note that permissionType + targetId should be unique
     */
    Permission createPermission(Permission permission);
    /**
     * Create permissions, note that permissionType + targetId should be unique
     */
    Set<Permission> createPermissions(Set<Permission> permissions);

}

3.1 DefaultRolePermissionService

com.ctrip.framework.apollo.portal.spi.defaultimpl.DefaultRolePermissionService ,实现 RolePermissionService 接口,默认 RolePermissionService 实现类。

老艿艿:下面的方法比较易懂,胖友看着代码注释理解。

3.1.1 createRoleWithPermissions

@Override
@Transactional
public Role createRoleWithPermissions(Role role, Set<Long> permissionIds) {
    // 获得 Role 对象,校验 Role 不存在
    Role current = findRoleByRoleName(role.getRoleName());
    Preconditions.checkState(current == null, "Role %s already exists!", role.getRoleName());

    // 新增 Role
    Role createdRole = roleRepository.save(role);

    // 授权给 Role
    if (!CollectionUtils.isEmpty(permissionIds)) {
        // 创建 RolePermission 数组
        Iterable<RolePermission> rolePermissions = permissionIds.stream().map(permissionId -> {
            RolePermission rolePermission = new RolePermission();
            rolePermission.setRoleId(createdRole.getId()); // Role 编号
            rolePermission.setPermissionId(permissionId);
            rolePermission.setDataChangeCreatedBy(createdRole.getDataChangeCreatedBy());
            rolePermission.setDataChangeLastModifiedBy(createdRole.getDataChangeLastModifiedBy());
            return rolePermission;
        }).collect(Collectors.toList());
        // 保存 RolePermission 数组
        rolePermissionRepository.save(rolePermissions);
    }

    return createdRole;
}

3.1.2 assignRoleToUsers

@Override
@Transactional
public Set<String> assignRoleToUsers(String roleName, Set<String> userIds, String operatorUserId) {
    // 获得 Role 对象,校验 Role 存在
    Role role = findRoleByRoleName(roleName);
    Preconditions.checkState(role != null, "Role %s doesn't exist!", roleName);

    // 获得已存在的 UserRole 数组
    List<UserRole> existedUserRoles = userRoleRepository.findByUserIdInAndRoleId(userIds, role.getId());
    Set<String> existedUserIds = existedUserRoles.stream().map(UserRole::getUserId).collect(Collectors.toSet());
    // 排除已经存在的
    Set<String> toAssignUserIds = Sets.difference(userIds, existedUserIds);

    // 创建需要新增的 UserRole 数组
    Iterable<UserRole> toCreate = toAssignUserIds.stream().map(userId -> {
        UserRole userRole = new UserRole();
        userRole.setRoleId(role.getId());
        userRole.setUserId(userId);
        userRole.setDataChangeCreatedBy(operatorUserId);
        userRole.setDataChangeLastModifiedBy(operatorUserId);
        return userRole;
    }).collect(Collectors.toList());
    // 保存 RolePermission 数组
    userRoleRepository.save(toCreate);

    return toAssignUserIds;
}

3.1.3 removeRoleFromUsers

@Override
@Transactional
public void removeRoleFromUsers(String roleName, Set<String> userIds, String operatorUserId) {
    // 获得 Role 对象,校验 Role 存在
    Role role = findRoleByRoleName(roleName);
    Preconditions.checkState(role != null, "Role %s doesn't exist!", roleName);

    // 获得已存在的 UserRole 数组
    List<UserRole> existedUserRoles = userRoleRepository.findByUserIdInAndRoleId(userIds, role.getId());
    // 标记删除
    for (UserRole userRole : existedUserRoles) {
        userRole.setDeleted(true); // 标记删除
        userRole.setDataChangeLastModifiedTime(new Date());
        userRole.setDataChangeLastModifiedBy(operatorUserId);
    }

    // 保存 RolePermission 数组 【标记删除】
    userRoleRepository.save(existedUserRoles);
}

3.1.4 queryUsersWithRole

@Override
public Set<UserInfo> queryUsersWithRole(String roleName) {
    // 获得 Role 对象,校验 Role 存在
    Role role = findRoleByRoleName(roleName);

    // Role 不存在时,返回空数组
    if (role == null) {
        return Collections.emptySet();
    }

    // 获得 UserRole 数组
    List<UserRole> userRoles = userRoleRepository.findByRoleId(role.getId());
    // 转换成 UserInfo 数组
    return userRoles.stream().map(userRole -> {
        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(userRole.getUserId());
        return userInfo;
    }).collect(Collectors.toSet());
}

3.1.5 findRoleByRoleName

public Role findRoleByRoleName(String roleName) {
    return roleRepository.findTopByRoleName(roleName);
}

3.1.6 userHasPermission 【重要】

@Override
public boolean userHasPermission(String userId, String permissionType, String targetId) {
    // 获得 Permission 对象
    Permission permission = permissionRepository.findTopByPermissionTypeAndTargetId(permissionType, targetId);
    // 若 Permission 不存在,返回 false
    if (permission == null) {
        return false;
    }

    // 若是超级管理员,返回 true 【有权限】
    if (isSuperAdmin(userId)) {
        return true;
    }

    // 获得 UserRole 数组
    List<UserRole> userRoles = userRoleRepository.findByUserId(userId);
    // 若数组为空,返回 false
    if (CollectionUtils.isEmpty(userRoles)) {
        return false;
    }

    // 获得 RolePermission 数组
    Set<Long> roleIds = userRoles.stream().map(UserRole::getRoleId).collect(Collectors.toSet());
    List<RolePermission> rolePermissions = rolePermissionRepository.findByRoleIdIn(roleIds);
    // 若数组为空,返回 false
    if (CollectionUtils.isEmpty(rolePermissions)) {
        return false;
    }

    // 判断是否有对应的 RolePermission 。若有,则返回 true 【有权限】
    for (RolePermission rolePermission : rolePermissions) {
        if (rolePermission.getPermissionId() == permission.getId()) {
            return true;
        }
    }

    return false;
}
  • 从目前的代码看下来,这个权限判断的过程,是 ACL 的方式。

  • 如果是 RBAC 的方式,获得 Permission 后,再获得 Permission 对应的 RolePermission 数组,最后和 User 对应的 UserRole 数组,求 roleId 是否相交。

3.1.7 isSuperAdmin

@Override
public boolean isSuperAdmin(String userId) {
    return portalConfig.superAdmins().contains(userId);
}
  • 通过 ServerConfig 的 "superAdmin" 配置项,判断是否存在该账号。

3.1.8 createPermissions

@Override
@Transactional
public Permission createPermission(Permission permission) {
    String permissionType = permission.getPermissionType();
    String targetId = permission.getTargetId();
    // 获得 Permission 对象,校验 Permission 为空
    Permission current = permissionRepository.findTopByPermissionTypeAndTargetId(permissionType, targetId);
    Preconditions.checkState(current == null, "Permission with permissionType %s targetId %s already exists!", permissionType, targetId);

    // 保存 Permission
    return permissionRepository.save(permission);
}

3.1.8 createPermissions

@Override
@Transactional
public Set<Permission> createPermissions(Set<Permission> permissions) {
    // 创建 Multimap 对象,用于下面校验的分批的批量查询
    Multimap<String, String> targetIdPermissionTypes = HashMultimap.create();
    for (Permission permission : permissions) {
        targetIdPermissionTypes.put(permission.getTargetId(), permission.getPermissionType());
    }

    // 查询 Permission 集合,校验都不存在
    for (String targetId : targetIdPermissionTypes.keySet()) {
        Collection<String> permissionTypes = targetIdPermissionTypes.get(targetId);
        List<Permission> current = permissionRepository.findByPermissionTypeInAndTargetId(permissionTypes, targetId);
        Preconditions.checkState(CollectionUtils.isEmpty(current), "Permission with permissionType %s targetId %s already exists!", permissionTypes, targetId);
    }

    // 保存 Permission 集合
    Iterable<Permission> results = permissionRepository.save(permissions);
    // 转成 Permission 集合,返回
    return StreamSupport.stream(results.spliterator(), false).collect(Collectors.toSet());
}

4. RoleInitializationService

com.ctrip.framework.apollo.portal.service.RoleInitializationService ,提供角色初始化相关的操作。代码如下:

public interface RoleInitializationService {

    /**
     * 初始化 App 级的 Role
     */
    void initAppRoles(App app);

    /**
     * 初始化 Namespace 级的 Role
     */
    void initNamespaceRoles(String appId, String namespaceName, String operator);

}

4.1 DefaultRoleInitializationService

com.ctrip.framework.apollo.portal.spi.defaultimpl.DefaultRoleInitializationService ,实现 RoleInitializationService 接口,默认 RoleInitializationService 实现类。

4.1.1 initAppRoles

  1: @Override
  2: @Transactional
  3: public void initAppRoles(App app) {
  4:     String appId = app.getAppId();
  5: 
  6:     // 创建 App 拥有者的角色名
  7:     String appMasterRoleName = RoleUtils.buildAppMasterRoleName(appId);
  8:     // has created before
  9:     // 校验角色是否已经存在。若是,直接返回
 10:     if (rolePermissionService.findRoleByRoleName(appMasterRoleName) != null) {
 11:         return;
 12:     }
 13:     String operator = app.getDataChangeCreatedBy();
 14:     //create app permissions
 15:     // 创建 App 角色
 16:     createAppMasterRole(appId, operator);
 17:     // 授权 Role 给 App 拥有者
 18:     // assign master role to user
 19:     rolePermissionService.assignRoleToUsers(RoleUtils.buildAppMasterRoleName(appId), Sets.newHashSet(app.getOwnerName()), operator);
 20: 
 21:     // 初始化 Namespace 角色
 22:     initNamespaceRoles(appId, ConfigConsts.NAMESPACE_APPLICATION, operator);
 23:     // 授权 Role 给 App 创建者
 24:     //assign modify、release namespace role to user
 25:     rolePermissionService.assignRoleToUsers(RoleUtils.buildNamespaceRoleName(appId, ConfigConsts.NAMESPACE_APPLICATION, RoleType.MODIFY_NAMESPACE), Sets.newHashSet(operator), operator);
 26:     rolePermissionService.assignRoleToUsers(RoleUtils.buildNamespaceRoleName(appId, ConfigConsts.NAMESPACE_APPLICATION, RoleType.RELEASE_NAMESPACE), Sets.newHashSet(operator), operator);
 27: }
  • 在 Portal 创建完本地 App 后,自动初始化对应的 Role 们。调用如下图:

  • =========== 初始化 App 级的 Role ===========

  • 第 7 行:调用 RoleUtils#buildAppMasterRoleName(appId) 方法,创建 App 拥有者的角色名。代码如下:

    // RoleUtils.java
    private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
    
    public static String buildAppMasterRoleName(String appId) {
        return STRING_JOINER.join(RoleType.MASTER, appId);
    }
    
    // RoleType.java
    public static final String MASTER = "Master";
    
  • 第 9 至 12 行:调用 RolePermissionService#findRoleByRoleName(appMasterRoleName) 方法,校验角色是否已经存在。若是,直接返回。

  • 第 16 行:调用 #createAppMasterRole(appId, operator) 方法,创建 App 拥有者角色。代码如下:

    private void createAppMasterRole(String appId, String operator) {
        // 创建 App 对应的 Permission 集合,并保存到数据库
        Set<Permission> appPermissions = Lists.newArrayList(PermissionType.CREATE_CLUSTER, PermissionType.CREATE_NAMESPACE, PermissionType.ASSIGN_ROLE)
                .stream().map(permissionType -> createPermission(appId, permissionType, operator) /* 创建 Permission 对象 */ ).collect(Collectors.toSet());
        Set<Permission> createdAppPermissions = rolePermissionService.createPermissions(appPermissions);
        Set<Long> appPermissionIds = createdAppPermissions.stream().map(BaseEntity::getId).collect(Collectors.toSet());
    
        // 创建 App 对应的 Role 对象,并保存到数据库
        // create app master role
        Role appMasterRole = createRole(RoleUtils.buildAppMasterRoleName(appId), operator);
        rolePermissionService.createRoleWithPermissions(appMasterRole, appPermissionIds);
    }
    
    • x

    • x

    • 创建并保存 App 对应的 Permission 集合。#createPermission(targetId, permissionType, operator) 方法,创建 Permission 对象。代码如下:

      private Permission createPermission(String targetId, String permissionType, String operator) {
          Permission permission = new Permission();
          permission.setPermissionType(permissionType);
          permission.setTargetId(targetId);
          permission.setDataChangeCreatedBy(operator);
          permission.setDataChangeLastModifiedBy(operator);
          return permission;
      }
      
    • 创建并保存 App 对应的 Role 对象,并授权对应的 Permission 集合。#createRole(roleName, operator) 方法,创建 Role 对象。代码如下:

      private Role createRole(String roleName, String operator) {
          Role role = new Role();
          role.setRoleName(roleName);
          role.setDataChangeCreatedBy(operator);
          role.setDataChangeLastModifiedBy(operator);
          return role;
      }
      
  • 第 19 行:调用 rolePermissionService.assignRoleToUsers(roleName, userIds, operatorUserId) 方法,授权 Role 给 App 拥有者。

  • =========== 初始化 Namespace 级的 Role ===========

  • 第 22 行:调用 #initNamespaceRoles(appId, namespaceName, operator) 方法,初始化 Namespace 的角色。详细解析,见 「4.2 initNamespaceRoles」 。

  • 第 23 至 26 行:调用 rolePermissionService.assignRoleToUsers(roleName, userIds, operatorUserId) 方法,授权 Role 给 App 创建者。注意,此处不是“拥有者”噢。为什么?因为,Namespace 是自动创建的,并且是通过创建人来操作的。

4.1.2 initNamespaceRoles

@Override
@Transactional
public void initNamespaceRoles(String appId, String namespaceName, String operator) {
    // 创建 Namespace 修改的角色名
    String modifyNamespaceRoleName = RoleUtils.buildModifyNamespaceRoleName(appId, namespaceName);
    // 若不存在对应的 Role ,进行创建
    if (rolePermissionService.findRoleByRoleName(modifyNamespaceRoleName) == null) {
        createNamespaceRole(appId, namespaceName, PermissionType.MODIFY_NAMESPACE, RoleUtils.buildModifyNamespaceRoleName(appId, namespaceName), operator);
    }

    // 创建 Namespace 发布的角色名
    String releaseNamespaceRoleName = RoleUtils.buildReleaseNamespaceRoleName(appId, namespaceName);
    // 若不存在对应的 Role ,进行创建
    if (rolePermissionService.findRoleByRoleName(releaseNamespaceRoleName) == null) {
        createNamespaceRole(appId, namespaceName, PermissionType.RELEASE_NAMESPACE,
                RoleUtils.buildReleaseNamespaceRoleName(appId, namespaceName), operator);
    }
}
  • 在 Portal 创建完 Namespace 后,自动初始化对应的 Role 们。调用如下图:

  • 创建并保存 Namespace 修改和发布对应的 Role 。

  • RoleUtils#buildModifyNamespaceRoleName(appId, namespaceName) 方法,创建 Namespace 修改的角色名。代码如下:

    // RoleUtils.java
    public static String buildModifyNamespaceRoleName(String appId, String namespaceName) {
        return STRING_JOINER.join(RoleType.MODIFY_NAMESPACE, appId, namespaceName);
    }
    
    // RoleType.java
    public static final String MODIFY_NAMESPACE = "ModifyNamespace";
    
  • RoleUtils#buildReleaseNamespaceRoleName(appId, namespaceName) 方法,创建 Namespace 发布的角色名。代码如下:

    // RoleUtils.java
    public static String buildReleaseNamespaceRoleName(String appId, String namespaceName) {
        return STRING_JOINER.join(RoleType.RELEASE_NAMESPACE, appId, namespaceName);
    }
    
    // RoleType.java
    public static final String RELEASE_NAMESPACE = "ReleaseNamespace";
    
  • #createNamespaceRole(...) 方法,创建 Namespace 的角色。代码如下:

    private void createNamespaceRole(String appId, String namespaceName, String permissionType,
                                     String roleName, String operator) {
        // 创建 Namespace 对应的 Permission 对象,并保存到数据库
        Permission permission = createPermission(RoleUtils.buildNamespaceTargetId(appId, namespaceName), permissionType, operator);
        Permission createdPermission = rolePermissionService.createPermission(permission);
    
        // 创建 Namespace 对应的 Role 对象,并保存到数据库
        Role role = createRole(roleName, operator);
        rolePermissionService.createRoleWithPermissions(role, Sets.newHashSet(createdPermission.getId()));
    }
    
    • x

    • 创建并保存 Namespace 对应的 Permission 对象。

    • 创建并保存 Namespace 对应的 Role 对象,并授权对应的 Permission 。

    • RoleUtils#buildNamespaceTargetId(appId, namespaceName) 方法,创建 Namespace 的目标编号。代码如下:

      public static String buildNamespaceTargetId(String appId, String namespaceName) {
          return STRING_JOINER.join(appId, namespaceName);
      }
      

5. PermissionValidator

com.ctrip.framework.apollo.portal.component.PermissionValidator ,权限校验器。代码如下:

@Component("permissionValidator")
public class PermissionValidator {

    @Autowired
    private UserInfoHolder userInfoHolder;
    @Autowired
    private RolePermissionService rolePermissionService;
    @Autowired
    private PortalConfig portalConfig;

    // ========== Namespace 级别 ==========

    public boolean hasModifyNamespacePermission(String appId, String namespaceName) {
        return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
                PermissionType.MODIFY_NAMESPACE,
                RoleUtils.buildNamespaceTargetId(appId, namespaceName));
    }

    public boolean hasReleaseNamespacePermission(String appId, String namespaceName) {
        return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
                PermissionType.RELEASE_NAMESPACE,
                RoleUtils.buildNamespaceTargetId(appId, namespaceName));
    }

    public boolean hasDeleteNamespacePermission(String appId) {
        return hasAssignRolePermission(appId) || isSuperAdmin();
    }

    public boolean hasOperateNamespacePermission(String appId, String namespaceName) {
        return hasModifyNamespacePermission(appId, namespaceName) || hasReleaseNamespacePermission(appId, namespaceName);
    }

    // ========== App 级别 ==========

    public boolean hasAssignRolePermission(String appId) {
        return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
                PermissionType.ASSIGN_ROLE,
                appId);
    }

    public boolean hasCreateNamespacePermission(String appId) {
        return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
                PermissionType.CREATE_NAMESPACE,
                appId);
    }

    public boolean hasCreateAppNamespacePermission(String appId, AppNamespace appNamespace) {
        boolean isPublicAppNamespace = appNamespace.isPublic();
        // 若满足如下任一条件:
        // 1. 公开类型的 AppNamespace 。
        // 2. 私有类型的 AppNamespace ,并且允许 App 管理员创建私有类型的 AppNamespace 。
        if (portalConfig.canAppAdminCreatePrivateNamespace() || isPublicAppNamespace) {
            return hasCreateNamespacePermission(appId);
        }
        // 超管
        return isSuperAdmin();
    }

    public boolean hasCreateClusterPermission(String appId) {
        return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
                PermissionType.CREATE_CLUSTER,
                appId);
    }

    public boolean isAppAdmin(String appId) {
        return isSuperAdmin() || hasAssignRolePermission(appId);
    }

    // ========== 超管 级别 ==========

    public boolean isSuperAdmin() {
        return rolePermissionService.isSuperAdmin(userInfoHolder.getUser().getUserId());
    }

}

在每个需要校验权限的方法上,添加 @PreAuthorize 注解,并在 value 属性上写 EL 表达式,调用 PermissionValidator 的校验方法。例如:

  • 创建 Namespace 的方法,添加了 @PreAuthorize(value = "@permissionValidator.hasCreateNamespacePermission(#appId)") 。

  • 删除 Namespace 的方法,添加了 @PreAuthorize(value = "@permissionValidator.hasDeleteNamespacePermission(#appId)") 。

通过这样的方式,达到功能 + 数据级的权限控制。

6. PermissionController

com.ctrip.framework.apollo.portal.controller.PermissionController ,提供权限相关的 API 。如下图所示:

  • 每个方法,调用 RolePermissionService 的方法,提供 API 服务。

  • ???? 代码比较简单,胖友自己查看。

对应界面为

  • App 级权限管理:

  • Namespace 级别权限管理:

666. 彩蛋

T T 老长一篇。哈哈哈,有种把所有代码 copy 过来的感觉。

突然发现没分享 com.ctrip.framework.apollo.portal.spi.configurationRoleConfiguration ,Role Spring Java 配置。代码如下:

@Configuration
public class RoleConfiguration {

    @Bean
    public RoleInitializationService roleInitializationService() {
        return new DefaultRoleInitializationService();
    }

    @Bean
    public RolePermissionService rolePermissionService() {
        return new DefaultRolePermissionService();
    }

}


欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

兄弟,艿一口,点个赞!????

Apollo配置中心渗透测试实战:从认证绕过到风险评估 在微服务架构中,配置中心作为系统的神经中枢,其安全性直接关系到整个应用生态的稳定与数据安全。配置中心的核心原理在于集中管理、动态下发应用配置,其技术价值在于提升运维效率和保证配置一致性。然而,若其自身存在安全漏洞,攻击者可能通过未授权访问、权限绕过等手段,获取敏感配置甚至控制服务器,导致严重的数据泄露与业务风险。因此,对配置中心进行专项安全测试,是保障微服务架构安全性的关键环节。本文聚焦于广泛应用的Apollo配置中心,深入剖析其在实际部署中可能存在的认证授权漏洞、接口安全风险及依赖组件隐患,并结合渗透测试 阅读详情

相关推荐

【信息科学与工程学】计算机科学与自动化 第二百三十三篇 凤凰架构01

编号类型领域系统软件/硬件或系统集成方案及系统架构模块子模块函数名称/算法名称算法的数学分析及计算机体系架构及数据结构与算法详细设计及参数列表及数值范围设计关联知识1共识算法分布式基石状态机复制架构:Proposer/Acceptor/Learner 多节点角色模型,基于操作转移(Operation Transfer)分布式共识PaxosPaxos 协议(Prepare/Promise/Accept/Accepted 两阶段)数学基础:多数派(Quorum)投票,基于鸽巢原理保证 W+R>N 时读写集合必有

weixin_49199313的博客 265

Apollo浅解1

apollo配置中心portal界面功能介绍

赵发财 2272

智能车学习日记【二】————十字和斜入十字

智能车学习日记【二】————十字和斜入十字 文章目录 智能车学习日记【二】————十字和斜入十字 前言 一、普通十字补线 二、斜入十字 1.判断是否为斜入十字和斜入十字类型 2.找斜入十字拐点 总结 前言 经过了一个多月的学习,小白的我终于也是能较稳定调出了2.8m以上的四轮车,过程中也遇到了很多困难,其中就有十字补线和斜入十字补线。尤其是斜入十字也是困扰了我快一周,现在和大家分享一下我处理斜入十字的方法(我的方法其实还有很多漏洞需要改进,仅供大家参考理解一下,欢迎各位在评论区指点出可

bsefef的博客 2740

apollo权限管理(五)

创建项目时,已经有项目管理的全部权限(项目负责人),项目负责人可以分配项目的权限 一,项目分配权限 在项目首页的左下方点击项目管理 添加项目管理人 添加成功显示如下: 二,集群分配权限 1.点击右上方的集群名称,然后出现集群配置的tab,点击 授权 2.选择修改权的管理员 3.选择环境 4.选择发布权的管理员 三,Namespace分配权限 具体步骤参考集群的分配权限...

weixin_42412601的博客 7455

深入解析Zynq Ultrascale+ RF Data Converter中的PLL配置与时钟管理

本文深入解析了Zynq Ultrascale+ RF Data Converter中PLL配置与时钟管理的核心原理与实践。文章将RFDC时钟系统比作交响乐团,详细阐述了PLL作为指挥的关键作用,以及系统时钟、Tile时钟、参考时钟与采样时钟的层级关系。重点剖析了PLL配置参数(如VCO频率、分频器设置)的计算方法与稳定性考量,并提供了从IP配置使能顺序到多Tile同步、DRP访问等常见问题的实战避坑指南与调试技巧,帮助工程师构建稳定的射频信号处理基石。

7up55的博客 449

关于MOS管栅极和栅介质(氧化物)的材料选择

大概解释了高K材料作为栅介质的好处,以及为什么会带来好处,同时引出了金属栅的优点,总结了使用高K材料和金属栅的原因。

qq_52054477的博客 1万+

Apollo 源码解析 —— Portal 认证与授权(一)之认证

点击上方“芋道源码”,选择“设为星标”管她前浪,还是后浪?能浪的浪,才是好浪!每天 8:55 更新文章,每天掉亿点点头发...源码精品专栏原创 | Java 2020 超神之路,很肝~...

芋艿V 516

抖音无人直播技术全解析:从原理、实操到合规化思考

直播技术作为内容分发的核心形式,其底层依赖于流媒体协议(如RTMP)与实时音视频编码推流。通过OBS等软件进行场景合成与虚拟摄像头技术,可以实现丰富的画面包装与自动化播放。这项技术的核心价值在于突破人力与时间的限制,实现内容的持续输出与流量获取,在电商带货、品牌展示、陪伴式内容等场景中具有应用潜力。然而,单纯依赖录播循环存在高风险,平台风控系统会通过内容重复性检测与互动行为模型进行识别。因此,更可持续的方向是探索“半无人直播”或自动化辅助工具,例如利用AI数字人驱动或直播切片技术,在合规前提下提升运营效率。

weixin_29895109的博客 508

portal认证 java_Apollo 源码解析 —— Portal 认证与授权(一)之认证

摘要: 原创出处 http://www.iocoder.cn/Apollo/portal-auth-1/ 「芋道源码」欢迎转载,保留摘要,谢谢!1. 概述本文分享 Portal 的认证与授权,侧重在认证部分。Apollo 是配置管理系统,会提供权限管理(Authorization),理论上是不负责用户登录认证功能的实现(Authentication)。所以 Apollo 定义了一些SPI用来解耦,...

weixin_30189297的博客 704

Qt Creator配置MSVC编译环境、调试环境

获取和安装:cdb.exe 在 Debugging tools for Windows 中,而现在已经不单独提供下载,只在Windows SDK 或者Windows Driver Kit里面包含 所以需要下载微软的调试工具,最少需要下载 Windows SDK。在windows上开发,一般使用Qt Creator自带mingw编译器,编译和调试都很方便,安装Qt时勾选后,自动配置完毕。但是有时候我们需要使用MSVC的编译器,这个时候我们没法直接使用,需要配置环境才能使用,以下是配置方案。

希望能帮助大家,希望大家多多支持,你们的支持是我前进的动力。 6986

【Windows Server 2019】DNS服务器的配置与管理——DNS正向解析

博文介绍了如何使用正向解析区,包括创建和添加并验证,以及术语 主机记录,别名,邮件交换记录的解释。

VMware, Windows, Linux, Cisco, Container, Kubernetes, Kali 1万+

Apollo 源码解析

Apollo 源码解析 —— 调试环境搭建 Apollo 源码解析 —— Portal 创建 App Apollo 源码解析 —— Portal 创建 Cluster Apollo 源码解析 —— Portal 创建 Namespace Apollo 源码解析 —— Portal 关联 Namespace Apollo 源码解析 —— Portal 创建 Item Apollo 源...

HIP的博客 5126

elevation mapping算法3之使用D435i相机离线或在线订阅点云和tf关系生成高程图

本文主要学习使用D435i相机发出的点云作为高程图的点云输入,如何离线输入点云生成高程图,进一步学习如何在线订阅点云生成高程图。

重点记录机器人和自动驾驶相关领域的技术,尤其是计算机视觉,视觉SLAM等,不仅有前言文献或必备算法的学习,也会有实际应用的点滴经验 3488

微服务配置中心 Apollo解析——Portal 关联 Namespace

Java架构学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!式,性能优化,微服务 高级架构开发等等。

2401_84046912的博客 1233

uniapp [全端兼容] - 实现内网使用天地图离线地图,详解项目加载天地图的离线地图服务器部署与使用教程示例源码,Uniapp如何实现天地图的离线加载部署在内网访问(兼容H5、微信小程序、App)

uniapp天地图如何离线访问,无需网络调用天地图API,天地图离线地图,天地图——内网使用离线地图,uniapp中加载天地图的离线地图,uniAPP实现天地图离线访问,在内网中无法直接使用外网天地图资源怎么办,在uni-app中加载地图 -使用天地图的瓦片数据。UNIAPP天地图离线怎么使用,内网部署天地图,天地图离线瓦片,部分瓦片数据,天地图如何离线,下载天地图离线服务,并用Uniapp+天地图离线部署,天地图私有化部署,实现离线加载天地图,Java+uniapp天地图离线开发,uniapp项目中无网络

🏆 前端领域优质创作者 4902

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

ApolloPortal权限模型深度解析

ApolloPortal采用基于用户、角色和权限的三层权限模型,实现了细粒度的功能级和数据级访问控制。认证通过UserService与UserInfoHolder组件完成,授权则由PermissionValidator执行核心校验逻辑,并整合SpringSecurity的@PreAuthorize注解实现方法级拦截。系统特色包括:资源绑定的动态角色机制、权限继承体系,以及面向OpenAPI的专门授权管理。其模块化设计(如可插拔的UserService接口)支持灵活对接企业SSO/LDAP系统,整体架构在权限

油墨香^-^的博客 707

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Apollo OpenAPI完全指南:如何通过API自动化管理微服务配置

你是否曾为微服务配置管理而烦恼?当你的应用规模从几个服务扩展到几十甚至上百个微服务时,手动配置管理变得异常困难。Apollo配置中心的OpenAPI正是为了解决这一痛点而生,它让你能够通过程序化方式自动化管理配置,实现配置的集中化、标准化和自动化管理。 Apollo是一个可靠的配置管理系统,特别适合微服务配置管理场景。通过OpenAPI,你可以将配置管理无缝集成到CI/CD流水线、自动化运维脚本

gitblog_00277的博客 841

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Spring Security动态权限管理实战:基于Apollo配置中心实现实时权限控制

在微服务架构中,权限管理是保障系统安全的核心环节。传统的硬编码权限配置方式存在流程冗长、响应迟缓的痛点,无法适应业务快速迭代的需求。动态权限管理技术应运而生,其核心原理是将权限规则的‘定义’与‘执行’解耦,实现权限策略的实时更新与生效,从而提升系统的敏捷性与安全性。Spring Security作为Java生态中成熟的安全框架,提供了强大的认证与授权能力扩展点。结合Apollo这类分布式配置中心,开发者可以构建出能够实时响应配置变更的动态权限系统。本文将以一个具体的后端开发场景为例,详细阐述如何整合Spri

weixin_34414650的博客 375

技术写作中AI的陷阱与人工主导的高质量内容生产流程

在软件开发领域,技术写作是知识传递与经验沉淀的关键环节,其核心在于确保内容的准确性、深度与可实践性。其基本原理是,通过严谨的逻辑、可验证的代码和清晰的上下文,将复杂的技术概念转化为可操作的指导。这一过程的技术价值在于,它不仅能帮助开发者解决实际问题,还能构建可靠的知识体系,避免因信息错误导致的工程风险。当前,随着AI写作工具的普及,许多开发者尝试利用大语言模型辅助生成技术文档,以期提升效率。然而,通用AI在生成需要严格事实核查和深度逻辑推理的技术内容时,存在明显的局限性,例如可能产生事实性错误或提供缺乏上下

weixin_30443731的博客 386

Apollo客户端使用与配置解析

前言: 本文主要是分享Apollo Client客户端使用过程中,遇到的问题、解决问题及分析代码逻辑的过程。其中一个重要问题就是关于apollo.bootstrap.enabled = true的使用及注意事项。 一、准备工作 1.1 环境要求 本文是基于Apollo v1.1.1版本,springboot项目客户端引入的是: <dependency&g...

2635

Apollo配置中心源码解读

Apollo配置中心源码分析 1. apollo的核心代码分享 SpringApplication启动的关键步骤 在SpringApplication中,会加载所有实现了Init方法的类 protected void applyInitializers(Configurable...

chengshiti9938的博客 1602
上一篇: 一二线城市知名 IT 互联网公司名单!虎~
下一篇: 开发工作生存指南 = =!
公众号-芋道源码
博客等级 码龄10年 1631粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值