2021SC@SDUSC
一.Demo代码
Shiro认证即为我们平时的“登录”,这篇文章我们来探究一下Shieo登录的底层实现。
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.ini.IniSecurityManagerFactory;
import org.apache.shiro.lang.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyQuickStart {
// 使用工厂模式创建日志工具,方便打印日志。
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
// 创建Shiro SecurityManager并配置realms, users, roles 和权限的最简单方式是通过INI文件。
// 我们向一个工厂中传入.ini文件,然后工厂会返回一个SecurityManager实例。
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:myshiro.ini");
SecurityManager securityManager = factory.getInstance();
// 在这个简单的demo中,将SecurityManager作为一个JVM单例进行访问。
// 大多数应用的代码不这么写,而是依赖他们的容器或是在web应用中的web.xml文件
SecurityUtils.setSecurityManager(securityManager);
// 获取当前正在操作的用户->1
Subject currentUser = SecurityUtils.getSubject();
// 登录当前用户->2
if (!currentUser.isAuthenticated()) {
// 3
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true); // 记住我->4
try {
currentUser.login(token); // 登录(进行认证)->5
} catch (UnknownAccountException uae) { // 用户不存在
// token.getPrincipal()->6
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) { // 密码错误
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) { // 用户被锁定
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... 在这里捕获更多异常,可以在你的应用程序中自定义一个
catch (AuthenticationException ae) {
// 意外情况? 错误?
}
}
// 报告谁登录成功了:
// 打印他们的认证凭据--principal(在这个例子中是用户名):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
// 退出登录->7
currentUser.logout();
System.exit(0);
}
}
二.源码分析
该部分源码将分四篇文章进行分析,本篇文章分析代码的5.3-5.7部分。
5
public class DelegatingSubject implements Subject {
public void login(AuthenticationToken token) throws AuthenticationException {
clearRunAsIdentitiesInternal(); // 5.1
Subject subject = securityManager.login(this, token); // 5.2
PrincipalCollection principals;
String host = null;
if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject) subject;
principals = delegating.principals;
host = delegating.host;
} else {
principals = subject.getPrincipals(); // 5.3
}
if (principals == null || principals.isEmpty()) { // 5.4
String msg = "Principals returned from securityManager.login( token ) returned a null or " +
"empty value. This value must be non null and populated with one or more elements.";
throw new IllegalStateException(msg);
}
this.principals = principals;
this.authenticated = true;
if (token instanceof HostAuthenticationToken) {
host = ((HostAuthenticationToken) token).getHost(); // 5.5
}
if (host != null) {
this.host = host;
}
Session session = subject.getSession(false); // 5.6
if (session != null) {
this.session = decorate(session); // 5.7
} else {
this.session = null;
}
}
}
5.3
public class DelegatingSubject implements Subject {
// 以PrincipalCollection的形式返回这个Subject的主体(标识属性);
// 如果这个Subject是匿名的,因为它还没有任何关联的帐户数据(例如,如果它们还没有登录),则返回null。
public PrincipalCollection getPrincipals() {
List<PrincipalCollection> runAsPrincipals = getRunAsPrincipalsStack();
return CollectionUtils.isEmpty(runAsPrincipals) ? this.principals : runAsPrincipals.get(0);
}
// 获取PrincipalCollection集合
private List<PrincipalCollection> getRunAsPrincipalsStack() {
Session session = getSession(false);
if (session != null) {
return (List<PrincipalCollection>) session.getAttribute(RUN_AS_PRINCIPALS_SESSION_KEY);
}
return null;
}
}
5.4
// PrincipalMap接口的默认实现。
// PrincipalMap是与相应Subject相关联的所有Subject的集合。
// Subject只是标识属性的安全术语,例如用户名、用户id或社会安全号码或任何可以被视为Subject的“标识”属性的东西。
// PrincipalCollection根据Subject第一次创建时它们来自的领域来组织其内部的principal。
// 要获取特定领域的主体,可以查看fromRealm方法,还可以通过getRealmNames()方法查看哪些领域对该集合做出了贡献。
public class SimplePrincipalMap implements PrincipalMap {
// 如果该集合为空则返回true,否则返回false.
public boolean isEmpty() {
return CollectionUtils.isEmpty(this.combinedPrincipals);
}
}
5.5
public class UsernamePasswordToken implements HostAuthenticationToken, RememberMeAuthenticationToken {
// 尝试登录的位置,如果不知道或显式省略,则为空。
private String host;
// 返回发起身份验证尝试的客户端的主机名,或者如果Shiro环境不能或选择不解析主机名以提高性能,该方法返回客户端的IP地址的String形式。
// 在web环境中使用时,该值通常与ServletRequest.getRemoteHost()值相同。
public String getHost() {
return host;
}
}
5.6
public class DelegatingSubject implements Subject {
// 返回与此主题关联的应用程序Session。基于布尔参数,该方法的功能如下:
// 如果已经有一个与该Subject关联的现有会话,则返回该会话并忽略create参数。
// 如果没有会话存在,并且create为真,那么将创建一个新的会话,并与这个Subject关联,然后返回。
// 如果没有会话,并且create为false,则返回null。
public Session getSession(boolean create) {
if (log.isTraceEnabled()) {
log.trace("attempting to get session; create = " + create +
"; session is null = " + (this.session == null) +
"; session has id = " + (this.session != null && session.getId() != null));
}
if (this.session == null && create) {
//added in 1.2:
if (!isSessionCreationEnabled()) {
String msg = "Session creation has been disabled for the current subject. This exception indicates " +
"that there is either a programming error (using a session when it should never be " +
"used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +
"for the current Subject. See the " + DisabledSessionException.class.getName() + " JavaDoc " +
"for more.";
throw new DisabledSessionException(msg);
}
log.trace("Starting session for host {}", getHost());
SessionContext sessionContext = createSessionContext();
Session session = this.securityManager.start(sessionContext);
this.session = decorate(session);
}
return this.session;
}
}
5.7
public class DelegatingSubject implements Subject {
protected Session decorate(Session session) {
if (session == null) {
throw new IllegalArgumentException("session cannot be null");
}
return new StoppingAwareProxiedSession(session, this);
}
// 内部类
private class StoppingAwareProxiedSession extends ProxiedSession {
private final DelegatingSubject owner;
private StoppingAwareProxiedSession(Session target, DelegatingSubject owningSubject) {
super(target);
owner = owningSubject;
}
public void stop() throws InvalidSessionException {
super.stop();
owner.sessionStopped();
}
}
}
(完)