关于 shiro 改造部分,之前有写文章专门讲过流程,长话短说,直接进入主题。
改造部分
SecurityManager 的配置如下:
@Bean
public SecurityManager securityManager(OAuthRealm oAuthRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(oAuthRealm);
securityManager.setSubjectFactory(new StatelessDefaultSubjectFactory());
securityManager.setCacheManager(getEhCache());
//securityManager.setSessionManager(sessionManager);
// 自定义缓存实现 使用redis
//securityManager.setCacheManager(cacheManager());
return securityManager;
}
StatelessDefaultSubjectFactory 的代码为:
public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
@Override
public Subject createSubject(SubjectContext context) {
//不创建session
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}
DisabledSessionException
运行后,在调用 subject.login(token)方法时报错,报错信息如下:
org.apache.shiro.subject.support.DisabledSessionException: 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 org.apache.shiro.subject.support.DisabledSessionException JavaDoc for more.
分析
这里不去描述太多,直接从源码一步步来看: org.apache.shiro.subject.support.DelegatingSubject#login:
public void login(AuthenticationToken token) throws AuthenticationException {
clearRunAsIdentitiesInternal();
Subject subject = securityManager.login(this, token);
PrincipalCollection principals;
String host = null;
...........
}
这里我们主要关注下 securityManager.login(this, token)即 org.apache.shiro.mgt.DefaultSecurityManager#login:
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = authenticate(token);
} catch (AuthenticationException ae) {
try {
onFailedLogin(token, ae, subject);
} catch (Exception e) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an " +
"exception. Logging and propagating original AuthenticationException.", e);
}
}
throw ae; //propagate
}
Subject loggedIn = createSubject(token, info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
进入 createSubject:
protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
SubjectContext context = createSubjectContext();
context.setAuthenticated(true);
context.setAuthenticationToken(token);
context.setAuthenticationInfo(info);
if (existing != null) {
context.setSubject(existing);
}
return createSubject(context);
}
再看 createSubject(context):
public Subject createSubject(SubjectContext subjectContext) {
//create a copy so we don't modify the argument's backing map:
SubjectContext context = copy(subjectContext);
//ensure that the context has a SecurityManager instance, and if not, add one:
context = ensureSecurityManager(context);
//Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
//sending to the SubjectFactory. The SubjectFactory should not need to know how to acquire sessions as the
//process is often environment specific - better to shield the SF from these details:
context = resolveSession(context);
//Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
//if possible before handing off to the SubjectFactory:
context = resolvePrincipals(context);
Subject subject = doCreateSubject(context);
//save this subject for future reference if necessary:
//(this is needed here in case rememberMe principals were resolved and they need to be stored in the
//session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
//Added in 1.2:
save(subject);
return subject;
}
到了这里,我们主要关注以下两点:
doCreateSubject(context):
protected Subject doCreateSubject(SubjectContext context) {
return getSubjectFactory().createSubject(context);
}
这里会调用 StatelessDefaultSubjectFactory 的 createSubject 方法,创建的是那个禁用 session 的 subject。
save(subject):
protected void save(Subject subject) {
this.subjectDAO.save(subject);
}
org.apache.shiro.mgt.DefaultSubjectDAO#save:
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;
}
protected boolean isSessionStorageEnabled(Subject subject) {
return getSessionStorageEvaluator().isSessionStorageEnabled(subject);
}
public SessionStorageEvaluator getSessionStorageEvaluator() {
return sessionStorageEvaluator;
}
org.apache.shiro.mgt.DefaultSessionStorageEvaluator#isSessionStorageEnabled(org.apache.shiro.subject.Subject):
public boolean isSessionStorageEnabled(Subject subject) {
return (subject != null && subject.getSession(false) != null) || isSessionStorageEnabled();
}
这里我索性把代码都放在一起,方便查看。可以看到默认用的是 DefaultSessionStorageEvaluator,它的 isSessionStorageEnabled 的结果为 true,从而会进入到 saveToSession(subject)方法,我们继续来看这个方法:
protected void saveToSession(Subject subject) {
//performs merge logic, only updating the Subject's session if it does not match the current state:
mergePrincipals(subject);
mergeAuthenticationState(subject);
}
protected void mergePrincipals(Subject subject) {
//merge PrincipalCollection state:
PrincipalCollection currentPrincipals = null;
//SHIRO-380: added if/else block - need to retain original (source) principals
//This technique (reflection) is only temporary - a proper long term solution needs to be found,
//but this technique allowed an immediate fix that is API point-version forwards and backwards compatible
//
//A more comprehensive review / cleaning of runAs should be performed for Shiro 1.3 / 2.0 +
if (subject.isRunAs() && subject instanceof DelegatingSubject) {
try {
Field field = DelegatingSubject.class.getDeclaredField("principals");
field.setAccessible(true);
currentPrincipals = (PrincipalCollection)field.get(subject);
} catch (Exception e) {
throw new IllegalStateException("Unable to access DelegatingSubject principals property.", e);
}
}
if (currentPrincipals == null || currentPrincipals.isEmpty()) {
currentPrincipals = subject.getPrincipals();
}
Session session = subject.getSession(false);
if (session == null) {
if (!isEmpty(currentPrincipals)) {
session = subject.getSession();
session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
}
// otherwise no session and no principals - nothing to save
} else {
PrincipalCollection existingPrincipals =
(PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (isEmpty(currentPrincipals)) {
if (!isEmpty(existingPrincipals)) {
session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
}
// otherwise both are null or empty - no need to update the session
} else {
if (!currentPrincipals.equals(existingPrincipals)) {
session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
}
// otherwise they're the same - no need to update the session
}
}
}
上面的代码比较长,我们主要关注下 session = subject.getSession();即 org.apache.shiro.subject.support.DelegatingSubject#getSession():
public Session getSession() {
return getSession(true);
}
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;
}
到这里你应该是看到异常出现的地方了。这里由于时间关系,就不再具体去分析了。
解决办法
securityManager:
@Bean
public SecurityManager securityManager(OAuthRealm oAuthRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(oAuthRealm);
securityManager.setSubjectFactory(new StatelessDefaultSubjectFactory());
securityManager.setCacheManager(getEhCache());
SubjectDAO subjectDAO = securityManager.getSubjectDAO();
if (subjectDAO instanceof DefaultSubjectDAO){
DefaultSubjectDAO defaultSubjectDAO = (DefaultSubjectDAO) subjectDAO;
SessionStorageEvaluator sessionStorageEvaluator = defaultSubjectDAO.getSessionStorageEvaluator();
if (sessionStorageEvaluator instanceof DefaultSessionStorageEvaluator){
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = (DefaultSessionStorageEvaluator) sessionStorageEvaluator;
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
}
}
//securityManager.setSessionManager(sessionManager);
// 自定义缓存实现 使用redis
//securityManager.setCacheManager(cacheManager());
return securityManager;
}




