shiro权限安全验证框架
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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。
@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 配置
具体如下:
具体如下:
<span style="font-size: 18px;"><?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 用来实现用户名密码的查询 --> <span style="font-family: Arial, Helvetica, sans-serif;"><!-- </span><span style="font-family: Arial, Helvetica, sans-serif;"><property name="shiroService" ref="shiroService" /> </span><span style="font-family: Arial, Helvetica, sans-serif;">--></span><span style="font-family: Arial, Helvetica, sans-serif;"> </span></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></span>
在applicationContext中引入该文件
<import resource="classpath*:/spring-shiro.xml" />
在springMVC中加入:
<!-- 开启Shiro的注解,实现对Controller的方法级权限检查(如@RequiresRoles,@RequiresPermissions),需
借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 -->
<!-- 需要在 sprimg-MVC的配置文件中 -->
<span style="font-size: 18px;">
<bean id="controllerAdvisorAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/> <bean id="controllerAuthorizationAttributeSourceAdvisor"
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>
</span>
上述这样就配置完毕了
我们要新建一个类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()));
}
}
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‘’的值
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 中 最合理!
以上是关于shiro权限安全验证框架的主要内容,如果未能解决你的问题,请参考以下文章