1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Spring Boot 中集成 Shiro

        共 18589字,需瀏覽 38分鐘

         ·

        2021-05-15 06:27

        ????關(guān)注后回復(fù) “進(jìn)群” ,拉你進(jìn)程序員交流群????

        作者丨武哥

        來源丨武哥聊編程

        Shiro 是一個強(qiáng)大、簡單易用的 Java 安全框架,主要用來更便捷的認(rèn)證,授權(quán),加密,會話管等等,可為任何應(yīng)用提供安全保障。本課程主要來介紹 Shiro 的認(rèn)證和授權(quán)功能。

        1. Shiro 三大核心組件

        Shiro 有三大核心的組件:Subject、SecurityManagerRealm。先來看一下它們之間的關(guān)系。

        三大核心組件的關(guān)系
        1. Subject:認(rèn)證主體。它包含兩個信息:Principals 和 Credentials??匆幌逻@兩個信息具體是什么。

        Principals:身份??梢允怯脩裘?,郵件,手機(jī)號碼等等,用來標(biāo)識一個登錄主體身份;
        Credentials:憑證。常見有密碼,數(shù)字證書等等。

        說白了,就是需要認(rèn)證的東西,最常見的就是用戶名密碼了,比如用戶在登錄的時候,Shiro 需要去進(jìn)行身份認(rèn)證,就需要 Subject 認(rèn)證主體。

        1. SecurityManager:安全管理員。這是 Shiro 架構(gòu)的核心,它就像 Shiro 內(nèi)部所有原件的保護(hù)傘一樣。我們在項目中一般都會配置 SecurityManager,開發(fā)人員大部分精力主要是在 Subject 認(rèn)證主體上面。我們在與 Subject 進(jìn)行交互的時候,實際上是 SecurityManager 在背后做一些安全操作。
        2. Realm:Realm 是一個域,它是連接 Shiro 和具體應(yīng)用的橋梁,當(dāng)需要與安全數(shù)據(jù)交互的時候,比如用戶賬戶、訪問控制等,Shiro 就會從一個或多個 Realm 中去查找。我們可以把 Realm 看成 DataSource,即安全數(shù)據(jù)源,一般我們會自己定制 Realm,在自定義 Realm 中,我們一般會從數(shù)據(jù)庫中獲取和認(rèn)證相關(guān)的信息,這在下文自定義 Realm 部分會詳細(xì)說明。

        1. Shiro 身份和權(quán)限認(rèn)證

        1.2 Shiro 身份認(rèn)證

        我們來分析一下 Shiro 身份認(rèn)證的過程,看一下官方的一個認(rèn)證圖:

        認(rèn)證過程

        Step1:應(yīng)用程序代碼在調(diào)用 Subject.login(token) 方法后,傳入代表最終用戶的身份和憑證的 AuthenticationToken 實例 token。

        Step2:將 Subject 實例委托給應(yīng)用程序的 SecurityManager(Shiro的安全管理)來開始實際的認(rèn)證工作。這里開始真正的認(rèn)證工作了。

        Step3,4,5:然后 SecurityManager 就會根據(jù)具體的 realm 去進(jìn)行安全認(rèn)證了。從圖中可以看出,realm 可以自定義(Custom Realm)。

        1.3 Shiro 權(quán)限認(rèn)證

        權(quán)限認(rèn)證,也就是訪問控制,即在應(yīng)用中控制誰能訪問哪些資源。在權(quán)限認(rèn)證中,最核心的三個要素是:權(quán)限,角色和用戶。

        權(quán)限(permission):即操作資源的權(quán)利,比如訪問某個頁面,以及對某個模塊的數(shù)據(jù)的添加,修改,刪除,查看的權(quán)利;角色(role):指的是用戶擔(dān)任的的角色,一個角色可以有多個權(quán)限;用戶(user):在 Shiro 中,代表訪問系統(tǒng)的用戶,即上面提到的 Subject 認(rèn)證主體。

        它們之間的的關(guān)系可以用下圖來表示:

        用戶、角色和權(quán)限的關(guān)系

        一個用戶可以有多個角色,而不同的角色可以有不同的權(quán)限,也可由有相同的權(quán)限。比如說現(xiàn)在有三個角色,1是普通角色,2也是普通角色,3是管理員,角色1只能查看信息,角色2只能添加信息,管理員都可以,而且還可以刪除信息,類似于這樣。

        2. Spring Boot 集成 Shiro 過程

        2.1 依賴導(dǎo)入

        Spring Boot 2.0.3 集成 Shiro 需要導(dǎo)入如下 starter 依賴:

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

        2.2 數(shù)據(jù)庫表數(shù)據(jù)初始化

        這里主要涉及到三張表:用戶表、角色表和權(quán)限表,其實在 demo 中,我們完全可以自己模擬一下,不用建表,但是為了更加接近實際情況,我們還是加入 mybatis,來操作數(shù)據(jù)庫。下面是數(shù)據(jù)庫表的腳本。

        CREATE TABLE `t_role` (
          `id` int(11NOT NULL AUTO_INCREMENT COMMENT '主鍵',
          `rolename` varchar(20DEFAULT NULL COMMENT '角色名稱',
          PRIMARY KEY (`id`)
        ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8

        CREATE TABLE `t_user` (
          `id` int(11NOT NULL AUTO_INCREMENT COMMENT '用戶主鍵',
          `username` varchar(20NOT NULL COMMENT '用戶名',
          `password` varchar(20NOT NULL COMMENT '密碼',
          `role_id` int(11DEFAULT NULL COMMENT '外鍵關(guān)聯(lián)role表',
          PRIMARY KEY (`id`),
          KEY `role_id` (`role_id`),
          CONSTRAINT `t_user_ibfk_1` FOREIGN KEY (`role_id`REFERENCES `t_role` (`id`)
        ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8

        CREATE TABLE `t_permission` (
          `id` int(11NOT NULL AUTO_INCREMENT COMMENT '主鍵',
          `permissionname` varchar(50NOT NULL COMMENT '權(quán)限名',
          `role_id` int(11DEFAULT NULL COMMENT '外鍵關(guān)聯(lián)role',
          PRIMARY KEY (`id`),
          KEY `role_id` (`role_id`),
          CONSTRAINT `t_permission_ibfk_1` FOREIGN KEY (`role_id`REFERENCES `t_role` (`id`)
        ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

        其中,t_user,t_role 和 t_permission,分別存儲用戶信息,角色信息和權(quán)限信息,表建立好了之后,我們往表里插入一些測試數(shù)據(jù)。t_user 表:

        idusernamepasswordrole_id
        1csdn11234561
        2csdn21234562
        3csdn31234563

        t_role 表:

        idrolename
        1admin
        2teacher
        3student

        t_permission 表:

        idpermissionnamerole_id
        1user:*1
        2student:*2

        解釋一下這里的權(quán)限:user:*表示權(quán)限可以是 user:create 或者其他,* 處表示一個占位符,我們可以自己定義,具體的會在下文 Shiro 配置那里說明。

        2.2 自定義 Realm

        有了數(shù)據(jù)庫表和數(shù)據(jù)之后,我們開始自定義 realm,自定義 realm 需要繼承 AuthorizingRealm 類,因為該類封裝了很多方法,它也是一步步繼承自 Realm 類的,繼承了 AuthorizingRealm 類后,需要重寫兩個方法:

        doGetAuthenticationInfo() 方法:用來驗證當(dāng)前登錄的用戶,獲取認(rèn)證信息
        doGetAuthorizationInfo() 方法:用來為當(dāng)前登陸成功的用戶授予權(quán)限和角色

        具體實現(xiàn)如下,相關(guān)的解釋我放在代碼的注釋中,這樣更加方便直觀:

        /**
         * 自定義realm
         * @author shengwu ni
         */

        public class MyRealm extends AuthorizingRealm {

            @Resource
            private UserService userService;

            @Override
            protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
                // 獲取用戶名
                String username = (String) principalCollection.getPrimaryPrincipal();
                SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
                // 給該用戶設(shè)置角色,角色信息存在t_role表中取
                authorizationInfo.setRoles(userService.getRoles(username));
                // 給該用戶設(shè)置權(quán)限,權(quán)限信息存在t_permission表中取
                authorizationInfo.setStringPermissions(userService.getPermissions(username));
                return authorizationInfo;
            }

            @Override
            protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
                // 根據(jù)token獲取用戶名,如果您不知道該該token怎么來的,先可以不管,下文會解釋
                String username = (String) authenticationToken.getPrincipal();
                // 根據(jù)用戶名從數(shù)據(jù)庫中查詢該用戶
                User user = userService.getByUsername(username);
                if(user != null) {
                    // 把當(dāng)前用戶存到session中
                    SecurityUtils.getSubject().getSession().setAttribute("user", user);
                    // 傳入用戶名和密碼進(jìn)行身份認(rèn)證,并返回認(rèn)證信息
                    AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "myRealm");
                    return authcInfo;
                } else {
                    return null;
                }
            }
        }

        從上面兩個方法中可以看出:驗證身份的時候是根據(jù)用戶輸入的用戶名先從數(shù)據(jù)庫中查出該用戶名對應(yīng)的用戶,這時候并沒有涉及到密碼,也就是說到這一步的時候,即使用戶輸入的密碼不對,也是可以查出來該用戶的,然后將該用戶的正確信息封裝到 authcInfo 中返回給 Shiro,接下來就是Shiro的事了,它會根據(jù)這里面的真實信息與用戶前臺輸入的用戶名和密碼進(jìn)行校驗, 這個時候也要校驗密碼了,如果校驗通過就讓用戶登錄,否則跳轉(zhuǎn)到指定頁面。同理,權(quán)限驗證的時候也是先根據(jù)用戶名從數(shù)據(jù)庫中獲取與該用戶名有關(guān)的角色和權(quán)限,然后封裝到 authorizationInfo 中返回給 Shiro。

        2.3 Shiro 配置

        自定義的 realm 寫好了,接下來需要對 Shiro 進(jìn)行配置了。我們主要配置三個東西:自定義 realm、安全管理器 SecurityManager 和 Shiro 過濾器。如下:

        配置自定義 realm:

        @Configuration
        public class ShiroConfig {

            private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

            /**
             * 注入自定義的realm
             * @return MyRealm
             */

            @Bean
            public MyRealm myAuthRealm() {
                MyRealm myRealm = new MyRealm();
                logger.info("====myRealm注冊完成=====");
                return myRealm;
            }
        }

        配置安全管理器 SecurityManager:

        @Configuration
        public class ShiroConfig {

            private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

            /**
             * 注入安全管理器
             * @return SecurityManager
             */

            @Bean
            public SecurityManager securityManager() {
                // 將自定義realm加進(jìn)來
                DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(myAuthRealm());
                logger.info("====securityManager注冊完成====");
                return securityManager;
            }
        }

        配置 SecurityManager 時,需要將上面的自定義 realm 添加進(jìn)來,這樣的話 Shiro 才會走到自定義的 realm 中。

        配置 Shiro 過濾器:

        @Configuration
        public class ShiroConfig {

            private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
            
            /**
             * 注入Shiro過濾器
             * @param securityManager 安全管理器
             * @return ShiroFilterFactoryBean
             */

            @Bean
            public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
                // 定義shiroFactoryBean
                ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();

                // 設(shè)置自定義的securityManager
                shiroFilterFactoryBean.setSecurityManager(securityManager);

                // 設(shè)置默認(rèn)登錄的url,身份認(rèn)證失敗會訪問該url
                shiroFilterFactoryBean.setLoginUrl("/login");
                // 設(shè)置成功之后要跳轉(zhuǎn)的鏈接
                shiroFilterFactoryBean.setSuccessUrl("/success");
                // 設(shè)置未授權(quán)界面,權(quán)限認(rèn)證失敗會訪問該url
                shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

                // LinkedHashMap是有序的,進(jìn)行順序攔截器配置
                Map<String,String> filterChainMap = new LinkedHashMap<>();

                // 配置可以匿名訪問的地址,可以根據(jù)實際情況自己添加,放行一些靜態(tài)資源等,anon表示放行
                filterChainMap.put("/css/**""anon");
                filterChainMap.put("/imgs/**""anon");
                filterChainMap.put("/js/**""anon");
                filterChainMap.put("/swagger-*/**""anon");
                filterChainMap.put("/swagger-ui.html/**""anon");
                // 登錄url 放行
                filterChainMap.put("/login""anon");

                // “/user/admin” 開頭的需要身份認(rèn)證,authc表示要身份認(rèn)證
                filterChainMap.put("/user/admin*""authc");
                // “/user/student” 開頭的需要角色認(rèn)證,是“admin”才允許
                filterChainMap.put("/user/student*/**""roles[admin]");
                // “/user/teacher” 開頭的需要權(quán)限認(rèn)證,是“user:create”才允許
                filterChainMap.put("/user/teacher*/**""perms[\"user:create\"]");

                // 配置logout過濾器
                filterChainMap.put("/logout""logout");

                // 設(shè)置shiroFilterFactoryBean的FilterChainDefinitionMap
                shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
                logger.info("====shiroFilterFactoryBean注冊完成====");
                return shiroFilterFactoryBean;
            }
        }

        配置 Shiro 過濾器時會傳入一個安全管理器,可以看出,這是一環(huán)套一環(huán),reaml -> SecurityManager -> filter。在過濾器中,我們需要定義一個 shiroFactoryBean,然后將 SecurityManager 添加進(jìn)來,結(jié)合上面代碼可以看出,要配置的東西主要有:

        默認(rèn)登錄的 url:身份認(rèn)證失敗會訪問該 url 認(rèn)證成功之后要跳轉(zhuǎn)的 url 權(quán)限認(rèn)證失敗會訪問該 url 需要攔截或者放行的 url:這些都放在一個 map 中

        從上述代碼中可以看出,在 map 中,針對不同的 url,有不同的權(quán)限要求,這里總結(jié)一下常用的幾個權(quán)限。

        Filter說明
        anon開放權(quán)限,可以理解為匿名用戶或游客,可以直接訪問的
        authc需要身份認(rèn)證的
        logout注銷,執(zhí)行后會直接跳轉(zhuǎn)到 shiroFilterFactoryBean.setLoginUrl(); 設(shè)置的 url,即登錄頁面
        roles[admin]參數(shù)可寫多個,表示是某個或某些角色才能通過,多個參數(shù)時寫 roles["admin,user"],當(dāng)有多個參數(shù)時必須每個參數(shù)都通過才算通過
        perms[user]參數(shù)可寫多個,表示需要某個或某些權(quán)限才能通過,多個參數(shù)時寫 perms[“user, admin”],當(dāng)有多個參數(shù)時必須每個參數(shù)都通過才算通過

        2.4 使用 Shiro 進(jìn)行認(rèn)證

        到這里,我們對 Shiro 的準(zhǔn)備工作都做完了,接下來開始使用 Shiro 進(jìn)行認(rèn)證工作。我們首先來設(shè)計幾個接口:

        接口一:使用 http://localhost:8080/user/admin 來驗證身份認(rèn)證 接口二:使用 http://localhost:8080/user/student 來驗證角色認(rèn)證 接口三:使用 http://localhost:8080/user/teacher 來驗證權(quán)限認(rèn)證 接口四:使用 http://localhost:8080/user/login 來實現(xiàn)用戶登錄

        然后來一下認(rèn)證的流程:

        流程一:直接訪問接口一(此時還未登錄),認(rèn)證失敗,跳轉(zhuǎn)到 login.html 頁面讓用戶登錄,登錄會請求接口四,實現(xiàn)用戶登錄功能,此時 Shiro 已經(jīng)保存了用戶信息了。流程二:再次訪問接口一(此時用戶已經(jīng)登錄),認(rèn)證成功,跳轉(zhuǎn)到 success.html 頁面,展示用戶信息。流程三:訪問接口二,測試角色認(rèn)證是否成功。流程四:訪問接口三,測試權(quán)限認(rèn)證是否成功。

        2.4.1 身份、角色、權(quán)限認(rèn)證接口

        @Controller
        @RequestMapping("/user")
        public class UserController {

            /**
             * 身份認(rèn)證測試接口
             * @param request
             * @return
             */

            @RequestMapping("/admin")
            public String admin(HttpServletRequest request) {
                Object user = request.getSession().getAttribute("user");
                return "success";
            }

            /**
             * 角色認(rèn)證測試接口
             * @param request
             * @return
             */

            @RequestMapping("/student")
            public String student(HttpServletRequest request) {
                return "success";
            }

            /**
             * 權(quán)限認(rèn)證測試接口
             * @param request
             * @return
             */

            @RequestMapping("/teacher")
            public String teacher(HttpServletRequest request) {
                return "success";
            }
        }

        這三個接口很簡單,直接返回到指定頁面展示即可,只要認(rèn)證成功就會正常跳轉(zhuǎn),如果認(rèn)證失敗,就會跳轉(zhuǎn)到上文 ShrioConfig 中配置的頁面進(jìn)行展示。

        2.4.2 用戶登錄接口

        @Controller
        @RequestMapping("/user")
        public class UserController {

            /**
             * 用戶登錄接口
             * @param user user
             * @param request request
             * @return string
             */

            @PostMapping("/login")
            public String login(User user, HttpServletRequest request) {

                // 根據(jù)用戶名和密碼創(chuàng)建token
                UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
                // 獲取subject認(rèn)證主體
                Subject subject = SecurityUtils.getSubject();
                try{
                    // 開始認(rèn)證,這一步會跳到我們自定義的realm中
                    subject.login(token);
                    request.getSession().setAttribute("user", user);
                    return "success";
                }catch(Exception e){
                    e.printStackTrace();
                    request.getSession().setAttribute("user", user);
                    request.setAttribute("error""用戶名或密碼錯誤!");
                    return "login";
                }
            }
        }

        我們重點(diǎn)分析一下這個登錄接口,首先會根據(jù)前端傳過來的用戶名和密碼,創(chuàng)建一個 token,然后使用 SecurityUtils 來創(chuàng)建一個認(rèn)證主體,接下來開始調(diào)用 subject.login(token) 開始進(jìn)行身份認(rèn)證了,注意這里傳了剛剛創(chuàng)建的 token,就如注釋中所述,這一步會跳轉(zhuǎn)到我們自定義的 realm 中,進(jìn)入 doGetAuthenticationInfo 方法,所以到這里,您就會明白該方法中那個參數(shù) token 了。然后就是上文分析的那樣,開始進(jìn)行身份認(rèn)證。

        2.4.3 測試一下

        最后,啟動項目,測試一下:瀏覽器請求 http://localhost:8080/user/admin 會進(jìn)行身份認(rèn)證,因為此時未登錄,所以會跳轉(zhuǎn)到 IndexController 中的 /login 接口,然后跳轉(zhuǎn)到 login.html 頁面讓我們登錄,使用用戶名密碼為 csdn1/123456 登錄之后,我們在瀏覽器中請求 http://localhost:8080/user/student 接口,會進(jìn)行角色認(rèn)證,因為數(shù)據(jù)庫中 csdn1 的用戶角色是 admin,所以和配置中的吻合,認(rèn)證通過;我們再請求 http://localhost:8080/user/teacher 接口,會進(jìn)行權(quán)限認(rèn)證,因為數(shù)據(jù)庫中 csdn1 的用戶權(quán)限為 user:*,滿足配置中的 user:create,所以認(rèn)證通過。

        接下來,我們點(diǎn)退出,系統(tǒng)會注銷重新讓我們登錄,我們使用 csdn2 這個用戶來登錄,重復(fù)上述操作,當(dāng)在進(jìn)行角色認(rèn)證和權(quán)限認(rèn)證這兩步時,就認(rèn)證不通過了,因為數(shù)據(jù)庫中 csdn2 這個用戶存的角色和權(quán)限與配置中的不同,所以認(rèn)證不通過。

        3. 總結(jié)

        本節(jié)主要介紹了 Shiro 安全框架與 Spring Boot 的整合。先介紹了 Shiro 的三大核心組件已經(jīng)它們的作用;然后介紹了 Shiro 的身份認(rèn)證、角色認(rèn)證和權(quán)限認(rèn)證;最后結(jié)合代碼,詳細(xì)介紹了 Spring Boot 中是如何整合 Shiro 的,并設(shè)計了一套測試流程,逐步分析 Shiro 的工作流程和原理,讓讀者更直觀地體會出 Shiro 的整套工作流程。Shiro 使用的很廣泛,希望讀者將其掌握,并能運(yùn)用到實際項目中。

        -End-

        最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

        點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

        在看點(diǎn)這里好文分享給更多人↓↓

        瀏覽 11
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            又爽 又黄 免费网站69 | 五月天丁香婷婷视频 | 摸进她的内裤里疯狂揉她小说片段 | 奇米网狠狠网 | 中国极品少妇xxxx做受 | 中文字幕日韩有码日韩无码 | 丁香五月激情综合婷婷在线播放 | 扒开美女狂揉网站原神 | 乱伦无码中文字幕 | 啊啊啊啊啊啊网站 |