基于Devise实现两个共享数据库的Rails应用跨应用用户认证
解决方案
方案1:基于共享密钥的请求签名(最适配共享DB场景)
既然两个应用共享同一个数据库,直接用用户ID + 共享密钥 + 时间戳生成签名即可,无需复杂的JWT配置:
在两个应用的配置文件(比如
config/secrets.yml或环境变量)里设置完全相同的共享密钥:# config/secrets.yml shared_auth_secret: "your_secure_unique_secret_here"发起请求的应用生成签名并携带参数:
user_id = current_user.id timestamp = Time.now.to_i signature = OpenSSL::HMAC.hexdigest('SHA256', Rails.application.secrets.shared_auth_secret, "#{user_id}#{timestamp}") # 调用API时带上这三个参数 response = HTTP.get("https://api-app.example.com/protected", params: { user_id: user_id, timestamp: timestamp, signature: signature })API应用侧验证签名并设置用户:
# 在API控制器里添加前置校验 before_action :authenticate_via_shared_secret def authenticate_via_shared_secret user_id = params[:user_id] timestamp = params[:timestamp].to_i signature = params[:signature] # 拦截过期请求(比如5分钟内有效,防止重放攻击) return head :unauthorized if (Time.now.to_i - timestamp) > 300 # 重新计算签名并安全对比 expected_signature = OpenSSL::HMAC.hexdigest('SHA256', Rails.application.secrets.shared_auth_secret, "#{user_id}#{timestamp}") return head :unauthorized unless ActiveSupport::SecurityUtils.secure_compare(expected_signature, signature) @current_user = User.find(user_id) end
方案2:复用Devise加密密码做验证
利用共享数据库里的encrypted_password字段,结合共享密钥生成临时验证token,无需传递明文密码:
发起请求的应用生成临时token:
user = current_user temp_token = OpenSSL::HMAC.hexdigest('SHA256', Rails.application.secrets.shared_auth_secret, user.encrypted_password) response = HTTP.get("https://api-app.example.com/protected", params: { email: user.email, temp_token: temp_token })API应用侧验证token:
before_action :authenticate_via_temp_token def authenticate_via_temp_token user = User.find_by(email: params[:email]) return head :unauthorized unless user expected_token = OpenSSL::HMAC.hexdigest('SHA256', Rails.application.secrets.shared_auth_secret, user.encrypted_password) return head :unauthorized unless ActiveSupport::SecurityUtils.secure_compare(expected_token, params[:temp_token]) @current_user = user end
方案3:修复JWT签名失败问题
你之前的JWT验证失败,90%是因为两边密钥不一致或算法/Payload不匹配:
- 确保两个应用使用完全相同的JWT密钥和算法,比如手动生成验证的代码:
重点检查:密钥是否完全一致(注意大小写、特殊字符)、算法是否统一(比如都是HS256)、Payload里的# 发起应用生成JWT payload = { user_id: current_user.id, exp: 1.hour.from_now.to_i } token = JWT.encode(payload, Rails.application.secrets.jwt_secret, 'HS256') # API应用验证JWT begin decoded = JWT.decode(token, Rails.application.secrets.jwt_secret, true, algorithm: 'HS256') @current_user = User.find(decoded[0]['user_id']) rescue JWT::DecodeError, ActiveRecord::RecordNotFound head :unauthorized endexp过期时间是否有效。
关于直接传递encrypted_password的说明
Devise的encrypted_password是BCrypt哈希,它的设计只能用明文密码验证哈希,无法直接用哈希做身份校验——你需要知道用户明文密码才能验证,但传递明文密码风险极高,完全不建议这么做。
内容的提问来源于stack exchange,提问作者Jeremy Thomas
相关产品推荐
相关产品推荐

