Spring Security(7)

来源:https://www.cnblogs.com/xiangwang1111/archive/2022/11/29/16936846.html
-Advertisement-
Play Games

您好,我是湘王,這是我的博客園,歡迎您來,歡迎您再來~ 有時某些業務或者功能,需要在用戶請求到來之前就進行一些判斷或執行某些動作,就像在Servlet中的FilterChain過濾器所做的那樣,Spring Security也有類似機制。Spring Security有三種增加過濾器的方式:addF ...


您好,我是湘王,這是我的博客園,歡迎您來,歡迎您再來~

 

有時某些業務或者功能,需要在用戶請求到來之前就進行一些判斷或執行某些動作,就像在Servlet中的FilterChain過濾器所做的那樣,Spring Security也有類似機制。Spring Security有三種增加過濾器的方式:addFilterBefaore()、 addFilterAt()和addFilterAfter(),也可以disable掉預設的過濾器,例如:

1、http.logout().disable();或者http.headers().disable();

2、用自定義過濾器http.addFilterAt(new MyLogoutFilter(), LogoutFilter.class)替換

Spring Security的官方列出了過濾器調用順序,具體可參考官方網站。

 

Spring Security已經定義好,可以直接使用的過濾器有下麵這些:

 

 

 

比如,現在的互聯網應用都有一個通用的「業務規則」是:在執行所有功能介面的時候都要檢查確認介面簽名的有效性。所謂介面簽名其實就是一套進入準則,客戶端按照伺服器規定的方式向伺服器證明自己確實是某個網站的用戶。

那麼,Spring Security裡面可以這麼乾:

/**
 * 自定義攔截過濾器
 *
 * @author 湘王
 */
@Component
public class CustomInterceptorFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException {
        // 保存參數=參數值對
        Map<String, String> mapResult = new HashMap<String, String>();
        // 請求中的參數
        Enumeration<String> em = request.getParameterNames();
        while (em.hasMoreElements()) {
            String paramName = em.nextElement();
            String value = request.getParameter(paramName);
            mapResult.put(paramName, value);
        }
        // 驗證參數,只要有一個不滿足條件,立即返回
        if (null == mapResult.get("platform") ||
            null == mapResult.get("timestamp") ||
            null == mapResult.get("signature")) {
                response.getWriter().write("api validate failure");
        }
        Object result = null;
        String platform = mapResult.get("platform");
        String timestamp = mapResult.get("timestamp");
        String signature = mapResult.get("signature");
        // 後端生成簽名:platform = "xiangwang" timestamp = "159123456789" signature = ""
        String sign = DigestUtils.md5DigestAsHex((platform + timestamp).getBytes());
        validateSignature(signature, sign, request, response, chain);
    }

    // 驗證簽名
    private void validateSignature(String signature, String sign, ServletRequest request,
                                   ServletResponse response, FilterChain chain) throws IOException {
        if (signature.equalsIgnoreCase(sign)) {
            try {
                // 讓調用鏈繼續往下執行
                chain.doFilter(request, response);
            } catch (Exception e) {
                response.getWriter().write("api validate failure");
            }
        } else {
            response.getWriter().write("api validate failure");
        }
    }

    public static void main(String[] args) {
        // 這裡的驗證簽名演算法可以隨便自定義實現(guid = "0" platform = "web" timestamp = 156789012345)
        // 下麵的代碼只是偽代碼,舉個例子而已
        String sign = "";
        if(StringUtils.isBlank(guid)) {
            // 首次登錄,後端 platform + timestamp + "xiangwang" 生成簽名
            sign = DigestUtils.md5DigestAsHex((platform + timestamp + "xiangwang").getBytes());
            validateSignature(signature, sign, request, response, chain);
        } else {
            // 不是首次登錄,後端 guid + platform + timestamp 生成簽名
            // 從Redis拿到token,這裡不實現
            // Object object = service.getObject("token#" + guid);
            Object object = "1234567890abcdefghijklmnopqrstuvwxyz";
            if(null == object) {
                response.getWrite().write("token expired");
            } else {
                token = (String) obejct;
                // 驗證sign
                sign = DigestUtils.md5DigestAsHex((platform + timestamp + "xiangwang").getBytes());
                validateSignature(signature, sign, request, response, chain);
            }
        }
        System.out.println(DigestUtils.md5DigestAsHex(("web" + "156789012345").getBytes()));
    }
}

 

 

然後修改WebSecurityConfiguration,加入剛纔自定義的「過濾器」:

// 控制邏輯
@Override
protected void configure(HttpSecurity http) throws Exception {
    // 執行UsernamePasswordAuthenticationFilter之前添加攔截過濾
    http.addFilterBefore(new CustomInterceptorFilter(), UsernamePasswordAuthenticationFilter.class);

    http.authorizeRequests()
            .anyRequest().authenticated()
            // 設置自定義認證成功、失敗及登出處理器
            .and().formLogin().loginPage("/login")
            .successHandler(successHandler).failureHandler(failureHandler).permitAll()
            .and().logout().logoutUrl("/logout").deleteCookies("JSESSIONID")
            .logoutSuccessHandler(logoutSuccessHandler).permitAll()
            // 配置無權訪問的自定義處理器
            .and().exceptionHandling().accessDeniedHandler(accessDeniedHandler)
            // 記住我
            .and().rememberMe()
            // 資料庫保存,這種方式在關閉服務之後仍然有效
            .tokenRepository(persistentTokenRepository())
            // 預設的失效時間會從用戶最後一次操作開始計算過期時間,過期時間最小值就是60秒,
            // 如果設置的值小於60秒,也會被更改為60秒
            .tokenValiditySeconds(30 * 24 * 60 * 60)
            .userDetailsService(customUserDetailsService)
            .and().cors().and().csrf().disable();
}

 

 

運行postman測試後的效果為:

 

 

 

增加了介面需要的簽名參數。

 

在前面的內容中,幾乎沒有對Spring Security真正的核心功能,也就是認證授權做什麼說明,也只是簡單演示了一些admin角色登錄。那麼在做完前面這些鋪墊之後,就需要接著來說說這一塊了。

先創建創建sys_permission表,為認證授權的細化做準備:

 

  

同樣,需要創建實體類和Service類。

/**
 * 許可權entity
 *
 * @author 湘王
 */
public class SysPermission implements Serializable, RowMapper<SysPermission> {
    private static final long serialVersionUID = 4121559180789799491L;

    private int id;
    private int roleid;
    private String path;
    private String permission;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    protected Date createtime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    protected Date updatetime;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getRoleid() {
        return roleid;
    }

    public void setRoleid(int roleid) {
        this.roleid = roleid;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public String getPermission() {
        return permission;
    }

    public void setPermission(String permission) {
        this.permission = permission;
    }

    public Date getCreatetime() {
        return createtime;
    }

    public void setCreatetime(Date createtime) {
        this.createtime = createtime;
    }

    public Date getUpdatetime() {
        return updatetime;
    }

    public void setUpdatetime(Date updatetime) {
        this.updatetime = updatetime;
    }

    @Override
    public SysPermission mapRow(ResultSet result, int i) throws SQLException {
        SysPermission permission = new SysPermission();

        permission.setId(result.getInt("id"));
        permission.setRoleid(result.getInt("roleid"));
        permission.setPath(result.getString("path"));
        permission.setPermission(result.getString("permission"));
        permission.setCreatetime(result.getTimestamp("createtime"));
        permission.setUpdatetime(result.getTimestamp("updatetime"));

        return permission;
    }
}

 

/**
 * 許可權Service
 *
 * @author 湘王
 */
@Service
public class PermissionService {
    @Autowired
    private MySQLDao mySQLDao;

    // 得到某個角色的全部許可權
    public List<SysPermission> getByRoleId(int roleid) {
        String sql = "SELECT id, url, roleid, permission, createtime, updatetime FROM sys_permission WHERE roleid = ?";
        return mySQLDao.find(sql, new SysPermission(), roleid);
    }
}

 

 

再在LoginController中增加幾個hasPermission()方法:

// 細化許可權
@GetMapping("/admin/create")
@PreAuthorize("hasPermission('/admin', 'create')")
public String adminCreate() {
    return "admin有ROLE_ADMIN角色的create許可權";
}

@GetMapping("/admin/read")
@PreAuthorize("hasPermission('/admin', 'read')")
public String adminRead() {
    return "admin有ROLE_ADMIN角色的read許可權";
}

@GetMapping("/manager/create")
@PreAuthorize("hasPermission('/manager', 'create')")
public String managerCreate() {
    return "manager有ROLE_MANAGER角色的create許可權";
}

@GetMapping("/manager/remove")
@PreAuthorize("hasPermission('/manager', 'remove')")
public String managerRemove() {
    return "manager有ROLE_MANAGER角色的remove許可權";
}

 

 

再來實現對hasPermission()方法的處理,也就是自定義許可權處理的過濾器:

/**
 * 自定義許可權處理
 *
 * @author 湘王
 */
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
    @Autowired
    private RoleService roleService;
    @Autowired
    private PermissionService permissionService;

    @Override
    public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
        // 獲得loadUserByUsername()方法的結果
        User user = (User) authentication.getPrincipal();
        // 獲得用戶授權
        Collection<GrantedAuthority> authorities = user.getAuthorities();

        // 遍歷用戶所有角色
        for(GrantedAuthority authority : authorities) {
            String roleName = authority.getAuthority();
            int roleid = roleService.getByName(roleName).getId();
            // 得到角色所有的許可權
            List<SysPermission> permissionList = permissionService.getByRoleId(roleid);
            if (null == permissionList) {
                continue;
            }

            // 遍歷permissionList
            for(SysPermission sysPermission : permissionList) {
                String pstr = sysPermission.getPermission();
                String path = sysPermission.getPath();
                // 判空
                if (StringUtils.isBlank(pstr) || StringUtils.isBlank(path)) {
                    continue;
                }
                // 如果訪問的url和許可權相符,返回true
                if (path.equals(targetUrl) && pstr.equals(permission)) {
                    return true;
                }
            }
        }

        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable serializable,
                                 String targetUrl, Object permission) {
        return false;
    }
}

 

 

最後,再把自定義的CustomPermissionEvaluator註冊到WebSecurityConfiguration中去,也就是在WebSecurityConfiguration中加入下麵的代碼:

// 註入自定義PermissionEvaluator
@Bean
public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
    DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
    handler.setPermissionEvaluator(permissionEvaluator);
    return handler;
}

 

 

運行postman進行測試,註意:啟動時要在配置文件中加入下麵這個配置:

spring.main.allow-bean-definition-overriding=true

從結果可以看到:

 

 

 


 

 

感謝您的大駕光臨!咨詢技術、產品、運營和管理相關問題,請關註後留言。歡迎騷擾,不勝榮幸~

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • <!-- 設置頁面編碼格式,中文問題--> <meta http-equiv="Content-Type" content="text/html; charset="utf-8" /> <!-- 強制文檔寬度與設備寬度保持1:1,且文檔最大寬度比例是1.0,不允許用戶點擊屏幕放大瀏覽,用戶是否可以手 ...
  • 基本描述 CSS偽類是很常用的功能,主要應用於選擇器的關鍵字,用來改變被選擇元素的特殊狀態下的樣式。 偽類類似於普通CSS類的用法,是對CSS選擇器的一種擴展,增強選擇器的功能。 目前可用的偽類有大概40多個,少部分有相容性問題。我們比較常見的,如::hover、:root、:first-child ...
  • 〇、參考資料 1、hutool介紹 https://blog.csdn.net/abst122/article/details/124091375 2、Spring Boot+Mybatis實現登錄註冊 https://www.cnblogs.com/wiki918/p/16221758.html ...
  • Filter過濾器02 5.Filter過濾器生命周期 Filter生命周期圖解 驗證-Tomcat來創建Filter實例,只會創建一個實例 package com.filter; import javax.servlet.*; import javax.servlet.http.HttpServl ...
  • 以前學習的過程中,有聽過 EasyExcel 這麼一個東西,不過從來沒用過,所以,正好藉此機會學習,看看如何使用它來實現需求。 在學習 EasyExcel 的這段時間里,也瞭解到工作中這種導入導出的需求還是挺常見的,所以決定記錄下來。 ...
  • 一.小結 1.字元串是封裝在String類中的對象。要創建一個字元串,可以使用11種構造方法之一,也可以使用字元串直接量進行簡捷初始化。 2.String對象是不可變的,它的內容不能改變。為了提高效率和節省記憶體,如果兩個直接量字元串有相同的字元序列,Java虛擬機就將它們存儲在一個對象中。這個獨特的 ...
  • layout: post categories: Java title: Java 中你絕對沒用過的一個關鍵字? tagline: by 子悠 tags: 子悠 前面的文章給大家介紹瞭如何自定義一個不可變類,沒看過的小伙伴建議去看一下,這節課給大家介紹一個 Java 中的一個關鍵字 Record,那 ...
  • 代碼1 #include <iostream> using namespace std; class A{ public: A(int _a):ma(_a){ cout<<"A()"<<endl; } ~A(){ cout<<"~A()"<<endl; } protected: int ma; }; ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...