前言
抽取了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);
}
複製程式碼