NutzCN Logo
问答 实现分布式session过程中遇到的一些问题
发布于 3021天前 作者 zrh091110225 6619 次浏览 复制 上一个帖子 下一个帖子
标签:

实现方式:
1. 主模块定义过滤器式的Session提供者为:@SessionBy(ShiroSessionProvider.class)
2. 实现方式:
自己实现一个RedisSessionDAO extends CachingSessionDAO
重写了doCreate(),readSession(),doReadSession(),doUpdate(),doDelete()通过redis读写session的方法

遇到的问题是:
如果是一台机器的话运行正常,如果两台机器均衡负载的时候会导致/home,/login页面循环重定向,查了很久也没有太多的头绪...

35 回复

同一个redis进程吗

来自炫酷的 NutzCN

@wendal
搭了一个3mater,3slave的reids集群
代码中用的是redis.clients.jedis.JedisCluster操作redis的

把集群先撤了, 先把单机搞通.

PS: 为啥需要实现CachingSessionDAO, 像nutz.cn这样只实现CacheManager也是个办法

@wendal ok,你是觉得有可能是因为集群组从同步延迟导致?
恩。。。确实是可以只实现。
我试试

@wendal 有没有例子? 我们贴下代码,帮忙看下有什么问题

public class RedisSessionDAO extends CachingSessionDAO {
private static final Log logger = Logs.get();
// 登录成功的信息存储在 session 的这个 attribute 里.
private static final String AUTHENTICATED_SESSION_KEY =
"org.apache.shiro.subject.support.DefaultSubjectContext_AUTHENTICATED_SESSION_KEY";

private String keyPrefix = "shiro_redis_session:";
private String deleteChannel = "shiro_redis_session:delete";
private int timeToLiveSeconds = 3600; // session缓存失效时间(单位:秒)

private RedisManager redisManager;

/**
 * DefaultSessionManager 创建完 session 后会调用该方法。
 * 把 session 保持到 Redis。
 * 返回 Session ID;主要此处返回的 ID.equals(session.getId())
 */
@Override
protected Serializable doCreate(Session session) {
    logger.debug("=> Create session with ID " + session.getId());

    // 创建一个Id并设置给Session
    Serializable sessionId = this.generateSessionId(session);
    assignSessionId(session, sessionId);

    // session 由 Redis 缓存失效决定
    String key = SerializationUtils.sessionKey(keyPrefix, session);
    String value = SerializationUtils.sessionFromString(session);
    redisManager.setex(key, value, timeToLiveSeconds);

    return sessionId;
}

/**
 * 考虑到集群的时候退出登录无法删除集群机器本地session故而所有的都从 Redis 读取 Session.
 *
 * @param sessionId
 * @return
 * @throws UnknownSessionException
 */
@Override
public Session readSession(Serializable sessionId) throws UnknownSessionException {
    Session s = doReadSession(sessionId);
    if (s == null) {
        throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
    }
    return s;
}

/**
 * 从 Redis 上读取 session,并缓存到本地 Cache.
 *
 * @param sessionId
 * @return
 */
@Override
protected Session doReadSession(Serializable sessionId) {
    logger.debug("=> Read session with ID " + sessionId);

    try {
        String value = redisManager.get(SerializationUtils.sessionKey(keyPrefix, sessionId));
        // 例如 Redis 调用 flushdb 情况了所有的数据,读到的 session 就是空的
        if (value != null) {
            Session session = SerializationUtils.sessionToString(value);
            return session;
        }
    } catch (Exception e) {
        logger.error(e.getMessage());
    }

    return null;
}

/**
 * 更新 session 到 Redis.
 *
 * @param session
 */
@Override
protected void doUpdate(Session session) {
    // 如果会话过期/停止,没必要再更新了
    if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
        logger.debug("=> Invalid session.");
        return;
    }

    logger.debug("=> Update session with ID " + session.getId());

    String key = SerializationUtils.sessionKey(keyPrefix, session);
    String value = SerializationUtils.sessionFromString(session);
    redisManager.setex(key, value, timeToLiveSeconds);
}

/**
 * 从 Redis 删除 session,并且发布消息通知其它 Server 上的 Cache 删除 session.
 *
 * @param session
 */
@Override
protected void doDelete(Session session) {
    logger.debug("=> Delete session with ID " + session.getId());

    redisManager.del(SerializationUtils.sessionKey(keyPrefix, session));
    // 发布消息通知其它 Server 上的 cache 删除 session.
    redisManager.publish(deleteChannel, SerializationUtils.sessionIdToString(session));
}

/**
 * 取得所有有效的 session.
 *
 * @return
 */
@Override
public Collection<Session> getActiveSessions() {
    logger.debug("=> Get active sessions");
    //Set<String> keys = redisManager.keys(keyPrefix + "*");//TODO 看是否用到
    Set<String> keys = new HashSet<>();
    Collection<String> values = redisManager.mget(keys.toArray(new String[0]));
    List<Session> sessions = new LinkedList<Session>();

    for (String value : values) {
        sessions.add(SerializationUtils.sessionToString(value));
    }

    return sessions;
}

public String getKeyPrefix() {
    return keyPrefix;
}

public void setKeyPrefix(String keyPrefix) {
    this.keyPrefix = keyPrefix;
}

public String getDeleteChannel() {
    return deleteChannel;
}

public void setDeleteChannel(String deleteChannel) {
    this.deleteChannel = deleteChannel;
}

public RedisManager getRedisManager() {
    return redisManager;
}

public void setRedisManager(RedisManager redisManager) {
    this.redisManager = redisManager;
}

public int getTimeToLiveSeconds() {
    return timeToLiveSeconds;
}

public void setTimeToLiveSeconds(int timeToLiveSeconds) {
    this.timeToLiveSeconds = timeToLiveSeconds;
}

}

public class RedisManager {
private static final Log logger = Logs.get();
private volatile boolean init = false;

private String configFile = "/config/custom/redis.properties";
private JedisCluster jedisPool;
private static final int MAX_NODE = 8;//最大支持节点数为8

public RedisManager() {
    init();
}

/**
 * Initializing jedis pool to connect to Jedis.
 */
public synchronized void init() {
    if (configFile != null && init == false) {
        PropertiesProxy properties = new PropertiesProxy(configFile);
        int timeout = properties.getInt("redis.timeout");
        int maxRedirections = properties.getInt("redis.maxRedirections");
        int maxTotal = properties.getInt("redis.maxTotal");
        int maxIdle = properties.getInt("redis.maxIdle");

        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxIdle(maxIdle);
        Set<HostAndPort> jedisClusterNodes = getHostAndPorts(properties);

        // 超时,最大的转发数,最大链接数,最小链接数都会影响到集群
        jedisPool = new JedisCluster(jedisClusterNodes, timeout, maxRedirections, config);

        init = true;
    }
}

private Set<HostAndPort> getHostAndPorts(PropertiesProxy properties) {
    Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
    HostAndPort hostAndPort;
    String host;
    int port;
    for (int i = 1; i <= MAX_NODE; i++) {
        host = properties.get("redis.host.node" + i);
        port = properties.getInt("redis.port.node" + i);
        if (StringUtils.isNotBlank(host) && port > 0) {
            hostAndPort = new HostAndPort(host, port);
            jedisClusterNodes.add(hostAndPort);
            logger.info("redis 集群添加节点:" + hostAndPort.toString());
        }
    }
    return jedisClusterNodes;
}

public String getConfigFile() {
    return configFile;
}

public void setConfigFile(String configFile) {
    this.configFile = configFile;
}

public JedisCluster getJedis() {
    if (jedisPool == null) {
        init();
    }
    return jedisPool;
}

/**
 * Get value from Redis
 *
 * @param key
 * @return
 */
public String get(String key) {
    logger.info("get key=" + key);
    return getJedis().get(key);
}

/**
 * Set value into Redis with default time to live in seconds.
 *
 * @param key
 * @param value
 */
public void set(String key, String value) {
    logger.info("set key=" + key);
    getJedis().set(key, value);
}

/**
 * Set value into Redis with specified time to live in seconds.
 *
 * @param key
 * @param value
 * @param timeToLiveSeconds
 */
public void setex(String key, String value, int timeToLiveSeconds) {
    logger.info("setex key=" + key);
    getJedis().setex(key, timeToLiveSeconds, value);
}

/**
 * Delete key and its value from Jedis.
 *
 * @param key
 */
public void del(String key) {
    logger.info("del key=" + key);
    getJedis().del(key);
}

/**
 * Get multiple values for the given keys.
 *
 * @param keys
 * @return
 */
public Collection<String> mget(String... keys) {
    if (keys == null && keys.length == 0) {
        Collections.emptySet();
    }
    return getJedis().mget(keys);
}

/**
 * Publish message to channel using subscribe and publish protocol.
 *
 * @param channel
 * @param value
 */
public void publish(String channel, String value) {
    getJedis().publish(channel, value);
}

}

shiro.ini配置
[main]

cacheManager

cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager.cacheManagerConfigFile=classpath:ehcache.xml

Session

sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager

---------------redis session conf start----------------

redisManager = cn.wizzer.common.redis.RedisManager
redisManager.configFile = /config/custom/redis.properties
sessionDAO = cn.wizzer.common.redis.RedisSessionDAO
sessionDAO.redisManager = $redisManager
sessionDAO.timeToLiveSeconds = 1800

---------------redis session conf end------------------

---------------local session conf start----------------

sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO

---------------local session conf end------------------

sessionDAO.cacheManager=$cacheManager

sessionDAO.activeSessionsCacheName=shiro-activeSessionCache
sessionManager.sessionDAO = $sessionDAO
securityManager.sessionManager = $sessionManager

现在已经出现异常了
2016-09-14 17:20:17,327 cn.wizzer.common.redis.RedisManager.get(RedisManager.java:91) INFO - get key=shiro_redis_session:aa0ca424-8e31-4093-abdb-2908e0e82c34
NULL
at org.beetl.core.statement.ForStatement.execute(ForStatement.java:83)
at org.beetl.core.statement.Program.execute(Program.java:70)
at org.beetl.core.engine.FilterProgram.execute(FilterProgram.java:31)
at org.beetl.core.Template.renderTo(Template.java:137)
at org.beetl.ext.tag.LayoutTag.render(LayoutTag.java:114)
at org.beetl.core.statement.TagStatement.runTag(TagStatement.java:108)
at org.beetl.core.statement.TagStatement.execute(TagStatement.java:87)
at org.beetl.core.statement.Program.execute(Program.java:70)
at org.beetl.core.engine.FilterProgram.execute(FilterProgram.java:31)
at org.beetl.core.Template.renderTo(Template.java:137)
at org.beetl.core.Template.renderTo(Template.java:103)
at org.beetl.ext.web.WebRender.render(WebRender.java:120)
at org.beetl.ext.nutz.BeetlView.render(BeetlView.java:28)
at org.nutz.mvc.impl.processor.ViewProcessor.process(ViewProcessor.java:66)
at org.nutz.mvc.impl.processor.AbstractProcessor.doNext(AbstractProcessor.java:44)
at org.nutz.mvc.impl.processor.MethodInvokeProcessor.process(MethodInvokeProcessor.java:28)
at org.nutz.mvc.impl.processor.AbstractProcessor.doNext(AbstractProcessor.java:44)
at org.nutz.mvc.impl.processor.AdaptorProcessor.process(AdaptorProcessor.java:33)
at org.nutz.mvc.impl.processor.AbstractProcessor.doNext(AbstractProcessor.java:44)
at org.nutz.mvc.impl.processor.ActionFiltersProcessor.process(ActionFiltersProcessor.java:58)
at org.nutz.mvc.impl.processor.AbstractProcessor.doNext(AbstractProcessor.java:44)
at cn.wizzer.common.processor.XssSqlFilterProcessor.process(XssSqlFilterProcessor.java:35)
at org.nutz.mvc.impl.processor.AbstractProcessor.doNext(AbstractProcessor.java:44)
at cn.wizzer.common.processor.NutShiroProcessor.process(NutShiroProcessor.java:52)
at org.nutz.mvc.impl.processor.AbstractProcessor.doNext(AbstractProcessor.java:44)
at org.nutz.mvc.impl.processor.ModuleProcessor.process(ModuleProcessor.java:123)
at org.nutz.mvc.impl.processor.AbstractProcessor.doNext(AbstractProcessor.java:44)
at org.nutz.mvc.impl.processor.EncodingProcessor.process(EncodingProcessor.java:27)
at org.nutz.mvc.impl.processor.AbstractProcessor.doNext(AbstractProcessor.java:44)
at org.nutz.mvc.impl.processor.UpdateRequestAttributesProcessor.process(UpdateRequestAttributesProcessor.java:15)
at org.nutz.mvc.impl.processor.AbstractProcessor.doNext(AbstractProcessor.java:44)
at cn.wizzer.common.processor.GlobalsSettingProcessor.process(GlobalsSettingProcessor.java:37)
at org.nutz.mvc.impl.processor.AbstractProcessor.doNext(AbstractProcessor.java:44)
at cn.wizzer.common.processor.LogTimeProcessor.process(LogTimeProcessor.java:21)
at org.nutz.mvc.impl.NutActionChain.doChain(NutActionChain.java:44)
at org.nutz.mvc.impl.ActionInvoker.invoke(ActionInvoker.java:67)
at org.nutz.mvc.ActionHandler.handle(ActionHandler.java:31)
at org.nutz.mvc.NutFilter.doFilter(NutFilter.java:198)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at cn.wizzer.common.filter.RouteFilter.doFilter(RouteFilter.java:34)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61)
at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)
at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:110)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:499)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Thread.java:745)
2016-09-14 17:20:17,342 org.beetl.ext.nutz.LogErrorHandler.processExcption(LogErrorHandler.java:32) DEBUG - null

05:20:17:表达式值为空(NULL):shiro 位于130行 资源:/layouts/private.html
127|
128| <%
129| var secondMenus=@shiro.getPrincipalProperty('secondMenus');
130| for(firstMenu in @shiro.getPrincipalProperty('firstMenus')){
131|
132| %>
========================
调用栈:
/layouts/private.html 行:130
/private/sys/home.html 行:2

2016-09-14 17:20:17,343 cn.wizzer.common.processor.LogTimeProcessor.process(LogTimeProcessor.java:26) DEBUG - [GET ]URI=/nutzwk/private/home 477ms
2016-09-14 17:20:17,880 cn.wizzer.common.redis.RedisSessionDAO.doReadSession(RedisSessionDAO.java:75) DEBUG - => Read session with ID aa0ca424-8e31-4093-abdb-2908e0e82c34

本站的源码就是例子... 链接在页脚.

你看EnterpriseCacheSessionDAO, 几乎什么都不干.

循环重定向的问题还是木有解决,悲剧啊。
远程调试的时候无法重现问题...本地跟代码的时候也看不出问题...
现在知道的是调用/private/home接口的时候通过SecurityUtils.getSubject();拿到的身份认证subject.isAuthenticated()是false

服务信息

两台ECA均衡负载
单机redis缓存session

现象:

1.配置两台机器负载权重50,50。这样基本就是循环重定向。
2.配置两台机器负载权重20,80 。可以减少循环重定向出现的概率,但是避免不了问题。
3.先配置机器负载权重0:100然后登陆,再重新配置机器负载权重100:0不需要重新登陆。
4.本地起服务然后登陆,关闭服务重新起一次不需要重新登陆。

实现方式(参考的nutz.cn):

使用ComboCacheManager替换了默认的CacheManager。
拷贝EnterpriseCacheSessionDAO替换CachingSessionDAO。

SessionBy注入

ShiroSessionProvider.class
````
# shiro.ini 配置

sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
sessionManager.sessionDAO = $sessionDAO
securityManager.sessionManager = $sessionManager

cacheManager_ehcache = org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager_ehcache.cacheManagerConfigFile=classpath:ehcache.xml
cacheManager_redis = cn.wizzer.common.shiro.cache.RedisCacheManager
cacheManager_redis.configFile = /config/custom/redis.properties
cacheManager = cn.wizzer.common.shiro.cache.ComboCacheManager
cacheManager.level1 = $cacheManager_ehcache
cacheManager.level2 = $cacheManager_redis
securityManager.cacheManager = $cacheManager

rememberMeCookie = org.apache.shiro.web.servlet.SimpleCookie
rememberMeCookie.name=remember
rememberMeCookie.maxAge = 604800
rememberMeCookie.httpOnly = true
rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager
rememberMeManager.cookie = $rememberMeCookie

sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
sha256Matcher.storedCredentialsHexEncoded = false
sha256Matcher.hashIterations = 1024
sha256Matcher.hashSalted = true

shiroDbRealm = cn.wizzer.common.shiro.realm.NutDaoRealm
shiroDbRealm.credentialsMatcher = $sha256Matcher

securityManager.realms = $shiroDbRealm
authcStrategy = cn.wizzer.common.shiro.authc.pam.AnySuccessfulStrategy
securityManager.authenticator.authenticationStrategy = $authcStrategy
securityManager.cacheManager = $cacheManager
securityManager.rememberMeManager = $rememberMeManager

authc = cn.wizzer.common.shiro.filter.SimpleAuthenticationFilter
authc.loginUrl = /private/login
logout.redirectUrl= /private/login

[urls]
/private/doLogin = anon
/assets/** = anon
/** = anon
/private/** = authc

```

@wendal 麻烦帮分析一下8楼的问题

shiro.ini 配置

sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
sessionManager.sessionDAO = $sessionDAO
securityManager.sessionManager = $sessionManager

cacheManager_ehcache = org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager_ehcache.cacheManagerConfigFile=classpath:ehcache.xml
cacheManager_redis = cn.wizzer.common.shiro.cache.RedisCacheManager
cacheManager_redis.configFile = /config/custom/redis.properties
cacheManager = cn.wizzer.common.shiro.cache.ComboCacheManager
cacheManager.level1 = $cacheManager_ehcache
cacheManager.level2 = $cacheManager_redis
securityManager.cacheManager = $cacheManager

rememberMeCookie = org.apache.shiro.web.servlet.SimpleCookie
rememberMeCookie.name=remember
rememberMeCookie.maxAge = 604800
rememberMeCookie.httpOnly = true
rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager
rememberMeManager.cookie = $rememberMeCookie

sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
sha256Matcher.storedCredentialsHexEncoded = false
sha256Matcher.hashIterations = 1024
sha256Matcher.hashSalted = true

shiroDbRealm = cn.wizzer.common.shiro.realm.NutDaoRealm
shiroDbRealm.credentialsMatcher = $sha256Matcher

securityManager.realms = $shiroDbRealm
authcStrategy = cn.wizzer.common.shiro.authc.pam.AnySuccessfulStrategy
securityManager.authenticator.authenticationStrategy = $authcStrategy
securityManager.cacheManager = $cacheManager
securityManager.rememberMeManager = $rememberMeManager

authc = cn.wizzer.common.shiro.filter.SimpleAuthenticationFilter
authc.loginUrl = /private/login
logout.redirectUrl= /private/login

[urls]
/private/doLogin = anon
/assets/** = anon
/** = anon
/private/** = authc

会不会是rememberMe导致的?

@wendal 呃...可以干掉试一下?

试啊... 尽量减少干预

我刚刚用论坛的代码测试一下, 环境如下:

win7 sp1 x64
tomcat 9.0.0.M9 分别位于D盘和E盘
redis 3.2 单机
mysql 5.7 单机

启动两个tomcat, 并使用nginx做简单均衡:

	upstream nutzbook {
		server 127.0.0.1:5080; # tomcat A
		server 127.0.0.1:6080; # tomcat B
	}

    server {
        listen       80;
        server_name  localhost;
        location / {
                        proxy_http_version 1.1;
                        client_max_body_size 1024m;
                        proxy_pass http://nutzbook;
                        proxy_set_header Host $http_host;
                        proxy_set_header        X-Real-IP       $remote_addr;
                        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
						proxy_set_header Upgrade $http_upgrade;
						proxy_set_header Connection $http_connection;
        }
    }

然后访问 http://127.0.0.1 登录, 刷新N次, 看到两个tomcat均有日志, 登录状态也没有变化.

@wendal刚试了一下把rememberMe相关的配置都去了,还是不行

rememberMeCookie = org.apache.shiro.web.servlet.SimpleCookie
rememberMeCookie.name=remember
rememberMeCookie.maxAge = 604800
rememberMeCookie.httpOnly = true
rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager
rememberMeManager.cookie = $rememberMeCookie
securityManager.rememberMeManager = $rememberMeManager

@wendal 这是抓取的包,有时候经常在login和home之间跳转

login:141 GET http://60.205.130.99/nutzwk/private/login net::ERR_TOO_MANY_REDIRECTSsuccess @ login:141options.success @ jquery.form.js:212j @ jquery-1.11.1.min.js:2fireWith @ jquery-1.11.1.min.js:2x @ jquery-1.11.1.min.js:4b @ jquery-1.11.1.min.js:4
Navigated to data:text/html,chromewebdata
home:1 GET http://60.205.130.99/nutzwk/private/home net::ERR_TOO_MANY_REDIRECTS
Navigated to data:text/html,chromewebdata
login:1 GET http://60.205.130.99/nutzwk/private/login net::ERR_TOO_MANY_REDIRECTS

@wendal 我是在nutzwk项目上增量开发实现集群的,然后搬的nuzt-book的cachemanager的代码。之前有人实现过nutzwk的集群吗?

@wendal 我们用的是nutzwk框架,NutDaoRealm和登入的controller没有修改,只是修改了session的缓存,炒的是nutzbook

我猜测的流程是这样:

假设每次请求都轮换服务器(50/50配置)

访问A机器的login链接, 得到Session X, 然后该Session是已经登录成功的,所以, 重定向到home链接

这时候,分配到B机器, 访问home链接,得到Session Y, 但 X != Y, 且Y是没有登录的Session, 所以又触发重定向到login链接

最后,因为是第三次访问,所以又跳到A机器, 得到Session X.

问题的关键是 X != Y 即两台机器得到的Session不一样(猜测).

所以呢, 首先,要验证一下home和login在两台机器上的session id是否一致.

如果一致,那么肯定是Session的持久化有问题. PS: Shiro的Session持久化必须用JDK原生的序列化,不可用第三方的!!!

如果不一致,那么就是CacheManager的问题了.

@wendal 兽总,很奇怪,home和login打印出来的session id多是空的,不清楚啥原因
2016-09-19 13:05:35,599 cn.wizzer.modules.back.sys.controllers.LoginController.login(LoginController.java:62) INFO - login session id=
2016-09-19 13:05:42,564 cn.wizzer.modules.back.sys.controllers.HomeController.home(HomeController.java:36) INFO - home session id=

打印代码
@At("")
@Ok("beetl:/private/sys/home.html")
@RequiresAuthentication
public void home() {
log.infof("home session id=", SecurityUtils.getSubject().getSession().getId());
//SecurityUtils.getSubject().getSession().setTimeout(1000);
}

    @At("")
@Ok("re")
@Filters
public String login() {
    Subject subject = SecurityUtils.getSubject();
    log.infof("login session id=",subject.getSession().getId());
    if (subject.isAuthenticated()) {
        return "redirect:/private/home";
    } else {
        return "beetl:/private/sys/login.html";
    }
}

序列化的问题,检查session序列化到redis的代码

来自炫酷的 NutzCN

@wendal RedisCache这个是拿的是nutzbook的代码 代码如下,用的是jdk的序列化

@Override
public V get(K key) throws CacheException {
    if (DEBUG)
        log.infof("GET name=%s key=%s", name, key);
    try (Jedis jedis = _pool().getResource()) {
        byte[] buf = jedis.hget(nameByteArray, key.toString().getBytes());
        if (buf == null)
            return null;
        return (V) toObject(buf);
    }
}


    @Override
public V put(K key, V value) throws CacheException {
    if (DEBUG)
        log.infof("SET name=%s key=%s", name, key);
    // TODO 应使用pipeline
    //V prev = get(key);
    try (Jedis jedis = _pool().getResource()) {
        jedis.hset(nameByteArray, key.toString().getBytes(), toByteArray(value));
        return null;
    }
}


 public static final byte[] toByteArray(Object obj) {
    try {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(out);
        oos.writeObject(obj);
        oos.flush();
        oos.close();
        return out.toByteArray();
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }    }

public static final Object toObject(byte[] buf) {
    try {
        ByteArrayInputStream ins = new ByteArrayInputStream(buf);
        ObjectInputStream ois = new ObjectInputStream(ins);
        return ois.readObject();
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

贴当前的shiro.ini

@wendal
`````````

Session

sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager

Session Cache

sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
sessionManager.sessionDAO = $sessionDAO
securityManager.sessionManager = $sessionManager

cacheManager_ehcache = org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager_ehcache.cacheManagerConfigFile=classpath:ehcache.xml
cacheManager_redis = cn.wizzer.common.shiro.cache.RedisCacheManager
cacheManager_redis.configFile = /config/custom/redis.properties
cacheManager = cn.wizzer.common.shiro.cache.ComboCacheManager
cacheManager.level1 = $cacheManager_ehcache
cacheManager.level2 = $cacheManager_redis
securityManager.cacheManager = $cacheManager

Cookie

sessionIdCookie=org.apache.shiro.web.servlet.SimpleCookie
sessionIdCookie.name=sid

sessionIdCookie.domain=wizzer.cn

sessionIdCookie.path=

sessionIdCookie.maxAge=946080000
sessionIdCookie.httpOnly=true
sessionManager.sessionIdCookie=$sessionIdCookie
sessionManager.sessionIdCookieEnabled=true
sessionManager.globalSessionTimeout=3600000

bak...

sessionManager=org.apache.shiro.web.session.mgt.DefaultWebSessionManager

sessionListener1 = cn.wizzer.common.shiro.listener.MySessionListener

sessionManager.sessionListeners = $sessionListener1

sessionManager.globalSessionTimeout=50000

securityManager.sessionManager=$sessionManager

rememberMeCookie = org.apache.shiro.web.servlet.SimpleCookie
rememberMeCookie.name=remember
rememberMeCookie.maxAge = 604800
rememberMeCookie.httpOnly = true
rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager
rememberMeManager.cookie = $rememberMeCookie

sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
sha256Matcher.storedCredentialsHexEncoded = false
sha256Matcher.hashIterations = 1024
sha256Matcher.hashSalted = true

shiroDbRealm = cn.wizzer.common.shiro.realm.NutDaoRealm
shiroDbRealm.credentialsMatcher = $sha256Matcher

securityManager.realms = $shiroDbRealm
authcStrategy = cn.wizzer.common.shiro.authc.pam.AnySuccessfulStrategy
securityManager.authenticator.authenticationStrategy = $authcStrategy
securityManager.cacheManager = $cacheManager
securityManager.rememberMeManager = $rememberMeManager

authc = cn.wizzer.common.shiro.filter.SimpleAuthenticationFilter
authc.loginUrl = /private/login
logout.redirectUrl= /private/login

[urls]
/private/doLogin = anon
/assets/** = anon
/** = anon
/private/** = authc

````````````

@wendal

#Session
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager

# Session Cache
sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
sessionManager.sessionDAO = $sessionDAO
securityManager.sessionManager = $sessionManager

cacheManager_ehcache = org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager_ehcache.cacheManagerConfigFile=classpath:ehcache.xml
cacheManager_redis = cn.wizzer.common.shiro.cache.RedisCacheManager
cacheManager_redis.configFile = /config/custom/redis.properties
cacheManager = cn.wizzer.common.shiro.cache.ComboCacheManager
cacheManager.level1 = $cacheManager_ehcache
cacheManager.level2 = $cacheManager_redis
securityManager.cacheManager = $cacheManager


# Cookie
sessionIdCookie=org.apache.shiro.web.servlet.SimpleCookie
sessionIdCookie.name=sid
#sessionIdCookie.domain=wizzer.cn
#sessionIdCookie.path=
sessionIdCookie.maxAge=946080000
sessionIdCookie.httpOnly=true
sessionManager.sessionIdCookie=$sessionIdCookie
sessionManager.sessionIdCookieEnabled=true
sessionManager.globalSessionTimeout=3600000

#bak...
#sessionManager=org.apache.shiro.web.session.mgt.DefaultWebSessionManager
#sessionListener1 = cn.wizzer.common.shiro.listener.MySessionListener
#sessionManager.sessionListeners = $sessionListener1
#sessionManager.globalSessionTimeout=50000
#securityManager.sessionManager=$sessionManager

rememberMeCookie = org.apache.shiro.web.servlet.SimpleCookie
rememberMeCookie.name=remember
rememberMeCookie.maxAge = 604800
rememberMeCookie.httpOnly = true
rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager
rememberMeManager.cookie = $rememberMeCookie

sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
sha256Matcher.storedCredentialsHexEncoded = false
sha256Matcher.hashIterations = 1024
sha256Matcher.hashSalted = true

shiroDbRealm = cn.wizzer.common.shiro.realm.NutDaoRealm
shiroDbRealm.credentialsMatcher = $sha256Matcher

securityManager.realms = $shiroDbRealm
authcStrategy = cn.wizzer.common.shiro.authc.pam.AnySuccessfulStrategy
securityManager.authenticator.authenticationStrategy = $authcStrategy
securityManager.cacheManager = $cacheManager
securityManager.rememberMeManager = $rememberMeManager

authc = cn.wizzer.common.shiro.filter.SimpleAuthenticationFilter
authc.loginUrl  = /private/login
logout.redirectUrl= /private/login

[urls]
/private/doLogin    = anon
/assets/**          = anon
/**                 = anon
/private/**         = authc

@qq_d6c9c504 sessionIdCookie怎么还有其他域名的信息?干掉

来自炫酷的 NutzCN

@wendal 你看下后面发的,已经注释掉了

为啥rememberMeCookie还在

来自炫酷的 NutzCN

@wendal 刚才我们干掉了也不行,后来我加日志发了一下覆盖了,我们在干掉试试

@wendal 干掉了也不行,还是一样sessionid为空


#Session sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager # Session Cache sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO sessionManager.sessionDAO = $sessionDAO securityManager.sessionManager = $sessionManager cacheManager_ehcache = org.apache.shiro.cache.ehcache.EhCacheManager cacheManager_ehcache.cacheManagerConfigFile=classpath:ehcache.xml cacheManager_redis = cn.wizzer.common.shiro.cache.RedisCacheManager cacheManager_redis.configFile = /config/custom/redis.properties cacheManager = cn.wizzer.common.shiro.cache.ComboCacheManager cacheManager.level1 = $cacheManager_ehcache cacheManager.level2 = $cacheManager_redis securityManager.cacheManager = $cacheManager # Cookie sessionIdCookie=org.apache.shiro.web.servlet.SimpleCookie sessionIdCookie.name=sid #sessionIdCookie.domain=wizzer.cn #sessionIdCookie.path= sessionIdCookie.maxAge=946080000 sessionIdCookie.httpOnly=true sessionManager.sessionIdCookie=$sessionIdCookie sessionManager.sessionIdCookieEnabled=true sessionManager.globalSessionTimeout=3600000 #bak... #sessionManager=org.apache.shiro.web.session.mgt.DefaultWebSessionManager #sessionListener1 = cn.wizzer.common.shiro.listener.MySessionListener #sessionManager.sessionListeners = $sessionListener1 #sessionManager.globalSessionTimeout=50000 #securityManager.sessionManager=$sessionManager #rememberMeCookie = org.apache.shiro.web.servlet.SimpleCookie #rememberMeCookie.name=remember #rememberMeCookie.maxAge = 604800 #rememberMeCookie.httpOnly = true #rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager #rememberMeManager.cookie = $rememberMeCookie sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher sha256Matcher.storedCredentialsHexEncoded = false sha256Matcher.hashIterations = 1024 sha256Matcher.hashSalted = true shiroDbRealm = cn.wizzer.common.shiro.realm.NutDaoRealm shiroDbRealm.credentialsMatcher = $sha256Matcher securityManager.realms = $shiroDbRealm authcStrategy = cn.wizzer.common.shiro.authc.pam.AnySuccessfulStrategy securityManager.authenticator.authenticationStrategy = $authcStrategy securityManager.cacheManager = $cacheManager #securityManager.rememberMeManager = $rememberMeManager authc = cn.wizzer.common.shiro.filter.SimpleAuthenticationFilter authc.loginUrl = /private/login logout.redirectUrl= /private/login [urls] /private/doLogin = anon /assets/** = anon /** = anon /private/** = authc

对比了一下nutzcn的shiro.ini, 似乎你们除了sessionIdGenerator之后都一样了

@wendal 是的,我们整了好几天也没发现有啥问题,登入进去之后,发现有时候使用里面的功能会重定向,大部分时候功能使用的时候不会重定向

加一下QQ群: 68428921

添加回复
请先登陆
回到顶部