1.shiro简介
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。
官网:shiro.apache.org
shiro作用:验证用户、对用户执行访问控制、可以使用多个数据库、单点登录功能(SSO)
shiro框架认证流程/原理(划重点)
Application Code:应用程序代码,由开发人员负责开发的(action)
Subject:框架提供的接口,代表当前用户对象(当前的登陆对象)
SecurityManager:框架提供的接口,代表安全管理器对象(核心对象)
Realm:可以开发人员编写,框架也提供一些,类似于DAO,用于访问权限数据(自己书写的校验对象)
2.shiro的使用
1>maven引入shiro依赖
<!-- 引入shiro框架的依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.2</version>
</dependency>
2>在web.xml中配置spring框架提供的用于整合shiro框架的过滤器(位置要在struts2的过滤器前面)
<!-- 配置spring框架提供的用于整合shiro框架的过滤器 -->
<filter>
<filter-name></filter-name><!-- shiroFilter是指定名称,要在applicationContext.xml中配置 -->
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3>在spring中配置bean,id为shiroFilter
<!-- 配置shiro框架过滤工厂bean -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 注入安全管理器对象 -->
<property name="securityManager" ref="securityManager"></property>
<!-- 注入相关页面访问URL -->
<property name="loginUrl" value="/login.jsp"/><!-- 登陆页面 -->
<property name="successUrl" value="/index.jsp"/><!-- 首页 -->
<property name="unauthorizedUrl" value="/unauthorized.jsp"/><!-- 没有权限时提示页面 -->
<!--shiro权限控制方式1:注入URL拦截规则 -->
<property name="filterChainDefinitions">
<value>
/css/** = anon
/js/** = anon
/images/** = anon
/validatecode.jsp* = anon
/login.jsp = anon
/userAction_login.action = anon
/page_base_staff.action = perms["staff-list"]<!--访问此路径时必须要有“staff-list权限” -->
/* = authc
</value>
</property>
</bean>
<!-- 注册安全管理对象 -->
<bean name="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="bosRealm"></property>
</bean>
<!-- 注册realm -->
<bean name="bosRealm" class="com.imwj.bos.realm.BOSRealm"></bean>
框架提供的过滤器
anon:无需认证、authc:认证过、perms:需要指定权限
4>UserAction中的login方法,使用shiro提供的方式
public String login() throws Exception {
//得到session域中的验证码
String validatecode = (String) ServletActionContext.getRequest().getSession().getAttribute("key");
//先判断验证码是否输入正确
if(StringUtils.isNotBlank(checkcode) && validatecode.equals(checkcode)){
//输入的验证码正确
//使用shiro框架提供的方式进行认证
Subject subject = SecurityUtils.getSubject();//获得当前登陆的用户对象,现在的状态未“未认证”
//用户名密码令牌
AuthenticationToken token = new UsernamePasswordToken(model.getUsername(), MD5Utils.md5(model.getPassword()));
try {
subject.login(token)//此处会跳转到我们所创建的realm
} catch (UnknownAccountException e) {
this.addActionError("用户名不存在");
return LOGIN;
} catch (IncorrectCredentialsException e) {
this.addActionError("密码输入错误");
return LOGIN;
}
User user = (User) subject.getPrincipal();
ServletActionContext.getRequest().getSession().setAttribute("loginUser", user);
return HOME;
}else{
//验证码输入不正确
this.addActionError("验证码输入错误");
return LOGIN;
}
}
5>自定义realm,并注入给安全管理器
public class BOSRealm extends AuthorizingRealm{
@Autowired
private IUserDao userDao;
//认证方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken mytoken = (UsernamePasswordToken) token;
String username = mytoken.getUsername();
//根据用户名查询数据库中的密码
User user = userDao.findUsernameByUserName(username);
if(user == null){
//用户名不存在
return null;
}
//如果能够查询到,再由框架比对数据中查询到的密码和页面提交的密码是否一致
AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
return info;
}
//授权方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("staff-list");//授予当前用户“staff-list”权限
//TODO 后期需要修改为根据当前登录用户查询数据库,获取实际对应的权限
return info;
}
}
3.shiro框架提供的四种权限控制方式
URL拦截权限控制(基于过滤器实现)、方法注解权限控制(基于代理技术实现)、页面标签权限控制(标签技术实现)、代码级别权限控制(作了解,基于代理技术)
1>URL拦截权限控制,前面已经实现
<value>
/css/** = anon
/js/** = anon
/images/** = anon
/validatecode.jsp* = anon
/login.jsp = anon
/userAction_login.action = anon
/page_base_staff.action = perms["staff-list"]<!--访问此路径时必须要有“staff-list权限” -->
/* = authc
</value>
2>方法注解控制@RequiresPermissions("staff-delete")
<!-- 开启shiro框架注解支持 -->
<bean id="defaultAdvisorAutoProxyCreator"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<!-- 必须使用cglib方式为Action对象创建代理对象 -->
<property name="proxyTargetClass" value="true"/>
</bean>
<!-- 配置shiro框架提供的切面类,用于创建代理对象 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"/>
<!-- action中 -->
@RequiresPermissions("staff-delete")//执行这个方法需要当前的登陆用户拥有staff-delete的权限
public String deleteBatch(){
权限不足时会抛出异常,所以我们做一个全局异常处理
<!-- 全局结果集配置 -->
<global-results>
<result name="login">/login.jsp</result>
<result name="unauthorized">/unauthorized.jsp</result>
</global-results>
<!-- 全局异常捕获 -->
<global-exception-mappings>
<!-- 权限不足异常 -->
<exception-mapping result="unauthorized" exception="org.apache.shiro.authz.UnauthorizedException"/>
</global-exception-mappings>
3>使用shiro提供的页面标签进行权限控制:<shiro:hasPermission name="staff-delete">
在jsp中引入shiro的标签库
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
将需要权限控制的按钮(比如删除)放置在shiro标签中:js代码也可以
<shiro:hasPermission name="staff-delete"> </shiro:hasPermission>
4>总结:使用shiro框架进行权限控制时,使用前三种权限控制混合的方式来达到权限控制的目的
4.shiro中的授权方法(划重点)
//授权方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//获取当前登录用户对象
User user = (User) SecurityUtils.getSubject().getPrincipal();
//User user2 = (User) principals.getPrimaryPrincipal();
// 根据当前登录用户查询数据库,获取实际对应的权限
List<Function> list = null;
if(user.getUsername().equals("admin")){//超级管理员内置用户,查询所有权限数据
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Function.class);
list = functionDao.findByCriteria(detachedCriteria);
}else{//普通用户,查询对应的权限数据
list = functionDao.findFunctionListByUserId(user.getId());
}
for (Function function : list) {
info.addStringPermission(function.getCode());
}
return info;
}
获取当前登陆用户对象:User user = (User) SecurityUtils.getSubject().getPrincipal();
5.ehcache缓存权限数据(划重点)
ehcache是专门缓存插件,可以缓存Java对象,提高系统性能。
优点:ehcache是shiro的缓存插件,导入ehcache后在授权时只需要授权一次,而不需要重复授权
1>在pom.xml文件引入ehcache的依赖(导入jar包)
<!-- 引入ehcache的依赖 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.6</version>
</dependency>
2>编写ehcache配置文件:ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>
3>在spring配置文件中配置缓存管理器对象,并注入给安全管理对象
<!-- 注册安全管理器对象 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="bosRealm"/>
<!-- 注入缓存管理器 -->
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- 注册缓存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<!-- 注入ehcache的配置文件 -->
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>