前言
抽取了shiro最基本的登陆流程(web登陆是基于这层开发的)。
详解源码
创建一个AuthenticationToken进行登录。
SecurityUtils.getSecurityManager().login(null,authenticationToken);
复制代码
登陆主流程
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
/**
* 取获校验token的信息
* 如果有返回就认为登陆成功
* 抛出任何AuthenticationException子类错误 就认为登陆失败
*/
info = authenticate(token);
} catch (AuthenticationException ae) {
throw ae;
}
/**
* 走到这里
* 证明已经登陆成功
*
* 下一步就是创建Subject
*/
Subject loggedIn = createSubject(token, info, subject);
return loggedIn;
}
复制代码
登陆身份校验
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
/**
* 使用登陆器去登陆
* 默认使用ModularRealmAuthenticator
*/
return this.authenticator.authenticate(token);
}
复制代码
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
//只演示单realm的情况
// return doMultiRealmAuthentication(realms, authenticationToken);
return null;
}
}
复制代码
/**
* 只有一个realm的情况下使用
* @param realm
* @param token
* @return
*/
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
/**
* 先判断这个realm是否可以校验这个token
* 这个方法需要我们在实现自己的realm时重写
*/
if (!realm.supports(token)) {
String msg = "Realm [" + realm + "] does not support authentication token [" +
token + "]. Please ensure that the appropriate Realm implementation is " +
"configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
}
/**
* 从我们自己的realm中获取校验后的登陆信息
*/
AuthenticationInfo info = realm.getAuthenticationInfo(token);
if (info == null) {
String msg = "Realm [" + realm + "] was unable to find account data for the " +
"submitted AuthenticationToken [" + token + "].";
throw new UnknownAccountException(msg);
}
return info;
}
复制代码
创建Subject
/**
* 登陆成功后创建Subject
* 如果已经有subject
* 则把现在的认证信息与原来的Subject绑定
* 如果没有Subject 则创建一个 并执行绑定操作
* @param token
* @param info
* @param existing
* @return
*/
protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
SubjectContext context = createSubjectContext();
context.setAuthenticated(true); //增加了登陆成功的标志
/**
* 同时保存了登陆的token和realm认证后返回的信息
*/
context.setAuthenticationToken(token);
context.setAuthenticationInfo(info);
if (existing != null) {
context.setSubject(existing);
}
return createSubject(context);
}
复制代码
public Subject createSubject(SubjectContext subjectContext) {
//复制了一遍subjectContext
SubjectContext context = copy(subjectContext);
//确保存在SecurityManager
context = ensureSecurityManager(context);
//解析Session
context = resolveSession(context);
Subject subject = doCreateSubject(context);
/**
* 保存subject
* web情况下 会保存在session中
*/
save(subject);
return subject;
}
复制代码
protected Subject doCreateSubject(SubjectContext context) {
//使用Subject工厂统一创建Subject
return getSubjectFactory().createSubject(context);
}
复制代码
public Subject createSubject(SubjectContext context) {
SecurityManager securityManager = context.resolveSecurityManager();
//解析session
Session session = context.resolveSession();
/**
* session是否自动创建标记
* ,没开启 会报错 @DelegatingSubject$getSession(boolean create)
*/
boolean sessionCreationEnabled = context.isSessionCreationEnabled();
//realm中返回的用户凭证信息
PrincipalCollection principals = context.resolvePrincipals();
boolean authenticated = context.resolveAuthenticated();
//创建
return new DelegatingSubject(principals, authenticated, session, sessionCreationEnabled, securityManager);
}
复制代码
保存subject
/**
* 具体保存逻辑
* 由subjectDAO的实现类完成
* @param subject
*/
protected void save(Subject subject) {
this.subjectDAO.save(subject);
}
复制代码
/**
* 默认的实现方式是把subject保存到session中
* @param subject
* @return
*/
public Subject save(Subject subject) {
if (isSessionStorageEnabled(subject)) {
saveToSession(subject);
} else {
log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " +
"authentication state are expected to be initialized on every request or invocation.", subject);
}
return subject;
}
复制代码
/**
* 保存登陆的信息和登陆的状态
* @param subject
*/
protected void saveToSession(Subject subject) {
mergePrincipals(subject);
mergeAuthenticationState(subject);
}
复制代码
protected void mergePrincipals(Subject subject) {
PrincipalCollection currentPrincipals = null;
if (currentPrincipals == null || currentPrincipals.isEmpty()) {
currentPrincipals = subject.getPrincipals();
}
Session session = subject.getSession(false);
/**
* 如果有Session
* 则把变动的PrincipalCollection保存进去
*/
if (session == null) {
if (!isEmpty(currentPrincipals)) {
session = subject.getSession();
session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
}
} else {
PrincipalCollection existingPrincipals =
(PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (isEmpty(currentPrincipals)) {
if (!isEmpty(existingPrincipals)) {
session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
}
} else {
//只有修改过了的,才会被保存
if (!currentPrincipals.equals(existingPrincipals)) {
session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
}
}
}
}
复制代码
/**
* 刷新session中的登陆标记
* @param subject
*/
protected void mergeAuthenticationState(Subject subject) {
Session session = subject.getSession(false);
if (session == null) {
if (subject.isAuthenticated()) {
/**
* 如果登陆成功,并且第一次访问Session
* 则会去创建也给新的
*/
session = subject.getSession();
session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
}
} else {
/**
* 如果有session
* 则更新标记
*/
Boolean existingAuthc = (Boolean) session.getAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
if (subject.isAuthenticated()) {
if (existingAuthc == null || !existingAuthc) {
session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
}
} else {
if (existingAuthc != null) {
session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
}
}
}
}
复制代码
保存subject时,如何生成的session(如果开启)
protected Session createSession(SessionContext context) throws AuthorizationException {
return doCreateSession(context);
}
复制代码
protected Session doCreateSession(SessionContext context) {
/**
* 使用工厂类创建session
* 默认SimpleSession
*/
Session s = newSessionInstance(context);
create(s);
return s;
}
复制代码
public Session createSession(SessionContext initData) {
/**
* 这里生成sessionId 可以吗?
*/
return new SimpleSession();
}
复制代码
shiro默认使用MemorySessionDAO
protected void create(Session session) {
if (log.isDebugEnabled()) {
log.debug("Creating new EIS record for new session instance [" + session + "]");
}
//最终交由sessionDAO生成
sessionDAO.create(session);
}
复制代码
protected Serializable doCreate(Session session) {
/**
* sessionId最终在SessionDAO中生成
* 默认使用java的uuid
*/
Serializable sessionId = generateSessionId(session);
assignSessionId(session, sessionId);
storeSession(sessionId, session);
return sessionId;
}
复制代码
修改session时,shiro是如何同步到本地的(或者HttpSession)
/**
* 如果没有session
* 则创建
* @param create
* @return
*/
@Override
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()) { //没开启sessionCreationEnabled 会报错
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);
}
/**
* 创建的是DefaultSessionContext
* 默认没什么内容
*/
SessionContext sessionContext = createSessionContext();
/**
* 最终交由securityManager来生成Session
*/
Session session = this.securityManager.start(sessionContext);
/**
* 最终让用户操作的多次包装的Session
*/
this.session = decorate(session);
}
return this.session;
}
复制代码
public Session start(SessionContext context) {
/**.
* 创建了一个SimpleSession
*/
Session session = createSession(context);
onStart(session, context);
/**
* 创建对外暴露的Session
* 其实就是代理了一下
*
* 主要为了让session在普通的操作下完成更多的功能
*/
return createExposedSession(session, context);
}
复制代码
protected Session createExposedSession(Session session, SessionContext context) {
//代理Session
return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
}
复制代码
/**
* 代理的session
* 他会把对所有对session的操作
* 提交给sessionManager去处理
* @Author: lilingyan
* @Date 2019/6/3 13:37
*/
public class DelegatingSession implements Session {
private final SessionKey key;
private final transient NativeSessionManager sessionManager;
@Override
public void setAttribute(Object attributeKey, Object value) throws InvalidSessionException {
if (value == null) {
removeAttribute(attributeKey);
} else {
//被代理了
sessionManager.setAttribute(this.key, attributeKey, value);
}
}
...
复制代码
//在SessionManager中代理的处理方法
public void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) throws InvalidSessionException {
if (value == null) {
removeAttribute(sessionKey, attributeKey);
} else {
Session s = lookupRequiredSession(sessionKey);
s.setAttribute(attributeKey, value);
onChange(s);
}
}
...
复制代码
protected void onChange(Session session) {
//最终任何修改都会被传递到sessionDAO
sessionDAO.update(session);
}
复制代码