一篇文章弄到Shiro 認證 授權(圖文詳解)

Shiro權限控制

0.1傳統的權限認證方式

1538968548578

特點:爲每個人單獨的分配權限模塊,能夠實現權限控制,但是當公司人員龐大之後,非常難管理

上述權限控制如何設計表?

關係:員工和菜單權限的關係:多對多

員工id 菜單名稱
1 取派管理
2 快遞員管理
2 運單管理

好處:可以方便的 實現權限控制

缺陷:比如當修改權限的時候,公司統一的給組長級別的人 加一個“計算工資”權限,這時候,得修改權限表中所有組長的權限,每個組長在數據庫中都得增加一條“計算工資”記錄的權限

後來,這個“計算工資”的功能,在給組長之後,發現,這個權限不合適,得收回這個權限,這個時候,需要刪除多條記錄

0.2 RBAC認證方式:

Role Based Access Controller :基於角色的訪問控制

前無古人後無來者

在這裏插入圖片描述

0.3 RBAC認證方式下的數據庫設計

數據庫設計:
在這裏插入圖片描述

1. 權限控制

1.1 概述

1.1.1 什麼是認證和授權

  • 認證和授權,控制項目資源的訪問。
    • 認證:先進行認證。例如:是否是QQ會員-----在程序中指的就是:登錄
    • 授權:再進行授權。例如:QQ會員級別(級別不同權限不同)-------對菜單的訪問控制

1.1.2 權限控制的解決方案

  • 方式1:自定義實現
    • 自己如何實現一個認證?寫一個Filter過濾器
  • 方式2:採用框架,例如:shiro、Spring Security 等
    • Shiro:輕量、簡單,apache
    • Spring Security:輕量、簡單,Spring

1.1.3 Shiro概述

Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。使用Shiro的易於理解的API,您可以快速、輕鬆地獲得任何應用程序,從最小的移動應用程序到最大的網絡和企業應用程序。

官網:http://shiro.apache.org/
在這裏插入圖片描述

Authentication:身份認證/登錄,驗證用戶是不是擁有相應的身份;

Authorization:授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具有某個權限;

Session Manager:會話管理,即用戶登錄後就是一次會話,在沒有退出之前,它的所有信息都在會話中;會話可以是普通JavaSE環境的,也可以是如Web環境的;

Cryptography:加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲;

Web Support:Web支持,可以非常容易的集成到Web環境;

Caching:緩存,比如用戶登錄後,其用戶信息、擁有的角色/權限不必每次去查,這樣可以提高效率;

Concurrency:shiro支持多線程應用的併發驗證,即如在一個線程中開啓另一個線程,能把權限自動傳播過去;

Testing:提供測試支持;

Run As:允許一個用戶假裝爲另一個用戶(如果他們允許)的身份進行訪問;

Remember Me:記住我,這個是非常常見的功能,即一次登錄後,下次再來的話不用登錄了。

1.1.4 三個核心組件

  • 三個核心組件:Subject, SecurityManager 和 Realms.
    • Subject,主體,即“當前操作用戶”。Subject代表了當前用戶的安全操作。(需要被認證的對象)
    • SecurityManager:它是Shiro框架的核心,管理所有用戶的安全操作,並提供安全管理的各種服務。
    • Realm: Realm充當了Shiro與應用安全數據間的“橋樑”或者“連接器”。也就是說,當對用戶執行認證(登錄)和授權(訪問控制)驗證時,Shiro會從應用配置的Realm中查找用戶及其權限信息。

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

1.1.5 傳統的登錄和shiro登錄的比較

shiro的使用不依賴Spring框架,javase可以用,javaee也可以用,移動應用程序可以用,大型的網絡和企業應用程序也可以使用Shiro。

傳統登錄方式:

在這裏插入圖片描述

Shiro安全框架實現登錄

傳統的方式,客戶端發出請求給Controller,Controller接受用戶的用戶名和密碼,然後由Controller調用業務邏輯,在Controller中調用Service,然後service負責調用數據庫進行處理,如果用戶名和密碼正確,則將用戶信息保存到session中,並且進入主頁面

如果用戶名和密碼錯誤,則保存錯誤信息,然後將信息輸出到客戶端。

在這裏插入圖片描述

答:客戶端發送請求到Controller,Controller接受用戶的用戶名和密碼,第一步還是一樣的,但是第二步不一樣了,以前第二步是Controller直接調用Service處理,現在Controller調用Shiro安全框架去處理,也就是說將認證授權抽取出來,有一個框架專門爲你做認證做授權,這裏有框架去幫我們完成認證和授權,然後告訴你這個用戶名和密碼是否可用還是不可用,當然此時,認證成功之後,只要從shiro中取出認證的結果,如果成功的話,將用戶保存至session中,然後在跳轉頁面

這個過程相比於早期的操作,相當於驗證用戶名和密碼的業務邏輯交給shiro安全框架來做。並且加密也交給shiro安全框架來做,然後由shiro安全框架來加密,由shiro拿密文與數據庫中的密文進行比較。

總結一下:shiro就是一個安全框架,幫助我們解決認證、授權、加密和密碼比較的過程。

1.2 整合shiro

1.2.1 maven座標

步驟1:在common-parent項目中,添加座標

<!--shiro start-->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>1.3.2</version>
    </dependency>
   <dependency>
		<groupId>org.apache.shiro</groupId>
		<artifactId>shiro-spring</artifactId>
		<version>1.3.2</version>
	</dependency>
	<!--shiro end-->

1.2.2 配置過濾器

使用Shiro時,需要配置的相關權限過濾器如下(共10個):

在這裏插入圖片描述

anon: 匿名過濾器,未登陸也可以訪問

authc: 認證過濾器, 登陸後訪問

perms : 需要xx權限,才能訪問

roles: 需要xx角色,才能訪問

user: 需要xx用戶,才能訪問

port:指定端口才能訪問

ssl:必須使用https協議才能訪問

logout :登出功能

rest :根據指定HTTP請求訪問才能訪問 ,get方式提交 或者 post方式提交才能訪問

1.2.3 配置config類

shiro的配置步驟 
1 配置安全管理器SecurityManager

2 realm域配置:由於SecurityManger需要使用realm域,涉及到用戶信息、權限信息,處理用戶信息的時候需要加密 

3 密碼比較器:用戶輸入的銘文進行加密,並且與數據庫中的密文進行比較 

4 配置生成過濾器的工廠類

在這裏插入圖片描述

根據配置文件報錯顯示,需要創建如下文件:

1 Realm域

2 CredentialsMatcher密碼比較器

1.2.4 創建類Realm類

在這裏插入圖片描述

//自定義Realm ,實現安全數據 連接
public class BosRealm extends AuthorizingRealm {

    // 認證...
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
        AuthenticationToken token) {
        System.out.println("shiro 認證管理... ");
        return null;
    }

    @Override
    // 授權...
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
        System.out.println("shiro 授權管理...");
        return null;
    }
}

1.2.5 編寫密碼比較器

在這裏插入圖片描述

public class BosCredentialsMatcher extends SimpleCredentialsMatcher {

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        System.out.println("密碼比較器");
        return false;
    }
}

1.2.6 實現認證方法

在這裏插入圖片描述

//自定義Realm ,實現安全數據 連接
public class BosRealm extends AuthorizingRealm {
    

    @Autowired
    private UserService userService;

    // 認證...
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
        AuthenticationToken token) {
        System.out.println("shiro 認證管理... ");
        //1 用戶信息
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;

        //2 通過username從數據庫中查找 User對象,如果找到,沒找到.
        User user = userService.findUserByUsername(upToken.getUsername());
        if(user == null){
            //返回null表示賬號不存在
            return null;
        }

        return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
    }

    @Override
    // 授權...
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
        System.out.println("shiro 授權管理...");

        return null;
    }
}

1.2.7編寫加密工具類

在這裏插入圖片描述


public class Encrypt {
	/*
	 * 散列算法一般用於生成數據的摘要信息,是一種不可逆的算法,一般適合存儲密碼之類的數據,
	 * 常見的散列算法如MD5、SHA等。一般進行散列時最好提供一個salt(鹽),比如加密密碼“admin”,
	 * 產生的散列值是“21232f297a57a5a743894a0e4a801fc3”,
	 * 可以到一些md5解密網站很容易的通過散列值得到密碼“admin”,
	 * 即如果直接對密碼進行散列相對來說破解更容易,此時我們可以加一些只有系統知道的干擾數據,
	 * 如用戶名和ID(即鹽);這樣散列的對象是“密碼+用戶名+ID”,這樣生成的散列值相對來說更難破解。
	 */
	
	//高強度加密算法,不可逆
	public static String md5(String password, String salt){
		return new Md5Hash(password,salt,2).toString();
	}
	
	public static void main(String[] args) {
		/**
		 * new Md5Hash("123456","lisi",1) :1b539b60601b934441308049a9526e7d
		 * new Md5Hash("123456","lisi",2) :42bd4e7685cb11d3ba02716c313cb04b
		 * new Md5Hash("123456","lisi",3) :16f807d62105b4896034552ee5caeb8a
		 * new Md5Hash("123456","KMNO4",3):8bd35dc14dc07f756478bb44513694f6
		 */
		//System.out.println(new Md5Hash("123456","KMNO4",3).toString());


		/**
		 * sha家族加密算法
		 * sha1:aca1eb31d2dcf8f1fcf3fd7a7104232785afad41     40 位
		 * sha256: 616a47d8e1e42f23693bb3a85749bf18d4b6e5380ddfd5717aafa61e33d5211e
		 * sha384:84f5cbb18e2d9f1c81b8cec6f443a2b229993689a2ebae97db37e13af1dfb00ec6168713a53fe19d33a63d4d30889553
		 * sha512:c3e5102b6a7ec6caa5b255dae2895b11c2ef0c7b9bfea8e848653372b53f3ef665d96ea283a21eac683cc0fe5c4b1f64692c2056a8a9636ee1931151043d2b5d
		 */
		System.out.println("sha1:"+new Sha1Hash("123456","lisi",2));
		System.out.println("sha256:"+new Sha256Hash("123456","lisi",2));
		System.out.println("sha384:"+new Sha384Hash("123456","lisi",2));
		System.out.println("sha512:"+new Sha512Hash("123456","lisi",2));

	}
}

1.2.8 編寫密碼比較器

在這裏插入圖片描述

public class BosCredentialsMatcher extends SimpleCredentialsMatcher {

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        //向下轉型
        UsernamePasswordToken upToken = (UsernamePasswordToken)token;
        //獲取用戶頁面輸入的密碼
        String pwd = new String(upToken.getPassword());
        //加密
        String newPwd =Encrypt.md5(pwd, upToken.getUsername()).toString();
        //獲取數據庫密碼
        String dbPwd = info.getCredentials().toString();

        return equals(newPwd, dbPwd);
    }
}

5 散列加密算法:加密的時候撒鹽

MD5:

SHA:

1.5.6 修改BosRealm

在這裏插入圖片描述


//自定義Realm ,實現安全數據 連接
public class BosRealm extends AuthorizingRealm {

   @Resource
   private UserService userService;

   @Resource
   private RoleService roleService;

   @Resource
   private PermissionService permissionService;

   @Override
   public String getName() {
      return "bosRealm";
   }
   
   @Override
   // 認證...
   protected AuthenticationInfo doGetAuthenticationInfo(
         AuthenticationToken token) throws AuthenticationException {
      System.out.println("shiro 認證管理... ");
      //1 獲得密碼
      String username = (String)token.getPrincipal();
        
      //2 通過username從數據庫中查找 User對象,如果找到,沒找到.
      User user = userService.findUserByUsername(username);;
      if(user == null){
         //返回null表示賬號不存在
          return null;
      }

      return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
   }
   
   @Override
   // 授權...
   protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
      System.out.println("shiro 授權管理...");

      SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
      // 根據當前登錄用戶 查詢對應角色和權限
//    User user = (User) SecurityUtils.getSubject().getPrincipal();
      User user = (User) pc.getPrimaryPrincipal();
      // 調用業務層,查詢角色
      List<Role> roles = roleService.findByUser(user);
      for (Role role : roles) {
         authorizationInfo.addRole(role.getKeyword());
      }
      // 調用業務層,查詢權限
      List<Permission> permissions = permissionService.findByUser(user);
      for (Permission permission : permissions) {
         authorizationInfo.addStringPermission(permission.getKeyword());
      }

      return authorizationInfo;
   }
}

1.5.7 確定權限不足時,顯示未授權頁面

  • 確定權限過濾器配置信息

在這裏插入圖片描述

  • 確定頁面

在這裏插入圖片描述

//攔截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();

//配置退出 過濾器,其中的具體的退出代碼Shiro已經替我們實現了
filterChainDefinitionMap.put("/login*", "anon");
filterChainDefinitionMap.put("/validatecode.jsp*", "anon");
filterChainDefinitionMap.put("/user/login*", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/services/**", "anon");
filterChainDefinitionMap.put("/pages/base/courier**", "perms[courier:list]");
filterChainDefinitionMap.put("/pages/base/area**", "roles[base]");
//<!-- 過濾鏈定義,從上向下順序執行,一般將 /**放在最爲下邊 -->:這是一個坑呢,一不小心代碼就不好使了;
//<!-- authc:所有url都必須認證通過纔可以訪問; anon:所有url都都可以匿名訪問-->
filterChainDefinitionMap.put("/**", "authc");

// 如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl("/login.html");
// 登錄成功後要跳轉的鏈接
shiroFilterFactoryBean.setSuccessUrl("/index.html");
//未授權界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized.html");

1.5.9 細粒度方法權限控制

需求:張三可以查看快遞員列表信息,但是無法添加快遞員

Shiro提供了若干註解用於在方法上進行權限控制

註解 描述
@RequiresPermissions() 用戶必須具有指定【權限】,纔可以訪問被註解修飾的方法。
@RequiresRoles() 用戶必須具有指定【角色】,纔可以訪問被註解修飾的方法。

在這裏插入圖片描述

1539038644786

  • 步驟1:在授權方法中獲取該用戶的所有的權限:包括role權限和Permission權限
  • 步驟2:在運單 CourierController的添加方法上添加courier:list權限標識。

在這裏插入圖片描述

測試:無權限訪問

在這裏插入圖片描述

1.6 動態菜單

動態菜單: 不同用戶登錄後,應該看到不同菜單結構

在這裏插入圖片描述

1、 修改index.html 加載基本菜單 url路徑

2、 在MenuController 添加 showMenu方法

@RestController
@RequestMapping("/menu")
public class MenuController {

    @Autowired
    private MenuService menuService;

    // 加載左側的菜單功能
    @GetMapping(value = "/showMenu")
    public ResponseEntity<List<Menu>>  showMenu(){
        // 調用業務層,查詢當前用戶具有菜單列表
        Subject subject = SecurityUtils.getSubject();
        User user = (User)subject.getPrincipal();
        // 查詢菜單列表
        List<Menu> result = menuService.findByUser(user);

        return new ResponseEntity<List<Menu>>(result,HttpStatus.OK);
    }
}

3、 編寫MenuService.java業務層

@Service
@Transactional
public class MenuService {

    @Autowired
    private MenuMapper menuMapper;

    /**查詢用戶*/
    public List<Menu> findByUser(User user) {
        // 針對admin用戶顯示所有的菜單
        if(user.getUsername().equals("admin")){
            return menuMapper.selectAll();
        }
        else{
            // 使用用戶ID,查詢當前用戶具有的菜單列表
            return menuMapper.findByUser(user.getId());
        }
    }
}

4、調用DAO

@org.apache.ibatis.annotations.Mapper
public interface MenuMapper extends Mapper<Menu> {
    @Select("select m.* from t_menu m,t_user u,t_user_role ur,t_role r,t_role_menu rm "
            + "where m.id = rm.menu_id and rm.role_id = r.id "
            + "and r.id = ur.role_id and ur.user_id = u.id "
            + "and u.id=#{id} order by m.priority")
    List<Menu> findByUser(Integer id);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章