Rails 经过五一仍未搞清这里为什么 Session 为空-.-

douya0808 · 2012年05月02日 · 最后由 hexawing 回复于 2013年03月06日 · 7023 次阅读
@account=Account.find_by_username_and_password(@username,@password)
    if @account
      session[:account]=@account#保存登录用户信息,再根据权限跳转相应页面

      redirect_to :controller=>"teacher",:action => "teacher" if @account.power==1
      redirect_to :controller=>"student",:action => "student" if @account.power==2


student 登录后在 student.html.erb 中可以正常访问到 session

<%=session[:account]%>


student.html.erb 中有个 ext 的改密码按钮,请求路径为

url:'/student/changepw'


但是我在 def changepw 中却取不到 session 了

session[:account]


为空

这是为什么呢?是 rails 中的 session 和以前的 java 有区别还是我理解错了 session 的本意呢?:)

Ext 改密码,这个表单渲染在 student.html.erb 的一个 div 标签中

var password=new Ext.form.FormPanel({
     defaultType:'textfield',
     title:'更改密码',
     frame:true,  
     url:'/student/changepw',      
     items:[{name:'op',fieldLabel:'原密码',allowBlank:false,inputType:"password"},
            {name:'np1',fieldLabel:'新密码',allowBlank:false,inputType:"password"},
            {name:'np2',fieldLabel:'确认密码',allowBlank:false,inputType:"password"},
            {
            xtype:'button',
            text:'修改密码',
            handler : function() {
                    password.getForm().submit({
                    success : function(password, action) { Ext.Msg.alert(action.result.msg1, action.result.msg2); },
                    failure : function() { Ext.Msg.alert('错误', '程序存在异常'); }
                    });  
                    }
           }]     
   });


url 转发了一下请求 在 changepw 这个方法就读不到 session 了 费解


def changepw
    @op=params[:op]#原密码    
    @np1=params[:np1]#新密码1  
    @np2=params[:np2]#新密码2  

    if session[:account]
      @test='OK'
    else
      @test='empty'
    end

    render :text=>"{success:true,msg1:'"+@test+"',msg2:'"+@test+"'}"
 end#函数结尾


Ext 的 alert 一直蹦的是 empty-.- 明明是在一个请求过程中的,为什么我的 session 么有了呢

看不到所有代码和实际环境,估计大家很难帮到你 提供个思路给你: 向 session 中写的这个对象实际上是被序列化以后放到了 cookie 中 建议你安装 http 抓包工具 (比如 http watch 或者 chrome 自带的),抓取 http 包,分析里面的 cookie 头,看看什么情况 cookie 字符串你只需要取出--前面的部分然后反 base64 即可看到明文了

你把整个 student 的 obj 存进 cookie,觉得不是太好,最好是存一个 id 就可以了,避免有 cookie 撑爆的情况。 tail 出 log 来看看有没有报错。

有诚意的话放整段代码,要不然就自己找 bug

在公司带我的两个老师说应该是 Ext 重新发送请求的时候相当于又开了一个浏览器 (因为他们说 ext 的界面都是在 div 的层上的),所以我的 session 没了 但是他们说他们 50% 确定-.-当然那两个老师都是搞 java 的不会 rails 不也就不好问是不是 rails 和 java 的区别了

登录是用 Ext 作的界面

Ext.onReady(function() {

    var form = new Ext.form.FormPanel( {
        labelAlign : 'right',
        title : '用户登录',
        labelWidth : 50,
        buttonAlign : 'center',
        frame : true,
        width : 220,
        items : [ {
            xtype:'textfield',
            fieldLabel : '用户',
            name : 'text1'   
               },{
            xtype:'textfield',
            inputType:"password",
            fieldLabel : '密码',
            name : 'text2'   
               }],
        buttons : [ {
            text : '登录',
            handler : function() {
                 form.getForm().getEl().dom.action='/login/login';
                 form.getForm().getEl().dom.submit();
                }
                } ]
    });
    form.render("form");
});

Login 的 controller

class LoginController < ApplicationController
  def index #显示登录页
  end

  def login
    @username=params[:text1]
    @password=params[:text2]
    @account=Account.find_by_username_and_password(@username,@password)
    if @account
      session[:account]=@account#保存登录用户信息,再根据权限跳转相应页面

      redirect_to :controller=>"teacher",:action => "teacher" if @account.power==1
      redirect_to :controller=>"student",:action => "student" if @account.power==2
    else
      render :action => "error"
    end
  end

  def error#显示错误页面
  end
end

Student的controller
class StudentController < ApplicationController
  def student#学生的默认界面student.html.erb
  end


def changepw
    @op=params[:op]#原密码
    @np1=params[:np1]#新密码1  
    @np2=params[:np2]#新密码2  
  if session[:account]
      @test='OK'      #我把业务代码都删除专门用这段判断出我的session没有值
    else                  #因为我Ext的对话框一直蹦的是empty
      @test='empty'
    end

    render :text=>"{success:true,msg1:'"+@test+"',msg2:'"+@test+"'}"
 end
end

Ext 改密码的表单 渲染在 student.html.erb 页面的 div 中

var password=new Ext.form.FormPanel({
     defaultType:'textfield',
     title:'更改密码',
     frame:true,  
     url:'/student/changepw',      
     items:[{name:'op',fieldLabel:'原密码',allowBlank:false,inputType:"password"},
            {name:'np1',fieldLabel:'新密码',allowBlank:false,inputType:"password"},
            {name:'np2',fieldLabel:'确认密码',allowBlank:false,inputType:"password"},
            {
            xtype:'button',
            text:'修改密码',
            handler : function() {
                    password.getForm().submit({
                    success : function(password, action) { Ext.Msg.alert(action.result.msg1, action.result.msg2); },
                    failure : function() { Ext.Msg.alert('错误', '程序存在异常'); }
                    });  
                    }
           }]     
   });


麻烦前辈再给瞅瞅:) 都是一个 request 下来的 只不过登录后进行改密码,改密码的时候在 Ext 里写的 url 进行重定位,结果到重定位的方法里就得不到 session 了

form 发出去的是 post 请求吧,rails 中的 post 请求默认都是需要 crsf_token 的,如果用 extjs 构造的 form,很可能没有 token 值。而 rails 在 post 请求中找不到 token 的话会重置 session。

@cantin 哦?嘿嘿 我都不知道 crsf_token 是啥 我得去百度一下

你可以去看 railsguides 的安全那一章 #10 楼 @douya0808

@cantin 多谢前辈 问题和您说的一样,是 crsf_token 的问题,在提交一个 ajax 请求的时候,当请求方式为 post 的时候,rails 如果开启了 protect_from_forgery。那么在提交的 post 数据中就必须加入 token 这个字段来通过 rails 的后端验证。 在 rails3.0.4 以前的版本,没有提交 token 字段会报错,但是在以后的版本里面,rails 会自动重置 session 即 session 丢失。 我把 application_controller.rb 中的 protect_from_forgery 去掉就可以得到之前的 session 了 但我知道这也不是个办法:) 继续探索中

-.-好吧 一时也找不到解决办法了 就先去掉 protect_from_forgery 凑合着用

class ApplicationController < ActionController::Base

  protect_from_forgery # 这里不用动

end

class PostsController < ApplicationController

  protect_from_forgery :except => :upload # 在这里加一句

end

之前也遇到这个问题,ajax 上传导致用户登录状态丢失

@lolychee 嘿嘿 谢谢 这是个好办法

没有用过 EXTJS 但是 RAILS.JS 里对于:method => :remote 的 LINK 是这样做的

var csrf_token = $('meta[name=csrf-token]').attr('content'),
    csrf_param = $('meta[name=csrf-param]').attr('content');

$('a[data-method]:not([data-remote])').live('click', function (e){
    var link = $(this),
        href = link.attr('href'),
        method = link.attr('data-method'),
        form = $('<form method="post" action="'+href+'"></form>'),
        metadata_input = '<input name="_method" value="'+method+'" type="hidden" />';

    if (csrf_param != null && csrf_token != null) {
      metadata_input += '<input name="'+csrf_param+'" value="'+csrf_token+'" type="hidden" />';
    }

    form.hide()
        .append(metadata_input)
        .appendTo('body');

    e.preventDefault();
    form.submit();
});

或许你可以在 EXTJS 里模仿一下

@raven 多谢啊 这是源码吗?看不太懂 我去研究研究

@douya0808 就是你项目中会引用到的rails.js文件。在public/javascripts下能找到

应该有两种方法,一是可以在 post 发过去的参数中加上 token 值,在 layouts/application.html.erb 中的 csrf_meta_tag 就是在 head 中加入一个 meta 标签,其中存放了 token 的值,可以使用 js 取得它的值,然后随着 post 请求发过去(可以参考其他 form 提交时发过去的参数),二是在 post 请求的 header 里面设置这个 token,这个方法我没使用过,网上应该是可以找得到的。

ps:我打错了好像,应该是 csrf,不好意思哈

#14 楼 @lolychee 用了这个办法。十分感谢!

需要 登录 后方可回复, 如果你还没有账号请 注册新账号