我爱编程程序员

Apache Shiro

2017-04-12  本文已影响0人  罗志贇

Apache Shiro

Apache Shiro 是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权,企业会话管理和加密。

Apache Shiro Features

Shiro 把 Shiro 开发团队称为“应用程序的四大基石”——身份验证,授权,会话管理和加密作为其目标。

开始一个应用程序

public static void main(String[] args){
  //1.
  Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
  //2.
  SecurityManager securityManager = factory.getInstance();
  //3.
  SecurityUtils.setSecurityManage(securityManger);
  
  System.exit(0);
}

在几乎所有环境中,都可以通过下面的调用获取当前正在执行的用户:

Subject currentUser = SecurityUtils.getSubject();   

subject指的是:"当前正在执行的用户的特定的安全视图",可以把Subject看成是shiro的"User"概念

subject获取会话

Session session = currentUser.getSession();
session.setAttribute("someKye","aValue");

为已知用户做检查

if(!currentUser.isAuthenticated()){
  UsernamePasswordToken token = new UsernamePasswordToken("lonestarr","vespa");
  token.setRememberMe(true);
  currentUser.login(token);
}

如果登陆失败则可以进行异常捕获

try{
  currentUser.login(token);
}catch(UnknownAccountException u){
  //username wasn't in the system ,show them an error message?
}catch(IncorrectCredentitalsException e){
  //password didn't match ,try again?
}catch(LockedAccountException l){
  //account for that username is locked -can't login.
}

获取登陆的角色

log.info("User ["+currentUser.getPrincipal()+"] logged in successfully.");

测试是否有特定的角色:

if(currentUser.hasRole("luoluo")){
  log.info("May the luoluo be with you ");
}else{
  log.info("hello ,mere mortal.");      
}

判断是否有权限在一个确定类型实体上进行操作:

if(currentUser.isPermitted("lightsaber:weild")){
  log.info("You may use a lightsaber ring. Use it wisely.");
}else{
  log.info("Sorry,lightsaber rings are for schwartz masters only");
}

注销

currentUser.logout();

完整事例:

public class Tutorial{
  private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);
  
  public static void main(String[] args){
    log.info("My First Apache Shiro Application");
    //加载用户信息
    Factory<SecurityManager> factory = new IniSecurityManagerFacotry("classpath:shiro:ini");
    SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    
    //get the currently executing user;
    Subject currentUser = SecurityUtils.getSubject();
    
    //Do some stuff with a session(no need for a web or EJB container!!!)
    Session session = currentUser.getSession();
    session.setAttribute("someKey","aValue");
    String value = (String)session.getAttribute("someKey");
    if(value.equals("aValue")){
      log.info("Retrieved the corrent value!["+value+"]");
    }
    
    //let's login the current user so we can check against and permissions;
    if(!currentUser.isAuthenticated()){
      UsernamePasswordToken token = new UsernamePasswordToken("lonestarr","vespa");
      token.setRememberMe(true);
      try{
        currentUser.login(token);
      }catch(UnknownAccountException u){
        log.info("There is no user with username of "+token.getPrincipal());
      }catch(IncorrentCredentitalsException i){
        log.info("Password for account "+token.getPrincipal()+" was incorrect!");
      }catch(LockedAccountException l){
        
      }
    }
    
    //sya who they are:
    //print their identitfying principal(in this case,a username)
    log.info("User["+currentUser.getPrincipal()+"]logged in successfully.");
    
    //test a role ;
    if(currentUser.hasRole("luoluo")){
      log.info("May the luoluo be with you ");
    }else{
      log.info("hello ,mere mortal");
    }
    
    //test a typed permission(not instance-level)
    if(currentUser.isPermitted("lightsaber:weild")){
      log.info("you may use a lightsaber:weild");
    }else{
      log.info("Sorry , lightsaber rings are for schwartz masters only.");
    }
    
    //log_out!
    currentUser.logout();
    
    System.exit(0);
  }
}

Apache Shiro Architecture

shiro的架构有3个主要的概念:Subject ,SecurityManager 和 Realms

Apache Shiro Configuration

首先需要创建一个SecurityManager

Realm realm = //instantiate or acquire a Realm instance.

SecurityManager securityManger = new DefaultSecurityManager(realm);

//Make the SecurityManager instance available to the entire application via static memory;
SecurityUtils.setSecurityManager(securityManager);

Authenticating Subjects(验证Subjects)

可以大致分为三步:

  1. 收集Subjects提交的Principals(身份)和Credentials(凭证)
  2. 提交Principals(身份)和Credentials(凭证)进行身份验证
  3. 如果提交成功,则允许访问,否则重新进行身份验证

Set1:

UsernamePasswordToken token = new UsernamePasswordToken(username,password);

token.setRememberMe(true);

Step2:

Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);

通过login方法,有效的体现了身份验证

Step3:处理成功或失败

如果验证成功,则调用subject.isAuthenticated()的调用将返回true

验证失败则运行AuthenticationException

try{
  curretnUser.login(token);
}catch(UnknownAccountException uae){
  
}catch(IncorrectCredentialsException ice){
  
}catch(LockedAccountException eas){
  
}catch(ExcessiveAttemptsException eae){
  
}catch(AuthenticationException ae){
  
}

AuthenticationStrategy

当一个应用程序配置了两个或两个以上的 Realm 时,ModularRealmAuthenticator 依靠内部的 AuthenticationStrategy 组件来确定这些认证尝试的成功或失败条件

AuthenticationStrategy 是一个无状态的组件,它在身份验证尝试中被询问 4 次(这 4 次交互所需的任何必要的 状态将被作为方法参数):

  1. 在任何Realm被调用之前被询问
  2. 在一个单独的Realm的getAuthenticationInfo方法被调用之前立即被询问
  3. 在一个单独的Realm的getAuthenticationInfo方法被调用之后立即被询问
  4. 在所有Realm被调用之后被询问

Shiro 有 3 个具体的 AuthenticationStrategy 实现:

AtLeastOneSuccessfulStrategy:如果一个(或更多)Realm 验证成功,则整体的 尝试被认为是成功的。如果没有一个验证成功,则整体尝试失败。

FirstSuccessfulStrategy:只有第一个成功地验证的 Realm 返回的信息将被 使用。所有进一步的 Realm 将被忽略。如果没有 一个验证成功,则整体尝试失败。

AllSucessfulStrategy为了整体的尝试成功,所有配置的 Realm 必须验 证成功。如果没有一个验证成功,则整体尝试失败。

Authorization(授权)

控制谁有权限在应用程序中做什么

在 Shiro 中执行授权可以有 3 种方式:

Role-Based Authorization(基于角色的授权)

Role checks

Subject currentUser = SecurityUtils.getSubject();

if(currentUser.hasRole("administrator")){
  //show the admin button
}else{
  //don't show the button?Grey it out?
}

Role Assertions(角色断言)

Subject currentUser = SecurityUtils.getSubject();
//guarantee that the current user is a bank teller and therefore allowed to open the account:
currentUser.checkRole("bankTeller");
openBankAccount();

通过使用 hasRole*方法的一个好处就是代码可以变得清洁,由于你不需要创建你自己的 AuthorizationException 如果 当前的 Subject 不符合预期条件

Permission-Based Authorization(基于权限的授权)

Permission Checks(权限检查)

1.基于对象

Permission printPermission = new PrinterPermission("laserjet4400h","print");
Subject currentUser = SecurityUtils.getSubject();
if(currentUser.isPermitted(printPermission)){
  //show the Print button
}else{
  //don't show the button?
}

2.基于字符串

Subject currentUser = SecurityUtils.getSubject();
if(currentUser.isPermitted("printer:print:laserjet4400n")){
  //TODO
}else{
  //TODO
}

基于字符串的权限是很有帮助的,由于你不必被迫实现一个接口,而且简单的字符串易于阅读。其缺点是,你不具 备类型安全,如果你需要更为复杂的行为将超出了字符串所能代表的范围,你就得实现你自己的基于权限接口的权 限对象。

Permission Assertions(权限断言)

Subject currentUser = SecurityUtils.getSubject();

Permission p = new AccountPersion("open");
currentUser.checkPermission(p);
openBankAccount();

或者,使用字符串权限:

Subject currentUser = SecurityUtils.getSubject();

currentUser.checkPermission("open");
openBankAccount();

Wildcard Permissions

为了使用易于处理且仍然可读的权限语句,Shiro 提供了强大而直观的语法,我们称之为 WildcardPermission。

一个极其简单的方法是授予用户"queryPrinter"权限。然后你可以检查用户是否具有 queryPrinter 权限通过调用:

subject.isPermitted("queryPrinter");

//上面的语法等同于
subject.isPermitted(new WildcardPermission("queryPrinter"));

Multiple Parts

通配符权限支持多层次或部件(parts)的概念

在该例中,第一部分是权限被操作的领域(打印机),第二部分是被执行的操作(查询)。上面其他的例子将被改 为:

printer:print
printer:manage

Multiple Vaules

printer:print,query

它能够赋予用户 print 和 query 打印机的能力。由于他们被授予了这两个操作,你可以通过调用下面的语句来判断用 户是否有能力查询打印机:

subject.isPermitted("print:query");//返回true

All Values

printer:query,print,manage

简单点:

printer:*

最后,在一个通配符权限字符串中的任何部分使用通配符 token 也是可以的。例如,如果你想对某个用户在所有领 域(不仅仅是打印机)授予"view"权限,你可以这样做:

*:view

Instance-Level Access Control

另一种常见的通配符权限用法是塑造实例级的访问控制列表。在这种情况下,你使用三个部件——第一个是域,第 二个是操作,第三个是被付诸实施的实例。

printer:query:lp7200
printer:print:epsoncolor

第一个定义了查询拥有 ID lp7200 的打印机的行为。第二条权限定义了打印到拥有 ID epsoncolor 的打印机的行为。

如果你授予这些权限给用户,那么他们能够在特定的实例上执行特定的行为。然后你可以在代码中做一个检查:

if(SecurityUtils.getSubject().isPermitted("printer:query:lp7200")){
  //Return the current jobs on printer lp 7200
}

Missing Parts

printer:print

等价于

printer:print:*
printer

等价于

printer:*.*

Apache Shiro Realms

Realm 是一个能够访问应用程序特定的安全数据(如用户、角色及权限)的组件。

Handling supported AuthenticationTokens

若 Realm 支持一个提交的 AuthenticationToken,那么 Authenticator 将会调用该 Realm 的 getAuthenticationInfo(token) 方法。这有效地代表了一个与 Realm 的后备数据源的授权尝试。该方法按以下方法进行:

  1. 为主要的识别信息检查token
  2. 基于principal在数据源中寻找相吻合的账户数据
  3. 确保token支持的credentials匹配那些存储在数据源的
  4. 若credentitals匹配,返回一个封装了Shiro能够理解的账户数据格式的AuthenticationInfo实例
  5. 若credentials不匹配,则抛出AuthenticationException异常

Session Management

Apache Shiro 提供安全框架界独一无二的东西:一个完整的企业级 Session 解决方案,从最简单的命令行及智能手机 应用到最大的集群企业 Web 应用程序。

即使你在一个 Servlet 或 EJB 容器中部署你的应用程序,仍然有令人信服的理由来使用 Shiro 的 Session 支持而不是容 器的。下面是一个 Shiro 的 Session 支持的最可取的功能列表:

使用方式:

Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute("someKey","someValue");

JSP Tag Library

引入:

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

The guest tag

guest 标签将显示它包含的内容,仅当当前的 Subject 被认为是'guest'时。'guest'是指没有身份 ID 的任何 Subject。也 就是说,我们并不知道用户是谁

<shiro:guest>
    Hi there ! Please<a href = "login.jsp">Login</a>or<a href="signup.jsp">Signup</a>today!
</shiro:guest>

The user Tag

user 标签将显示它包含的内容,仅当当前的 Subject 被认为是'user'时。'user'在上下文中被定义为一个已知身份 ID 的 Subject,或是成功通过身份验证及通过'RememberMe'服务的。请注意这个标签在语义上与 authenticated 标签是 不同的,authenticated 标签更为严格。

<shiro:user>
  Welcome back luoluo ! NOt luoluo? Click<a href="login.jsp">here<a>to login.
</shiro:user>  

The authenticated tag

仅仅只当当前用户在当前会话中成功地通过了身份验证 authenticated 标签才会显示包含的内容。它比'user'标签更 为严格。

<shiro:authenticated>
  <a href="updateAccount.jsp">Update your contact information</a>
</shiro:authenticated>

The principal tag

principal 标签将会输出 Subject 的主体(标识属性)或主要的属性。

Hello,<shiro:principal />,how are you today?

等价于

Hello,<%=SecurityUtils.getSubject().getPrincipal().toString() %>,how are you today?

Typed principal

principal 标签默认情况下,假定该 principal 输出的是 subject.getPrincipal()的值。但若你想输出一个不是主要 principal 的值,而是属于另一个 Subject 的 principal collection,你可以通过类型来获取该 principal 并输出该值。

User ID:<principal type="java.lang.Integer" />

等价于

User ID:<%=SecurityUtils.getSubject().getPrincipals().oneByType(Integer.class).toString()%>

Principal property

但如果该 principal(是默认主要的 principal 或是上面的'typed' principal)是一个复杂的对象而不是一个简单的字符串, 而且你希望引用该 principal 上的一个属性该怎么办呢?你可以使用 property 属性来来表示 property 的名称来理解 (必须通过 JavaBeans 兼容的 getter 方法访问)。

Hello,<shiro:principal property="firstName" />,how are you today

等价于

Hello,<%=SecurityUtils.getSubject().getPrincipal().getFirstName().toString() %>,how are you today?

或者结合属性

Hello,<shiro:principal type="com.foo.User" property="firstName" /> ,how are you today?

等价于

Hello ,<%=SecurityUtils.getSubject().getPrincipals.oneByType(com.foo.User.class).getFirstName().toString() %>,how are you today?

The hasRole tag

<shiro:hasRole name="administrator">
  <a href="admin.jsp">Administer the system</a>
</shiro:hasRole>

The hasAnyRole tag

<shiro:hasAnyRole name="developer,project manager , administrator">
    You are either a developer,project manager,or administrater.
</shiro:hasAnyRole>

The hasPermission tag

hasPermission 标签将会显示它所包含的内容,仅当当前 Subject“拥有”(蕴含)特定的权限。也就是说,用户具 有特定的能力。

<shiro:hasPermission name="user:create">
    <a href="createUser.jsp">Create a new User</a>
</shiro:hasPermission>

The lacksPermission tag

lacksPermission 标签将会显示它所包含的内容,仅当当前 Subject 没有拥有(蕴含)特定的权限。也就是说,用户没 有特定的能力。

<shiro:lacksPermission name="user:delete">
    Sorry,you are not allowed to deleted user accounts;
</shiro:lacksPermission>

Understanding Subjects in Apache Shiro

一个 Shiro Subject 实例代表了一个单一应用程序用户的安全状态和操作

这些操作包括:

The Currently Executing Subject

getSubject()方法调用一个独立的应用程序,该应用程序可以返回一个在应用程序特有位置上基于用户数据的 Subject, 在服务器环境中(如,Web 应用程序),它基于与当前线程或传入的请求相关的用户数据上获得 Subject。

Subject currentUser = SecurityUtils.getSubject();

可得的他们的 session:

Session session = currentUser.getSession();
session.setAttribute("someKey","aValue");

可以获取用户信息

log.info("User["+currentUser.getPrincipal()+"]logged in successfully");

可以测试是否有特定的角色

if(currentUser.hasRole("schwartz")){
  log.info(".....");
}else{
  log.info(".....");
}

可以判断否有权限

if(currentUser.isPermitted("...")){
  
}else{
  
}

可以注销

currentUser.logout();

Spring Framework

这里是在 Spring 应用程序中启用应用程序单例 SecurityManager 的最简单的方法:

web.xml

 <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       
    xmlns:p="http://www.springframework.org/schema/p"  
    xmlns:context="http://www.springframework.org/schema/context"   
    xmlns:tx="http://www.springframework.org/schema/tx"  
    xmlns:aop="http://www.springframework.org/schema/aop"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans    
    http://www.springframework.org/schema/beans/spring-beans.xsd    
    http://www.springframework.org/schema/aop    
    http://www.springframework.org/schema/aop/spring-aop.xsd    
    http://www.springframework.org/schema/tx    
    http://www.springframework.org/schema/tx/spring-tx.xsd    
    http://www.springframework.org/schema/context    
    http://www.springframework.org/schema/context/spring-context.xsd">
    
    <description>Shrio的配置文件</description>
    
    <!-- SecurityManager配置 -->
    <!-- 配置Realm域 -->
    <!-- 密码比较器 -->
    <!-- 代理如何生成? 用工厂来生成Shiro的相关过滤器-->
    <!-- 配置缓存:ehcache缓存 -->
    <!-- 安全管理 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->
        <property name="realm" ref="authRealm"/><!-- 引用自定义的realm -->
        <!-- 缓存 -->
        <property name="cacheManager" ref="shiroEhcacheManager"/>
    </bean>

    <!-- 自定义权限认证 -->
    <bean id="authRealm" class="cn.itcast.jk.shiro.AuthRealm">
        <property name="userService" ref="userService"/>
        <!-- 自定义密码加密算法  -->
        <property name="credentialsMatcher" ref="passwordMatcher"/>
    </bean>
    
    <!-- 设置密码加密策略 md5hash -->
    <bean id="passwordMatcher" class="cn.itcast.jk.shiro.CustomCredentialsMatcher"/>

    <!-- filter-name这个名字的值来自于web.xml中filter的名字 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!--登录页面  -->
        <property name="loginUrl" value="/index.jsp"></property>
        <!-- 登录成功后 -->      
        <property name="successUrl" value="/home.action"></property>
        
        
        <property name="unauthorizedUrl" value="/index.jsp"></property>
        
        <property name="filterChainDefinitions">
            <!-- /**代表下面的多级目录也过滤 -->
            <value>
                /index.jsp* = anon
                /home* = anon
                /sysadmin/login/login.jsp* = anon
                /sysadmin/login/logout.jsp* = anon
                /login* = anon
                /logout* = anon
                /components/** = anon
                /css/** = anon
                /images/** = anon
                /js/** = anon
                /make/** = anon
                /skin/** = anon
                /stat/** = anon
                /ufiles/** = anon
                /validator/** = anon
                /resource/** = anon
                /sysadmin/deptAction_* = perms["部门管理"]
                /** = authc
                /*.* = authc
            </value>
        </property>
    </bean>

    <!-- 用户授权/认证信息Cache, 采用EhCache  缓存 -->
    <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
    </bean>

    <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- 生成代理,通过代理进行控制 -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor">
        <property name="proxyTargetClass" value="true"/>
    </bean>
    
    <!-- 安全管理器 -->
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>
</beans>

如果需要设置注解,则要添加如下配置

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" />
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager" />
</bean>
上一篇下一篇

猜你喜欢

热点阅读