问题:当使用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提供了三种实现:AllSuccessfulStrategy
,AtLeastOneSuccessfulStrategy
,FirstSuccessfulStrategy
当时用单点登陆时,可以使用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/