Shiro的介绍(一)

1、什么是Apahce Shiro

Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能:

  • Authentication 认证 - 用户身份识别,常被称为用户“登录”;
  • Authorization 授权 - 访问控制;
  • Cryptography 密码加密 - 保护或隐藏数据防止被偷窥;
  • Session Management 会话管理 - 每个用户会话时效性的状态。

2、Apahce Shiro框架介绍

首先介绍Shiro的三大核心组件:Subject、SecurityManager、Realm,如下图:

Subject:主体,指”当前执行用户“,这个“用户”不一定是人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着”与当前应用交互的任何东西“,当然, 我们一般把它当做”用户“来看。它获取的方式如下:

import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
...
Subject currentUser = SecurityUtils.getSubject();
一旦你获取了主体,你可以在Shiro中对当前用户进行大部分的安全操作,例如登录、注销、访问会话、执行授权检查等等。你可以在代码的任何地方很方便的访问主体,在有需要的地方进行安全操作。

SecurityManager:安全管理器,是Apache Shiro框架的核心,它是典型的Facade模式,Shiro框架通过它来管理所有的安全服务。Subject代表了当前用户的安全操作,而SecurityManager管理了所有用户的安全操作。如何建立SecurityManager呢?这取决于你的应用程序环境,若是web应用程序,通常在web.xml指定Shiro web servlet过滤器;若是独立的应用程序,你需要其他配置方式。在每个应用程序中几乎只有一个SecurityManager实例,它本质上是一个应用程序单例。在Shiro几乎所有的东西,默认SercurtyManager实现POJOs和一些POJO兼容的配置 - normal Java code,Spring XML,YAML,.properties和.ini文件等等。因此,Shiro通过基于文本的INI配置一个默认的“共同点”的解决方案。最简单的INI文件配置如下:

[main]
cm = org.apache.shiro.authc.credential.HashedCredentialsMatcher
cm.hashAlgorithm = SHA-512
cm.hashIterations = 1024
# Base64 encoding (less text):
cm.storedCredentialsHexEncoded = false

iniRealm.credentialsMatcher = $cm

[users]
jdoe = TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJpcyByZWFzb2
asmith = IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbXNoZWQsIG5vdCB
上述看出INI配置文件配置了”[main]“和"[users]"两部分,在这不论述配置的含义,以后再说。

加载INI配置文件的方式:

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.util.Factory;

...

//1. 加载INI配置文件
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");

//2.创建SecurityManager实例
SecurityManager securityManager = factory.getInstance();

//3. 绑定SecurityManager实例到SecurityUtils
SecurityUtils.setSecurityManager(securityManager);

Realm:域,它是Shiro和应用程序的安全数据之间的”桥梁“或”连接器“。也就说,当对用户执行认证或授权的时候,Shiro会从应用程序配置的一个或多个Realm中查找很多信息(认证和权限信息等)。在这个意义上Shiro本质上是一个安全相关的DAO,它封装了数据源的连接细节,并在有需要时把相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm用于认证或(和)授权。可以配置一个或多个Realm,至少需要的一个。Shiro提供了开箱即用的Realms去连接许多安全的数据源(目录),例如:LDAP、关系型数据库(JDBC)和文本配置源(.ini和.properties文件等)等等。如果默认的Realms不能满足你的需求时,你可以插入你自己的Rleam来实现自定义数据源。Realm配置连接LDAP用户数据源如下:

[main]
ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
ldapRealm.userDnTemplate = uid={0},ou=users,dc=mycompany,dc=com
ldapRealm.contextFactory.url = ldap://ldapHost:389
ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5 

除了上述的三个核心组件,接着要论述Shiro其他主要的组件,先看一张Shiro的完整架构图:


从架构图可以看出,Shiro的Security Manager组件除了包含Realms组件,还包含了Authenticator、Authorizer、Session Manager、 Session DAO和Cache Manager组件。Shiro还包含了Cryptography组件。

Authenticator:认证器。认证就是验证用户身份的过程,其实就是用户”登录“的过程。它分为三个步骤,如下:

  1. 收集用户身份信息,也称为principals(主体),并提供身份证明,也称为credentials(凭证)。
  2. 提交主体和凭证到系统。
  3. 如果提供的凭证匹配系统期望的用户身份,则用户身份验证通过,否则反之。

这个过程常见的例子就是账号/密码组合。Shiro认证的示例如下:

//1. 必须提供principals(主体) and credentials(凭证)
AuthenticationToken token = new UsernamePasswordToken(username, password);

//2. 获取当前的Subject实例
Subject currentUser = SecurityUtils.getSubject();

try {
  //3. 登录:
  currentUser.login(token);

  //4. 抛出异常
} catch (IncorrectCredentialsException ice) { …
} catch (LockedAccountException lae) { …
}…
  catch (AuthenticationException ae) {…}

 如此所见,Shiro API很容易反映通用的工作流。当Shiro的登录方法被调用时,SecurityManager将接收AuthenticationToken,并将其分派到一个或多个配置的Realm,允许每个Realm按要求执行认证检查。若登录失败,则抛出AuthenticationException异常。当然你也可以抛出AuthenticationException异常的子类,不过,一般应用程序建议抛出AuthenticationException异常,提示用户“账号/密码错误”,防止被攻击。当然,具体应用程序具体分析。 

Subject登录成功后,虽然它们的身份已经被认证,但是仅仅只是身份认证并不意味着你允许它们在应用程序的任何地方都可以操作。“我如何控制用户是否允许做什么?”,这就是授权。

Authorizer:授权器。授权实质就是访问控制 - 控制你的用户可以访问应用程序的哪些地方,例如资源、网页、按钮等。大多数用户执行访问控制是通过角色和权限这类概念完成的。Shiro也是如此,下面的示例就是主体是否拥有一个特殊的角色:

Subject subject = SecurityUtils.getSubject();
if ( subject.hasRole(“administrator”) ) {
    //show the ‘Create User’ button
} else {
    //grey-out the button?
} 
 如你所见,你的应用程序基于访问控制检查是否启动或禁用功能。 

权限检查是执行授权的另一种方式。对于一个应用程序而言,角色并不是固定的,角色的名称会经常性的变更(增加、修改和删除),若在应用程序中硬编码角色名称,会由于角色名称的变更导致授权问题。而权限是相对固定的,例如“博客编辑页面”,在部署应用程序时,它的权限是固定不变,通常应用程序中权限是绑定在角色下的,当然也有单独绑定用户的。下面的示例重写了角色检查,代替的是角色权限检查:

Subject subject = SecurityUtils.getSubject();
if ( subject.isPermitted(“user:create”) ) {
    //show the ‘Create User’ button
} else {
    //grey-out the button?
} 

Session Manager:会话管理器。Apache Shiro提供了一个独特的安全框架:可在任何应用程序或架构层一致的使用Session API。意味着Shiro为任何的应用程序提供了会话编程模式 - 从小型的后台独立应用程序到大型的集群Web应用程序。Shiro会话的重要好处如下:

  •     它独立于容器之外,例如:会话集群,有很多容器在容错和故障转移都有自己特殊的方式,Tomcat与jetty不同,也与Websphere不同,等等。但是使用Shiro会话,你就拥有独立于容器的集群解决方案。Shiro架构支持可插拔的会话数据存储,例如企业缓存、关系型数据库和NoSQL等等。这就意味着只要你一旦配置会话集群,它就会同样的方式工作,跟部署的环境无关 - Tomcat、Jetty、JEE服务器和单独的应用程序等等。
  •     跨客户端技术共享会话数据。例如:如果用户同时使用Swing桌面客户端和Web应用程序时,它们可以共享会话数据。

在任何的环境中如何访问主体的会话呢?有两种方式,示例如下:

Session session = subject.getSession();
Session session = subject.getSession(boolean create);
如你所见,这些方法和HttpServletRequest API是相同的概念。第一个方法将返回Subject`s现存在的会话,若没有,则先创建新的会话并返回它。第二个方法接收一个布尔型的参数,这个参数用于判断会话不存在时是否创建新的会话。一旦你获得Shiro会话,你几乎可以像使用HttpSession一样进行会话管理。最大的不同是你可以在任何应用程序使用Shiro会话,而不仅仅局限于Web应用程序。请查看会话示例:

Session session = subject.getSession();

session.getAttribute(“key”, someValue);
Date start = session.getStartTimestamp();
Date timestamp = session.getLastAccessTime();
session.setTimeout(millis);
...

Session DAO:会话数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能。

CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能。

Cryptography:加密模块。加密就是隐藏或混淆数据以防止被窥视的过程。加密可以用在Shiro任何的地方。Shiro加密是为了简化jdk加密的。Shiro关注的两个领域是加密哈希(信息摘要)和加密密码。

            哈希:如果你使用JDK的MessageDigest类,就会感觉有点麻烦,看示例:

public static byte[] md5(byte[] data) throws NoSuchAlgorithmException {
    MessageDigest md = MessageDigest.getInstance("MD5");
    md.update(data);
    return resolve(md.digest());
}

private static byte[] resolve(byte[] key) {
    StringBuilder ret = new StringBuilder(key.length<<1);
    for(int i=0;i<key.length;i++){
	ret.append(Character.forDigit((key[i]>>4) & 0xf,16));
  	ret.append(Character.forDigit(key[i] & 0xf,16));
    }
    return ret.toString().getBytes();
}
如你所见,首先你的把文本转为字节数组,然后MessageDigest类的静态工厂方法获取实例,接着加密并抛出异常,接着编码成十六进制字符(不转换显示的是乱码),最后返回字节数组。整个过程比较麻烦。而Shiro在使用哈希就比较简单,看示例:

String hex = new Md5Hash(myFile).toHex();
如你所见,Shiro简化了过程。完成SHA-512和密码的Base64编码也简单,示例:

String encodedPassword = new Sha512Hash(password, salt, count).toBase64();

          密码:加密是使用密钥对数据进行可逆转换的加密算法。如果是你使用过JDK的Cryptography APIs,特别是javax.crypto.Cipher类,同样会感到麻烦,示例如下:

KeyGenerator kgen = KeyGenerator.getInstance("AES");  
kgen.init(128, new SecureRandom(password.getBytes()));  
SecretKey secretKey = kgen.generateKey();  
byte[] enCodeFormat = secretKey.getEncoded();  
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");  
Cipher cipher = Cipher.getInstance("AES");// 创建密码器   
byte[] byteContent = content.getBytes("utf-8");  
cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化   
byte[] result = cipher.doFinal(byteContent);  
return result; // 加密 
如你所见,比较麻烦,我们来看Shiro的,示例如下:

AesCipherService cipherService = new AesCipherService();
cipherService.setKeySize(256);

// create a test key:
byte[] testKey = cipherService.generateNewKey();

// sencrypt a file’s bytes:
byte[] encrypted = cipherService.encrypt(fileBytes, testKey);
较之JDK的Cipher API,Shiro的示例要简单的多。Shiro的CipherService API还有其他好处,如同时支持基于字节数组的加密/解密(称为“块”操作)和基于流的加密/解密(如加密音频或视频)。

3、Web支持

Shiro附带了一个帮助保护Web应用程序的强健的Web支持模块。在Web应用程序加入Shiro非常简单,只需要在web.xml定义Shiro Servlet过滤器。如下:

<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
      <!-- 没有init-param属性就表示从classpath:shiro.ini装入INI配置 --> 
</filter>

<filter-mapping>
     <filter-name>ShiroFilter</filter-name>
     <url-pattern>/*</url-pattern>
</filter-mapping>
上述的配置会读取shiro.ini文件。一旦配置完成,Shiro Filter就会过滤每个请求,并且确保在请求期间特定请求的Subject是可访问的。同时由于它过滤了每个请求,你可以执行安全特定的逻辑以保证只有满足一定标准的请求才被允许通过。(上述是Shiro 1.1的配置,1.2以后的配置有所变更)

URL特定的Filter链:Shiro通过其创新的URL过滤器链功能支持安全特定的过滤规则。先看下shiro.ini配置文件的URL的Filter链示例:

[urls]
/assets/** = anon
/user/signup = anon
/user/** = user
/rpc/rest/** = perms[rpc:invoke], authc
/** = authc
如你所见,Web应用程序可以使用[urls]的INI部分。其中等号的左边值是一个相对上下文的Web应用程序路径,等号的右边值是定义的过滤器链 - 一个逗号分隔的有序的Servlet过滤器列表,它会针对给出的路径进行执行。anon,user,perms,authc等等是Shiro内置的安全相关的特殊过滤器,Shiro提供开箱即用。当然你也可以重写这些过滤器。

JSP标签库:Shiro提供了一个JSP标签库,允许你根据当前的Subject状态控制JSP页面输出。看示例:

<!-- 引入shiro标签库 -->
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
...
<p>Hello
<shiro:user>
    <!-- shiro:principal打印Subject的主体 - 在示例中,就是用户名 -->
    <shiro:principal/>!
</shiro:user>
<shiro:guest>
    <!-- 没有登陆 - 就是guest,显示注册链接  -->
    ! <a href=”register.jsp”>Register today!</a>
</shiro:guest>
</p> 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值