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

最近看了好幾個認證框架,什麼 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) {
        //編輯處理接口,需要管理員權限
    }
}

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

  • 組合使用方式

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

三、本案源碼

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

四、其它生產項目應用

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

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

附:Solon 項目地址

附:Solon 其它入門示例