在线办公系统之二 整合Shiro框架
为什么要用Shiro?
什么是shiro
Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
为什么要学shiro
既然shiro将安全认证相关的功能抽取出来组成一个框架,使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。
shiro使用广泛,shiro可以运行在web应用,非web应用,集群分布式应用中越来越多的用户开始使用shiro。
java领域中spring security(原名Acegi)也是一个开源的权限管理框架,但是spring security依赖spring运行,而shiro就相对独立,最主要是因为shiro使用简单、灵活,所以现在越来越多的用户选择shiro。
其实上面两点都比较官方,但是的确,做完这个项目过后,我感觉到了SHIRO的强大,在还没接触类似安全认证框架之前,写个登陆,每次都得做大量的业务操作,假设没有安全性框架,我们就不能做项目了?并不是,为了保证用户的登陆认证的安全性,我们就必须花费大量时间在用户名的唯一性,密码加密以及密码认证逻辑上,虽然这些对熟手的程序员造成不了什么麻烦,但是其实也是时间消耗,效率会降低,所以加入Shiro之后,我们可以撇除这部分的操作,特别是权限授予的时候。所以SHIRO为什么能够在如今的框架内加入,主要是为了给程序员提速,让我们更专注解决业务上的逻辑。当然,他还有如下的优点:
1、作为安全验证框架,使用起来比spring-security方便
2、提供web支持,可以在jsp中通过shiro标签方便的做到细粒度的权限管控
3、可以直接使用annotation对所使用的方法做权限管控,节省代码。
4、可扩展,可插拔。
配置Shiro
那么知道为什么要用Shiro框架之后,我们就要知道怎么用了。
配置文件如下:
- applicationContext-shiro.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 开启aop,对类代理 -->
<aop:config proxy-target-class="true"></aop:config>
<!-- 开启shiro注解支持 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<!-- web.xml中shiro的filter对应的bean -->
<!-- Shiro 的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
<property name="loginUrl" value="/login.action" />
<!-- 认证成功统一跳转到first.action,建议不配置,shiro认证成功自动到上一个请求路径 -->
<property name="successUrl" value="/first.action" />
<!-- 通过unauthorizedUrl指定没有权限操作时跳转页面 -->
<property name="unauthorizedUrl" value="/refuse.jsp" />
<!-- 自定义filter配置 -->
<property name="filters">
<map>
<!-- 将自定义 的FormAuthenticationFilter注入shiroFilter中 -->
<entry key="authc" value-ref="formAuthenticationFilter" />
</map>
</property>
<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
<property name="filterChainDefinitions">
<value>
<!-- 所有的静态资源要匿名访问 -->
/js/**=anon
/bootstrap/**=anon
/static/**=anon
/css/**=anon
<!--授权过滤器 , 建议使用注解授权或在jsp页面使用标签 ,USER字段的,permission都能过,因为权限已经是包含了已经被认证的用户信息-->
/toapply_expense.action=perms[expence:apply]
/myExpenseBill.action=perms[expence:billquery]
/myExpenseTaskList.action=perms[expence:tasklist]
/toapply_process.action=perms[expence:publish]
/processDefinitionList.action=perms[expence:processlist]
/findUserList.action=perms[user:query]
/toAddRole.action=perms[user:create]
/delDeployment.action=perms[expence:remove]
/viewImage.action=perms[expence:viewimage]
/findRoles.action=perms[user:rolelist]
/delDeployment.action=perms[expence:delprocess]
<!-- 验证码jsp文件,匿名访问 -->
/validatecode.jsp=anon
/first.action=user
/index.jsp=user
/welcome.jsp=user
<!-- 退出系统 -->
/logout.action=logout
/**=authc
</value>
</property>
</bean>
<!-- securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm" />
<property name="cacheManager" ref="cacheManager"/>
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
<!-- realm -->
<bean id="customRealm" class="com.activiti.ssm.shiro.CustomRealm">
<!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->
<property name="credentialsMatcher" ref="credentialsMatcher"></property>
</bean>
<!-- 凭证匹配器 -->
<bean id="credentialsMatcher"
class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5" />
<property name="hashIterations" value="2" />
</bean>
<!-- 自定义form认证过虑器 -->
<!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->
<bean id="formAuthenticationFilter" class="com.activiti.ssm.shiro.CustomFormAuthenticationFilter">
<!-- 表单中账号的input名称 -->
<property name="usernameParam" value="username" />
<!-- 表单中密码的input名称 -->
<property name="passwordParam" value="password" />
</bean>
<!-- 缓存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
</bean>
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="rememberMeCookie" />
</bean>
<!-- 记住我cookie -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- rememberMe是cookie的名字 -->
<constructor-arg value="rememberMe" />
<!-- 记住我cookie生效时间30天 -->
<property name="maxAge" value="2592000" />
</bean>
</beans>
当然,一般配置文件都有核心部分,如果用类似Spring的框架理解的话,Shiro也不难理解,里面最核心的就是shiroFilter以及开启securityManager,从命名就可以知道,其实Shiro也是一个很大的拦截器
<property name="securityManager" ref="securityManager" />
<!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
<property name="loginUrl" value="/login.action" />
<!-- 认证成功统一跳转到first.action,建议不配置,shiro认证成功自动到上一个请求路径 -->
<property name="successUrl" value="/first.action" />
<!-- 通过unauthorizedUrl指定没有权限操作时跳转页面 -->
<property name="unauthorizedUrl" value="/refuse.jsp" />
<!-- 自定义filter配置 -->
<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
<property name="filterChainDefinitions">
这几个属性可以说是整个filter中的重中之重,分别对应登陆、成功登陆之后怎么做以及未授权跳转的页面。
当然,securityManager中最重要的就是数据域,数据域负责管理与数据库对接的用户名与密文,用户输入用户名与密码之后,通过用户名进行口令匹配,找到对应的用户,我们也就能从securityUtils中获取主体,进一步获取pricipal,在后台中随时获取对应的用户,这点类似Session的获取。
为什么要加缓存?
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--diskStore:缓存数据持久化的目录 地址 -->
<diskStore path="d:\develop\ehcache" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
对于这里的理解,我们可以类比一下AJAX的异步,其实当我们页面的数据量很大,但是访问频率也很高同时内容变化比较小的时候,我们一般可以采取异步刷新的方法去进行分页输出,而缓存其实他的存在,很大程度上也是为了解决对应的问题,因为Shiro能很好的配合JSP进行精细粒度的权限控制。
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>
<shiro:hasPermission name="item:view">
<a href="${pageContext.request.contextPath }/item/queryItem/${item.id}">商品查看</a>
</shiro:hasPermission>
上述代码就是通过页面的taglib标签库进行Shiro指令的引入,判断具有item:view的权限值的用户才能看到这个标签,否则这个a标签会在页面消失,对于这种方式的权限控制,如果我们在JSP的循环中,多项读出,则会造成卡顿,所以我们采取缓存,其实我上面说类比,并不是类比他们之间的技术,而是类比他们的目的,其实都是为了提高系统的整体性能。
可以参照:
https://blog.csdn.net/he90227/article/details/77976022
认证与授权
package com.activiti.ssm.shiro;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpSession;
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;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import com.activiti.ssm.pojo.ActiveUser;
import com.activiti.ssm.pojo.Employee;
import com.activiti.ssm.pojo.Permission;
import com.activiti.ssm.pojo.TreeMenu;
import com.activiti.ssm.service.SysService;
public class CustomRealm extends AuthorizingRealm{
@Autowired
private SysService sysService;
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName=(String) token.getPrincipal();
//高仿数据库姓名密码
Employee emp=null;
String userpwd_db=null;
List<TreeMenu> menuTree=null;
try {
emp = sysService.findSysUserByUserCode(userName);
if (emp==null) {
return null;
}
userpwd_db=emp.getPassword();
menuTree=sysService.getMenuList();
} catch (Exception e) {
e.printStackTrace();
}
String salt_db=emp.getSalt();
ActiveUser activeUser=new ActiveUser();
activeUser.setUserid(emp.getId());
activeUser.setUsername(emp.getName());
activeUser.setRealname(emp.getRealname());
activeUser.setManagerId(emp.getManagerId());
activeUser.setMenuTree(menuTree);
// 第一个参数:表示认证主体的主体信息(一般是User 对象),第二个参数:数据库需要比对的密码
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(activeUser,userpwd_db,ByteSource.Util.bytes(salt_db), "CustomRealm");
return info;
// 自定义Realm里面的内容,其实就是将自定义域通过依赖的方式注入到安全管理器里面
// 方法1:通过本地的INI文件当做一个数据库文件,然后通过主体读入TOKEN的比对,访问---->ini---->subject.login---->认证结果
// 方法2:通过自定义类,里面进行数据库访问或者自定义一个用户TOKEN,自定义域通过ini写明关系依赖注入到对应的subject中。
// #自定义Realm
// customRealm=com.gec.shiro.realm.CustomRealm
// securityManager.realms=$customRealm
}
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//必须是认证的用户才进行授权
ActiveUser activeUser=(ActiveUser) principal.getPrimaryPrincipal();
List<Permission> permissionList=null;
try {
permissionList=sysService.findRoleandPermissionListByUserId(activeUser.getUsername()).getPermissionList();
} catch (Exception e) {
e.printStackTrace();
}
List<String> list=new ArrayList<String>();
for (Permission sysPermission : permissionList) {
list.add(sysPermission.getPercode());
}
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
//将获取的权限字段放入对应的授权信息中
info.addStringPermissions(list);
return info;
}
}
上述代码就是叙述Shiro的核心,认证以及授权。当然,其实每一个用户Bean里面的信息都未必是必要的,这时候为了保证我们获取到的用户信息Bean是一个我们需要的模板的时候,我们可以自定义一个ActiveUser,不做赘述。
在登陆进来的那一刻,我们就必须在这两个方法做文章,而授权的前提是,这个传过来的principal是必须通过了认证的,然后才能授权,最终传出一个SimpleAuthorizationInfo 标准的授权信息,通过ShiroFilter中的权限字段,控制对应的用户的权限,在ShiroFilter中有的字段我们才能进行访问,否则一并回到
<property name="unauthorizedUrl" value="/refuse.jsp" />
这里面去。
最后,当然还是要到web.xml那里给门卫打声招呼
<!-- shiro的filter -->
<!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 设置true由servlet容器控制filter的生命周期 -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean -->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Tips:
1.其实建议在整合整个在线办公系统的时候,建议是先将Activiti整合进去,毕竟在调试的时候,可以免除一些不必要的麻烦,例如每次都要登陆,部分权限控制等,当然,你也可以屏蔽了功能再去做~!
2./viewImage.action=perms[expence:viewimage]例如这样的权限字段,是不能进行等号换行的,会报错,这部分要小心翼翼处理,分毫不差的进行导入
3.当然,我们在嵌入Shiro的过程中,一定要在开启服务器的时候,不能报错,这个是前提,否则,就证明你的Shiro+SSM中没有配置正确
总结
Shiro其实在权限以及认证方面的确做出了很好的全局控制,整体项目下来,Shiro在控制用户权限、密文认证以及用户授权等提供了明确的规定,让我们对应的用户信息更饱满,控制力度更精细,其次Shiro对于权限控制需要高度的与数据库进行紧密配合,这个是在项目设计阶段的时候就需要根据用户需求进行设计,是否需要用Shiro,对于数据库有比较大的影响。