Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。
这篇打算记录一个shiro关于登录的认证和授权的入门案例。
一、认证+授权
第一步:导入shiro-all.jar
第二步:在web.xml中配置一个spring用于整合shiro的过滤器
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
第三步:在spring配置文件中配置一个bean,id必须和上面的过滤器名称相同
<!-- 配置一个工厂bean,用于创建shiro框架用到过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 注入安全管理器 -->
<property name="securityManager" ref="securityManager"></property>
<!-- 注入当前系统的登录页面 -->
<property name="loginUrl" value="/login.jsp"/>
<!-- 注入成功页面 -->
<property name="successUrl" value="/index.jsp"/>
<!-- 注入权限不足提示页面 -->
<property name="unauthorizedUrl" value="/unauthorizedUrl.jsp"/>
<!-- 注入URL拦截规则 -->
<property name="filterChainDefinitions">
<value>
/css/** = anon
/images/** = anon
/js/** = anon
/login.jsp* = anon
/validatecode.jsp* = anon
/userAction_login.action = anon
/page_base_staff.action = perms["staff"]
/* = authc
</value>
</property>
</bean>
第四步:在spring配置文件中注册安全管理器,为安全管理器注入realm
<!-- 注册自定义realm -->
<bean id="bosRealm" class="com.zyj.bos.shiro.BOSRealm"></bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 注入上面的realm -->
<property name="realm" ref="bosRealm"/>
</bean>
第五步:自定义一个BOSRealm
public class BOSRealm extends AuthorizingRealm {
@Resource
private IUserDao userDao;
/**
* 认证方法
*/
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();// 从令牌中获得用户名
User user = userDao.findUserByUsername(username);
if (user == null) {
// 用户名不存在
return null;
} else {
// 用户名存在
String password = user.getPassword();// 获得数据库中存储的密码
// 创建简单认证信息对象
/***
* 参数一:签名,程序可以在任意位置获取当前放入的对象
* 参数二:从数据库中查询出的密码
* 参数三:当前realm的名称
*/
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,
password, this.getClass().getSimpleName());
return info;//返回给安全管理器,由安全管理器负责比对数据库中查询出的密码和页面提交的密码
}
}
/**
* 授权方法
*/
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//根据当前登录用户查询其对应的权限,授权
User user = (User) principals.getPrimaryPrincipal();
List<Function> list = null;
if(user.getUsername().equals("admin")){
//当前用户是超级管理员,查询所有权限,为用户授权
list = functionDao.findAll();
}else{
//普通用户,根据用户id查询对应的权限
list = functionDao.findListByUserid(user.getId());
}
for (Function function : list) {
info.addStringPermission(function.getCode());
}
return info;
}
}
第六步:完善UserAction的login方法
public String login(){
//生成的验证码
String key = (String) ServletActionContext.getRequest().getSession().getAttribute("key");
//判断用户输入的验证码是否正确
if(StringUtils.isNotBlank(checkcode) && checkcode.equals(key)){
//验证码正确
//获得当前用户对象
Subject subject = SecurityUtils.getSubject();//状态为“未认证”
String password = model.getPassword();
password = MD5Utils.md5(password);
//构造一个用户名密码令牌
AuthenticationToken token = new UsernamePasswordToken(model.getUsername(), password);
try{
subject.login(token);
}catch (UnknownAccountException e) {
e.printStackTrace();
//设置错误信息
this.addActionError(this.getText("usernamenotfound"));
return "login";
}catch (Exception e) {
e.printStackTrace();
//设置错误信息
this.addActionError(this.getText("loginError"));
return "login";
}
//获取认证信息对象中存储的User对象
User user = (User) subject.getPrincipal();
ServletActionContext.getRequest().getSession().setAttribute("loginUser", user);
return "home";
}else{
//验证码错误,设置错误提示信息,跳转到登录页面
this.addActionError(this.getText("validateCodeError"));
return "login";
}
}
第七步:在自定义Realm中编写授权方法
/**
* 授权方法
*/
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//根据当前登录用户查询其对应的权限,授权
User user = (User) principals.getPrimaryPrincipal();
List<Function> list = null;
if(user.getUsername().equals("admin")){
//当前用户是超级管理员,查询所有权限,为用户授权
list = functionDao.findAll();
}else{
//普通用户,根据用户id查询对应的权限
list = functionDao.findListByUserid(user.getId());
}
for (Function function : list) {
info.addStringPermission(function.getCode());
}
return info;
}
二、shiro提供的权限控制方式
1. URL拦截权限控制
2. 方法注解权限控制
第一步:在spring配置文件中开启shiro的注解支持
<!-- 开启shiro注解 -->
<!-- 自动代理 -->
<bean id="defaultAdvisorAutoProxyCreator"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<!-- 强制使用cglib为Action创建代理对象 -->
<property name="proxyTargetClass" value="true"></property>
</bean>
<!-- 切面类 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"/>
第二步:在Action的方法上使用shiro的注解描述执行当前方法需要具有的权限
/**
* 批量删除功能(逻辑删除)
* @return
*/
@RequiresPermissions(value="staff")//执行当前方法需要具有staff权限
@RequiresRoles(value="manager")//执行当前方法需要拥有manager角色
public String delete(){
staffService.deleteBatch(ids);
return "list";
}
第三步:修改BaseAction的构造方法
第四步:在struts.xml中配置全局异常捕获,统一跳转到权限不足的页面
三、页面标签权限控制
第一步:在jsp页面中引入shiro的标签
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
第二步:使用shiro的标签根据当前用户拥有的权限动态展示页面元素
<shiro:hasPermission name="staff">
{
id : 'button-delete',
text : '作废',
iconCls : 'icon-cancel',
handler : doDelete
},
</shiro:hasPermission>
四、使用ehcache缓存权限数据
第一步:导入ehcache的jar包到项目中,com.springsource.net.sf.ehcache-1.6.2.jar
第二步:提供ehcache的xml配置文件(可以从jar包中获得)
<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>
第三步:在spring配置文件中注册一个缓存管理器,并注入给安全管理器