最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于Spring Security的動(dòng)態(tài)權(quán)限系統(tǒng)設(shè)計(jì)與實(shí)現(xiàn)

 更新時(shí)間:2025年07月21日 10:12:12   作者:用戶9198372681846  
本文介紹一個(gè)基于Spring Boot 2.7.18和SpringSecurity實(shí)現(xiàn)的權(quán)限系統(tǒng),支持接口級(jí)權(quán)限控制,支持權(quán)限,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

本文介紹一個(gè)基于 Spring Boot 2.7.18 和 Spring Security 實(shí)現(xiàn)的權(quán)限系統(tǒng),支持接口級(jí)權(quán)限控制,支持權(quán)限點(diǎn)的動(dòng)態(tài)配置與加載。

技術(shù)棧

  • Spring Boot 2.7.18
  • Spring Security
  • MyBatis Plus(用于持久化)
  • MySQL

核心表結(jié)構(gòu)設(shè)計(jì)

權(quán)限點(diǎn)表auth_permission_point

用于定義所有權(quán)限點(diǎn)(如 user:create, user:update):

字段名類型說明
idbigint主鍵
codevarchar權(quán)限點(diǎn)編碼(唯一)
namevarchar權(quán)限點(diǎn)名稱
typevarchar類型(操作、頁(yè)面、字段等)
resourcevarchar資源模塊標(biāo)識(shí)
actionvarchar操作標(biāo)識(shí)
remarkvarchar備注說明

角色表auth_role

字段名類型說明
idbigint主鍵
role_codevarchar角色編碼
namevarchar角色名稱
is_builtinboolean是否為系統(tǒng)內(nèi)置角色
enabledboolean是否啟用

用戶角色關(guān)聯(lián)表auth_user_role

字段名類型說明
idbigint主鍵
user_idvarchar用戶唯一 ID
role_codevarchar關(guān)聯(lián)角色編碼

角色權(quán)限點(diǎn)關(guān)聯(lián)表auth_role_permission_point

字段名類型說明
idbigint主鍵
role_codevarchar角色編碼
permission_codevarchar權(quán)限點(diǎn)編碼

接口權(quán)限映射表auth_url_permission_point

字段名類型說明
idbigint主鍵
urlvarchar接口路徑
methodvarchar請(qǐng)求方法(GET/POST/PUT/DELETE)
permission_codevarchar所需權(quán)限點(diǎn)編碼

? 每個(gè)接口可以綁定多個(gè)權(quán)限點(diǎn),滿足任意一個(gè)即視為擁有權(quán)限。

權(quán)限系統(tǒng)運(yùn)行機(jī)制

1. 動(dòng)態(tài)加載權(quán)限點(diǎn)

實(shí)現(xiàn)自定義 FilterInvocationSecurityMetadataSource,在系統(tǒng)啟動(dòng)和權(quán)限點(diǎn)發(fā)生變更時(shí),自動(dòng)掃描 auth_url_permission_point 表,將 URL、METHOD -> 權(quán)限點(diǎn)集合 的映射加載至內(nèi)存。

@Component
@RequiredArgsConstructor
public class CustomSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    @Resource
    private UrlPermissionMappingService urlPermissionMappingService;

    // TODO: 后期可替換為 Redis 或數(shù)據(jù)庫(kù)緩存
    private static final Map<String, List<PermissionExpressionConfigAttribute>> URL_PERMISSION_MAP = new ConcurrentHashMap<>();

    private volatile Map<String, List<PermissionExpressionConfigAttribute>> permissionMap = new ConcurrentHashMap<>();


    static {
        // 示例數(shù)據(jù),正式請(qǐng)從數(shù)據(jù)庫(kù)加載
        URL_PERMISSION_MAP.put("/api/user/**", List.of(new PermissionExpressionConfigAttribute("user:query")));
        URL_PERMISSION_MAP.put("/api/user/updatePassword", List.of(new PermissionExpressionConfigAttribute("user:updatePassword")));
    }

    @PostConstruct
    public void init() {
        // 啟動(dòng)時(shí)加載一次
        reload();
    }

    public void reload() {
        Map<String, List<PermissionExpressionConfigAttribute>> newMap = new HashMap<>();
        for (UrlPermissionMapping mapping : urlPermissionMappingService.loadAllUrlPermissionMappings()) {
            newMap.computeIfAbsent(mapping.getUrlPattern(), k -> new ArrayList<>())
                    .add(new PermissionExpressionConfigAttribute(mapping.getPermissionCode()));
        }
        this.permissionMap = newMap;
    }


    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) {
        String requestPath = ((FilterInvocation) object).getRequest().getRequestURI();
        // 先嘗試精確匹配
        List<PermissionExpressionConfigAttribute> exact = permissionMap.get(requestPath);
        if (exact != null) {
            return new HashSet<>(exact);
        }

        // 再嘗試通配匹配
        for (Map.Entry<String, List<PermissionExpressionConfigAttribute>> entry : permissionMap.entrySet()) {
            if (pathMatcher.match(entry.getKey(), requestPath)) {
                return new HashSet<>(entry.getValue());
            }
        }
        return null;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return URL_PERMISSION_MAP.values().stream()
                .flatMap(List::stream)
                .collect(Collectors.toSet());
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }

}

2. 動(dòng)態(tài)權(quán)限校驗(yàn)

實(shí)現(xiàn) AccessDecisionVoter<FilterInvocation>,對(duì)每個(gè)請(qǐng)求:

  • SecurityMetadataSource 拿到該接口需要的權(quán)限點(diǎn)
  • Authentication#getAuthorities() 拿到用戶權(quán)限點(diǎn)集合
  • 判斷是否命中
public class PermissionExpressionVoter implements AccessDecisionVoter<FilterInvocation> {

    @Override
    public int vote(Authentication authentication, FilterInvocation filterInvocation,
                    Collection<ConfigAttribute> attributes) {
        Assert.notNull(authentication, "authentication must not be null");
        Assert.notNull(filterInvocation, "filterInvocation must not be null");
        Assert.notNull(attributes, "attributes must not be null");
        Set<String> requiredExpressions = findConfigAttribute(attributes);

        // 獲取當(dāng)前登錄用戶擁有的權(quán)限點(diǎn)表達(dá)式
        Set<String> userPermissions = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toSet());

        if (CollectionUtils.isEmpty(requiredExpressions)) {
            // 如果沒有定義表達(dá)式,棄權(quán),交給下一個(gè) voter
            log.trace("Abstained since did not find a config attribute of instance WebExpressionConfigAttribute");
            return ACCESS_ABSTAIN;
        }

        for (String required : requiredExpressions) {
            if (userPermissions.contains(required)) {
                return ACCESS_GRANTED;
            }
        }

        log.warn("權(quán)限校驗(yàn)失敗: 當(dāng)前用戶權(quán)限 = {}, 資源需要權(quán)限 = {}", userPermissions, requiredExpressions);
        return ACCESS_DENIED;

    }

    private Set<String> findConfigAttribute(Collection<ConfigAttribute> attributes) {
        // 取出當(dāng)前資源對(duì)應(yīng)的權(quán)限表達(dá)式
        return attributes.stream()
                .filter(attribute -> attribute instanceof PermissionExpressionConfigAttribute)
                .map(ConfigAttribute::getAttribute)
                .collect(Collectors.toSet());
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return attribute instanceof PermissionExpressionConfigAttribute;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }


}

?? 未配置權(quán)限點(diǎn)的接口可設(shè)置默認(rèn)放行,也可以走 fallback 權(quán)限點(diǎn)邏輯。

總結(jié)

該系統(tǒng)實(shí)現(xiàn)了:

  • 權(quán)限點(diǎn)粒度統(tǒng)一、接口權(quán)限與角色權(quán)限解耦
  • 接口權(quán)限點(diǎn)支持動(dòng)態(tài)注冊(cè)與配置
  • 權(quán)限控制基于 Spring Security 標(biāo)準(zhǔn)擴(kuò)展機(jī)制,具備良好擴(kuò)展性

TODO(可選增強(qiáng))

  • 支持權(quán)限表達(dá)式解析(如 @hasAny('user:create', 'admin')
  • 支持字段級(jí)、按鈕級(jí)權(quán)限點(diǎn)
  • 權(quán)限點(diǎn)變更自動(dòng)刷新緩存
  • 提供權(quán)限控制臺(tái)(前端聯(lián)動(dòng))

到此這篇關(guān)于基于Spring Security的動(dòng)態(tài)權(quán)限系統(tǒng)設(shè)計(jì)與實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringSecurity動(dòng)態(tài)權(quán)限內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

莲花县| 新营市| 驻马店市| 武山县| 郎溪县| 曲阜市| 东方市| 安宁市| 察雅县| 格尔木市| 五寨县| 集贤县| 霍林郭勒市| 临泉县| 呼伦贝尔市| 金溪县| 嘉鱼县| 公主岭市| 凤城市| 宜兰县| 金沙县| 沙坪坝区| 苏州市| 青海省| 个旧市| 县级市| 米易县| 延吉市| 永登县| 象山县| 荥经县| 吴桥县| 怀远县| 阿拉尔市| 临泉县| 巧家县| 吕梁市| 莲花县| 全州县| 深水埗区| 灵山县|