最新亚洲精品福利在线,欧美一区二区三区大片,久久91无码一区二区三区,色哟哟免费观看视频入口,美女裸露双奶头屁股无裸体

Spring Boot中如何集成 Shiro及實(shí)際應(yīng)用

時(shí)間:2022-10-30 16:27:15 類(lèi)型:JAVA
字號(hào):    

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

  1. Shiro 三大核心組件

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

    1.png


  2. 1. Subject:認(rèn)證主體。它包含兩個(gè)信息:Principals 和 Credentials??匆幌逻@兩個(gè)信息具體是什么。

  3. Principals:身份??梢允怯脩?hù)名,郵件,手機(jī)號(hào)碼等等,用來(lái)標(biāo)識(shí)一個(gè)登錄主體身份;
    Credentials:憑證。常見(jiàn)有密碼,數(shù)字證書(shū)等等。

說(shuō)白了,就是需要認(rèn)證的東西,最常見(jiàn)的就是用戶(hù)名密碼了,比如用戶(hù)在登錄的時(shí)候,Shiro 需要去進(jìn)

行身份認(rèn)證,就需要 Subject 認(rèn)證主體。

2. SecurityManager:安全管理員。這是 Shiro 架構(gòu)的核心,它就像 Shiro 內(nèi)部所有原件的保護(hù)傘一樣。我們?cè)陧?xiàng)目中一般都會(huì)配置 SecurityManager,開(kāi)發(fā)人員大部分精力主要是在 Subject 認(rèn)證主體上面。我們?cè)谂c Subject 進(jìn)行交互的時(shí)候,實(shí)際上是 SecurityManager 在背后做一些安全操作。

 3. Realms:Realms 是一個(gè)域,它是連接 Shiro 和具體應(yīng)用的橋梁,當(dāng)需要與安全數(shù)據(jù)交互的時(shí)候, 比如用戶(hù)賬戶(hù)、訪(fǎng)問(wèn)控制等,Shiro 就會(huì)從一個(gè)或多個(gè) Realms 中去查找。我們一般會(huì)自己定制Realm,這在下文會(huì)詳細(xì)說(shuō)明。

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

1.2 Shiro 身份認(rèn)證

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

    2.png

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

Step2:將 Subject 實(shí)例委托給應(yīng)用程序的 SecurityManager(Shiro的安全管理)來(lái)開(kāi)始實(shí)際的認(rèn)證工

作。這里開(kāi)始真正的認(rèn)證工作了。

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

realm 可以自定義(Custom Realm)。


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


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

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

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

2.png

一個(gè)用戶(hù)可以有多個(gè)角色,而不同的角色可以有不同的權(quán)限,也可由有相同的權(quán)限。

比如說(shuō)現(xiàn)在有三個(gè)角色,1是普通角色,2也是普通角色,3是管理員,角色1只能查看信息,角色2只能添加信息,管理員都可以,而且還可以刪除信息,類(lèi)似于這樣。


2. Spring Boot 集成 Shiro 過(guò)程


2.1 依賴(lài)導(dǎo)入

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

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

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

    這里主要涉及到三張表:用戶(hù)表、角色表和權(quán)限表, 表信息結(jié)構(gòu)如下:

//角色表
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `rolename` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名稱(chēng)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'admin');
INSERT INTO `role` VALUES (2, 'teacher');
INSERT INTO `role` VALUES (3, 'student');

SET FOREIGN_KEY_CHECKS = 1;
//權(quán)限表
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `permissionname` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '權(quán)限名',
  `roleid` int(11) NULL DEFAULT NULL COMMENT '外鍵關(guān)聯(lián)role',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `roleid`(`roleid`) USING BTREE,
  CONSTRAINT `permission_ibfk_1` FOREIGN KEY (`roleid`) REFERENCES `role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES (1, 'user:*', 1);
INSERT INTO `permission` VALUES (2, 'student:*', 2);
INSERT INTO `permission` VALUES (3, 'user:*', 2);

SET FOREIGN_KEY_CHECKS = 1;
//用戶(hù)表:
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用戶(hù)主鍵',
  `username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用戶(hù)名',
  `password` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密碼',
  `roleid` int(11) NULL DEFAULT NULL COMMENT '外鍵關(guān)聯(lián)role表',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `role_id`(`roleid`) USING BTREE,
  CONSTRAINT `user_ibfk_1` FOREIGN KEY (`roleid`) REFERENCES `role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'csdn1', '123456', 1);
INSERT INTO `user` VALUES (2, 'csdn2', '123456', 2);
INSERT INTO `user` VALUES (3, 'csdn3', '123456', 3);

SET FOREIGN_KEY_CHECKS = 1;

添加一些數(shù)據(jù)如下:

r.pngp.pngu.png

解釋一下這里的權(quán)限: user:* 表示權(quán)限可以是 user:create 或者其他, * 處表示一個(gè)占位符,我們

可以自己定義(比如:add表示添加,delete表示刪除,update表示修改,list表示查詢(xún)),具體的會(huì)在下文 Shiro 配置那里說(shuō)明。

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

//自定義reaml
public class MyRealm extends AuthorizingRealm {
    @Resource
    private UserService userService;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //獲取用戶(hù)名
        String username = (String) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //給該用戶(hù)設(shè)置角色,角色信息豐在role表中取
        authorizationInfo.setRoles(userService.getRoles(username));
        authorizationInfo.setStringPermissions(userService.getPermissions(username));
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    //根據(jù)token獲取用戶(hù)名, 后面會(huì)提講到該token是怎么來(lái)的
     String username = (String) authenticationToken.getPrincipal();
     //根據(jù)用戶(hù)名用數(shù)據(jù)庫(kù)查詢(xún)?cè)撚脩?hù)
        User user = userService.getByUsername(username);
        if(user != null){
            //把當(dāng)前用戶(hù)存到session中
            SecurityUtils.getSubject().getSession().setAttribute("user",user);
            //傳入用戶(hù)名和密碼進(jìn)行身份認(rèn)證, 并返回認(rèn)證信息
            AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),"myRealm");
            return authenticationInfo;
        }
        else{
            return null;
        }
    }
}

從上面兩個(gè)方法中可以看出:

驗(yàn)證身份的時(shí)候是根據(jù)用戶(hù)輸入的用戶(hù)名先從數(shù)據(jù)庫(kù)中查出該用戶(hù)名對(duì)應(yīng)的用戶(hù),這時(shí)候并沒(méi)有涉及到密碼,也就是說(shuō)到這一步的時(shí)候,即使用戶(hù)輸入的密碼不對(duì),也是可以查出來(lái)該用戶(hù)的,然后將該用戶(hù)的正確信息封裝到 authcInfo 中返回給 Shiro,接下來(lái)就是Shiro的事了,它會(huì)根據(jù)這里面的真實(shí)信息與用戶(hù)前臺(tái)輸入的用戶(hù)名和密碼進(jìn)行校驗(yàn), 這個(gè)時(shí)候也要校驗(yàn)密碼了,如果

校驗(yàn)通過(guò)就讓用戶(hù)登錄,否則跳轉(zhuǎn)到指定頁(yè)面。同理,權(quán)限驗(yàn)證的時(shí)候也是先根據(jù)用戶(hù)名從數(shù)據(jù)庫(kù)中獲取與該用戶(hù)名有關(guān)的角色和權(quán)限,然后封裝到 authorizationInfo 中返回給 Shiro。

2.3 Shiro 配置

自定義的 realm 寫(xiě)好了,接下來(lái)需要對(duì) Shiro 進(jìn)行配置了。我們主要配置三個(gè)東西:自定義 realm、安

全管理器 SecurityManager 和 Shiro 過(guò)濾器。如下:

配置自定義 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注冊(cè)完成=====");
        return myRealm;
    }

    /**
     * 注入安全管理器
     * @return SecurityManager
     */
    @Bean
    public SecurityManager securityManager() {
        // 將自定義realm加進(jìn)來(lái)
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(myAuthRealm());
        logger.info("====securityManager注冊(cè)完成====");
        return securityManager;
    }

    /**
     * 注入Shiro過(guò)濾器
     * @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)證失敗會(huì)訪(fǎng)問(wèn)該url
        shiroFilterFactoryBean.setLoginUrl("/login");
// 設(shè)置成功之后要跳轉(zhuǎn)的鏈接
        shiroFilterFactoryBean.setSuccessUrl("/success");
// 設(shè)置未授權(quán)界面,權(quán)限認(rèn)證失敗會(huì)訪(fǎng)問(wèn)該url
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
// LinkedHashMap是有序的,進(jìn)行順序攔截器配置
        Map<String,String> filterChainMap = new LinkedHashMap<>();
// 配置可以匿名訪(fǎng)問(wèn)的地址,可以根據(jù)實(shí)際情況自己添加,放行一些靜態(tài)資源等,anon表示放行
        filterChainMap.put("/css/**", "anon");
        filterChainMap.put("/img/**", "anon");
        filterChainMap.put("/js/**", "anon");
        filterChainMap.put("/swagger-*/**", "anon");
        filterChainMap.put("/swagger-ui.html/**", "anon");
// 登錄url 放行
        filterChainMap.put("/login", "anon");
// “/user/admin” 開(kāi)頭的需要身份認(rèn)證,authc表示要身份認(rèn)證
        filterChainMap.put("/user/admin*", "authc");
// “/user/student” 開(kāi)頭的需要角色認(rèn)證,是“admin”才允許
        filterChainMap.put("/user/student*/**", "roles[admin]");
// “/user/teacher” 開(kāi)頭的需要權(quán)限認(rèn)證,是“user:create”才允許
        filterChainMap.put("/user/teacher*/**", "perms[\"user:create\"]");
// 配置logout過(guò)濾器
        filterChainMap.put("/logout", "logout");
// 設(shè)置shiroFilterFactoryBean的FilterChainDefinitionMap
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
        logger.info("====shiroFilterFactoryBean注冊(cè)完成====");
        return shiroFilterFactoryBean;
    }


}

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

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

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

1.png

Dao接口內(nèi)容如下:

@Repository
public interface UserDao {
    @Select("select * from user where username = #{username}")
    User getByUsername(String username);

    @Select("select r.rolename from user u,role r " +
            "where u.roleid = r.id and u.username = #{username}")
    Set<String> getRoles(String username);
    //返回集合,避免重復(fù)
    @Select("select p.permissionname from user u,role r,permission p " +
            "where u.roleid = r.id and p.roleid = r.id and u.username = #{username}")
    Set<String> getPermissions(String username);
}

UserServiceImpl:

@Service("userService")
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Override
    public User getByUsername(String username) {
        return userDao.getByUsername(username);
    }

    @Override
    public Set<String> getRoles(String username) {
        return userDao.getRoles(username);
    }

    @Override
    public Set<String> getPermissions(String username) {
        return userDao.getPermissions(username);
    }
}

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

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

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

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

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

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

@Controller
@RequestMapping("/user")

public class UserController {

    @RequestMapping("/logout")
    public String logout() {
        SecurityUtils.getSubject().logout();
        return "user/login";
    }
    /**
     * 身份認(rèn)證測(cè)試接口
     * @param request
     * @return
     */
    @RequestMapping("/admin")
    public String admin(HttpServletRequest request) {
        Object user = request.getSession().getAttribute("user");
        return "user/success";
    }
    /**
     * 角色認(rèn)證測(cè)試接口這三個(gè)接口很簡(jiǎn)單,直接返回到指定頁(yè)面展示即可,只要認(rèn)證成功就會(huì)正常跳轉(zhuǎn),如果認(rèn)證失敗,就會(huì)
     跳轉(zhuǎn)到上文 ShrioConfig 中配置的頁(yè)面進(jìn)行展示。
     2.4.2 用戶(hù)登錄接口
     * @param request
     * @return
     */
    @RequestMapping("/student")
    public String student(HttpServletRequest request) {
        return "user/success";
    }
    /**
     * 權(quán)限認(rèn)證測(cè)試接口
     * @param request
     * @return
     */
    @RequestMapping("/teacher")
    public String teacher(HttpServletRequest request) {
        return "user/success";
    }

    /**
     * 用戶(hù)登錄接口
     * @param user user
     * @param request request
     * @return string
     */
    @PostMapping("/login")
    public String login(User user, HttpServletRequest request) {
// 根據(jù)用戶(hù)名和密碼創(chuàng)建token
        UsernamePasswordToken token = new
                UsernamePasswordToken(user.getUsername(), user.getPassword());
// 獲取subject認(rèn)證主體
        Subject subject = SecurityUtils.getSubject();
        try{
// 開(kāi)始認(rèn)證,這一步會(huì)跳到我們自定義的realm中
            subject.login(token);
            request.getSession().setAttribute("user", user);
            return "user/success";
        }catch(Exception e){
            System.out.println(e.getMessage());
            request.getSession().setAttribute("user", user);
            request.setAttribute("error", "用戶(hù)名或密碼錯(cuò)誤!");
            return "/user/login";
        }
    }



}

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

2.4.3 測(cè)試一下

最后,啟動(dòng)項(xiàng)目,測(cè)試一下:



<