@(Shiro)[shiro,安全框架]
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
——参考《百度百科》
从上图可以看出,shiro有4个主要特性,分别是认证(Authentication)、授权(Authorization)、会话管理(Session Management)和密码(Cryptography)服务。
以及6个附加(支持)特性,分别是Web支持(Web Support)、缓存(Caching)、并发(Concurrency)、测试(Testing)支持、以…运行(Run As)、记住我(Remember Me)。
参照开涛的解释,如下:
Authentication
:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization
:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager
:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
Cryptography
:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support
:Web支持,可以非常容易的集成到Web环境;
Caching
:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency
:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing
:提供测试支持;
Run As
:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me
:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
Shiro框架有三个核心组件,分别是:Subject
, SecurityManager
和 Realms
.
Subject
:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。
Subject
代表了当前用户的安全操作,SecurityManager
则管理所有用户的安全操作。
SecurityManager
:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager
来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm
: Realm
充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm
中查找用户及其权限信息。
从这个意义上讲,Realm
实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm
,用于认证和(或)授权。配置多个Realm
是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm
,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm
不能满足需求,你还可以插入代表自定义数据源的自己的Realm
实现。
<!-- 引入shiro框架的依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.2</version>
</dependency>
<!-- 配置Spring整合shiro框架的过滤器,过滤器必须配置在Struts2核心过滤器上面 -->
<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>
<!-- 配置安全管理器对象 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
</bean>
<!-- 配置Shiro提供的一个工厂对象,用于产生过滤器对象 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 配置相关URL -->
<!-- 配置登录URL -->
<property name="loginUrl" value="/login.jsp"/>
<!-- 配置登录成功URL -->
<property name="successUrl" value="/home.jsp"/>
<!-- 配置未授权URL -->
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<!-- 配置URL拦截规则 -->
<property name="filterChainDefinitions">
<value>
/css/** = anon
/js/** = anon
/images/** = anon
/login.jsp* = anon
/userAction_login* = anon
/manager* = perms["manager"]
/* = authc
</value>
</property>
<property name="securityManager" ref="securityManager"/>
</bean>
PS:Bean的ID要和过滤器中名称一致,因为过滤器在初始化时,就会加载与其名称一致的Bean。
package com.service.realm;
import java.util.List;
import javax.annotation.Resource;
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.authc.UsernamePasswordToken;
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.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import com.dao.FunctionDao;
import com.dao.UserDao;
import com.domain.Function;
import com.domain.User;
/**
* 用户Realm
*
* @author Switch
* @data 2016年12月26日
* @version V1.0
*/
public class UserRealm extends AuthorizingRealm {
// 注入用户Dao对象
@Resource(name = "userDao")
private UserDao userDao;
// 注入权限Dao对象
@Resource(name = "functionDao")
private FunctionDao functionDao;
// 授予权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 创建简单授权对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 为用户授权
// 权限列表
List<Function> functions = null;
// 获取当前用户
User user = (User) principals.getPrimaryPrincipal();
if ("admin".equals(user.getUsername())) {
// 超级管理员,获取所有权限
functions = functionDao.findAll();
} else {
// 普通用户,通过用户id获取其对应的权限
functions = functionDao.findByUserId(user.getId());
}
if (functions != null && functions.size() > 0) {
for (Function function : functions) {
// 添加许可
info.addStringPermission(function.getCode());
}
}
return info;
}
// 权限认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 强转为子类,用户名密码token,获取Subject传递过来的数据
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// 获取用户名
String username = userToken.getUsername();
// 在数据库中查询客户
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(User.class);
detachedCriteria.add(Restrictions.eq("username", username));
List<User> userList = userDao.findByCriteria(detachedCriteria);
if (userList != null && userList.size() > 0) {
User user = userList.get(0);
// 获取密码
String password = user.getPassword();
// 简单认证信息对象,由SecurityManager负责对比密码数据
// 参数1:Object principal————一般是认证对象,在这里就是用户对象
// 参数2:Object credentials————证书,也就是负责校验认证的对象
// 参数3:String realmName————realm的名字,随意,但必须唯一
AuthenticationInfo info = new SimpleAuthenticationInfo(user, password, this.getName());
return info;
}
return null;
}
}
<!-- 配置用户Realm Bean -->
<bean id="userRealm" class="com.itheima.bos.service.realm.UserRealm"/>
<!-- 配置安全管理器对象 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 管理Realm -->
<property name="realm" ref="userRealm"/>
</bean>
public String login() throws Exception {
// 获取session中的验证码
String loginCheckCode = (String) ActionContext.getContext().getSession().get("loginCheckCode");
if (StringUtils.isBlank(checkcode) || !loginCheckCode.equals(checkcode)) {
this.addActionError("验证码错误");
return LOGIN;
}
// Shiro认证
// 获取subject,代表当前用户对象,状态为:未认证
Subject subject = SecurityUtils.getSubject();
AuthenticationToken token = new UsernamePasswordToken(model.getUsername(), MD5Utils.md5(model.getPassword()));
try {
// 验证
subject.login(token);
// 从本地线程中获取SimpleAuthenticationInfo放入的对象
User loginUser = (User) subject.getPrincipal();
// 加入Session
ActionContext.getContext().getSession().put("loginUser", loginUser);
return HOME;
} catch (Exception e) {
e.printStackTrace();
this.addActionError("用户名或密码错误!");
return LOGIN;
}
}
Shiro内置了很多默认的拦截器,比如身份验证、授权等相关的。默认拦截器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter
中的枚举拦截器:
默认拦截器名 | 拦截器类 | 说明(括号里的表示默认值) |
---|---|---|
身份验证相关的 | ||
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter | 基于表单的拦截器;如“/**=authc”,如果没有登录会跳到相应的登录页面登录; 主要属性: usernameParam:表单提交的用户名参数名( username ) ; passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe); loginUrl:登录页面地址(/login.jsp); successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储key(shiroLoginFailure); |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter | Basic HTTP身份验证拦截器, 主要属性: applicationName:弹出登录框显示的信息(application); |
logout | org.apache.shiro.web.filter.authc.LogoutFilter | 退出拦截器, 主要属性: redirectUrl:退出成功后重定向的地址(/);示例“/logout=logout” |
user | org.apache.shiro.web.filter.authc.UserFilter | 用户拦截器,用户已经身份验证/记住我登录的都可;示例“/**=user” |
anon | org.apache.shiro.web.filter.authc.AnonymousFilter | 匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤; 示例“/static/**=anon” |
授权相关的 | ||
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter | 角色授权拦截器,验证用户是否拥有所有角色; 主要属性: loginUrl:登录页面地址(/login.jsp); unauthorizedUrl:未授权后重定向的地址; 示例“/admin/**=roles[admin]” |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter | 权限授权拦截器,验证用户是否拥有所有权限; 属性和roles 一样; 示例“/user/**=perms[“user:create”]” |
port | org.apache.shiro.web.filter.authz.PortFilter | 端口拦截器,主要属性:port(80):可以通过的端口;示例“/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80 并重定向到该80 端口,其他路径/参数等都一样 |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter | rest 风格拦截器,自动根据请求方法构建权限字符串 ( GET=read,POST=create,PUT=update,DELETE=delete, HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串; 示例“/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配,isPermittedAll); |
ssl | org.apache.shiro.web.filter.authz.SslFilter | SSL 拦截器,只有请求协议是https 才能通过;否则自动跳转会https端口(443);其他和port拦截器一样; |
其他 | ||
noSessionCreation | org.apache.shiro.web.filter.session.NoSessionCreationFilter | 不创建会话拦截器, 调用subject.getSession(false)不会有什么问题,但是如果subject.getSession(true)将抛出DisabledSessionException异常; |
PS:在配置URL拦截规则的时候,使用了很多默认拦截器。
在了解权限控制之前,先模拟下授权操作,授予manager的权限。
// 授予权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 创建简单授权对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 为用户授权
// TODO : 这里只是模拟授权,一般还需要到数据库中认证
info.addStringPermission("manager");
return info;
}
<!-- 配置URL拦截规则 -->
<property name="filterChainDefinitions">
<value>
/css/** = anon
/js/** = anon
/images/** = anon
/login.jsp* = anon
/userAction_login* = anon
/manager* = perms["manager:*"]
/* = authc
</value>
</property>
<aop:config proxy-target-class="true"/>
<!-- 自动代理 -->
<bean id="defaultAdvisorAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<!-- 强制使用cglib进行代理 -->
<property name="proxyTargetClass" value="true"/>
</bean>
<!-- 配置注解方式权限控制的切面类 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor" />
//调用该方法,需要manager:delete权限
@RequiresPermissions("manager:delete")
public void deleteIds() throws Exception {
}
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%-- 拥有manager:view才会显示 --%>
<shiro:hasPermission name="manager:view">
<input type="text" name="name"/>
</shiro:hasPermission>
public void edit() throws Exception {
// 编程实现权限控制
// 调用Shiro框架提供的工具类,获取本地线程上的Subject对象
Subject subject = SecurityUtils.getSubject();
// 检测权限,无则抛出异常
subject.checkPermission("manager:update");
}
PS:当尚未授予manager权限时,会抛出org.apache.shiro.authz.UnauthorizedException
这个异常,所以注意捕获并处理这个异常。如果使用Struts2
框架,可以采取如下方式捕获并处理:
<package name="basePackage" extends="struts-default" abstract="true">
<!-- 全局视图 -->
<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>
</package>
<!-- 引入ehcache的依赖 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.6</version>
</dependency>
<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"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
</ehcache>
PS:将配置文件ehcache.xml
放在类根目录下。
<!-- 配置安全管理器对象 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 管理Realm -->
<property name="realm" ref="userRealm"/>
<!-- 注入缓存管理器 -->
<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>
———–参考《张开涛,跟我学Shiro》
———–参考《官网文档》
原文:http://blog.csdn.net/q547550831/article/details/54174158