shiro权限管理基本原理和实现的整理

shiro权限管理基本原理和实现的整理


引言:这两天学习了一个对权限管理的新的框架shiro,在这里做一个总结,既为了帮助有需要的人,也方便自己以后来回顾。
本篇文章主要针对下面几个关键点来说明:
1. shiro简介
2. 集成spring,快速搭建环境
3. shiro认证(即登录,重点)
4. shiro授权(重点)
5. shiro会话管理(Session)
6. shiro缓存(remember Me)

下面就让我来根据这六点,详细的说明一下shiro的基础原理和操作实现。
一、shiro简介
(1)什么是shiro?
Apache Shiro 是 Java 的一个安全(权限)框架。
• Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在
JavaSE 环境,也可以用在 JavaEE 环境。
• Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、 缓存
等。
• 下载: 下载地址
shiro的架构:
在这里插入图片描述
二.shiro集成spring所做的准备工作,本次的案例是集成SpringMvc,一些基本的配置已经忽略。

1.导入jar包
  2.在web.xml里面加入shiro的过滤器

	<!-- 
	1. 配置  Shiro 的 shiroFilter.  
	2. DelegatingFilterProxy 实际上是 Filter 的一个代理对象. 默认情况下, Spring 会到 IOC 容器中查找和 
	<filter-name> 对应的 filter bean. 也可以通过 targetBeanName 的初始化参数来配置 filter bean 的 id. 
	-->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

3.创建二个shiro的配置文件applicationContext.xml和ehcache.xml

在applicationContext.xml配置文件中,我们需要加入这么几样东西:
1) 配置 SecurityManager!

 <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="authenticator" ref="authenticator"></property>
        
        <property name="realms">
        	<list>
    			<ref bean="jdbcRealm"/>
    			<ref bean="secondRealm"/>
    		</list>
        </property>
        
       <!--  <property name="rememberMeManager.cookie.maxAge" value="10"></property> -->
    </bean>

2)配置 CacheManager.

<--需要加入 ehcache 的 jar 包及配置文件. 
    -->     
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <!-- Set a net.sf.ehcache.CacheManager instance here if you already have one.  If not, a new one
             will be creaed with a default config:
             <property name="cacheManager" ref="ehCacheManager"/> -->
        <!-- If you don't have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want
             a specific Ehcache configuration to be used, specify that here.  If you don't, a default
             will be used.: -->
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> 
    </bean>

3)配置 Realm

  <!-- 
    	3. 配置 Realm 
    	3.1 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean
    -->     
    <bean id="jdbcRealm" class="com.atguigu.shiro.realms.ShiroRealm">
    	<property name="credentialsMatcher">
    		<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    			<property name="hashAlgorithmName" value="MD5"></property>
    			<property name="hashIterations" value="1024"></property>
    		</bean>
    	</property>
    </bean>
    
   <bean id="secondRealm" class="com.atguigu.shiro.realms.SecondRealm">
    	<property name="credentialsMatcher">
    		<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    			<property name="hashAlgorithmName" value="SHA1"></property>
    			<property name="hashIterations" value="1024"></property>
    		</bean>
    	</property>
    </bean>

4)配置 LifecycleBeanPostProcessor

 4. 配置 LifecycleBeanPostProcessor. 可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法. 
    -->       
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- Enable Shiro Annotations for Spring-configured beans.  Only run after
         the lifecycleBeanProcessor has run: -->

5)启用 IOC 容器中使用 shiro 的注解.

  <!--  
    5. 启用 IOC 容器中使用 shiro 的注解. 但必须在配置了 LifecycleBeanPostProcessor 之后才可以使用. 
    -->     
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

6)配置 ShiroFilter.

6.1 id 必须和 web.xml 文件中配置的 DelegatingFilterProxy 的 <filter-name> 一致.
                      若不一致, 则会抛出: NoSuchBeanDefinitionException. 因为 Shiro 会来 IOC 容器中查找和 <filter-name> 名字对应的 filter bean.
    -->     
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login.jsp"/>
        <property name="successUrl" value="/list.jsp"/>
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
        
       <!-- <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>  -->
        
        <!--  
        	配置哪些页面需要受保护. 
        	以及访问这些页面需要的权限. 
        	1). anon 可以被匿名访问
        	2). authc 必须认证(即登录)后才可能访问的页面. 
        	3). logout 登出.
        	4). roles 角色过滤器
        -->
      <property name = "filterChainDefinitions">
        	<value>
        		/login.jsp = anon
        		/shiro/login = anon
        		/shiro/logout = logout
        		
        		/user.jsp = roles[user]
        		/admin.jsp = roles[admin]
        		#everything else requires authentication
        		/** = authc
        	</value>
        </property>

    </bean>

---------》配置完了applicationContext.xml 我们接下来配置ehcache.xml
ehcache.xml里面配置的都是接下来我们要说的关于缓存的一些信息,暂且可以忽略。

<ehcache>


    <diskStore path="java.io.tmpdir"/>
    <!-- 缓存策略 -->
    <cache name="authorizationCache"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

    <cache name="authenticationCache"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

    <cache name="shiro-activeSessionCache"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />

  
    <cache name="sampleCache1"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />

    <cache name="sampleCache2"
        maxElementsInMemory="1000"
        eternal="true"
        timeToIdleSeconds="0"
        timeToLiveSeconds="0"
        overflowToDisk="false"
        /> -->

  

</ehcache>

4.代码实现。
这里我将用几个简单的逻辑类和jsp页面来完成对登录授权访问的验证,为了方便,我先把全部代码粘上,后面一一解释。
1)创建两个Realm实现类 继承AuthorizingRealm

public class ShiroRealm extends AuthorizingRealm {

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		System.out.println("[FirstReaml] doGetAuthenticationInfo");
		//1. 将AuthenticationToken  强制转换成UsernamePasswordToken
		UsernamePasswordToken uptoken =(UsernamePasswordToken)token;
	
		//2. 从UsernamePasswordToken 中来获取username
		String username = uptoken.getUsername();
		//3. 调用数据库中的方法,来查询username对应的记录
		System.out.println("从数据库中获取"+username+"所对应的信息");
		//4. 若用户不存在,则抛出异常
		if("unknow".equals(username)) {
			throw new 	UnknownAccountException("用户不存在!");
		}
		//5 根据用户信息的情况,决定是否需要抛出AuthenticationException的其他异常
		if("monster".equals(username)){
			throw new LockedAccountException("用户已被锁定");
		}
		//6. 根据用户情况,构建AuthenticationInfo 对象并且返回
		//下面对象的三个参数是从数据库中获取的
		//1.principal:认证的实体信息,可以是username,也可以是数据表对应的用户实体对象
		Object  principal = username;
		//2. credentials:密码
		Object credentials =null;
		if("admin".equals(username)) {
			 credentials ="038bdaf98f2037b31f1e75b5b4c9b26e";
		}else if("user".equals(username)) {
			 credentials ="098d2c478e9c11555ce2823231e02ec1";
		}
		
		//3.realmName:当前realm对象的name,调用父类的getName()方法即可
		String realmName = getName();
		//4.盐值
		ByteSource credentialsSalt = ByteSource.Util.bytes(username);
		//SimpleAuthenticationInfo info =new SimpleAuthenticationInfo(principal, credentials, realmName);
		SimpleAuthenticationInfo info =new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
		return info;
	}
}
public class SecondRealm extends AuthenticatingRealm {

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		System.out.println("[SecondReaml] doGetAuthenticationInfo");
		
		//1. 把 AuthenticationToken 转换为 UsernamePasswordToken 
		UsernamePasswordToken upToken = (UsernamePasswordToken) token;
		
		//2. 从 UsernamePasswordToken 中来获取 username
		String username = upToken.getUsername();
		
		//3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
		System.out.println("从数据库中获取 username: " + username + " 所对应的用户信息.");
		
		//4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
		if("unknown".equals(username)){
			throw new UnknownAccountException("用户不存在!");
		}
		
		//5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常. 
		if("monster".equals(username)){
			throw new LockedAccountException("用户被锁定");
		}
		
		//6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
		//以下信息是从数据库中获取的.
		//1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象. 
		Object principal = username;
		//2). credentials: 密码. 
		Object credentials = null; //"fc1709d0a95a6be30bc5926fdb7f22f4";
		if("admin".equals(username)){
			credentials = "ce2f6417c7e1d32c1d81a797ee0b499f87c5de06";
		}else if("user".equals(username)){
			credentials = "073d4c3ae812935f23cb3f2a71943f49e082a718";
		}
		
		//3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可
		String realmName = getName();
		//4). 盐值. 
		ByteSource credentialsSalt = ByteSource.Util.bytes(username);
		
		SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal, credentials, realmName);
		info = new SimpleAuthenticationInfo("secondRealmName", credentials, credentialsSalt, realmName);
		return info;
	}
}

2)创建ShiroHandler,来处理我们要进行的逻辑

@Controller
@RequestMapping("/shiro/")
public class ShiroHandler {
	@Autowired
	private ShiroService shiroService;
	@RequestMapping("/testShiroannotation")
	public String testShiroannotation(HttpSession session) {
		shiroService.testMethod();
		session.setAttribute("key","value13245");
		return "redirect:/list.jsp";
	}
	
	@RequestMapping("login")
	public String login(@RequestParam("username")String username,@RequestParam("password")String password) {
		System.out.println(password);
		Subject currentUser = SecurityUtils.getSubject();
		if(!currentUser.isAuthenticated()) {
			UsernamePasswordToken token = new UsernamePasswordToken(username,password);
			token.setRememberMe(true);
			try {
				//执行登录
				currentUser.login(token);
			} catch (Exception e) {
				System.out.println("登录失败:"+e.getMessage());
			}
		}
		return "redirect:/list.jsp";
	}
}

3)创建业务逻辑层service

public class ShiroService {
	@RequiresRoles(value={"admin"})
	public void testMethod() {
		System.out.println("test service time:"+new Date());
		
		Session session =SecurityUtils.getSubject().getSession();
		Object  val = session.getAttribute("key");
		System.out.println("Service SessionVal:"+val);
	}
}

4)创建几个简单的网页,来实现登录、跳转、分配权限
admin.jsp模拟管理员才能访问的页面

<body>
Admin Page
</body>

user.jsp模拟普通用户访问的页面

<body>
User Page
</body>

login.jsp模拟登录页面

<body>
Login Page

<form action="shiro/login" method="post">
	username:<input type="text" name="username">
	<br><br>
	password: <input type="password" name = "password">
	<br><br>
	<input type="submit"  value="Submit">
</form>
</body>

unauthorized.jsp用于跳转到没有授权的页面

<body>
Unauthorized Page
</body>

list.jsp用来模拟登录成功跳转页面

<body>
<h4>List Page</h4> </br>

WelCome:<shiro:principal></shiro:principal>

	<shiro:hasRole name="admin">
		</br>
		<a href="admin.jsp">Admin Page</a></br>
	</shiro:hasRole>
	<shiro:hasRole name="user">
		</br>
		<a href="user.jsp">User Page</a></br>
	</shiro:hasRole>
	</br>
	<a href="shiro/testShiroannotation">TestShiroannotation</a>
	</br>
	<a href="shiro/logout">Logout</a>
</body>

三、认证(因为上面代码都粘上了,所以我这里讲解的时候就截取关键部分)
 shiro的认证,说白了就是登录,我们在handler类里面有login方法
图解:

在这里插入图片描述
角色:
 • 身份验证:一般需要提供如身份 ID 等一些标识信息来表明登录者的身
 份,如提供 email,用户名/密码来证明。
 • 在 shiro 中,用户需要提供 principals (身份)和 credentials(证
 明)给 shiro,从而应用能验证用户身份:
 • principals:身份,即主体的标识属性,可以是任何属性,如用户名、
 邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个
 Primary principals,一般是用户名/邮箱/手机号。
 • credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证
 书等。
 • 最常见的 principals 和 credentials 组合就是用户名/密码了
流程:
 1.通过login.jsp页面收集到用户输入的username和password
 2.首先handler调用 Subject.login(token) 进行登录,其会自动委托给SecurityManager
 3. SecurityManager 负责真正的身份验证逻辑;它会委托给Authenticator 进行身份验证;
 4.Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
 5.Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用AuthenticationStrategy 进行多 Realm 身份验证;
 6. Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处
可以配置多个Realm,将按照相应的顺序及策略进行访问。
 7.创建Realm类接收handler传来的token,完成对用户名和密码的校验(Realm类上面代码已经创建)
四、授权
Shiro 支持三种方式的授权:
 1– 编程式:通过写if/else 授权代码块完成
 2– 注解式:通过在执行的Java方法上放置相应的注解完成,没有权限将抛出相应的异常
 3– JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成
授权的底层原理实现:
 • 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,否则返回false表示 授权失败。
两张验证有关的表格:
在这里插入图片描述

在这里插入图片描述

(1)编程式的实验在上面realm类中代码已经体现,下面我截取出主要部分。

		Object credentials =null;
		if("admin".equals(username)) {
			 credentials ="038bdaf98f2037b31f1e75b5b4c9b26e";
		}else if("user".equals(username)) {
			 credentials ="098d2c478e9c11555ce2823231e02ec1";
		}

(2)注解的方式实现,可以在service的相关方法上面写注解,也可以直接在handler或者controller类上面写注解。注意:当service类上面有@trationcal事务注解时,不能在service上写注解,只能在controller类上写注解。
注解的话有下面几种,我们选择其中一个来举例:
 在这里插入图片描述
我们可以为一个service中的某个方法写权限注解,来限制某个用户是否能访问某个方法,从而实现限制访问某个页面的目的。
在这里插入图片描述
同样,注解的方式也能在controller层使用
在这里插入图片描述
3)通过shiro标签在jsp页面进行授权
 这里通过list.jsp页面来进行页面访问的授权
 我们可以看到,当角色的身份谁是admin时,可以访问admin的页面,是user时,可以访问user的页面,但是由于我们前面代码给的权限,admin可以同时访问这两个页面,但是user就只能访问user的页面而不能访问admin的页面,剩下的没有给授权限制的页面,两种角色都能访问。在这里插入图片描述
 五、会话管理
  概述:Shiro 提供了完整的企业级会话管理功能,不依赖于底层容器(如web容器tomcat),不管 JavaSE 还是 JavaEE 环境都可以 使用,提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对Web 的透明支持、 SSO 单点登录的支持 等特性。
  shiro对实现session的增删改查操作的主要接口是sessionDao。
  在这里插入图片描述
  如果我们需要对session实现增删改查操作的话,那么我们需要增加如下配置,并且创建SessionDao的类在这里插入图片描述
  如果不需要实现对session的操作,那么我们来简单示范一下:
  (1)我们在controller中对用HttpSession对session添加key value。

	@RequestMapping("/testShiroannotation")
	public String testShiroannotation(HttpSession session) {
		shiroService.testMethod();
		session.setAttribute("key","value13245");
		return "redirect:/list.jsp";
	}
	(2)我们在service中用shiro的session来获取到我们前面存的键值对
		
		Session session =SecurityUtils.getSubject().getSession();
		Object  val = session.getAttribute("key");
		System.out.println("Service SessionVal:"+val);

六、shiro缓存
 
 (1)缓存的话分两种缓存:
 1.Realm 缓存
• Shiro 提供了 CachingRealm,其实现了CacheManagerAware 接口,提供了缓存的一些基础实现;
• AuthenticatingRealm 及 AuthorizingRealm 也分别提供了对AuthenticationInfo 和 AuthorizationInfo 信息的缓
存。
  2.Session 缓存
• 如 SecurityManager 实现了 SessionSecurityManager,其会判断 SessionManager 是否实现了CacheManagerAware 接口,如果实现了会把CacheManager 设置给它。
• SessionManager 也会判断相应的 SessionDAO(如继承自CachingSessionDAO)是否实现了CacheManagerAware,如果实现了会把 CacheManager设置给它
• 设置了缓存的 SessionManager,查询时会先查缓存,如果找不到才查数据库。
我们常用的缓存就是rememberMe
(2)rememberMe
概述:
• Shiro 提供了记住我(RememberMe)的功能,比如访问如淘宝等一些网站时,关闭了浏览器,下次再打开时还是能记住你是谁,下次访问时无需再登录即可访问,基本流程如下:
  • 1、首先在登录页面选中 RememberMe 然后登录成功;如果是浏览器登录,一般会把 RememberMe 的Cookie 写到客户端并
  保存下来;
  • 2、关闭浏览器再重新打开;会发现浏览器还是记住你的;
  • 3、访问一般的网页服务器端还是知道你是谁,且能正常访问;
  • 4、但是比如我们访问淘宝时,如果要查看我的订单或进行支付时,此时还是需要再进行身份认证的,以确保当前用户还是你。
  这个比较简单,我们只需要在controller中添加如下代码:

if(!currentUser.isAuthenticated()) {
			UsernamePasswordToken token = new UsernamePasswordToken(username,password);
			token.setRememberMe(true);

我们还可以在配置文件中设置时长:
在applicationContext.xml中的securityManager bean中添加如下代码,value就是代表生效的时长,以秒为单位。

<property name="rememberMeManager.cookie.maxAge" value="10"></property>

以上就是我对shiro基础的一些介绍,因为还没有时间深入研究,讲的都比较浅显,等后面掌握之后还会更新内容。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值