zl程序教程

您现在的位置是:首页 >  前端

当前栏目

Shiro(四):Shiro 多Realm验证和认证策略

认证 策略 验证 shiro realm
2023-09-11 14:20:19 时间

Shiro(三)介绍Shiro 密码的比对与MD5盐值加密,接下来对 多Realm验证和认证策略进行简单的介绍。

存在这样一种场景,我们可能会把安全数据放到不同的数据库,比方说 mysql里有,oracle里也有,mysql里面的加密算法是MD5,oracle里面的加密算法是SHA1。这个时候我们进行用户认证时,就需要同时访问这两个数据库,就需要多个Realm。如果有多个Realm的话,还需要涉及到认证策略的问题。

一、多Realm验证(方式一)

多重认证,主要的类是ModularRealmAuthenticator,他有两个需要配置的属性,一个是Collection(用于存储Realm),另一个是AuthenticationStrategy(用于存储验证的策略 )。

通过查看源码可以看到 ModularRealmAuthenticator.class 中的 doAuthenticate

    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {//一个realm
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {//多个realm
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }

从上面doAuthenticate方法中可以看到:

  • 如果有一个Realm 使用的是
    doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);

  • 如果有多个Realm 使用的是
    doMultiRealmAuthentication(realms, authenticationToken);

所以我们可以配置多个Realm 给到 ModularRealmAuthenticator 这个bean,将ModularRealmAuthenticator 单独配置为一个bean,将这个bean 配置给SecurityManager。

配置流程:

1.添加第二个Realm SecondRealm.java

SecondRealm.java 中的 加密算法 为 SHA1

package com.example.shiro.realms;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;

public class SecondRealm extends AuthenticatingRealm{

	/**
	 * 认证 
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		System.out.println("[SecondRealm] doGetAuthenticationInfo:"+token);
		//1.把AuthenticationToken 转换称 UsernamePasswordToken
		UsernamePasswordToken upToken =(UsernamePasswordToken) token;
		//2.从UsernamePasswordToken中获取用户名
		String username=upToken.getUsername();
		//3.调用数据库方法,从数据库中查询username对应的用户记录
		System.out.println("从数据库中获取username:"+username+"对应的用户信息");
		//4.判断用户是否存在,若不存在,则抛出UnknownAccountException异常
		if("unknown".equals(username)){
			throw new UnknownAccountException("用户不存在!");
		}
		//5.根据用户信息的情况,决定是否需求抛出其他的AutheticationException异常。如用户锁定等
		if("monster".equals(username)){
			throw new LockedAccountException("用户被锁定!");
		}
		//6.根据用户的情况,来构建AuthenticationInfo对像并返回,通常使用的实现类是SimpleAuthenticationInfo
		//以下信息是从数据库获取的
		//1).principal:认证的实体类信息。可以是username,也可以是数据表对应的用户的体类对象
		Object principal=username;
		//2).credentials:密码(数据库获取的用户的密码)
		Object credentials=null;//"fc1709d0a95a6be30bc5926fdb7f22f4";
		if(username.equals("admin")){
			credentials = "ce2f6417c7e1d32c1d81a797ee0b499f87c5de06";//由下面的main方法对应不同的盐值得到
		}else if(username.equals("user")){
			credentials = "073d4c3ae812935f23cb3f2a71943f49e082a718";
		}
		//3).realmName:当前realm对象的name,调用父类的getName()方法即可
		String realmName=getName();
		//4).盐值
		ByteSource credentialsSalt = ByteSource.Util.bytes(username);//盐值 要唯一
		
		//SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(principal, credentials, realmName);
		SimpleAuthenticationInfo info= new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
		return info;
	}


	public static void main(String[] args) {
		String hashAlgorithmName="SHA1";//加密算法(与配置文件中的一致)
		String credentials="123456";//密码
		ByteSource salt=ByteSource.Util.bytes("admin");//盐值
		int hashIterations=1024;//加密次数(与配置文件中的一致)
		System.out.println(new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations));
	}

}

2.修改applicationContext.xml文件

把上面添加的SecondRealm配置到 IOC容器中:在applicationContext.xml文件里配置这个Realm:

2.1配置第二Realm

在这里插入图片描述

2.2需要把两个Realm配置为一个认证器(ModularRealmAuthenticator)

<!-- 配置多个Realm -->
	<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
	    <!-- 多个验证策略 realms -->
	   <property name="realms">
	   	  <list>
	   	    <!-- 这个认证,有一个先后的顺序:从上往下 -->
	   	    <ref bean="jdbcRealm"/>
	   	    <ref bean="secondRealm"/>
	   	  </list>	
	   </property>
	</bean>

2.3将 2.2 的 bean 配置给SecurityManager

在这里插入图片描述
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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- =========================================================
         Shiro Core Components - Not Spring Specific
         ========================================================= -->
    <!-- Shiro's main business-tier object for web-enabled applications
         (use DefaultSecurityManager instead when there is no web environment)-->
   <!-- 
   1.配置securityManager 
   -->
   <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="authenticator" ref="authenticator"/>
    </bean>

    <!-- Let's use some enterprise caching support for better performance.  You can replace this with any enterprise
         caching framework implementation that you like (Terracotta+Ehcache, Coherence, GigaSpaces, etc -->
    <!-- 
     2.配置 cacheManager
       2.1需要加入ehcache的jar和配置文件
     -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <!-- Set a net.sf.ehcache.CacheManager instance here if you already have one.  If not, a new one
             will be creaed with a default config:
             <property name="cacheManager" ref="ehCacheManager"/> -->
        <!-- If you don't have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want
             a specific Ehcache configuration to be used, specify that here.  If you don't, a default
             will be used.:-->
        <!--  -->
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
    </bean>
    
    <!-- 配置多个Realm -->
	<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
	    <!-- 多个验证策略 realms -->
	   <property name="realms">
	   	  <list>
	   	    <!-- 这个认证,有一个先后的顺序:从上往下 -->
	   	    <ref bean="jdbcRealm"/>
	   	    <ref bean="secondRealm"/>
	   	  </list>	
	   </property>
	</bean>
    <!-- Used by the SecurityManager to access security data (users, roles, etc).
         Many other realm implementations can be used too (PropertiesRealm,
         LdapRealm, etc. -->
    <!-- 
      3.配置Realm
        3.1直接配置实现了org.apache.shiro.realm.Realm接口的bean
     -->
    <bean id="jdbcRealm" class="com.example.shiro.realms.ShiroRealm">
      <!-- 配置凭证匹配器 -->
      <!-- 加入了密码匹配器之后,就会默认将前台传递过来的密码自动MD5加密  -->
      <property name="credentialsMatcher">
        <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
          <!-- 加密的算法-->
          <property name="hashAlgorithmName" value="MD5"></property>
          <!-- 加密的次数 -->
          <property name="hashIterations" value="1024"></property>
        </bean>
      </property>
    </bean>
    <!--配置第2个Realm  -->
    <bean id="secondRealm" class="com.example.shiro.realms.SecondRealm">
      <!-- 配置凭证匹配器 -->
      <property name="credentialsMatcher">
        <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
          <!-- 加密的算法-->
          <property name="hashAlgorithmName" value="SHA1"></property>
          <!-- 加密的次数 -->
          <property name="hashIterations" value="1024"></property>
        </bean>
      </property>
    </bean>
    <!-- =========================================================
         Shiro Spring-specific integration
         ========================================================= -->
    <!-- Post processor that automatically invokes init() and destroy() methods
         for Spring-configured Shiro objects so you don't have to
         1) specify an init-method and destroy-method attributes for every bean
            definition and
         2) even know which Shiro objects require these methods to be
            called. -->
    <!-- 
    4.配置 LifecycleBeanPostProcessor。可以自动的来调用配置在Spring IOC 容器中shiro bean的生命周期方法
    -->     
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- Enable Shiro Annotations for Spring-configured beans.  Only run after
         the lifecycleBeanProcessor has run: -->
    <!-- 
    5.启用IOC容器中使用shiro的注解。但是必须在配置了 LifecycleBeanPostProcessor之后才可以使用
    -->     
    <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>


    <!-- Define the Shiro Filter here (as a FactoryBean) instead of directly in web.xml -
         web.xml uses the DelegatingFilterProxy to access this bean.  This allows us
         to wire things with more control as well utilize nice Spring things such as
         PropertiesPlaceholderConfigurer and abstract beans or anything else we might need: -->
    <!-- 
    6.配置ShiroFilter
      6.1 id必须和web.xml文件中配置的DelegatingFilterProxy的<filter-name>一致。
      若不一致,则会抛出:NoSuchBeanDefinitionException。因为Shiro会来 IOC容器中查找和<filter-name>名字对应的filter bean。	
     -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/toLogin"/>
        <property name="successUrl" value="/list"/>
        <property name="unauthorizedUrl" value="/unauthorized"/>
        <!-- 
                                配置哪些页面需要受保护。
                                以及访问这些页面需要的权限
           1)  anon 可以匿名访问
           2)  authc 必须认证(即登录)  之后才可以访问    
           3)  logout:登出
         -->
        <property name="filterChainDefinitions">
            <value>
                /toLogin = anon
                /shiro/login = anon
                /shiro/logout = logout
                
                # everything else requires authentication:
                /** = authc
            </value>
        </property>
    </bean>

</beans>

这样流程就配置完成了!

3.测试

下面启动项目,多处打断点,看下运行效果
(1)在ModularRealmAuthenticator.class的

在这里插入图片描述
处打断点,会看到 Realm的个数。

(2)在UsernamePasswordToken.class 的

在这里插入图片描述
处打断点,会看到断点停两次。
(3)由于两种都使用的HashedCredentialsMatcher 时的两种算法:
在这里插入图片描述
在这里插入图片描述
测试成功:
在这里插入图片描述
从打印日志可以看出,这两个的使用是有先后顺序的,这是因为我们在配置文件中的配置:

 <!-- 配置多个Realm -->
	<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
	    <!-- 多个验证策略 realms -->
	   <property name="realms">
	   	  <list>
	   	    <!-- 这个认证,有一个先后的顺序:从上往下 -->
	   	    <ref bean="jdbcRealm"/>
	   	    <ref bean="secondRealm"/>
	   	  </list>	
	   </property>
	</bean>

二、认证策略

如果有多个Realm,怎样才能认证成功,这就我们所谓的认证策略。

1.概念

(1)当一个应用程序配置了两个或两个以上的Realm 时,ModularRealmAuthenticator 依靠内部的AuthenticationStrategy 组件来判定认证的成功或失败。

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

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

(2)认证策略的另外一项工作就是聚合所有Realm的结果信息封装至一个AuthenticationInfo实例中,并将此信息返回,以此作为Subject的身份信息。

2.认证策略的使用

认证策略主要使用的是 AuthenticationStrategy 接口。
在这里插入图片描述
这个接口有三个实现类(认证策略的实现):

策略意义
AllSuccessfulStrategy所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了
AtLeastOneSuccessfulStrategy(默认)只要有一个Realm验证成功即可,和FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信息
FirstSuccessfulStrategy只要有一个 Realm 验证成功即可只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;

ModularRealmAuthenticator内置的认证策略默认实现是AtLeastOneSuccessfulStrategy 方式,因为这种方式也是被广泛使用的一种认证策略。
在这里插入图片描述

2.1 默认使用

验证下AtLeastOneSuccessfulStrategy:通过源码的方法验证
①为了看效果,修改下 SecondRealm的认证消息的返回值

SimpleAuthenticationInfo info= new SimpleAuthenticationInfo("SecondRealmName", credentials, credentialsSalt, realmName);

②debug方式启动项目,输入 用户名和密码,点击submit,进入如下断点,可以看到 认证策略:
在这里插入图片描述
默认AtLeastOneSuccessfulStrategy策略下,有一个Realm认证成功就可以了。就算另一个Realm认证失败也没关系。

修改SecondRealm的密码,

if(username.equals("admin")){
			credentials = "ce2f6417c7e1d32c1d81a797ee0b499f87c5de06--";//由下面的main方法对应不同的盐值得到
}else if(username.equals("user")){
	credentials = "073d4c3ae812935f23cb3f2a71943f49e082a718--";
}

这样改完之后,第二个Realm就无法匹配成功,但是它也一样可以真正成功。但它仅返回认证成功的那个Realm信息。
输入用户名admin,密码123456,进行验证:
在这里插入图片描述

2.1 切换认证策略

(1)如何切换认证策略?
比如:切换成 AllSuccessfulStrategy 即所有认证策略都通过了,才算认证成功。

答:可以看出 认证策略是ModularRealmAuthenticator 类的一个属性 authenticationStrategy,即在applicationContext.xml中添加配置:

    <!-- 配置多个Realm -->
	<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
	    <!-- 多个验证策略 realms -->
	   <property name="realms">
	   	  <list>
	   	    <!-- 这个认证,有一个先后的顺序:从上往下 -->
	   	    <ref bean="jdbcRealm"/>
	   	    <ref bean="secondRealm"/>
	   	  </list>	
	   </property>
	   <!-- 认证策略 -->
	   <property name="authenticationStrategy">
	     <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean>
	   </property>
	</bean>

即可。

(2)验证AllSuccessfulStrategy:
打断点,debug重启项目,进行验证:发现第二个Realm报异常
在这里插入图片描述
登录失败
在这里插入图片描述

三、多重认证方式二:把realms配置给SecurityManager

修改applicationContext.xml文件:
(1)securityManager 配置中 添加realms属性

   <!-- 
   1.配置securityManager 
   -->
   <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 缓存管理器 -->
        <property name="cacheManager" ref="cacheManager"/>
        <property name="authenticator" ref="authenticator"/>
        <!-- 配置多个Realm -->
        <property name="realms">
	       <list>
		   	 <!-- 这个认证,有一个先后的顺序:从上往下 -->
		   	 <ref bean="jdbcRealm"/>
		   	 <ref bean="secondRealm"/>
		   </list>	
        </property>
        <!-- 验证-->
    </bean>

(2)authenticator bean中去除 realms的属性配置

    <!-- 认证策略 -->
	<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
	   <!-- 认证策略 -->
	   <property name="authenticationStrategy">
	     <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean>
	   </property>
	</bean>

启动项目,运行结果如下:
在这里插入图片描述
使用这种方式的原因:在做授权时,我们是需要从 SecurityManager去读Realms,所以需要把realms配置给SecurityManager,而不是第一种方式。

为什么好用?:实际上认证时调用的是SecurityManager的Realm属性,还是ModularRealmAuthenticator的Realm属性,打断点,查看源码,得出是实际上认证时调用的是SecurityManager的Realm属性。

总结:
多重认证操作的是多个Realm。
多重认证的第一种方式是:在ModularRealmAuthenticator里面可以配置多个Reamls,默认的验证策略是,至少一个满足即可(默认 AtLeastOneSuccessfulStrategy)。
多重认证的第二种方式:将多Realm配置在DefaultWebSecurityManager ,将验证策略和Reaml分开来管理(建议使用这种方式