Solon Auth 認證框架使用演示(更簡單的認證框架)

劉之西東發表於2021-06-09

最近看了好幾個認證框架,什麼 Appache Shiro 啦、Sa-Token 啦、Spring Security啦。。。尤其是Spring Security,做為對標 Spring Boot & Cloud 的框架 Solon 怎麼的也要有自己的親生安全認證框架。所以在適配了 Sa-Token(satoken-solon-plugin) 和 sureness(sureness-solon-plugin) 之後,也開發了Solon 的親兒子:Solon Auth (solon.extend.auth)。設計目標是更加簡單些、更直接些,同時為使用者提供更豐富而不同的認證框架選擇。

Solon Auth (solon.extend.auth)

Solon Auth 的定位是,只做認證控制。側重對驗證結果的適配,及在此基礎上的統一控制和應用。功能會少,但適配起來不會暈。

Solon Auth 支援規則控制和註解控制兩種方案,各有優缺點,也可組合使用:

  • 規則控制,適合在一個地方進行整體的巨集觀控制
  • 註解控制,方便在細節處精準把握

一、開始適配,完成2步動作即可

  • 第1步,構建一個認證介面卡
@Configuration
public class Config {
    @Bean
    public AuthAdapter init() {
        //
        // 構建介面卡
        //
        return new AuthAdapter()
                .loginUrl("/login") //設定登入地址,未登入時自動跳轉(如果不設定,則輸出401錯誤)
                .addRule(r -> r.include("**").verifyIp().failure((c, t) -> c.output("你的IP不在白名單"))) //新增規則
                .addRule(b -> b.exclude("/login**").exclude("/run/**").verifyPath()) //新增規則
                .processor(new AuthProcessorImpl()) //設定認證處理器
                .failure((ctx, rst) -> { //設定預設的驗證失敗處理
                    ctx.render(rst);
                });
    }
}

//規則配置說明
//1.include(path) 規則包函的路徑範圍,可多個
//2.exclude(path) 規則排序的路徑池圍,可多個
//3.failure(..)   規則失則後的處理
//4.verifyIp()... 規則要做的驗證方案(可多個不同的驗證方案)

  • 第2步,實現一個認證處理器

先了解一下 AuthProcessor 的介面,它對接的是一系列的驗證動作結果。可能使用者得自己也得多幹點活,但很直觀。

//認證處理器
public class AuthProcessorImpl implements AuthProcessor {

    @Override
    public boolean verifyIp(String ip) {
        //驗證IP,是否有權訪問
    }

    @Override
    public boolean verifyLogined() {
        //驗證登入狀態,使用者是否已登入
    }

    @Override
    public boolean verifyPath(String path, String method) {
        //驗證路徑,使用者可訪問
    }

    @Override
    public boolean verifyPermissions(String[] permissions, Logical logical) {
        //驗證特定許可權,使用者是權有限
    }

    @Override
    public boolean verifyRoles(String[] roles, Logical logical) {
        //驗證特定角色,使用者是否角色
    }
}

現在做一次適配實戰,用的是一份生產環境的程式碼:

public class AuthProcessorImpl implements AuthProcessor {
    private int puid() {
        return Context.current().session("puid", 0);
    }

    @Override
    public boolean verifyIp(String ip) {
        return true; //ip不限制,直接返回true
    }

    @Override
    public boolean verifyLogined() {
        return puid() > 0; //使用者id大於0,說明已登入
    }

    @Override
    public boolean verifyPath(String path, String method) {
        try {
            if (BcfClient.hasUrlpath(path)) {
                return BcfClient.hasUrlpathByUser(puid(), path);
            } else {
                return true;
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean verifyPermissions(String[] permissions, Logical logical) {
        int puid = puid();

        try {
            if (logical == Logical.AND) {
                boolean isOk = true;

                for (String p : permissions) {
                    isOk = isOk && BcfClient.hasResourceByUser(puid, p);
                }

                return isOk;
            } else {
                for (String p : permissions) {
                    if (BcfClient.hasResourceByUser(puid, p)) {
                        return true;
                    }
                }
                return false;
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean verifyRoles(String[] roles, Logical logical) {
        int puid = puid();

        try {
            if (logical == Logical.AND) {
                boolean isOk = true;

                for (String p : roles) {
                    isOk = isOk && BcfClient.isUserInGroup(puid, p);
                }

                return isOk;
            } else {
                for (String p : roles) {
                    if (BcfClient.isUserInGroup(puid, p)) {
                        return true;
                    }
                }
                return false;
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

二、2種應用方式(一般組合使用)

剛才我們算是適配好了,現在就應用的活了。

  • 第1種,在 AuthAdapter 直接配置所有規則,或部分規則(也可以不配)
//參考上面的介面卡 addRule(...)

配置的好處是,不需要侵入業務程式碼;同時在統一的地方,巨集觀可見;但容易忽略掉細節。

  • 第2種,基於註解做一部份(一般特定許可權 或 特定角色時用)
@Mapping("/rock/agroup")
@Controller
public class AgroupController {
    @Mapping("")
    public void home() {
        //agroup 首頁
    }

    @Mapping("inner")
    public void inner() {
        //內部列表頁
    }

    
    @AuthPermissions("agroup:edit") //需要特定許可權
    @Mapping("edit/{id}")
    public void edit(int id) {
        //編輯顯示頁,需要編輯許可權
    }

    @AuthRoles("admin")  //需要特定角色
    @Mapping("edit/{id}/ajax/save")
    public void save(int id) {
        //編輯處理介面,需要管理員許可權
    }
}

註解的好處是,微觀可見,在一個方法上就可以看到它需要什麼許可權或角色,不容易忽略。

  • 組合使用方式

一般,用配置規則,控制所有需要登入的地址;用註解,控制特定的許可權或角色。

三、本案原始碼

https://gitee.com/noear/solon_demo/tree/master/demo16.solon_auth

四、其它生產專案應用

https://gitee.com/noear/water/tree/master/wateradmin

https://gitee.com/noear/sponge/tree/main/spongeadmin

附:Solon 專案地址

附:Solon 其它入門示例

相關文章