Shiro學習二(基於springboot的授權操作)

基於springboot的授權操作
首先而言,要進行授權,就是要實現看人下菜碟這個操作,即賦予成員相應的角色,針對不同的角色,可以訪問相應的頁面,使用相應的資源,首先,在數據表設計時,每個成員具有一個role屬性,指定這個成員所屬的角色,角色在對應一個授權表,完成角色和權限的一對多關係,下面是數據表的結構
customer:
在這裏插入圖片描述下面是role,比較簡單:
在這裏插入圖片描述接下來是權限表:
在這裏插入圖片描述在這個表中使用一個role_id完成和角色的對應,使得角色和權限完成一對多的映射
聲明:以上數據表結構設計存在一些冗餘和缺陷,然學習期間,一切從簡
接下來我們需要進行授權,其實授權,說白了就是,資源的攔截和放行,在第一季已經做過,shiro總共有四種認證過濾器,第一季我們用了無需認證的anon和需要登陸才能進行操作的authc,下面可以使用perms進行授權,首先要進行資源的權限字符串的對應,這會精確到某個controller的某個方法,例如下面這個controller:

package com.example.lclepro.Controller;

import com.example.lclepro.Bean.LcleCustomer;
import com.example.lclepro.Bean.Msg;
import com.example.lclepro.Service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;

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

    @Autowired
    UserService userService;

    @PostMapping("/login")
    public ModelAndView login(@RequestParam("username") String username, @RequestParam("password") String password){
        ModelAndView mv = new ModelAndView();
        //獲取到Subject對象
        Subject suject = SecurityUtils.getSubject();
        //將收集到的用戶名和密碼存儲到token中
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //執行登入方法
        try {
            suject.login(token);
            mv.setViewName("pages/customer");
        }
        catch (UnknownAccountException e){
            //用戶名不存在時拋出的異常
            mv.addObject("msg", "用戶不存在,請找到合適的介紹人加入組織(๑•̀ㅂ•́)و✧");
            mv.setViewName("pages/index");
        }
        catch (IncorrectCredentialsException e){
            //用戶名密碼不匹配時拋出的異常
            mv.addObject("msg", "用戶名與密碼不匹配( ̄_, ̄ )");
            mv.setViewName("pages/index");
        }
        return mv;
    }

    //使用ajax進行數據交互的方法,需要加上@ResponseBody註解
    @GetMapping("/getAllCustomers")
    @ResponseBody
    public Msg getAllCustomers(){
        return userService.getAllCustomers();
    }
}

如果要對方法/getAllCustomers進行授權,就可以在自定義的Realm中完成授權方法如下

package com.example.lclepro.Config;


import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {


    //實現攔截器
    //@Bean註解註釋在方法上,將返回的實體類交給容器處理,id是方法名
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //設置攔截成功以後,訪問的頁面
        shiroFilterFactoryBean.setLoginUrl("/");
        //攔截器Map的創建
        Map<String, String> map = new HashMap<String, String>();
        //針對不同的url,使用不同的攔截器,放到map中,即可生效,完成攔截
        //攔截所有請求
        map.put("/*", "authc");
        //放行登陸請求
        map.put("/", "anon");
        //放行登錄認證請求
        map.put("/user/login", "anon");
        map.put("/user/getAllCustomers", "perms[user:getAllCustomers]");
        //設置相關的map集合,完成攔截功能的收集
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    //實現web安全管理器的加載
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("lcleRealm") LcleRealm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;

    }

    //實現Realm的加載
    @Bean
    public LcleRealm lcleRealm(){
        return new LcleRealm();
    }

    //要加入註解,指定方法的攔截,則需要加入以下的三個方法,開啓註解模式
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * *
     * 開啓Shiro的註解(如@RequiresRoles,@RequiresPermissions),需藉助SpringAOP掃描使用Shiro註解的類,並在必要時進行安全邏輯驗證
     * *
     * 配置以下兩個bean(DefaultAdvisorAutoProxyCreator(可選)和AuthorizationAttributeSourceAdvisor)即可實現此功能
     * * @return
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

}

注意這一條代碼:

map.put("/user/getAllCustomers", "perms[user:getAllCustomers]");

我們將,剛纔那個方法,映射出了一個授權字符串即中括號裏面的內容“user:getAllCustomers”,這時,用戶進行訪問,由於未進行授權,請求則會被攔截:
在這裏插入圖片描述注意看第二個401,這就是我們請求的方法,他由於沒有被授權,請求失敗了,那麼,如何進行授權操作呢,我們不妨在自定義的Realm中加上一句授權代碼:

package com.example.lclepro.Config;

import com.example.lclepro.Bean.LcleCustomer;
import com.example.lclepro.Service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

public class LcleRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    //執行授權邏輯
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        Subject subject = SecurityUtils.getSubject();
        LcleCustomer customer = (LcleCustomer)subject.getPrincipal();
        System.out.println(customer.getUsername()+"開始執行授權邏輯!");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermission("user:getAllCustomers");
        return info;
    }

    //執行認證邏輯,執行login()方法以後,會進入到這裏,進行用戶名密碼的比對
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        //執行認證邏輯
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //獲取密碼
        String password = userService.isUser(token.getUsername());
        if(password==null){
            throw new UnknownAccountException();
        }
        else{
            LcleCustomer customer = userService.selectCustomerByName(token.getUsername());
            return new SimpleAuthenticationInfo(customer, password, "");
        }
    }
}

注意這個授權方法中的代碼`

Subject subject = SecurityUtils.getSubject();
        LcleCustomer customer = (LcleCustomer)subject.getPrincipal();
        System.out.println(customer.getUsername()+"開始執行授權邏輯!");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermission("user:getAllCustomers");
        return info;

我們先從shiro中獲取到,我們本次操作的對象,然後定義一個授權信息相關的String(多個授權也可以使用list),,如此我們就完成了簡單的授權,重新啓動頁面,進行測試
在這裏插入圖片描述我們看到,原先的401已經消失,資源訪問正常,但是這樣做未免會很傻,因爲一個正常的項目,硬編碼進去如此多的映射url,未免有些難受(儘管用戶的權限可以持久化到數據庫),這時候就體現出了註解的重要性,能否通過註解的方式,對每個controller的方法進行標記相應的授權url呢,答案是肯定的,不過我們的shiroConfig類需要做出變動,用來開啓註解:

package com.example.lclepro.Config;


import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {


    //實現攔截器
    //@Bean註解註釋在方法上,將返回的實體類交給容器處理,id是方法名
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //設置攔截成功以後,訪問的頁面
        shiroFilterFactoryBean.setLoginUrl("/");
        //攔截器Map的創建
        Map<String, String> map = new HashMap<String, String>();
        //針對不同的url,使用不同的攔截器,放到map中,即可生效,完成攔截
        //攔截所有請求
        map.put("/*", "authc");
        //放行登陸請求
        map.put("/", "anon");
        //放行登錄認證請求
        map.put("/user/login", "anon");
        map.put("/user/getAllCustomers", "perms[user:getAllCustomers]");
        //設置相關的map集合,完成攔截功能的收集
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    //實現web安全管理器的加載
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("lcleRealm") LcleRealm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;

    }

    //實現Realm的加載
    @Bean
    public LcleRealm lcleRealm(){
        return new LcleRealm();
    }

    //要加入註解,指定方法的攔截,則需要加入以下的三個方法,開啓註解模式
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * *
     * 開啓Shiro的註解(如@RequiresRoles,@RequiresPermissions),需藉助SpringAOP掃描使用Shiro註解的類,並在必要時進行安全邏輯驗證
     * *
     * 配置以下兩個bean(DefaultAdvisorAutoProxyCreator(可選)和AuthorizationAttributeSourceAdvisor)即可實現此功能
     * * @return
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

}

和之前相比,我們在最後加了三個方法,用來開啓註解,那麼我們接下來可以在每個方法頭上加上一個註解@RequiresPermissions,用來標記這個方法的授權字符串,下面是controller的代碼
package com.example.lclepro.Controller;

import com.example.lclepro.Bean.LcleCustomer;
import com.example.lclepro.Bean.Msg;
import com.example.lclepro.Service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;

@Controller
@RequestMapping(“user”)
public class UserController {

@Autowired
UserService userService;

@PostMapping("/login")
public ModelAndView login(@RequestParam("username") String username, @RequestParam("password") String password){
    ModelAndView mv = new ModelAndView();
    //獲取到Subject對象
    Subject suject = SecurityUtils.getSubject();
    //將收集到的用戶名和密碼存儲到token中
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    //執行登入方法
    try {
        suject.login(token);
        mv.setViewName("pages/customer");
    }
    catch (UnknownAccountException e){
        //用戶名不存在時拋出的異常
        mv.addObject("msg", "用戶不存在,請找到合適的介紹人加入組織(๑•̀ㅂ•́)و✧");
        mv.setViewName("pages/index");
    }
    catch (IncorrectCredentialsException e){
        //用戶名密碼不匹配時拋出的異常
        mv.addObject("msg", "用戶名與密碼不匹配( ̄_, ̄ )");
        mv.setViewName("pages/index");
    }
    return mv;
}

//使用ajax進行數據交互的方法,需要加上@ResponseBody註解
@RequiresPermissions("user:getAllCustomers")
@GetMapping("/getAllCustomers")
@ResponseBody
public Msg getAllCustomers(){
    return userService.getAllCustomers();
}

}
至此,簡單的授權已經完成,shiro的工作也基本完成,要想完成多個權限的操作,不妨將權限和角色進行映射,再將每個人賦予不同的角色,然後在自定義的Realm中使用Service方法,獲取當前用戶的授權字符串,逐個加入info中,完成授權

爲之則易,不爲則難

發佈了57 篇原創文章 · 獲贊 3 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章