1、shiro框架

shiro框架

shiro介绍

什么是shiro

Apache Shiro 是Java的一个安全框架。Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。

Shiro官网网站:http://shiro.apache.org/

为什么要学Shiro

既然shiro将安全认证相关的功能抽取出来形成一个框架,使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。

shiro使用广泛,shiro既可以做web开发,也可以做非web开发。集群分布式应用中越来越多的用户开始使用shiro。

基本功能

Authentication

身份认证/登录,验证用户是不是拥有相应的身份;

Authorization

授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者粒度的验证某个用户对某个资源具有某个权限 ;

realm

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

注意:不要把realm理解成只是从数据取数据,在realm中还有认证授权校验的相关的代码。

Session Manager

sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,可以将分布式应用的会话集中在一点管理,此特性可以使它实现单点登录。

SesseionDAO

SessionDAO即会话dao,也是对session会话操作的一套接口,比如将session存储到数据库,可以通过jdbc将会话存储到数据库。

CacheManager

CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。

Cryptography

Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加密/解密等功能。

shiro依赖包

与其他java开源框架类似,将shiro的jar包加入项目就可以使用shiro提供的功能了。shiro-core是核心包必须选用,还提供了与web整合的shiro-web与spring整合的shiro-spring与任务调度quartz整合的shiro-quartz等。

下边是shiro各个jar包的maven坐标。

<dependencies>
	
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>1.2.3</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-web</artifactId>
			<version>1.2.3</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.2.3</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-quartz</artifactId>
			<version>1.2.3</version>
		</dependency>
		
		<!-- 日志 -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.21</version>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.12</version>
		</dependency>
	</dependencies>

也可以通过引入shiro-all包括shiro所有的包:(不能看源码)

<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-all</artifactId>
			<version>1.2.3</version>
		</dependency>

shiro认证

身份验证

即应用中谁能证明他就是他本人。一般提供如他们的身份的ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明。

在shiro中,用户需要提供principals(身份)和credentials(证明)给shiro,从而应用能验证用户身份(是存入token中的,需要从token中取就行了)

principals

身份,即主体的标识属性,可以是任何东西,如用户、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般用户名/邮箱/手机号。

credentials

证明/凭证,即只有主体知道的安全值,如密码/数字证书等。

最常见的principals和credentials组合就是用户名/密码了。

认证流程

shiro入门程序工程 环境

创建一个maven项目

shiro认证入门程序

shiro.ini

Shiro默认可以读取.ini后缀的配置文件作为shiro项目的配置在classpath下面定义一个shiro.ini文件

通过此配置文件创建securityManager工厂。

eclipse对ini文件的支持

入门程序

package cn.zj.shiro;

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.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;

public class ShiroTest {

	// 测试使用Shiro的登入登出
	@Test
	public void testLoginLogOut() throws Exception {

		// 1.创建SecurityManager对象,通过读取 shiro.ini配置文件创建
		IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");

		// 2.创建SecurityManager安全管理器对象
		SecurityManager securityManager = factory.createInstance();

		// 3.将安全管理器对象设置到当前环境中
		SecurityUtils.setSecurityManager(securityManager);

		// 4.创建认证(通俗讲,就是登陆)令牌用(一般都使用账号密码令牌)
		UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "1231");

		// 5.创建主体对象,用于登陆
		Subject subject = SecurityUtils.getSubject();

		// 5.1判断是否认证(登陆),没有认证就开始认证
		boolean isAuthenticated = subject.isAuthenticated();
		System.out.println("是否通过认证 :" + isAuthenticated);
		if (!isAuthenticated) {
			try {
				subject.login(usernamePasswordToken);
			} catch (UnknownAccountException e) {
				System.out.println("账号不存在");
			} catch (IncorrectCredentialsException e) {
				System.out.println("无效的凭证(密码)");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		// 判断是否登陆
		System.out.println("是否通过认证:" + subject.isAuthenticated());
		// 退出
		subject.logout();
		System.out.println("是否通过认证:" + subject.isAuthenticated());
	}
}

执行流程

1、通过ini配置文件创建securityManager

2、调用subject.login方法主体提交认证,提交的token

3、securityManager进行认证,securityManager最终由ModularRealmAuthenticator进行认证。

4、ModularRealmAuthenticator调用IniRealm(给realm传入token) 去ini配置文件中查询用户信息

5、IniRealm根据输入的token(UsernamePasswordToken)从 shiro-first.ini查询用户信息,根据账号查询用户信息(账号和密码)

​ 如果查询到用户信息,就给ModularRealmAuthenticator返回用户信息(账号和密码)

​ 如果查询不到,就给ModularRealmAuthenticator返回null

6、ModularRealmAuthenticator接收IniRealm返回Authentication认证信息

​ 如果返回的认证信息是null,ModularRealmAuthenticator抛出异常

​ 如果返回的认证信息不是null(说明inirealm找到了用户),对IniRealm返回用户密码 (在ini文件中存在)和 token中的密码 进行对比,如果不一致抛出异常

DisabledAccountException(帐号被禁用)

LockedAccountException(帐号被锁定)

ExcessiveAttemptsException(登录失败次数过多)

ExpiredCredentialsException(凭证过期)等

小结

ModularRealmAuthenticator作用进行认证,需要调用realm查询用户信息(在数据库中存在用户信息)

ModularRealmAuthenticator进行密码对比(认证过程)。

realm:需要根据token中的身份信息去查询数据库(入门程序使用ini配置文件),如果查到用户返回认证信息,如果查询不到返回null。

自定义realm

realm即领域,范围的意思。通俗讲:你可以理解为一个特殊的DAO,他主要用于对认证信息和授权信息的存取和操作,实际开发中主要调用开发者开发操作数据库的相关Service获取Dao来进行授权认证。

在我们实际开发中,使用ini的配置文件就将我们的用户名,密码写死了,我们是需要从数据库中取数据的,所有我们要写自己的业务逻辑,从数据库中取出来。此时我们需要自定义realm,而不是调用它默认的realm。

realm接口

realm实现

package cn.zj.shiro.realm;

import java.util.Arrays;
import java.util.List;

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;

public class CustomRealm extends AuthorizingRealm {

	/*
	 * 认证方法,此方法返回认证信息对象AuthenticationInfo 
	 * 开发者在此方法内部完成自定义的认证逻辑
	 * 
	 * 认证思路
	 * 
	 * 1,获取tolen的身份 (账号)
	 * 	String username = (String) token.getPrincipal();
	 * 
	 * 2,注入UserService userService;
	 * 
	 * 3, 调用UserService中的根据账号查询用户信息的方法
	 * User user = userService.selectByUsername(username);
	 * 
	 * 	3,1如果账号不存在,此方法返回null	
	 * 	认证的代码地方就会抛出一个 UnknownAccountException 账号不存在异常
	 * 
	 * 	3,2如果账号存在,获取 User对象中的密码
	 * 	String password = user.getPassword();
	 * 
	 * 
	 * 4,创建AuthenticationInfo 传入 身份(User对象)和凭证(密码),Shiro底层会自动
	 * 	把用户token中的密码,和 User对象中获取的密码进行匹配
	 * 
	 * 	4.1 匹配失败
	 * 	认证的代码地方就会抛出一个 IncorrectCredentialsException 无效的凭证异常(秘密错误)
	 * 
	 *  4.2 密码比对成功
	 *  	------认证完成
	 * 
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		//1.获取tolen的身份 (账号)
		String username = (String) token.getPrincipal();
		
		
		//模拟数据库中一堆账号
		List<String> users = Arrays.asList("zhangsan","lisi","lucy","lili");
		
		//模拟数据库查询是否有账号
		if(!users.contains(username)) {
			return null;
		}
			
		//模拟从user对象中获取密码 String password = user.getPassword();
		String password = "123";
		
		
		//创建认证信息对象AuthenticationInfo
		SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, this.getName());
		
		
		return authenticationInfo;
	}

	// 授权方法
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}
}

配置realm

#自定义配置
[main]
#自定义Realm
#自定义realm名称=全限定名
customRealm=cn.zj.shiro.CustomRealm

#将自定义realm设置给SecurityManager的realm属性(类型Spring的依赖注入)
securityManager.realms=$customRealm

测试

package cn.zj.shiro;

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.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;

public class ShiroTest {

	// 测试使用Shiro的登入登出
	@Test
	public void testLoginLogOut() throws Exception {

		// 1.创建SecurityManager对象,通过读取 shiro.ini配置文件创建
		IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");

		// 2.创建SecurityManager安全管理器对象
		SecurityManager securityManager = factory.createInstance();

		// 3.将安全管理器对象设置到当前环境中
		SecurityUtils.setSecurityManager(securityManager);

		// 4.创建认证(通俗讲,就是登陆)令牌用(一般都使用账号密码令牌)
		UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "123");

		// 5.创建主体对象,用于登陆
		Subject subject = SecurityUtils.getSubject();

		// 5.1判断是否认证(登陆),没有认证就开始认证
		boolean isAuthenticated = subject.isAuthenticated();
		System.out.println("是否通过认证 :" + isAuthenticated);
		if (!isAuthenticated) {
			try {
				subject.login(usernamePasswordToken);
			} catch (UnknownAccountException e) {
				System.out.println("账号不存在");
			} catch (IncorrectCredentialsException e) {
				System.out.println("无效的凭证(密码)");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		// 判断是否登陆
		System.out.println("是否通过认证:" + subject.isAuthenticated());
		// 退出
		subject.logout();
		System.out.println("是否通过认证:" + subject.isAuthenticated());
	}
}

加密算法

上述案例我们的密码都是明文的,这样相对可能来讲对系统不是很安全,如果密码进行加密,即使是系统数据泄露,也拿不到真实的密码。

通常需要对密码 进行散列,常用的加密方式有md5、sha (安全散列算法(英语:Secure Hash Algorithm,缩写为SHA))

对md5密码,如果知道散列后的值可以通过穷举算法,得到md5密码对应的明文。

建议对md5进行散列时加salt(盐),进行加密相当 于对原始密码+盐进行散列。

正常使用时散列方法:

在程序中对原始密码+盐(随机数)进行散列,将散列值存储到数据库中,并且还要将盐也要存储在数据库中。

如果进行密码对比时,使用相同 方法,将原始密码+盐进行散列,进行比对。

md5散列测试

package cn.zj.shiro;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.junit.Test;

public class CredentialsSecurityTest {
	
	@Test
	public void md5() throws Exception {
		//原始密码
		String source = "admin";
		//盐
		String salt = "query";
		//散列次数
		int hashIterations = 2;
		/*
		 * source: 原始密码
		 * salt: 盐
		 * hashIterations: 散列次数 md5(md5('admin'))
		 */
		//方式一
		Md5Hash md5Hash = new Md5Hash(source, salt, hashIterations);
		String md5_pwd = md5Hash.toString();
		System.out.println(md5_pwd);
		//散列一次: 27ce446ce439ee775b91e4f9a45bf053
		//散列二次: 662b46d0168aa5353771f45084378881
		/*
		 * algorithmName:算法 md5
		 * source:原密码
		 * salt :盐
		 * hashIterations :散列次数
		 */
		//方式二
		SimpleHash simpleHash = new SimpleHash("md5", source, salt, hashIterations);
		
		System.out.println(simpleHash);
	}
}

自定义realm支持散列算法

需求:实际开发时realm要进行md5值(明文散列后的值)的对比。

package cn.zj.shiro.realm;

import java.util.Arrays;
import java.util.List;

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;

public class CustomRealm extends AuthorizingRealm {
	
	
	/*
	 * 认证方法,开发者在方法内部自定认证的规则
	 * 
	 * token :令牌,在 主体login 传递过来的
	 * return AuthenticationInfo
	 * 		返回认证信息
	 * 		如果返回null,认为认证失败
	 * 
	 */
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		/*
		 * 认证思路
		 * 	1.获取 token令牌的身份(账号)
		 *  2.在当前类中注入 UserService,调用serice的根据账号去数据库查询用户方法
		 *  	service调用Mapper/Dao层的根据账号查询用户方法
		 *    2.1 如果没有此用户,返回null,当前认证方法也返回null
		 *    2.1 如果有此用户,把用户的密码和token的凭证(密码)进行匹配
		 *    	2.2.1,匹配不成功,当前认证方法也返回null
		 *    	2.2.1  匹配成功,创建一个AuthenticationInfo 认证信息对象,认证成功
		 */
		//1.获取 token令牌的身份(账号)
		String username = (String) token.getPrincipal();
		/*
			User user = userService.selectByUserName(username);
			if(user !=null){
				//进一步比对密码
			}
		*/
		
		//模拟数据库中的账号
		List<String> usernames = Arrays.asList("admin","zhangsan","lisi");
		
		if(usernames.contains(username)) {
			//身份(账号)匹配成功,进一步匹配凭证(密码)
			System.out.println("账号匹配成功");
			//String password = user.getPassword();
			//(数据库中的密码) admin+ query+散列三次后的密码
			String hashedCredentials = "662b46d0168aa5353771f45084378881";
			//数据库中的盐
			ByteSource credentialsSalt = ByteSource.Util.bytes("query");
			
			
			//认证信息对象
			AuthenticationInfo authenticationInfo = 
					new SimpleAuthenticationInfo(username, hashedCredentials, credentialsSalt, this.getName());
			
			
			return authenticationInfo;
		}
		
		return null;
	}
	/*
	 * 授权方法
	 */
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) 	
		return null;
	}
}

凭证匹配器

在shiro,的Realms中中提供了很多凭证匹配器

在realm中配置凭证匹配器

#自定义配置
[main]

#定义凭证器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#算法
credentialsMatcher.hashAlgorithmName=md5
#散列次数
credentialsMatcher.hashIterations=2
#---------------------------------------------------------------------
#自定义Realm
#自定义realm名称=全限定名
customRealm=cn.zj.shiro.Md5Realm
#---------------------------------------------------------------------
#设置realm的凭证匹配器
customRealm.credentialsMatcher=$credentialsMatcher
#----------------------------------------------------------------------
#将自定义realm设置给SecurityManager的realm属性(类型Spring的依赖注入)
securityManager.realms=$customRealm


授权

授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作

等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、

角色(Role)。

关键对象介绍

主体

主体,即访问应用的用户,在Shiro中使用Subject代表该用户。用户只有授权后才允许访

问相应的资源。

资源

在应用中用户可以访问的任何东西,比如访问JSP 页面、查看/编辑某些数据、访问某个业

务方法、打印文本等等都是资源。用户只要授权后才能访问。

权限

安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的

权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)打印文档等等。。。

角色

角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权

限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总

监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。

授权流程

流程如下:

1、首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager,而

SecurityManager接着会委托给Authorizer;

2、Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过

PermissionResolver把字符串转换成相应的Permission实例;

3、在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的

角色/权限;

4、Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给

ModularRealmAuthorizer 进行循环判断,如果匹配如isPermitted/hasRole会返回true,否

三种授权方法

1.编程式:通过写if/else 授权代码块完成

Subject subject = SecurityUtils.*getSubject*();
if(subject.hasRole("admin")){	
	//有权限
}else{	//无权限}




2. 注解式:通过在执行的Java方法上放置相应的注解完成

@RequiresPermissions("admin:list") public void list() {	
	System.out.println("有权限才能执行此方法");
}

3. JSP 标签:在JSP 页面通过相应的标签完成

<shiro:hasRole name=*"admin"*>执行代码</shiro:hasRole>

shiro.ini

shiro.ini里边的内容相当于在数据库。

#自定义用户信息[users] #账号=密码,角色1,角色2xiaojingge=abc,role1 #配置角色[roles]#角色= 多个角色使用逗号隔开role1 = user:list,user:delete,user:createrole2 = dept:list

权限标识符号规则:资源:操作:实例(中间使用半角:分隔)

user:create:01  表示对用户资源的01实例进行create操作。

user:create:表示对用户资源进行create操作,相当于user:create:*,对所有用户资源实例进行create操作。

 

user:*:01  表示对用户资源实例01进行所有操作。

编写程序代码

package cn.zj.shiro;

import java.util.Arrays;
import java.util.List;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;

public class ShiroTest {

	// 测试使用Shiro的登入登出
	@Test
	public void testLoginLogOut() throws Exception {

		// 1.创建SecurityManager对象,通过读取 shiro.ini配置文件创建
		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");

		// 2.创建SecurityManager安全管理器对象
		SecurityManager securityManager = factory.getInstance();

		// 3.将安全管理器对象设置到当前环境中
		SecurityUtils.setSecurityManager(securityManager);

		// 4.创建认证(通俗讲,就是登陆)令牌用(一般都使用账号密码令牌)
		UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("xiaojingge", "abc");

		// 5.创建主体对象,用于登陆
		Subject subject = SecurityUtils.getSubject();

		// 5.1判断是否认证(登陆),没有认证就开始认证
		boolean isAuthenticated = subject.isAuthenticated();
		System.out.println("是否通过认证 :" + isAuthenticated);
		if (!isAuthenticated) {
			try {
				subject.login(usernamePasswordToken);

				//1. 判断是否有单个角色
				System.out.println(subject.hasRole("role1"));
				//2.判断是否有多个角色
				List<String> roles = Arrays.asList("role1","role2");
				boolean[] results = subject.hasRoles(roles);
				System.out.println("result[0] :"+results[0]);
				System.out.println("result[1] :"+results[1]);
				
				//--------------------------------------
				//2.判断是否有权限
				boolean isPermitted = subject.isPermitted("user:list");
				System.out.println(isPermitted);
				//3.判断是否有多个权限
				boolean isAllPermitted = subject.isPermittedAll("user:list","user:create");
				System.out.println(isAllPermitted);
			} catch (AuthenticationException e) {
				System.out.println("身份验证失败");
			}
		}
	}
}

自定义realm进行授权

在原来自定义的realm中,修改doGetAuthorizationInfo方法。

package cn.zj.shiro.realm;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

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;

public class CustomRealm extends AuthorizingRealm {
	
	
	/*
	 * 认证方法,开发者在方法内部自定认证的规则
	 * 
	 * token :令牌,在 主体login 传递过来的
	 * return AuthenticationInfo
	 * 		返回认证信息
	 * 		如果返回null,认为认证失败
	 * 
	 */
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		/*
		 * 认证思路
		 * 	1.获取 token令牌的身份(账号)
		 *  2.在当前类中注入 UserService,调用serice的根据账号去数据库查询用户方法
		 *  	service调用Mapper/Dao层的根据账号查询用户方法
		 *    2.1 如果没有此用户,返回null,当前认证方法也返回null
		 *    2.1 如果有此用户,把用户的密码和token的凭证(密码)进行匹配
		 *    	2.2.1,匹配不成功,当前认证方法也返回null
		 *    	2.2.1  匹配成功,创建一个AuthenticationInfo 认证信息对象,认证成功
		 */
		//1.获取 token令牌的身份(账号)
		String username = (String) token.getPrincipal();
		/*
			User user = userService.selectByUserName(username);
			if(user !=null){
				//进一步比对面膜
			}
		*/
		
		//模拟数据库中的账号
		List<String> usernames = Arrays.asList("admin","zhangsan","lisi");
		
		if(usernames.contains(username)) {
			//身份(账号)匹配成功,进一步匹配凭证(密码)
			System.out.println("账号匹配成功");
			//String password = user.getPassword();
			String credentials = "abc";
			
			SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,credentials, this.getName());
			return authenticationInfo;
		}
		
		return null;
	}


	/*
	 * 授权方法
	 * 
	 * principals : 身份
	 * 
	 * return 
	 * 	AuthorizationInfo 授权信息对象
	 * 
	 */
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		
		
		Object username = principals.getPrimaryPrincipal();
		System.out.println(username);
		/*
		 * 授权思路
		 * 	1.通过当前认证的身份去数据库里面查询出当前身份对应的角色--》对应的权限
		 * 		注入RoleService
		 * 		Role role = roleService.selectByPrimarykey(roleId); 
		 * 		role.permissionIds = 10,1,13,15,16,17,11,18,19,20,21,12,22,23,24,25
		 * 	    List<String> permissionExpressions = permission.selectExpressionsByIds(权限id数组集合)
		 * 		例如
		 * 			user:lsit
		 * 			user:create 
		 * 			student:list
		 * 			等等
		 *  2. 将当前身份 对应的角色对应的所有权限设置给Shiro 授权信息对象
		 *  3. 程序运行shiro会自动判断当前身份是否有权限
		 */
		//模拟数据库查询权限
		List<String> permissions = new ArrayList<String>();
		//添加
		permissions.add("user:list");
		permissions.add("user:create");
		//创建一个授权信息对象
		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
		//将权限添加到shiro 授权信息对象:不能为空null
		authorizationInfo.addStringPermissions(permissions);
		
		System.out.println("CustomRealm.doGetAuthorizationInfo()");
		
		return authorizationInfo;
	}

	
}

shiro.ini

在shiro.ini中配置自定义的realm,将realm设置到securityManager中。

#自定义配置
[main]
#自定义Realm
#自定义realm名称=全限定名
customRealm=cn.zj.shiro.CustomRealm

#将自定义realm设置给SecurityManager的realm属性(类型Spring的依赖注入)
securityManager.realms=$customRealm

测试程序

package cn.zj.shiro;


import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;

public class ShiroTest {

	// 测试使用Shiro的登入登出
	@Test
	public void testLoginLogOut() throws Exception {

		// 1.创建SecurityManager对象,通过读取 shiro.ini配置文件创建
		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");

		// 2.创建SecurityManager安全管理器对象
		SecurityManager securityManager = factory.getInstance();

		// 3.将安全管理器对象设置到当前环境中
		SecurityUtils.setSecurityManager(securityManager);

		// 4.创建认证(通俗讲,就是登陆)令牌用(一般都使用账号密码令牌)
		UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("xiaojingge", "111111");

		// 5.创建主体对象,用于登陆
		Subject subject = SecurityUtils.getSubject();

		// 5.1判断是否认证(登陆),没有认证就开始认证
		boolean isAuthenticated = subject.isAuthenticated();
		System.out.println("是否通过认证 :" + isAuthenticated);
		if (!isAuthenticated) {
			try {
				subject.login(usernamePasswordToken);

				
				//1.判断是否有权限
				boolean isPermitted = subject.isPermitted("user:list");
				System.out.println(isPermitted);
				//2.判断是否有多个权限
				boolean isAllPermitted = subject.isPermittedAll("user:list","user:create");
				System.out.println(isAllPermitted);
				
				
			} catch (AuthenticationException e) {
				System.out.println("身份验证失败");
			}
		}
	}
}
posted @ 2022-04-07 19:38  站着说话不腰疼  阅读(292)  评论(0)    收藏  举报