Shiro实现多Realm认证、SecurityManager配置realms

认证策略实际上是AuthenticationStrategy这个接口,它有三个实现:

• AuthenticationStrategy 接口的默认实现:

• FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第 一个 Realm 身份验证成功的认证信息,其他的忽略;

• AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和 FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信 息;

• AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有 Realm身份验证成功的认证信息,如果有一个失败就失败了。

• ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy 策略

在实际的业务场景中,会遇到安全数据存储在不同的数据库中的情况(例如两库用户登录同一系统),比如一个是Mysql数据库中的数据,一个是Oracle数据库中的数据,其中Mysql中使用的加密算法是MD5,而Oracle中使用的加密算法是SHA1或其它与Mysql不同的加密算法。此时我们进行用户登录认证的时候,就需要同时访问这两个数据库,也就需要多个Realm。

我们通过查看源码,来看一下Subject的login方法底层是如何使用Realm校验的。在login的深层,会调用ModularRealmAuthenticator的doAuthenticate()方法,该方法如下:

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

可以看到,该方法中通过getRealms()获取Realm集合,如果realm只有一个,走的是doSingleRealmAuthentication方法,如果有多个,走的是doMultiRealmAuthentication方法。所以当我们使用ModularRealmAuthenticator类来配置多个Realm的时候,Shiro会使用我们配置的多个Realm进行认证。

package com.test.shiro.realms;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
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;
import com.test.shiro.po.User;
 
public class ShiroRealm extends AuthenticatingRealm{
	
	private static Map<String,User> userMap = new HashMap<String,User>();
	static{
		//使用Map模拟数据库获取User表信息
		userMap.put("jack", new User("jack","43e66616f8730a08e4bf1663301327b1",false));//密码明文:aaa123
		userMap.put("tom", new User("tom","3abee8ced79e15b9b7ddd43b95f02f95",false));//密码明文:bbb321
		userMap.put("jean", new User("jean","1a287acb0d87baded1e79f4b4c0d4f3e",true));//密码明文:ccc213
	}
 
 
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		System.out.println("[ShiroRealm]");
		//1.把AuthenticationToken转换为UsernamePasswordToken
		UsernamePasswordToken userToken = (UsernamePasswordToken) token;
		
		//2.从UsernamePasswordToken中获取username
		String username = userToken.getUsername();
		
		//3.调用数据库的方法,从数据库中查询Username对应的用户记录
		System.out.println("从数据看中获取UserName为"+username+"所对应的信息。");
		//Map模拟数据库取数据
		User u = userMap.get(username);
		
		//4.若用户不行存在,可以抛出UnknownAccountException
		if(u==null){
			throw new UnknownAccountException("用户不存在");
		}
		
		//5.若用户被锁定,可以抛出LockedAccountException
		if(u.isLocked()){
			throw new LockedAccountException("用户被锁定");
		}
		
		//7.根据用户的情况,来构建AuthenticationInfo对象,通常使用的实现类为SimpleAuthenticationInfo
		//以下信息是从数据库中获取的
		//1)principal:认证的实体信息,可以是username,也可以是数据库表对应的用户的实体对象
		Object principal = u.getUsername();
		//2)credentials:密码
		Object credentials = u.getPassword();
		//3)realmName:当前realm对象的name,调用父类的getName()方法即可
		String realmName = getName();
		//4)credentialsSalt盐值
		ByteSource credentialsSalt = ByteSource.Util.bytes(principal);//使用账号作为盐值
		
		SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal,credentials,realmName);
		info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
		return info;
	}
}

该ShiroRealm使用MD5加盐算法,为密码加密,并且采用MD5策略封装认证信息。

下面我们来进行多Realm认证的编写,首先创建一个名为“SecordRealm”的Realm类,复制之前ShiroRealm的代码,将加密方式改为“SHA1”,新增3个用户名不同的测试账户:

package com.test.shiro.realms;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
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;
import com.test.shiro.po.User;
 
public class SecordRealm extends AuthenticatingRealm{
	
	private static Map<String,User> userMap = new HashMap<String,User>();
	static{
		//使用Map模拟数据库获取User表信息
		userMap.put("jack2", new User("jack2","837b21a5a86ed8df842a4c2114a8b9f7d7c6d02d",false));//密码明文:aaa123
		userMap.put("tom2", new User("tom2","ca578a1c0498fb93b7b0f06e30b2eecd155930db",false));//密码明文:bbb321
		userMap.put("jean2", new User("jean2","d523305baa947918891aaa578d7b195d3122d8d0",true));//密码明文:ccc213
	}
 
 
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		System.out.println("[SecordRealm]");
		//1.把AuthenticationToken转换为UsernamePasswordToken
		UsernamePasswordToken userToken = (UsernamePasswordToken) token;
		
		//2.从UsernamePasswordToken中获取username
		String username = userToken.getUsername();
		
		//3.调用数据库的方法,从数据库中查询Username对应的用户记录
		System.out.println("从数据看中获取UserName为"+username+"所对应的信息。");
		//Map模拟数据库取数据
		User u = userMap.get(username);
		
		//4.若用户不行存在,可以抛出UnknownAccountException
		if(u==null){
			throw new UnknownAccountException("用户不存在");
		}
		
		//5.若用户被锁定,可以抛出LockedAccountException
		if(u.isLocked()){
			throw new LockedAccountException("用户被锁定");
		}
		
		//7.根据用户的情况,来构建AuthenticationInfo对象,通常使用的实现类为SimpleAuthenticationInfo
		//以下信息是从数据库中获取的
		//1)principal:认证的实体信息,可以是username,也可以是数据库表对应的用户的实体对象
		Object principal = u.getUsername();
		//2)credentials:密码
		Object credentials = u.getPassword();
		//3)realmName:当前realm对象的name,调用父类的getName()方法即可
		String realmName = getName();
		//4)credentialsSalt盐值
		ByteSource credentialsSalt = ByteSource.Util.bytes(principal);//使用账号作为盐值
		
		SimpleAuthenticationInfo info = null; 
		info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
		return info;
	}
}

然后将该Realm配置到Spring的IOC容器中,即在Spring的applicationContext.xml配置文件中加入SecordRealm类的Bean配置:

<bean id="secordRealm" class="com.test.shiro.realms.SecordRealm">
	<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>

(方式一:在ModularRealmAuthenticator中指定相应realms为了实现多Realm校验,需要把这两个Realm(之前的ShiroRealm和现在的secordRealm)配置到ModularRealmAuthenticator认证器中:

注意:执行的顺序和list的顺序有关!

<!-- 认证器 -->
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
    <property name="realms">
        <list>
            <ref bean="shiroRealm"/>
            <ref bean="secordRealm"/>
        </list>
    </property>
</bean>
 

(方式二:在DefaultWebSecurityManager中指定相应的realms,这样就不用在ModularRealmAuthenticator指定相应的realms

注意:执行的顺序和list的顺序有关

<!-- 
1. 配置 SecurityManager!
-->    
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="cacheManager" ref="cacheManager"/>
    <property name="authenticator" ref="authenticator"></property>
   
    <property name="realms">
       <list>
         <ref bean="jdbcRealm"/>
         <ref bean="secondRealm"/>
      </list>
    </property>
   
    <property name="rememberMeManager.cookie.maxAge" value="10"></property>
</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>

<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
   <property name="authenticationStrategy">
      <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
   </property>
</bean>

SecurityManager配置realms

在没有使用认证器之前,我们是这样配置Realm的:  

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="cacheManager" ref="cacheManager"/>
    <property name="realm" ref="shiroRealm"/>
</bean>
<bean id="shiroRealm" class="com.test.shiro.realms.ShiroRealm"></bean>

后来使用了多Realm验证时,会将多个Realm配置到认证器中,再将认证器配置给securityManager:

<!--1. 配置 SecurityManager-->     
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="cacheManager" ref="cacheManager"/>
    <property name="authenticator" ref="authenticator"/>
</bean>
<!-- 认证器 -->
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
    <property name="realms">
        <list>
            <ref bean="shiroRealm"/>
            <ref bean="secordRealm"/>
        </list>
    </property>
    <property name="authenticationStrategy">
        <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
    </property>
</bean>

那么,我们如果将authenticator中的realms删除,而重新在securityManager添加两个Realm配置,
我们的工程还能跑起来吗?可以试验一下,首先将配置改为一下配置:

<!--1. 配置 SecurityManager-->     
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="cacheManager" ref="cacheManager"/>
    <property name="authenticator" ref="authenticator"/>
    <property name="realms">
        <list>
            <ref bean="shiroRealm"/>
            <ref bean="secordRealm"/>
        </list>
    </property>
</bean>
<!-- 认证器 -->
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
    <property name="authenticationStrategy">
        <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
    </property>
</bean>

重启Shiro3项目,然后使用jack登录:

发现依然可以进行正常的校验:

那么可以证明这种写法是没有问题的,但是我们要想一下,为什么这样可以呢?可以看到,我们是在securityManager中配置了authenticator的,所以在进行校验的时候是通过authenticator来获取认证信息并且校验的(ModularRealmAuthenticator源码):

而使用的realms集合是authenticator类(ModularRealmAuthenticator)自己的realms属性,而我们在上面的配置中又没有给ModularRealmAuthenticator配置realms参数,而是给securityManager配置了realms属性,那么我们的程序是如何获取两个realm的认证信息的呢?

观察ModularRealmAuthenticator的源码我们可以看到,它除了包含名为realms的集合变量外,还有相应的get和set方法(当然这是IOC必须的),是不是有人在某个时候给它悄悄set了realms属性呢?

我们在AuthenticationSecurityManager中找到了答案:

发现在上面的方法中,只要校验出authenticator的类型属于ModularRealmAuthenticator,则会将SecurityManager的realms属性赋值给对应的authenticator类。所以确实是SecurityManager将自己的realms集合属性给了authenticator,所以authenticator才有realm可以用。

以后我们将使用这种写法来配置realm,因为后面我们讲解授权的时候,需要使用到SecurityManager的realms属性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值