Session是个状态性的数据上下文,可以理解为每个用户都有一个特定数据库,该数据库存储着每个用户自己的数据,在shiro里,它是和Subject绑定在一起的,通常用户通过Subject.getSession来获取使用。它在系统内会存活一段时间为用户提供客户端浏览器和应用服务器通讯的一些功能。以下是一些关于Session的使用场景。
1.用户登陆成功后,应用服务器产生个Session,且返回该Session的唯一标识符ID给客户端保存(可通过cookie,或者uri参数等形式)。这样用户访问需要验证后的URL时,应用服务器就能识别。注意:shiro在用户第一次通过浏览器第一次访问应用服务器时,就会产生Session了,且返回SessionID,后面如果用户再登陆的话,则会在Session里存储该用户已经登陆的。
2.像网上商城,用户通常都会浏览自己喜欢商品,满意后则添加到购物车,由于Session相当于个人的小数据库,此时可以利用Session存储用户添加到购物车的商品,等用户选择完去付款时,此时从Session中获取该用户购物车的商品进行计费、生成订单付费即可。
Shiro实现了自己的Session,即由shiro来管理Session的生命周期。可以为任意的应用提供Session支持。
1.Session
public interface Session {
//返回Session的标识符
Serializable getId();
//返回Session的创建时间
Date getStartTimestamp();
//返回用户的最后次访问时间
Date getLastAccessTime();
//返回Session过期时间。 负数则永远不过期,正数则规定时间内过期
long getTimeout() throws InvalidSessionException;
void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException;
//返回hostName 或者 IP
String getHost();
//更新用户最后访问时间,用来确保用户session的存活时间.该方法对于富客户端(APP,FLASH)来说是非常有意义的。
//因为我们通常在使用富客户端时候,很长一段时间富客户端可能没有和服务器交互,此时服务度的Session由于没有访问,
//随着设置Session的存活时间接近而过期。此时我们可以通过富客户端直接调用该方法来确保用户是存活的。
void touch() throws InvalidSessionException;
//停止该Session。由于用户的登陆状态存储在Session当中,如果你手动调用该方法停止Session,那么该用户将的登陆验证
//将被认为无效的。你可以通过subject.logout方法去退出登陆,该方法不仅会调用session.stop停止session,还会清除一些过期缓存信息
void stop() throws InvalidSessionException;
//获取Session存储的所有值的key
Collection<Object> getAttributeKeys() throws InvalidSessionException;
//根据key获取Session存储的值
Object getAttribute(Object key) throws InvalidSessionException;
//设置值和key到session中
void setAttribute(Object key, Object value) throws InvalidSessionException;
//根据key删除值
Object removeAttribute(Object key) throws InvalidSessionException;
}
2.SimpleSession
//SimpleSession实现了ValidatingSession和Serializable接口,支持验证session操作和序列化
public class SimpleSession implements ValidatingSession, Serializable {
private static final long serialVersionUID = -7125642695178165650L;
private transient static final Logger log = LoggerFactory.getLogger(SimpleSession.class);
protected static final long MILLIS_PER_SECOND = 1000;
protected static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
protected static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
//serialization bitmask fields. DO NOT CHANGE THE ORDER THEY ARE DECLARED!
static int bitIndexCounter = 0;
private static final int ID_BIT_MASK = 1 << bitIndexCounter++;
private static final int START_TIMESTAMP_BIT_MASK = 1 << bitIndexCounter++;
private static final int STOP_TIMESTAMP_BIT_MASK = 1 << bitIndexCounter++;
private static final int LAST_ACCESS_TIME_BIT_MASK = 1 << bitIndexCounter++;
private static final int TIMEOUT_BIT_MASK = 1 << bitIndexCounter++;
private static final int EXPIRED_BIT_MASK = 1 << bitIndexCounter++;
private static final int HOST_BIT_MASK = 1 << bitIndexCounter++;
private static final int ATTRIBUTES_BIT_MASK = 1 << bitIndexCounter++;
/** 由于重写writerObject和readObject,默认首先调用defaultWriteObject和defaultReadObject, 为了避免序列化两次。所以以下字段都标识为了transient **/
//sessonID,用于保持客户端浏览器和服务端Session存储容器之间的标识
private transient Serializable id;
//session创建时间
private transient Date startTimestamp;
//session停止时间,如果不为null,则代表已停止
private transient Date stopTimestamp;
//session的最后访问时间
private transient Date lastAccessTime;
//session的过期时间
private transient long timeout;
//session是否过期
private transient boolean expired;
//客户端IP或者host name
private transient String host;
private transient Map<Object, Object> attributes;
//当session第一次创建时候,初始化session创建时间和session最后访问时间和session过期时间
public SimpleSession() {
this.timeout = DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT; //TODO - remove concrete reference to DefaultSessionManager
this.startTimestamp = new Date();
this.lastAccessTime = this.startTimestamp;
}
public SimpleSession(String host) {
this();
this.host = host;
}
public Serializable getId() {
return this.id;
}
public void setId(Serializable id) {
this.id = id;
}
public Date getStartTimestamp() {
return startTimestamp;
}
public void setStartTimestamp(Date startTimestamp) {
this.startTimestamp = startTimestamp;
}
public Date getStopTimestamp() {
return stopTimestamp;
}
public void setStopTimestamp(Date stopTimestamp) {
this.stopTimestamp = stopTimestamp;
}
public Date getLastAccessTime() {
return lastAccessTime;
}
public void setLastAccessTime(Date lastAccessTime) {
this.lastAccessTime = lastAccessTime;
}
public boolean isExpired() {
return expired;
}
public void setExpired(boolean expired) {
this.expired = expired;
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Map<Object, Object> getAttributes() {
return attributes;
}
public void setAttributes(Map<Object, Object> attributes) {
this.attributes = attributes;
}
public void touch() {
this.lastAccessTime = new Date();
}
public void stop() {
if (this.stopTimestamp == null) {
this.stopTimestamp = new Date();
}
}
protected boolean isStopped() {
return getStopTimestamp() != null;
}
protected void expire() {
stop();
this.expired = true;
}
//判断是否停止和过期
public boolean isValid() {
return !isStopped() && !isExpired();
}
//判断是否过期具体验证
protected boolean isTimedOut() {
//如果之前已设置为过期,则true
if (isExpired()) {
return true;
}
long timeout = getTimeout();
/** 如果session有设置过期时间且大于0,则认为它的存活时间是有限制的,否则无限制时间.但是由于shiro 默认设置sessionID到cookie中,该cookie是没有设置存活时间的,则浏览器关闭则消失,再重新打开浏览器时, 即使服务端session存在且有效,还是找不到该session。会重新分配一个新的session。 如果需要保持浏览器和服务端session始终对应,不管浏览器关闭与否。可以设置存储sessionID的cookie为永远存活。 需要注意的是: 1.浏览器直接清空cookie,这样就找不到服务端session了,且服务端session永远停留在存储容器中 **/
if (timeout >= 0l) {
Date lastAccessTime = getLastAccessTime();
if (lastAccessTime == null) {
String msg = "session.lastAccessTime for session with id [" +
getId() + "] is null. This value must be set at " +
"least once, preferably at least upon instantiation. Please check the " +
getClass().getName() + " implementation and ensure " +
"this value will be set (perhaps in the constructor?)";
throw new IllegalStateException(msg);
}
//session过期时间规则判断。 是否过期 = 最后次访问时间 < (当前时间-存活时间限制)
long expireTimeMillis = System.currentTimeMillis() - timeout;
Date expireTime = new Date(expireTimeMillis);
return lastAccessTime.before(expireTime);
} else {
if (log.isTraceEnabled()) {
log.trace("No timeout for session with id [" + getId() +
"]. Session is not considered expired.");
}
}
return false;
}
public void validate() throws InvalidSessionException {
//验证该session是否停止
if (isStopped()) {
//timestamp is set, so the session is considered stopped:
String msg = "Session with id [" + getId() + "] has been " +
"explicitly stopped. No further interaction under this session is " +
"allowed.";
throw new StoppedSessionException(msg);
}
//验证是否过期
if (isTimedOut()) {
expire();
//throw an exception explaining details of why it expired:
Date lastAccessTime = getLastAccessTime();
long timeout = getTimeout();
Serializable sessionId = getId();
DateFormat df = DateFormat.getInstance();
String msg = "Session with id [" + sessionId + "] has expired. " +
"Last access time: " + df.format(lastAccessTime) +
". Current time: " + df.format(new Date()) +
". Session timeout is set to " + timeout / MILLIS_PER_SECOND + " seconds (" +
timeout / MILLIS_PER_MINUTE + " minutes)";
if (log.isTraceEnabled()) {
log.trace(msg);
}
throw new ExpiredSessionException(msg);
}
}
private Map<Object, Object> getAttributesLazy() {
Map<Object, Object> attributes = getAttributes();
if (attributes == null) {
attributes = new HashMap<Object, Object>();
setAttributes(attributes);
}
return attributes;
}
public Collection<Object> getAttributeKeys() throws InvalidSessionException {
Map<Object, Object> attributes = getAttributes();
if (attributes == null) {
return Collections.emptySet();
}
return attributes.keySet();
}
public Object getAttribute(Object key) {
Map<Object, Object> attributes = getAttributes();
if (attributes == null) {
return null;
}
return attributes.get(key);
}
public void setAttribute(Object key, Object value) {
if (value == null) {
removeAttribute(key);
} else {
getAttributesLazy().put(key, value);
}
}
public Object removeAttribute(Object key) {
Map<Object, Object> attributes = getAttributes();
if (attributes == null) {
return null;
} else {
return attributes.remove(key);
}
}
//比较session。如果引用相同则true,如果session属于SimpleSession且sessionID相同则true,否则,对比各项字段是否相等
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof SimpleSession) {
SimpleSession other = (SimpleSession) obj;
Serializable thisId = getId();
Serializable otherId = other.getId();
if (thisId != null && otherId != null) {
return thisId.equals(otherId);
} else {
//fall back to an attribute based comparison:
return onEquals(other);
}
}
return false;
}
protected boolean onEquals(SimpleSession ss) {
return (getStartTimestamp() != null ? getStartTimestamp().equals(ss.getStartTimestamp()) : ss.getStartTimestamp() == null) &&
(getStopTimestamp() != null ? getStopTimestamp().equals(ss.getStopTimestamp()) : ss.getStopTimestamp() == null) &&
(getLastAccessTime() != null ? getLastAccessTime().equals(ss.getLastAccessTime()) : ss.getLastAccessTime() == null) &&
(getTimeout() == ss.getTimeout()) &&
(isExpired() == ss.isExpired()) &&
(getHost() != null ? getHost().equals(ss.getHost()) : ss.getHost() == null) &&
(getAttributes() != null ? getAttributes().equals(ss.getAttributes()) : ss.getAttributes() == null);
}
@Override
public int hashCode() {
Serializable id = getId();
if (id != null) {
return id.hashCode();
}
int hashCode = getStartTimestamp() != null ? getStartTimestamp().hashCode() : 0;
hashCode = 31 * hashCode + (getStopTimestamp() != null ? getStopTimestamp().hashCode() : 0);
hashCode = 31 * hashCode + (getLastAccessTime() != null ? getLastAccessTime().hashCode() : 0);
hashCode = 31 * hashCode + Long.valueOf(Math.max(getTimeout(), 0)).hashCode();
hashCode = 31 * hashCode + Boolean.valueOf(isExpired()).hashCode();
hashCode = 31 * hashCode + (getHost() != null ? getHost().hashCode() : 0);
hashCode = 31 * hashCode + (getAttributes() != null ? getAttributes().hashCode() : 0);
return hashCode;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getName()).append(",id=").append(getId());
return sb.toString();
}
//调用该方法实现序列化
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
short alteredFieldsBitMask = getAlteredFieldsBitMask();
out.writeShort(alteredFieldsBitMask);
if (id != null) {
out.writeObject(id);
}
if (startTimestamp != null) {
out.writeObject(startTimestamp);
}
if (stopTimestamp != null) {
out.writeObject(stopTimestamp);
}
if (lastAccessTime != null) {
out.writeObject(lastAccessTime);
}
if (timeout != 0l) {
out.writeLong(timeout);
}
if (expired) {
out.writeBoolean(expired);
}
if (host != null) {
out.writeUTF(host);
}
if (!CollectionUtils.isEmpty(attributes)) {
out.writeObject(attributes);
}
}
//调用该方法实现反序列化
@SuppressWarnings({"unchecked"})
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
short bitMask = in.readShort();
if (isFieldPresent(bitMask, ID_BIT_MASK)) {
this.id = (Serializable) in.readObject();
}
if (isFieldPresent(bitMask, START_TIMESTAMP_BIT_MASK)) {
this.startTimestamp = (Date) in.readObject();
}
if (isFieldPresent(bitMask, STOP_TIMESTAMP_BIT_MASK)) {
this.stopTimestamp = (Date) in.readObject();
}
if (isFieldPresent(bitMask, LAST_ACCESS_TIME_BIT_MASK)) {
this.lastAccessTime = (Date) in.readObject();
}
if (isFieldPresent(bitMask, TIMEOUT_BIT_MASK)) {
this.timeout = in.readLong();
}
if (isFieldPresent(bitMask, EXPIRED_BIT_MASK)) {
this.expired = in.readBoolean();
}
if (isFieldPresent(bitMask, HOST_BIT_MASK)) {
this.host = in.readUTF();
}
if (isFieldPresent(bitMask, ATTRIBUTES_BIT_MASK)) {
this.attributes = (Map<Object, Object>) in.readObject();
}
}
/** * Returns a bit mask used during serialization indicating which fields have been serialized. Fields that have been * altered (not null and/or not retaining the class defaults) will be serialized and have 1 in their respective * index, fields that are null and/or retain class default values have 0. * * @return a bit mask used during serialization indicating which fields have been serialized. * @since 1.0 */
private short getAlteredFieldsBitMask() {
int bitMask = 0;
bitMask = id != null ? bitMask | ID_BIT_MASK : bitMask;
bitMask = startTimestamp != null ? bitMask | START_TIMESTAMP_BIT_MASK : bitMask;
bitMask = stopTimestamp != null ? bitMask | STOP_TIMESTAMP_BIT_MASK : bitMask;
bitMask = lastAccessTime != null ? bitMask | LAST_ACCESS_TIME_BIT_MASK : bitMask;
bitMask = timeout != 0l ? bitMask | TIMEOUT_BIT_MASK : bitMask;
bitMask = expired ? bitMask | EXPIRED_BIT_MASK : bitMask;
bitMask = host != null ? bitMask | HOST_BIT_MASK : bitMask;
bitMask = !CollectionUtils.isEmpty(attributes) ? bitMask | ATTRIBUTES_BIT_MASK : bitMask;
return (short) bitMask;
}
/** * Returns {@code true} if the given {@code bitMask} argument indicates that the specified field has been * serialized and therefore should be read during deserialization, {@code false} otherwise. * * @param bitMask the aggregate bitmask for all fields that have been serialized. Individual bits represent * the fields that have been serialized. A bit set to 1 means that corresponding field has * been serialized, 0 means it hasn't been serialized. * @param fieldBitMask the field bit mask constant identifying which bit to inspect (corresponds to a class attribute). * @return {@code true} if the given {@code bitMask} argument indicates that the specified field has been * serialized and therefore should be read during deserialization, {@code false} otherwise. * @since 1.0 */
private static boolean isFieldPresent(short bitMask, int fieldBitMask) {
return (bitMask & fieldBitMask) != 0;
}
}
3.DelegatingSession
/** 服务端的代理的Session,该DelegatingSession 只是保存了真正的底层Session(SimpleSession)的key, 然后根据该key来查找到SimpleSession再代理它的操作。Subject.getSession()获取的就是该DelegatingSession, 也许是为了不让用户破坏底层Session的一些特性吧 **/
public class DelegatingSession implements Session, Serializable {
private final SessionKey key;
private Date startTimestamp = null;
private String host = null;
//用于根据SessionKey来操作真正的底层Session
private final transient NativeSessionManager sessionManager;
public DelegatingSession(NativeSessionManager sessionManager, SessionKey key) {
if (sessionManager == null) {
throw new IllegalArgumentException("sessionManager argument cannot be null.");
}
if (key == null) {
throw new IllegalArgumentException("sessionKey argument cannot be null.");
}
if (key.getSessionId() == null) {
String msg = "The " + DelegatingSession.class.getName() + " implementation requires that the " +
"SessionKey argument returns a non-null sessionId to support the " +
"Session.getId() invocations.";
throw new IllegalArgumentException(msg);
}
this.sessionManager = sessionManager;
this.key = key;
}
public Serializable getId() {
return key.getSessionId();
}
//根据SessoinKey获取底层Session的创建时间
public Date getStartTimestamp() {
if (startTimestamp == null) {
startTimestamp = sessionManager.getStartTimestamp(key);
}
return startTimestamp;
}
//根据SessoinKey获取底层Session的最后访问时间
public Date getLastAccessTime() {
//can't cache - only business pojo knows the accurate time:
return sessionManager.getLastAccessTime(key);
}
public long getTimeout() throws InvalidSessionException {
return sessionManager.getTimeout(key);
}
public void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException {
sessionManager.setTimeout(key, maxIdleTimeInMillis);
}
根据SessoinKey获取底层Session的host name或IP
public String getHost() {
if (host == null) {
host = sessionManager.getHost(key);
}
return host;
}
//根据SessoinKey调用底层session的touch方法,重设session最后访问时间
public void touch() throws InvalidSessionException {
sessionManager.touch(key);
}
//停止Session
public void stop() throws InvalidSessionException {
sessionManager.stop(key);
}
public Collection<Object> getAttributeKeys() throws InvalidSessionException {
return sessionManager.getAttributeKeys(key);
}
public Object getAttribute(Object attributeKey) throws InvalidSessionException {
return sessionManager.getAttribute(this.key, attributeKey);
}
public void setAttribute(Object attributeKey, Object value) throws InvalidSessionException {
if (value == null) {
removeAttribute(attributeKey);
} else {
sessionManager.setAttribute(this.key, attributeKey, value);
}
}
public Object removeAttribute(Object attributeKey) throws InvalidSessionException {
return sessionManager.removeAttribute(this.key, attributeKey);
}
}
4.ProxiedSession
//该类主要作用是代理真正的Session,为子类提供可重写方法。如:不可调用代理某个方法,如果调用则抛出异常
public class ProxiedSession implements Session {
//真正的Session
protected final Session delegate;
public ProxiedSession(Session target) {
if (target == null) {
throw new IllegalArgumentException("Target session to proxy cannot be null.");
}
delegate = target;
}
public Serializable getId() {
return delegate.getId();
}
public Date getStartTimestamp() {
return delegate.getStartTimestamp();
}
public Date getLastAccessTime() {
return delegate.getLastAccessTime();
}
public long getTimeout() throws InvalidSessionException {
return delegate.getTimeout();
}
public void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException {
delegate.setTimeout(maxIdleTimeInMillis);
}
public String getHost() {
return delegate.getHost();
}
public void touch() throws InvalidSessionException {
delegate.touch();
}
public void stop() throws InvalidSessionException {
delegate.stop();
}
public Collection<Object> getAttributeKeys() throws InvalidSessionException {
return delegate.getAttributeKeys();
}
public Object getAttribute(Object key) throws InvalidSessionException {
return delegate.getAttribute(key);
}
public void setAttribute(Object key, Object value) throws InvalidSessionException {
delegate.setAttribute(key, value);
}
public Object removeAttribute(Object key) throws InvalidSessionException {
return delegate.removeAttribute(key);
}
}
5.ImmutableSession
//ImmutableProxiedSession 继承与ProxiedSession ,该类主要作用是返回一个不可修改Session的代理Session,对于修改的方法都重写抛异常
public class ImmutableProxiedSession extends ProxiedSession {
public ImmutableProxiedSession(Session target) {
super(target);
}
protected void throwImmutableException() throws InvalidSessionException {
String msg = "This session is immutable and read-only - it cannot be altered. This is usually because " +
"the session has been stopped or expired already.";
throw new InvalidSessionException(msg);
}
public void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException {
throwImmutableException();
}
public void touch() throws InvalidSessionException {
throwImmutableException();
}
public void stop() throws InvalidSessionException {
throwImmutableException();
}
public void setAttribute(Object key, Object value) throws InvalidSessionException {
throwImmutableException();
}
public Object removeAttribute(Object key) throws InvalidSessionException {
throwImmutableException();
//we should never ever reach this point due to the exception being thrown.
throw new InternalError("This code should never execute - please report this as a bug!");
}
}
6.StoppingAwareProxiedSession
//StoppingAwareProxiedSession 主要是增强代理Session的方法
private class StoppingAwareProxiedSession extends ProxiedSession {
private final DelegatingSubject owner;
private StoppingAwareProxiedSession(Session target, DelegatingSubject owningSubject) {
super(target);
owner = owningSubject;
}
//session停止时,顺便清空DelegatingSubject的Session
public void stop() throws InvalidSessionException {
super.stop();
owner.sessionStopped();
}
}
7.HttpServletSession
该Session仅仅只是代理了servlet的HttpSession。方便与ServletContainerSessionManager和shiro实现可易配置。把session交由servlet container来控制生命周期。