shiro权限验证框架

shiro权限验证框架

1.什么是Shiro?

    Shiro 是一个用 Java 语言实现的框架,通过一个简单易用的 API 提供身份验证和授权。使用 Shiro,您就能够为您的应用程序提供安全性而又无需从头编写所有代码。

2.为什么要用Shiro?

    shiro在大多数的企业级系统中,我们一般都是采用角色关联资源,然后对用户指定一些角色,这样用户就拥有了一些url,菜单等资源,登陆后页面即可相应的一些功能,但是这种情况下存在这安全问题,因为用户页面只是没有显示相应的功能菜单,若某个用户知道url地址,直接去访问,系统是没法控制该用户操作的,于是便迎来了权限验证框架这一说。

3.Shiro能够有哪些方式控制权限呢?

   同样shiro有两种配置方式,xml和注解,当然xml相对而言不灵活,只能指定经过认证授权后可以访问哪些页面以及不能访问哪些页面,对于注解就灵活一些了,可以适应于各种情景,
方法上加上
@RequiresAuthentication  (验证用户是否登录)
@RequiresUser  验证用户是否被记忆,user有两种含义:一种是成功登录的(subject.isAuthenticated() 结果为true);另外一种是被记忆的(subject.isRemembered()结果为true)。
@RequiresGuest 验证是否是一个guest的请求
@RequiresRoles 如果subject中有aRoleName角色才可以访问方法someMethod。如果没有这个权限则会抛出异常AuthorizationException。
@RequiresPermissions  要求subject中必须同时含有file:read和write:aFile.txt的权限才能执行方法someMethod()。否则抛出异常AuthorizationException。


4.说说怎样使用Shiro?


先添加 spring-shiro.xml 配置
具体如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd"
	default-lazy-init="true">
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
	<property name="securityManager" ref="securityManager" />
	<property name="loginUrl" value="/shiro/turnlogin.do" />
	<property name="successUrl" value="/shiro/success.do" />
	<property name="unauthorizedUrl" value="/shiro/unauth.do" />
	<property name="filterChainDefinitions">
		<value>
			/shiro/success = authc <!-- authc 表示需要认证才能访问的页面 -->
			/shiro/success = authc, perms[/home]  <!-- perms 表示需要该权限才能访问的页面 -->
		</value>
	</property>
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
	<property name="realm" ref="myShiroRealm"></property>
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

<bean id="myShiroRealm" class="com.hfmx.util.shiro.MyShiroRealm">
	<!-- businessManager 用来实现用户名密码的查询 -->
	<!-- <property name="shiroService" ref="shiroService" />  -->
</bean>
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
      <property name="exceptionMappings">
          <props>
              <!--登录-->
              <prop key="org.apache.shiro.authz.UnauthenticatedException">
                  redirect:/shiro/turnlogin.do
              </prop>
              <!--授权-->
              <prop key="org.apache.shiro.authz.UnauthorizedException">
              	  redirect:/shiro/turnlogin.do
              </prop>
          </props>
      </property>
      <property name="defaultErrorView" value="s/403" />
</bean>
</beans>


在applicationContext中引入该文件
<import resource="classpath*:/spring-shiro.xml" />



在springMVC中加入:
<!-- 开启Shiro的注解,实现对Controller的方法级权限检查(如@RequiresRoles,@RequiresPermissions),需
借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 -->  
<!-- 需要在 sprimg-MVC的配置文件中 -->    
<bean id="controllerAdvisorAutoProxyCreator" 
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-
on="lifecycleBeanPostProcessor"/>
<beanid="controllerAuthorizationAttributeSourceAdvisor"  class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>



上述这样就配置完毕了

我们要新建一个类MyShiroRealm ,继承AuthorizingRealm

重写doGetAuthorizationInfo() //获取授权信息
以及doGetAuthenticationInfo()//获取认证信息
这两个方法

因为公司系统 已经使用了一定时间了,用户登录这块已经很完善就仍然使用原来的,所以这里的doGetAuthenticationInfo方法 可以不做太多的处理,这里也把应有的流程贴出来
	@Override
	public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken at) throws AuthenticationException {
		
		//1. 把AuthenticationToken 转化为 UsernamePasswordToken
			UsernamePasswordToken token = (UsernamePasswordToken) at;
			
		//2. 从UsernamePasswordToken 中获取 username
			String username = token.getUsername();
			char[] password = token.getPassword();
			
		//3. 调用数据库的方法,从数据库中查询 username 对应的用户记录

			
		//4. 若用户不存在,则可以抛出 UnknownAccountException
		
			
		//5. 根据用户信息的情况,决定是否需要抛出其他的AuthenticationException 异常
		
		//6. 根据用户的情况,来构建 AuthenticationInfo 对象并返回
			
		//不带 盐值的 加密 -- 可能存在密码相同时  加密后的密文也是相同的
		return new SimpleAuthenticationInfo(username, new String(password), getName());
	
		//带盐值加密 -- 最安全
		//ByteSource credentialsSalt = ByteSource.Util.bytes(user.getUsername());
		//return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), credentialsSalt, getName());
	}



因为公司的系统 用户登陆后,拥有的资源保存在session中,于是有了这样一个想法,将这些资源再交给shiro框架,让它来限制住用户只能访问自己的资源,那么就使用@RequiresPermissions ("url地址")  注解方式,可以达到最完美的权限控制,这里也贴一下代码:
@Override
	public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
		//1. 从PrincipalCollection 中获取登录用户的信息
		Object principal = pc.getPrimaryPrincipal();
		
		//2. 利用登录用户的信息来获取当前用户的角色或权限(可能需要查询数据库)
		
		
		//3. 创建SimpleAuthorizationInfo,并设置roles属性
		
		
		//4. 返回SimpleAuthorizationInfo对象

		
		//通过从shiro  session值中获取信息
		List<String> urllist = (List<String>) SecurityUtils.getSubject().getSession().getAttribute("url");
		
		if(null!=urllist&&urllist.size()!=0){
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			for (String url : urllist) {
				if(null!=url&&(!(url.trim()).equals(""))){
					info.addStringPermission(url);
				}
			}
			return info;
		}

		
		
		/*// 根据自己系统规则的需要编写获取授权信息,这里只获取了用户对应角色的资源url信息
		String username = (String) pc.fromRealm(getName()).iterator().next();
		
		//指定url访问     通过查找数据库的方式进行
		if (username != null) {
			ArrayList<String> urls = shiroService.getUrlByName(username);
			if(null!=urls&&urls.size()!=0){
				SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
				for (String url : urls) {
					if(null!=url&&(!(url.trim()).equals(""))){
						info.addStringPermission(url);
					}
				}
				return info;
			}
		}*/
		
		return null;
	}


其实注解验证的方式原理很简单,通过doGetAuthorizationInfo(PrincipalCollection pc)方法参数pc可以获得到用户的登录名,通过登录名来查找该用户的url资源,然后将这些url资源 添加至SimpleAuthorizationInfo中(通过info.addStringPermission(url)方法),然后在controller层的方法上加上@RequiresPermissions ("url地址")  注解方式,注意这里的url地址需要与真实的RequestMapping映射地址一致,用户请求了该地址后系统就会自动匹配刚刚添加的SimpleAuthorizationInfo中是否含有该url地址,如果有则通过,反之抛出异常。


那么PrincipalCollection pc 的参数是谁传过来的呢?其实在登录时成功后,就需要进行设置了,

if(!currentUser.isAuthenticated()){
 UsernamePasswordToken token = new UsernamePasswordToken(_userName, password); 
 token.setRememberMe(true);
try{
  currentUser.login(token);   //添加用户信息
}catch(AuthenticationException ac){
System.out.println("登录异常:" + ac.getMessage());
this.writeJson(response, new AjaxMsg(false, "登录异常:" + ac.getMessage()));
   }
}


5. 最后说一下,这几天解决的问题,

doGetAuthorizationInfo()是获取授权的方法,通常我们是通过用户名查询数据库的方式获取所有资源,然后包装成SimpleAuthorizationInfo对象进行返回,显然这种方式不太可取,每次认证都要查询数据库,肯定是不行的,这里shiro提供了缓存技术可以查阅一下,由于该系统是支持集群的,所以这种方式也不适合,而该系统中session交由redis进行管理,所以有个想法,把这些资源放进session,也就是redis,这样获取的时候能够保证高效。最有趣的事来了,
先前测试案例中我是将 资源放到Httpsession中,然后在doGetAuthorizationInfo()方法中获取,而这里获取的是org.apache.shiro.session.Session,需要通过转化才能成为httpsession,但是效果达到了,后来到正式使用的时候,发现获取不到了。

找啊找.........突然发现可以直接把资源放到org.apache.shiro.session.Session中,这样岂不是不用转化了,于是登陆成功后  执行
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute("url", list);	
将url资源添加至org.apache.shiro.session.Session中,

然后在doGetAuthorizationInfo()是获取授权的方法  直接 拿出来进行包装,
		//通过从shiro  session值中获取信息
		List<String> urllist = (List<String>) SecurityUtils.getSubject().getSession().getAttribute("url");
		
		if(null!=urllist&&urllist.size()!=0){
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			for (String url : urllist) {
				if(null!=url&&(!(url.trim()).equals(""))){
					info.addStringPermission(url);
				}
			}
			return info;
		}
问题解决了,先就记录到这儿吧,其实还有进一步的优化。




补充:!!!针对上述 称述有问题的地方特别指出

上述说的shiro是部署在集群系统中的,那么在登录时候直接将url资源放置到org.apache.shiro.session.Session中是不合理的,因为集群下,shiro的session不依懒于任何容器,也不会自动像HttpSession一样通过spring-session自动放置到redis中管理,于是集群下这种方式就行不通了,那么该怎么办呢?

其实 最初的时候已经 做了,那就是登陆成功后 直接 
request.getSession().setAttribute("url", list);   将url资源也直接放置到httpsession中,这样一切都变得简单了,session周期也不用自己去管理了,
然后在doGetAuthorizationInfo()方法中获取刚刚放进去的资源,也就是HttpSession的一个Attribute为‘’url‘’的值

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
List<String> urllist = (List<String>)request.getSession().getAttribute("url");

即可获得HttpSession
于是也就支持了集群和非集群下的使用!


总结一下:大致有三种方式 权限验证时获取 资源值

		//1. 通过从shiro  session  直接获取登录时存放的url资源   (非集群 下 使用)
/*		List<String> urllist = (List<String>) SecurityUtils.getSubject().getSession().getAttribute("url");
		if(null!=urllist&&urllist.size()!=0){
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			for (String url : urllist) {
				if(null!=url&&(!(url.trim()).equals(""))){
					info.addStringPermission(url);
				}
			}
			return info;
		}*/

		
		
		//2. 通过 request的session获取登录时存放的url资源     (集群 非集群 下 通用--效率高)
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		List<String> urllist = (List<String>)request.getSession().getAttribute("url");
		
		if(null!=urllist&&urllist.size()!=0){
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			for (String url : urllist) {
				if(null!=url&&(!(url.trim()).equals(""))){
					info.addStringPermission(url);
					System.err.println("添加资源"+url);
				}
			}
			return info;
		}
		
		
		//3. 根据自己系统规则的需要编写获取授权信息,这里只获取了用户对应角色的资源url信息  (集群 非集群 下 通用--效率低下)
		/*String username = (String) pc.fromRealm(getName()).iterator().next();
		
		//指定url访问     通过查找数据库的方式进行
		if (username != null) {
			ArrayList<String> urls = shiroService.getUrlByName(username);
			if(null!=urls&&urls.size()!=0){
				SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
				for (String url : urls) {
					if(null!=url&&(!(url.trim()).equals(""))){
						info.addStringPermission(url);
					}
				}
				return info;
			}
		}*/


到此 最完美的解决方法也就是 (第二种)资源还是  放在 HttpSession 中 最合理!

--谢谢您的阅读,不足地方请指出!








发布了48 篇原创文章 · 获赞 74 · 访问量 22万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章