Spring MVC 整合 Shiro 權(quán)限控制

Apache Shiro 是一個(gè)功能強(qiáng)大且靈活的開放源代碼安全框架,可以細(xì)粒度地處理認(rèn)證 (Authentication),授權(quán) (Authorization),會(huì)話 (Session) 管理和加密 (cryptography) 等企業(yè)級(jí)應(yīng)用中常見的安全控制流程。

Apache Shiro 的首要目標(biāo)是易于使用和理解。 有時(shí)候安全性的流程控制會(huì)非常復(fù)雜,對(duì)開發(fā)人員來說是件很頭疼的事情,但并不一定如此。 框架就應(yīng)該盡可能地掩蓋復(fù)雜性,并公開一個(gè)簡(jiǎn)潔而直觀的 API,從而簡(jiǎn)化開發(fā)人員的工作,確保其應(yīng)用程序安全性。這次我們聊一聊如何在 Spring Web 應(yīng)用中使用 Shiro 實(shí)現(xiàn)權(quán)限控制。

功能

Apache Shiro 是一個(gè)具有許多功能的綜合型應(yīng)用程序安全框架。 下圖為 Shiro 中的最主要的幾個(gè)功能:

Shiro 的主要目標(biāo)是“應(yīng)用安全的四大基石” - 認(rèn)證,授權(quán),會(huì)話管理和加密:

  • 身份驗(yàn)證:也就是通常所說的 “登錄”,為了證明用戶的行為所有者。
  • 授權(quán):訪問控制的過程,即確定什么用戶可以訪問哪些內(nèi)容。
  • 會(huì)話管理:即使在非 Web 應(yīng)用程序中,也可以管理用戶特定的會(huì)話,這也是 Shiro 的一大亮點(diǎn)。
  • 加密技術(shù):使用加密算法保證數(shù)據(jù)的安全,非常易于使用。

架構(gòu)

從整體概念上理解,Shiro 的體系架構(gòu)有三個(gè)主要的概念:Subject (主體,也就是用戶),Security Manager (安全管理器)和 Realms (領(lǐng)域)。 下圖描述了這些組件之間的關(guān)系:

這幾大組件可以這樣理解:

  • Subject (主體):主體是當(dāng)前正在操作的用戶的特定數(shù)據(jù)集合。主體可以是一個(gè)人,也可以代表第三方服務(wù),守護(hù)進(jìn)程,定時(shí)任務(wù)或類似的東西,也就是幾乎所有與該應(yīng)用進(jìn)行交互的事物。
  • Security Manager (安全管理器):它是 Shiro 的體系結(jié)構(gòu)的核心,扮演了類似于一把 “傘” 的角色,它主要負(fù)責(zé)協(xié)調(diào)內(nèi)部的各個(gè)組件,形成一張安全網(wǎng)。
  • Realms (領(lǐng)域):Shiro 與應(yīng)用程序安全數(shù)據(jù)之間的 “橋梁”。當(dāng)需要實(shí)際與用戶帳戶等安全相關(guān)數(shù)據(jù)進(jìn)行交互以執(zhí)行認(rèn)證和授權(quán)時(shí),Shiro 將從 Realms 中獲取這些數(shù)據(jù)。

數(shù)據(jù)準(zhǔn)備

在 Web 應(yīng)用中,對(duì)安全的控制主要有角色、資源、權(quán)限(什么角色能訪問什么資源)幾個(gè)概念,一個(gè)用戶可以有多個(gè)角色,一個(gè)角色也可以訪問多個(gè)資源,也就是角色可以對(duì)應(yīng)多個(gè)權(quán)限。落實(shí)到數(shù)據(jù)庫設(shè)計(jì)上,我們至少需要建 5 張表:用戶表、角色表、資源表、角色-資源表、用戶-角色表,這 5 張表的結(jié)構(gòu)如下:

id username password
1 張三 123456
2 李四 666666
3 王五 000000

用戶表:

id username password
1 張三 123456
2 李四 666666
3 王五 000000
id rolename
1 管理員
2 經(jīng)理
3 員工

角色表:

id rolename
1 管理員
2 經(jīng)理
3 員工
id resname
1 /user/add
2 /user/delete
3 /compony/info

資源表:

id resname
1 /user/add
2 /user/delete
3 /compony/info
id roleid resid
1 1 1
2 1 2
3 2 3

角色-資源表:

id roleid resid
1 1 1
2 1 2
3 2 3
id userid roleid
1 1 1
2 1 2
3 1 3

用戶-角色表:

id userid roleid
1 1 1
2 1 2
3 1 3

對(duì)應(yīng)的 POJO 類如下:

/**
 * 用戶
 */
public class User {

    private Integer id;

    private String username;

    private String password;

    //getter & setter...
}
/**
 * 角色
 */
public class Role {

    private String id;

    private String rolename;
}
/**
 * 資源
 */
public class Resource {

    private String id;

    private String resname;
}
/**
 * 角色-資源
 */
public class RoleRes {

    private String id;

    private String roleid;

    private String resid;
}
/**
 * 用戶-角色
 */
public class UserRole {

    private String id;

    private String userid;

    private String roleid;
}

Spring 與 Shiro 整合的詳細(xì)步驟,請(qǐng)參閱我的博客 《 Spring 應(yīng)用中整合 Apache Shiro 》。

這里補(bǔ)充一下:需要提前引入 Shiro 的依賴,打開 mvnrepository.com,搜索 Shiro,我們需要前三個(gè)依賴,也就是 Shiro-Core、Shiro-Web 以及 Shiro-Spring,以 Maven 項(xiàng)目為例,在 pom.xml 中的 <dependencies> 節(jié)點(diǎn)下添加如下依賴:

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

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

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

application-context.xml 中需要這樣配置 shiroFilter bean:

<!-- 配置shiro的過濾器工廠類,id- shiroFilter要和我們?cè)趙eb.xml中配置的過濾器一致 -->  
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">   
    <property name="securityManager" ref="securityManager"/>   
    <!-- 登錄頁面 -->   
    <property name="loginUrl" value="/login"/>    
    <!-- 登錄成功后的頁面 -->  
    <property name="successUrl" value="/index"/>    
    <!-- 非法訪問跳轉(zhuǎn)的頁面 -->  
    <property name="unauthorizedUrl" value="/403"/>    
    <!-- 權(quán)限配置 -->  
    <property name="filterChainDefinitions">    
        <value>    
            <!-- 無需認(rèn)證即可訪問的靜態(tài)資源,還可以添加其他 url -->  
            /static/** = anon
            <!-- 除了上述忽略的資源,其他所有資源都需要認(rèn)證后才能訪問 -->    
            /** = authc
        </value>    
    </property>    
</bean>  

接下來就需要定義 Realm 了,自定義的 Realm 集成自 AuthorizingRealm 類:

public class MyRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 驗(yàn)證權(quán)限
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String loginName = SecurityUtils.getSubject().getPrincipal().toString();
        if (loginName != null) {
            String userId = SecurityUtils.getSubject().getSession().getAttribute("userSessionId").toString();

            // 權(quán)限信息對(duì)象,用來存放查出的用戶的所有的角色及權(quán)限
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            // 用戶的角色集合      
            ShiroUser shiroUser = (ShiroUser) principalCollection.getPrimaryPrincipal();
            info.setRoles(shiroUser.getRoles());
            info.addStringPermissions(shiroUser.getUrlSet());               

            return info;
        }
        return null;
    }

    /**
     * 認(rèn)證回調(diào)函數(shù),登錄時(shí)調(diào)用
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
        String username = (String) token.getPrincipal();
        User user = new User();
        sysuser.setUsername(username);
        try {
            List<SysUser> users = userService.findByNames(user);
            List<String> roleList= userService.selectRoleNameListByUserId(users.get(0).getId());
            if (users.size() != 0) {
                String pwd = users.get(0).getPassword();
                // 當(dāng)驗(yàn)證都通過后,把用戶信息放在 session 里
                Session session = SecurityUtils.getSubject().getSession();
                session.setAttribute("userSession", users.get(0));
                session.setAttribute("userSessionId", users.get(0).getId());
                session.setAttribute("userRoles", org.apache.commons.lang.StringUtils.join(roleList,","));
                return new SimpleAuthenticationInfo(username,users.get(0).getPassword());
            } else {
                // 沒找到該用戶
                throw new UnknownAccountException();
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return null;
    }
    /**
     * 更新用戶授權(quán)信息緩存.
     */
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }
    /**
     * 更新用戶信息緩存.
     */
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        super.clearCachedAuthenticationInfo(principals);
    }

    /**
     * 清除用戶授權(quán)信息緩存.
     */
    public void clearAllCachedAuthorizationInfo() {
        getAuthorizationCache().clear();
    }

    /**
     * 清除用戶信息緩存.
     */
    public void clearAllCachedAuthenticationInfo() {
        getAuthenticationCache().clear();
    }

    /**
     * 清空所有緩存
     */
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }


    /**
     * 清空所有認(rèn)證緩存
     */
    public void clearAllCache() {
        clearAllCachedAuthenticationInfo();
        clearAllCachedAuthorizationInfo();
    }
}

最后定義一個(gè)用戶登錄的控制器,接受用戶的登錄請(qǐng)求:

@Controller  
public class UserController {  

    /**
     * 用戶登錄
     */
    @PostMapping("/login")
    public String login(@Valid User user,BindingResult bindingResult,RedirectAttributes redirectAttributes){  
        try {  
            if(bindingResult.hasErrors()){  
                return "login";
            }  
            //使用權(quán)限工具進(jìn)行認(rèn)證,登錄成功后跳到 shiroFilter bean 中定義的 successUrl  
            SecurityUtils.getSubject().login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
            return "redirect:index";  
        } catch (AuthenticationException e) {  
            redirectAttributes.addFlashAttribute("message","用戶名或密碼錯(cuò)誤");  
            return "redirect:login";
        }  
    }  

    /**
     * 注銷登錄
     */
    @GetMapping("/logout")    
    public String logout(RedirectAttributes redirectAttributes ){
        SecurityUtils.getSubject().logout();   
        return "redirect:login";
    }
}  
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,272評(píng)論 6 342
  • 說明:本文很多觀點(diǎn)和內(nèi)容來自互聯(lián)網(wǎng)以及各種資料,如果侵犯了您的權(quán)益,請(qǐng)及時(shí)聯(lián)系我,我會(huì)刪除相關(guān)內(nèi)容。 權(quán)限管理 基...
    寇寇寇先森閱讀 7,753評(píng)論 8 76
  • 「跑步」失敗。 「練字」失敗。 「仰起」x20。 轟趴累死現(xiàn)在咸魚的朔君 土曜日
    寒川朔閱讀 155評(píng)論 0 0
  • 每個(gè)人的生活都有其明確的形態(tài),但其實(shí)都是由各種各樣的習(xí)慣構(gòu)成的。我們每天早晨起來做的第一件事,坐到辦公室最開...
    某某菇?jīng)?/span>閱讀 1,137評(píng)論 1 0

友情鏈接更多精彩內(nèi)容