通过IP访问服务器session失效,计算机名则可以,什么原因?

IP访问异常
开发者使用SpringMVC+Spring+iBatis架构遇到问题,通过IP访问时登录后点击任意链接会退出系统,而通过计算机名称访问正常。涉及session管理和拦截器配置。
最近开发一个新项目采用Spring MVC+Spring+iBatis结构,出现了一个奇怪的现象,通过计算机名称可以正常访问,并且一切正常。而通过IP访问则会出现一个奇怪的现象:
登录系统后随便点哪个链接都会退出系统。下面贴出我的部分代码:

[b]配置文件[/b]
[code]<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="encodingInterceptor" class="com.cm.web.interceptor.EncodingInterceptor"/>
<bean id="securityInterceptor" class="com.cm.web.interceptor.SecurityInterceptor"/>
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView">
<value>error</value>
</property>
<property name="exceptionMappings">
<props>
<prop key="java.lang.Exception">error</prop>
</props>
</property>
</bean>

<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename"><value>cm</value></property>
<property name="defaultParentView"><value>modelView</value></property>
</bean>
<!--Not Need Logon.......................-->
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list>
<ref local="encodingInterceptor"/>
</list>
</property>
<property name="mappings">
<props>
<prop key="/login.do">accountController</prop>
<prop key="/loginproc.do">accountController</prop>
<prop key="/passwordmod.do">accountController</prop>
<prop key="/passmodprocess.do">accountController</prop>
<prop key="/forgetpassword.do">accountController</prop>
<prop key="/forgetpasswordprocess.do">accountController</prop>
</props>
</property>
</bean>
<!--Need Logon....................................-->
<bean id="securityUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list>
<ref local="encodingInterceptor"/>
<ref local="securityInterceptor"/>
</list>
</property>
<property name="mappings">
<props>
<prop key="/index.do">indexController</prop>
<prop key="/resource/resourceIndex.do">indexController</prop>
<prop key="/department/departmentMember.do">indexController</prop>
<prop key="/index/linkmanGcheng.do">indexController</prop>
<prop key="/index/linkmanJsdw.do">indexController</prop>
<prop key="/index/linkmanXy.do">indexController</prop>
<prop key="/index/customerXyIndex.do">indexController</prop>
<prop key="/index/customerDZD.do">indexController</prop>
</props>
</property>
</bean>

<bean id="cmMethodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver">
<property name="mappings">
<props>
<prop key="/login.do">handleLoginView</prop>
<prop key="/loginproc.do">handleLoginProcess</prop>
<prop key="/index.do">handleIndex</prop>
<prop key="/passwordmod.do">handleModifyPasswordView</prop>
<prop key="/passmodprocess.do">handleModifyPasswordProcess</prop>
<prop key="/forgetpassword.do">handelForgetPassword</prop>
<prop key="/forgetpasswordprocess.do">handleForgetPasswordProcess</prop>
<prop key="/resource/resourceIndex.do">handelIndexResource</prop>
<prop key="/department/departmentMember.do">handleDepartmentMember</prop>
<prop key="/index/linkmanGcheng.do">handleLinkmanGcheng</prop>
<prop key="/index/linkmanJsdw.do">handleLinkmanJsdw</prop>
<prop key="/index/linkmanXy.do">handleLinkmanXy</prop>
<prop key="/index/customerXyIndex.do">handleCustomerXyIndex</prop>
<prop key="/index/customerDZD.do">handleIndexCustomerDZD</prop>
</props>
</property>
</bean>

<bean id="accountController" class="com.cm.web.controller.AccountController">
<property name="methodNameResolver"><ref local="cmMethodNameResolver"/></property>
<property name="userService"><ref bean="userService"/></property>
</bean>

<bean id="indexController" class="com.cm.main.web.controller.IndexController">
<property name="methodNameResolver"><ref local="cmMethodNameResolver"/></property>
<property name="generalService"><ref bean="generalService"/></property>
<property name="userService"><ref bean="userService"/></property>
</bean>


</beans>[/code]

[b]登录代码[/b]

[code]
public class AccountController extends AbstractSuperController {

//登录系统代码
public ModelAndView handleLoginProcess(HttpServletRequest request,
HttpServletResponse response) throws ServletException {
try {
User user = new User();
String username = request.getParameter("username");
String password = request.getParameter("password");
user.setName(username);
user.setPassword(password);
user = userService.selectUserByUsernameAndPassword(user);
if (null != user) {
log.info("User " + user.getName() + " Department "
+ user.getCmdeptname() + " logon...................");
HttpSession session = request.getSession(true);
session.setMaxInactiveInterval(3600);
//WebUtils.setSessionAttribute(request, "user", user);
session.setAttribute("user", user);
Cookie cookie = new Cookie("username", encode(username));
cookie.setMaxAge(60 * 60 * 24 * 365);
response.addCookie(cookie);
return new ModelAndView(new RedirectView("/cm/index.do"));
}
} catch (Exception e) {
log.error("", e);
}
return new ModelAndView(new RedirectView("/cm/login.do"));
}
}[/code]


[b]拦截器代码[/b]
[code]public class SecurityInterceptor extends HandlerInterceptorAdapter {
protected final Log log = LogFactory.getLog(SecurityInterceptor.class);
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {


// User user = (User) WebUtils.getSessionAttribute(request, "user");
// if (user == null) {
// ModelAndView modelAndView = new ModelAndView(new RedirectView(
// "/cm/login.do"));
// log.info("user is
// logout.............................................");
// throw new ModelAndViewDefiningException(modelAndView);
// } else {
// return true;
// }
ModelAndView modelAndView = new ModelAndView(new RedirectView(
"/cm/login.do"));
HttpSession session = request.getSession(false);
if (null == session)
{
throw new ModelAndViewDefiningException(modelAndView);
}
User user = (User) session.getAttribute("user");
if (user == null) {
log.info("#user is logout.............");
throw new ModelAndViewDefiningException(modelAndView);
}
return true;

}
}[/code]

因为我在拦截器中配置session失效则自动到首页。
在login.jsp页面写如下代码:

[code]<%
java.util.Enumeration headers = request.getHeaderNames();
while(headers.hasMoreElements())
{
String headName = (String)headers.nextElement();
String headValue = request.getHeader(headName);
out.println(headName+"="+headValue+"<br>");
}
%>[/code]

[b]打印出的结果如下:[/b]

[code]JSESSIONID=FKyzH2hmpdjJ2vZrZjY0HvM68K4FnvnDTrwLJm4ZT0hwR121tCCS!-401940707
JSESSIONID=FKyF42pkgshMDlKzPnv6tP196b2wrGqJH1Q4wVrhBTJSj16JnJm3!-401940707
JSESSIONID=FKyhDPvVrZ2zJfJkyCNQLTyDy2QBZ2ym6ZhsPJKGVQw19KxZm1Jm!-401940707[/code]

只贴出了JSESSIONID的部分,[color=red]我奇怪的是为什么浏览器会发送不同的JSESSIONID.
而通过计算机名称访问则没有任何问题。另外在本机部署一个老的应用用Struts开发,无论通过IP还是计算机名访问都无问题,第一次碰到这种问题,束手无策,另外声明我在web.xml中没有进行任何有关session的配置。[/color]
<think>我们正在讨论为未登录用户生成唯一ID的方案,用户询问使用express-session的saveUninitialized属性是否可行。 首先,回顾express-session的工作机制: - 当启用express-session中间件时,它会为每个请求创建一个session对象(req.session) - 默认情况下,只有在session被修改时(即给req.session添加属性或修改属性值),才会在响应时自动设置一个包含session ID的cookie(并保存session到存储中) - 选项`saveUninitialized`:如果设置为true,则无论session是否被修改,只要请求中没有session cookie,就会创建一个新的session,并设置cookie。 因此,使用`saveUninitialized: true`可以确保每个新访问的用户(未携带session cookie)都会立即获得一个session ID(存储在cookie中)。这个session ID就可以作为唯一标识。 但是,我们需要考虑以下几点: (1)方案对比: 之前的方案(基于IP+UA生成哈希ID)和session ID方案的区别: - 之前的方案:基于用户的信息(IP+UA)生成,相同的信息生成相同的ID(但IP可能变化,UA也可能变化)。 - session ID方案:完全随机生成的唯一ID(通常使用UUID),与用户信息无关,存储在cookie中。 (2)express-session的`saveUninitialized`选项的作用: 如果设置`saveUninitialized: true`,那么每个新用户(没有session cookie)都会立即创建一个session(即使你还没有往session里存任何东西)。这会导致: - 优点:可以立即获得一个唯一ID(通过req.sessionID) - 缺点:可能会创建大量未使用的session(因为用户可能只是访问一下,没有交互),占用服务器内存(或存储空间) (3)隐私和合规性: 使用session ID需要设置cookie(通常为connect.sid),这需要符合GDPR等法规(需要用户同意?)。但注意,我们的目的是匿标识,并不存储个人数据。 (4)与IP+UA方案集成: 我们也可以结合两者:使用express-session生成session ID,但同时在session中存储我们生成的IP+UA哈希(或者单纯使用session ID)。这样,即使session过期,我们也可以通过重新生成相同的IP+UA哈希来恢复同一个用户?但是,IP和UA可能会变,所以这个策略并不完美。 结论:使用express-session的saveUninitialized属性是可行的,但需要注意以下事项: 1. 使用方式: ```javascript const session = require('express-session'); app.use(session({ secret: 'your_secret', resave: false, saveUninitialized: true, // 关键:为每个新请求创建session cookie: { maxAge: 30 * 24 * 60 * 60 * 1000, // 例如30天 httpOnly: true, secure: process.env.NODE_ENV === 'production' } })); ``` 然后,在中间件或路由中,我们可以通过`req.sessionID`来获取这个唯一标识。 2. 注意事项: - 存储开销:启用saveUninitialized:true会导致为每个新用户创建session存储条目(即使没有数据),如果使用内存存储(默认),可能会占用大量内存。在生产环境中,应该配置持久化存储(如Redis,MongoDB等)。 - 性能:每次新会话都需要生成ID并存储,可能会增加一些开销(但通常可以接受)。 - 用户识别:这个ID存储在cookie中,如果用户清除cookie或使用隐私模式,就会丢失。但IP+UA方案也会因为切换网络或设备而改变。 3. 如何获取ID并传递给模板? 在中间件或路由中: ```javascript app.use((req, res, next) => { // 此时req.sessionID已经存在(因为session中间件已经处理过) res.locals.anonymousId = req.sessionID; // 使得在模板中可用 next(); }); ``` 然后在Handlebars模板中可以直接使用`{{anonymousId}}`。 4. 与IP+UA方案的结合: 如果我们希望在某些情况下(例如session过期后)还能通过IP+UA恢复相同的标识,可以在session中存储一个自定义生成的ID(基于IP+UA的哈希),但这样会引入之前方案的缺点(相同的网络环境下的多个用户会被认为是同一个)。所以,通常建议使用session ID作为唯一标识即可。 5. 安全性:session ID是随机生成的,比基于IP+UA的哈希更安全(因为IP和UA可能被伪造,但session ID的随机性更强)。同时,由于cookie设置了httpOnly,无法被客户端JavaScript读取,减少了XSS攻击窃取的风险。 综合来看,使用express-session的saveUninitialized属性生成唯一ID是可行的,并且是更常见的做法(因为session管理是Web开发的标准实践),但需要注意存储配置。 然而,如果我们的应用不需要在服务器端存储任何与用户相关的数据(除了一个ID),那么使用session存储可能会造成浪费。在这种情况下,可以仅使用一个cookie存储自定义生成的ID(如之前方案),而不使用express-session。 两种方案的比较: | 方案 | 优点 | 缺点 | |--------------------|----------------------------------------|-------------------------------------------------------| | express-session | 标准做法,有成熟机制;ID随机且唯一性高 | 需要session存储,可能导致存储开销;必须配置cookie | | IP+UA哈希 | 无需存储,计算即可得到;无cookie要求 | 可能重复(相同网络环境多用户);不稳定(IP/UA变化) | 因此,如果您已经使用了express-session(例如用于登录用户),那么利用它来生成匿ID是合理的。否则,您需要评估是否值得仅为生成一个匿ID而引入整个session机制。 建议:如果应用规模较小,且不希望引入额外的存储,可以使用之前的IP+UA方案(配合cookie持久化ID)。如果应用规模较大,并且预期会有登录用户的需求,建议使用express-session方案。 最后,关于您之前提到的引用[1],它描述了session的工作机制,包括生成session ID并设置cookie,这正是我们这里讨论的。 因此,回答用户的问题:使用express-session的saveUninitialized属性是可行的,并且是一种标准做法。 相关问题: 1. 如何为express-session配置Redis存储以降低内存占用? 2. 如果用户禁用Cookie,如何实现未登录用户的唯一标识? 3. express-sessionsession ID生成算法是什么?如何确保唯一性? </think>### 使用 `express-session` 的 `saveUninitialized` 属性是否可行? **可行,但需谨慎评估适用场景**。以下是具体分析和建议: #### 1. `saveUninitialized` 的工作机制 ```javascript app.use(session({ secret: 'your_secret_key', resave: false, saveUninitialized: true, // 关键属性 cookie: { maxAge: 30*24*60*60*1000 } // 30天有效期 })) ``` - ✅ **当 `saveUninitialized: true` 时**: - 所有新访问的用户(无 session cookie)会立即生成唯一 session ID - 服务器会自动设置包含 `connect.sid` 的 Cookie - 无需手动处理标识生成逻辑(对比 IP+UA 方案更简便) - ❗ **核心特性**: - 生成的 ID 是**完全随机的 UUID**(例:`d3b07384d113edec49eaa6238ad5ff00`) - **不与用户特征(IP/UA)绑定**,而是依赖 Cookie 持久化 #### 2. 与 IP+UA 方案的对比 | **特性** | `saveUninitialized` 方案 | IP+UA 哈希方案 | |------------------------|----------------------------------|---------------------------| | 标识生成方式 | 服务器随机生成 UUID | 基于用户特征计算哈希 | | 跨设备/网络一致性 | ❌ (Cookie 清除即丢失) | ⚠️ (IP/UA 变化则失效) | | 存储开销 | 需 session 存储空间 | 无状态,零存储 | | GDPR 合规风险 | 较低(匿随机ID) | 中(涉及设备指纹) | | 实现复杂度 | 低(自动完成) | 中(需处理代理/IP获取) | #### 3. 推荐的适用场景 ✅ **适合以下情况**: - 需要跟踪短期用户行为(如购物车) - 已在使用 `express-session` 管理登录态 - 允许用户标识在清除 Cookie 后重置 ❌ **不适合以下情况**: - 需要长期跨会话的稳定标识(如分析工具) - 无法承担 session 存储开销(高流量场景) - 需避免设置 Cookie(合规要求严格时) #### 4. 优化实践建议 ```javascript // 中间件示例:获取匿ID app.use((req, res, next) => { if (!req.session.anonymousId) { // 可混合增强:IP前两段 + UA哈希头 const ipSegment = req.ip.split('.').slice(0,2).join('.'); const uaHash = crypto.createHash('sha1').update(req.headers['user-agent']).digest('hex').slice(0,8); req.session.anonymousId = `${ipSegment}_${uaHash}_${req.sessionID.slice(0,8)}`; } res.locals.anonymousId = req.session.anonymousId; // 供Handlebars使用 next(); }); ``` #### 5. 关键注意事项 1. **存储压力**:启用后会产生大量空 session 记录,需配置持久化存储(如 Redis)[^1] ```javascript const RedisStore = require('connect-redis')(session); app.use(session({ store: new RedisStore() })) ``` 2. **隐私合规**: - 在欧盟地区需在 Cookie Banner 中声明用途 - 避免将 session ID 与个人敏感数据关联 3. **安全加固**: ```javascript cookie: { secure: true, // 仅HTTPS sameSite: 'Lax', // 防CSRF httpOnly: true // 防XSS读取 } ``` > **结论**:在已使用 session 体系且不要求长期标识的场景下,`saveUninitialized: true` 是简洁有效的方案;如需更持久的跨会话标识,建议采用 **IP+UA 哈希为主 + Session 为辅**的混合策略[^5]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值