权限管理(一)---Shiro框架的认证和授权

主流权限管理方案

- -Shiro框架(Apache组织)

优势:简单易用,开发人员只需花费很短的学习时间,就可以完成项目中复杂的权限管理的开发。

- -Spring Security框架(Spring技术栈)


1 权限的管理

1.1 什么是权限管理

基本上有用户参与的系统都要进行权限管理。权限管理实现对用户访问系统的控制,并按照安全策略控制用户可以访问且只能访问被授权的资源。

权限管理包括用户身份认证授权两部分。认证,即看当前访问系统的用户是否具有访问该系统的权限;授权,即看认证通过的用户是否具有系统资源的访问权限。

1.2 什么是身份认证

身份认证就是判断一个用户是否为合法用户的处理过程。
最常用的认证方式是系统通过验证 用户输入的用户名和密码 与 系统中存储的该用户的用户名和密码 是否一致,判断该用户是否通过认证访问系统。

1.3 什么是授权

授权即访问控制,控制不同身份的用户能访问哪些资源。进行身份认证后,需进行权限控制,用户有权限才能授权访问资源;对没有权限的一些资源则无法访问。


2 Shiro

Shiro是Apache旗下一个功能强大且易于使用的开源安全框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。

2.1 Shiro详细架构

在这里插入图片描述

1.Subject(主体):

外部应用与subject进行交互,它记录了当前操作用户,可以将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序或第三方服务等。

2.SecurityManager(安全管理器) :

Shiro 的核心,用来协调管理组件工作,对全部的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等。

SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager三个接口。源码如下:

public interface SecurityManager extends Authenticator, Authorizer, SessionManager {

    Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;

    void logout(Subject subject);

    Subject createSubject(SubjectContext context);

}
3.Authenticator(认证管理器):

负责对用户身份进行认证。

4.Authorizer(授权管理器):

用户认证通过后,在访问资源时需要通过授权器判断用户是否有操作权限。

5.SessionManager(会话管理):

负责创建并管理用户 Session 生命周期,提供一个强有力的 Session 体验。

6.SessionDAO:

代表 SessionManager 执行 Session 持久(CRUD)动作,它允许任何存储的数据挂接到 session 管理基础上。

7.CacheManager(缓存管理器):

提供创建缓存实例和管理缓存生命周期的功能。

8.Cryptography(加密管理器):

提供了加密方式的设计及管理。

9.Realms(领域对象):

Realm相当于datasource,SecurityManager进行安全认证需要通过Realm获取用户权限数据,如:若用户身份数据在数据库,那么realm就需要从数据库获取用户身份信息。

注意:Realm不光是从数据源取数据,他其中还有认证授权校验相关的代码。


3 Shiro中的认证

3.1 关键对象

  • Subject:主体
    主体可以是访问系统的用户、程序等,进行认证的都可称为主体

  • Principal:身份信息
    主体进行身份认证的标识,必须具有唯一性,如用户名、手机号等。一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。

  • credential:凭证信息
    只有主体自己知道的安全信息,如密码、证书等。

3.2 认证流程

在这里插入图片描述

3.3 Shiro入门案例

注意:本案例中的shiro.ini配置文件是把数据库中的用户名和密码已写死。
在这里插入图片描述
subject.login(token); 这行加断点,看底层源码的调用过程如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
总结

  • 1、 最终在 SimpleAccountRealmdoGetAuthenticationInfo() 方法中完成了用户名校验;
    AuthenticatingRealmassertCredentialsMatch() 方法中完成了密码校验(自动完成密码校验)。

  • 2、Realm类关系图:
    在这里插入图片描述
    认证realm:
    AuthenticatingRealm --> doGetAuthenticationInfo()
    授权realm:
    AuthorizingRealm --> doGetAuthorizationInfo()

  • 3、当我们自定义Realm时,只需继承 AuthorizingRealm 就可以了,他的源码中有两个方法,分别是认证和授权。

3.4 自定义Realm实现认证

3.4.1 自定义CustomerRealm

package src.main.java.cn.edu.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * 自定义realm实现
 * 将认证、授权数据的来源转为数据库的实现
 */
public class CustomerRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //在token中获取用户名
        String principal = (String) token.getPrincipal();
        System.out.println(principal);
        //根据身份信息使用jdbc mybatis查询相关数据库
        if ("xiaochen".equals(principal)){
            //参数1:返回数据库中正确的用户名
            //参数2:返回数据库中正确的密码
            //参数3:提供当前realm的名字 this.getName()
            return new SimpleAuthenticationInfo(principal,"123",this.getName());
        }
        return null;
    }
}

3.4.2 测试使用自定义realm

package cn.edu.shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import src.main.java.cn.edu.realm.CustomerRealm;

/**
 * 使用自定义realm
 */
public class TestCusRealmAuthenticator {
    public static void main(String[] args) {
        //1.创建SecurityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //2.设置自定义realm
        defaultSecurityManager.setRealm(new CustomerRealm());
        //3.将安全工具类设置安全管理器
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //4.通过安全工具类获取subject
        Subject subject = SecurityUtils.getSubject();
        //5.创建token
        UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
        try {
            subject.login(token);
            subject.isAuthenticated();
            System.out.println("登录成功");
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("用户名错误");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密码错误");
        }
    }
}

3.5 MD5加密和Salt

3.5.1 MD5算法

  • 特点:
    MD5算法不可逆,只能根据明文生成密文,不能由密文反推出明文;
    如果内容相同,无论执行多少次MD5,生成结果是一致的;
    MD5生成结果始终是一个16进制的32位长度的字符串。

  • 作用:
    加密签名(校验和)
    如对用户输入的密码加密存储或对文件内容做校验,判断两个文件内容是否一致等。

3.5.2 MD5&Salt业务流程

在这里插入图片描述

3.5.3 MD5加密入门案例

package cn.edu.shiro;

import org.apache.shiro.crypto.hash.Md5Hash;

public class TestShiroMD5 {
    public static void main(String[] args) {
        //创建一个md5算法:并未加密
//        Md5Hash md5Hash = new Md5Hash();
//        md5Hash.setBytes("123".getBytes());
//        String s = md5Hash.toHex();
//        System.out.println(s);//313233

        //使用MD5
        Md5Hash md5Hash = new Md5Hash("123");
        System.out.println(md5Hash.toHex());//202cb962ac59075b964b07152d234b70

        //使用MD5+Salt
        Md5Hash md5Hash1 = new Md5Hash("123", "X0*7ps");
        System.out.println(md5Hash1.toHex());//8a83592a02263bfe6752b2b5b03a4799

		//使用MD5+Salt+hash散列
        Md5Hash md5Hash2 = new Md5Hash("123", "X0*7ps", 1024);
        System.out.println(md5Hash2.toHex());//e4f9bf3e0c58f045e62c23c533fcf633
    }
}

3.5.4 使用MD5和Salt认证入门案例

实际是将盐和散列后的值存在数据库中,realm从数据库取出盐和加密后的值由shiro自动完成密码校验。

3.5.4.1 自定义Md5Realm

package cn.edu.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

/**'
 * 使用自定义realm:加入md5+salt+hash
 */
public class MD5Realm extends AuthorizingRealm {

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取身份信息
        String principal = (String) token.getPrincipal();
        //根据用户名查询数据库
        if("xiaochen".equals(principal)){
            //参数1:数据库用户名
            //参数2:数据库md5+salt之后的密码
            //参数3:注册时的随机盐
            //参数4:realm的名字
            return new SimpleAuthenticationInfo(principal,
                    "e4f9bf3e0c58f045e62c23c533fcf633",
                    ByteSource.Util.bytes("X0*7ps"),
                    this.getName());
        }
        return null;
    }
}

3.5.4.2 测试Md5Realm

package cn.edu.shiro;

import cn.edu.realm.MD5Realm;
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.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;

public class TestMd5RealmAuthenticator {
    public static void main(String[] args) {
        //创建安全管理器
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //注入realm
        MD5Realm realm = new MD5Realm();
        //设置realm使用hash凭证匹配器
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //使用算法
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //散列次数
        hashedCredentialsMatcher.setHashIterations(1024);
        realm.setCredentialsMatcher(hashedCredentialsMatcher);

        defaultSecurityManager.setRealm(realm);
        //将安全管理器注入安全工具类
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //通过安全工具类获取subject
        Subject subject = SecurityUtils.getSubject();
        //认证
        UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
        try {
            subject.login(token);
            System.out.println("登录成功");
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("用户名错误");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密码错误");
        }
    }
}


4 Shiro中的授权

4.1 授权

授权,即访问控制,控制不同身份的用户能访问哪些资源。主体进行身份认证后,需进行权限控制,用户有权限才能授权访问资源;对没有权限的一些资源则无法访问。

4.2 关键对象

授权,简单理解就是:谁 对 什么资源 进行 什么操作。

  • 主体(Subject)
    访问系统资源的用户或程序等。
  • 资源(Resource)
    如:系统菜单、页面、按钮等。
  • 权限/许可(Permission)
    规定了主体对资源的操作许可,如用户的查询权限、添加权限、某个用户的修改权限等,通过权限可以知道主体都对哪些资源有哪些操作许可。

4.3 授权流程

在这里插入图片描述

4.4 权限管理的授权方式

  • 基于角色的访问控制
    RBAC(Role-Based Access Control)以角色为中心进行访问控制
if(subject.hasRole("admin")){
	//操作什么资源
}
  • 基于资源的访问控制
    RBAC(Resource-Based Access Control)以资源为中心进行访问控制
if(subject.isPermission("user:find:*")){//资源标识符(who):操作:资源(资源实例/资源类型)标识符
	//对user的所有资源都能进行查询操作
}

4.5 权限字符串

  • 规则:资源标识符 : 操作 : 资源实例标识符
    对哪个资源的哪个实例有哪些操作。
    例如:
    用户创建权限:user:create
    用户实例001的所有权限:user:*:001

4.6 shiro授权编程实现方式

  • 编程式
Subject subject=SecurityUtils.getSubject();
if(subject.hasRole("admin")){
	//有权限
}else{
	//无权限
}
  • 注解式
@RequiresRoles("admin")
public void hello(){
	//有权限
}
  • 标签式
<!-- JSP 标签:在JSP页面通过相应标签完成 -->
<shiro:hasRole name="admin">
	<!-- 有权限 -->
</shiro:hasRole>

注意:Thymeleaf中使用shiro需要额外集成。

4.7 shiro授权编程实现

4.7.1 编辑Md5Realm

package cn.edu.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.util.ByteSource;

/**'
 * 使用自定义realm:加入md5+salt+hash
 */
public class MD5Realm extends AuthorizingRealm {
	//授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    	System.out.println("=============");
    	String primaryPrincipal = (String) principals.getPrimaryPrincipal();
    	System.out.println("身份信息:"+primaryPrincipal);
    	
    	//根据身份信息 用户名 获取当前用户的角色信息以及权限信息 xiaocehn admin user
    	SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
    	//将数据库中查询角色信息赋值给权限对象
    	simpleAuthorizationInfo.addRole("admin");
    	simpleAuthorizationInfo.addRole("user");
    	
    	//将数据库中查询权限信息赋值给权限对象
    	simpleAuthorizationInfo.addStringPermission("user:*:01");
    	simpleAuthorizationInfo.addStringPermission("product:create");
    	
    	return simpleAuthorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取身份信息
        String principal = (String) token.getPrincipal();
        //根据用户名查询数据库
        if("xiaochen".equals(principal)){
            //参数1:数据库用户名
            //参数2:数据库md5+salt之后的密码
            //参数3:注册时的随机盐
            //参数4:realm的名字
            return new SimpleAuthenticationInfo(principal,
                    "e4f9bf3e0c58f045e62c23c533fcf633",
                    ByteSource.Util.bytes("X0*7ps"),
                    this.getName());
        }
        return null;
    }
}

4.7.2 测试shiro授权

package cn.edu.shiro;

import cn.edu.realm.MD5Realm;

import java.util.Arrays;

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.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;

public class TestMd5RealmAuthenticator {
    public static void main(String[] args) {
        //创建安全管理器
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //注入realm
        MD5Realm realm = new MD5Realm();
        //设置realm使用hash凭证匹配器
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //使用算法
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //散列次数
        hashedCredentialsMatcher.setHashIterations(1024);
        realm.setCredentialsMatcher(hashedCredentialsMatcher);

        defaultSecurityManager.setRealm(realm);
        //将安全管理器注入安全工具类
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //通过安全工具类获取subject
        Subject subject = SecurityUtils.getSubject();
        //认证
        UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
        try {
            subject.login(token);
            System.out.println("登录成功");
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("用户名错误");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密码错误");
        }
        //认证用户进行授权
        if(subject.isAuthenticated()){
			//基于角色权限控制
			System.out.println(subject.hasRole("admin"));//true
			
			//基于多角色权限控制
			System.out.println(subject.hasAllRoles(Arrays.asList("admin","user")));//true
			
			//是否有其中一个角色
			boolean[] roles = subject.hasRoles(Arrays.asList("admin","user","super"));
			for (boolean b : roles) {
				System.out.println(b);//true true false
			}
			System.out.println("******************");
			
			//基于权限字符串的访问控制   资源标识符:操作:资源类型
			System.out.println("权限:"+subject.isPermitted("user:*:01"));//true
	 		
			//分别具有哪些权限
			boolean[] permitted = subject.isPermitted("user:*:01","order:*:10");
			for (boolean b : permitted) {
				System.out.println(b);//true   false
			}
			
			//同时具有哪些权限
			boolean permittedAll = subject.isPermittedAll("user:*:01","product:*");
			System.out.println(permittedAll);//false
			
		}
    }
}

猜你喜欢

转载自blog.csdn.net/showLo1120/article/details/107657106