shiro源码第一天:登陆验证部分

登陆验证部分:

  • 1.单点登陆系统中不同系统密码验证方式不一样

1.单点登陆系统中不同系统密码验证方式不一样

问题:当使用shiro作为鉴权框架时,首先用到的应该就是登陆认证了,登录成功后的操作再根据用户权限来判定。

如果只是单系统用户,直接定义一个Realm,来进行用户的登录和鉴权。登录直接对用户密码的验证,验证成功则告诉shiro,然后放行。

如果用在单点登陆系统中,则一个简单的登录验证就有可能会比较麻烦。因为不同系统用户不同,并且密码的加密验证方式也有可能不同,如果用同一个验证规则对用户的登录来源进行判断,就需要在同一个接口里面进行各种if else判断,这样的方式可能不太优雅。既然已经用了shiro框架了,那么就来看下使用shiro怎么优雅的解决这个问题吧。

既然这时一个Realm则可能不能满足登陆验证的需求了。那么能不能定义多个Realm,然后动态的让对应的Realm去处理对应的验证机制,这样代码的入侵性就比较小了,当增加一种新的系统,我们直接添加对应的Realm即可,不会影响到别的。

解决方案:可以自定义多个Realm,然后将realm都传入SecurityManager
MyRealm1.java

package com.lin.test.chapter2.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.realm.Realm;
import org.slf4j.LoggerFactory;

public class MyRealm1 implements Realm {

	org.slf4j.Logger log = LoggerFactory.getLogger(getClass());
	
	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return "myRealm1";
	}

	@Override
	public boolean supports(AuthenticationToken token) {
		// 仅支持UsernamePasswordToken类型的Token  
		return token instanceof UsernamePasswordToken;
	}

	@Override
	public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		String username = (String) token.getPrincipal(); //得到用户名
		String password = new String((char[])token.getCredentials());//得到密码  
		if (!"zhang".equals(username)) {
			throw new UnknownAccountException(" UnknownAccountException Msg");//如果用户名错误  
		}
		if (!"123".equals(password)) {
			throw new IncorrectCredentialsException("IncorrectCredentialsException Msg");//如果密码错误
		}
		//如果身份认证验证成功,返回一个AuthenticationInfo实现;
		log.info("wang 123 login success");
		return new SimpleAuthenticationInfo(username, password, getName());
	}

}

MyRealm2.java

package com.lin.test.chapter2.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.realm.Realm;
import org.slf4j.LoggerFactory;

public class MyRealm2 implements Realm {

	org.slf4j.Logger log = LoggerFactory.getLogger(getClass());
	
	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return "myRealm2";
	}

	@Override
	public boolean supports(AuthenticationToken token) {
		// 仅支持UsernamePasswordToken类型的Token  
		return token instanceof UsernamePasswordToken;
	}

	@Override
	public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		String username = (String) token.getPrincipal(); //得到用户名
		String password = new String((char[])token.getCredentials());//得到密码  
		if (!"wang".equals(username)) {
			throw new UnknownAccountException();//如果用户名错误  
		}
		if (!"123".equals(password)) {
			throw new IncorrectCredentialsException();//如果密码错误
		}
		//如果身份认证验证成功,返回一个AuthenticationInfo实现;
		log.info("zhang 123 login success");
		return new SimpleAuthenticationInfo(username, password, getName());
	}

}

  以上demo中,自定义 Realm直接继承了Realm类,在使用的时候我们 一般继承 AuthorizingRealm 就可以了,我们可以看下该类的继承关系:其继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现),最顶端的父类还是Realm。这里自定义之后要想让shiro知道我们自定义的,还必须将自定义的Realm传入SecurityManager中才会生效。

  如果定义了多个 realm 则 shiro 会根据 securityManager 中设置的顺序 来以此进行 realm 的验证。 每个realm验证成功后返回一个 SimpleAuthenticationInfo(username, password, getName()) 对象,getName方法可在realm中自定义实现,如返回realm的名称,验证失败则抛出对应的异常。

  当securityManager中传入了多个realm时,在执行subject.login(usernamePasswordToken);后,可以使用subject.getPrincipals(); 得到一个身份集合,其包含了Realm验证成功的身份信息。可以对返回的身份集合的大小(principalCollection.asList().size())进行判断:全部满足,部分满足,至少一个满足等.

(具体见源码:org.apache.shiro.authc.pam.ModularRealmAuthenticator.doMultiRealmAuthentication(Collection, AuthenticationToken))

当需要对多个realm执行后的结果进行判断时,可以自定义 AuthenticationStrategy 实现。
其api包含:

  • beforeAllAttempts在所有Realm验证之前调用
  • beforeAttempt在每个Realm之前调用
  • afterAttempt在每个Realm之后调用
  • afterAllAttempts在所有Realm之后调用

可以看到源码ModularRealmAuthenticator.doMultiRealmAuthentication()方法中AuthenticationInfo 只有一个实例。

    /**
     * Performs the multi-realm authentication attempt by calling back to a {@link AuthenticationStrategy} object
     * as each realm is consulted for {@code AuthenticationInfo} for the specified {@code token}.
     *
     * @param realms the multiple realms configured on this Authenticator instance.
     * @param token  the submitted AuthenticationToken representing the subject's (user's) log-in principals and credentials.
     * @return an aggregated AuthenticationInfo instance representing account data across all the successfully
     *         consulted realms.
     */
    protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {

        AuthenticationStrategy strategy = getAuthenticationStrategy();

        AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);

        if (log.isTraceEnabled()) {
            log.trace("Iterating through {} realms for PAM authentication", realms.size());
        }

        for (Realm realm : realms) {

            aggregate = strategy.beforeAttempt(realm, token, aggregate);

            if (realm.supports(token)) {

                log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);

                AuthenticationInfo info = null;
                Throwable t = null;
                try {
                    info = realm.getAuthenticationInfo(token);
                } catch (Throwable throwable) {
                    t = throwable;
                    if (log.isWarnEnabled()) {
                        String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
                        log.warn(msg, t);
                    }
                }

                aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);

            } else {
                log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
            }
        }

        aggregate = strategy.afterAllAttempts(token, aggregate);

        return aggregate;
    }

这是因为每个AuthenticationStrategy实例都是无状态的,所以每次都通过接口将相应的认证信息传入下一次流程;通过如上接口可以进行如合并/返回第一个验证成功的认证信息。

自定义 AuthenticationStrategy 实现时一般继承org.apache.shiro.authc.pam.AbstractAuthenticationStrategy 即可,shiro提供了三种实现:AllSuccessfulStrategyAtLeastOneSuccessfulStrategyFirstSuccessfulStrategy

当时用单点登陆时,可以使用AtLeastOneSuccessfulStrategy这个类,至少保证有一个认证成功,才能算登陆成功。

但是这样又带来了额外的压力,因为遍历了所有的realm,要是我们能在用户进来时,提前知道他来自哪个系统,然后直接告诉shiro,用那种系统对应的realm的话,就能省事多了。

当一个用户过来了,我们可以提前知道

这个解决方式还真有:https://blog.csdn.net/visket2008/article/details/78539334?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param

很好的shiro系列博客:http://www.iocoder.cn/Shiro/good-collection/

你可能感兴趣的:(【Shiro】)