You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从新打开的应用标签返回后丢失后端用户认证问题排查

跨应用认证后Portal后端丢失用户身份问题

我有一个名为Portal的应用,后端基于Ruby on Rails开发,前端采用Vue.js。流程如下:

  • 用户完成认证后,主页面加载其有权访问的应用列表
  • 用户点击应用时,通过JavaScript的window.open()函数在新标签页打开目标应用,并自动完成该应用内的用户认证,实现类超链接的便捷访问

被打开的目标应用同样采用Ruby on Rails后端+Vue.js前端架构,且与Portal共享同一JWT认证API。注:同一用户在不同应用内拥有不同权限角色,因此必须在两个应用中分别完成认证以获取对应功能权限。

目前已实现Portal打开目标应用的功能:通过URL参数传递用户数据,目标应用前端捕获参数后完成用户认证并跳转至初始页面。但返回Portal应用时,前端仍能识别用户,但后端控制器无法再识别用户身份,尝试在调用window.open()后为Portal添加重新登录功能也未成功。预期Portal的后端不会丢失用户认证信息,恳请协助分析原因并提供解决方案。


Portal应用(应用1)的认证控制器函数

module Api
  module V1
    class SessoesController < ActionController::API
      include ::CasacivilgoPermissoes::SessaoApi

      def autenticar
        resposta = login_helper
        render json: resposta, status: resposta[:code]
      end
    end
  end
end

共享的login_helper函数(应用1和应用2共用)

require 'net/http'
require 'casacivilgo_permissoes/usuario'

module CasacivilgoPermissoes
  module SessaoApi
    extend ActiveSupport::Concern
    include CasacivilgoPermissoes::JwToken

    def login_helper
      if ::CasacivilgoPermissoes.configuration.sgu_url.blank?
        raise CasacivilgoPermissoes::Errors::ConfigConstantMissing, 
          'CasacivilgoPermissoes.configuration.sgu_url não está configurada'
      end
      if ::CasacivilgoPermissoes.configuration.sistema_sigla.blank?
        raise CasacivilgoPermissoes::Errors::ConfigConstantMissing, 
          'CasacivilgoPermissoes.configuration.sistema_sigla não está configurada'
      end

      login = params.dig(:login)&.strip
      response = Net::HTTP.post_form(URI("http://localhost:3001/acesso_externo/api/v1/autenticacao/login"), 
        login: login, senha: params[:senha], sistema: ::CasacivilgoPermissoes.configuration.sistema_sigla)
      if response.code == '200'
        parsed_body = JSON.parse response.body
        usuario = ::CasacivilgoPermissoes::Usuario.new parsed_body
        generate_auth_cookie(usuario)

        { usuario: usuario.to_hash, sucesso: true, code: 200 }
      else
        format_error(response)
      end
    end
  end
end

JWT令牌生成、编解码算法

require 'jwt'

module CasacivilgoPermissoes
  module JwToken
    extend ActiveSupport::Concern
    include CasacivilgoPermissoes::Helpers

    ALGORITMO_JWT = 'HS***'.freeze
    ISS = 'Se******** da **** ***** do ****** de *****'.freeze

    def criar_token(usuario, v_key)
      validar_secret_key_jwt

      payload = { 
        exp: expire_in(usuario).to_i,
        iss: ISS, 
        iat: Time.now.to_i, 
        usuario: usuario.to_hash, 
        v_key: v_key 
      }

      JWT.encode payload, CasacivilgoPermissoes.configuration.secret_key_jwt, ALGORITMO_JWT
    end

    def decodificar_token(private_token, public_token)
      validar_secret_key_jwt

      leeway = 30 # seconds

      begin
        token = JWT.decode(private_token, CasacivilgoPermissoes.configuration.secret_key_jwt, true,
                          { algorithm: ALGORITMO_JWT, exp_leeway: leeway, iss: ISS, verify_iss: true,
                            verify_iat: true })
        raise CasacivilgoPermissoes::Errors::InvalidAuthToken if token.first&.dig('v_key') != public_token

        token
      rescue JWT::ExpiredSignature, JWT::InvalidIatError
        raise CasacivilgoPermissoes::Errors::Unauthenticated
      rescue JWT::ImmatureSignature, JWT::InvalidIssuerError, JWT::InvalidAudError,
             JWT::InvalidJtiError, JWT::InvalidSubError
       
        raise CasacivilgoPermissoes::Errors::Unauthenticated
      rescue JWT::DecodeError, 
           CasacivilgoPermissoes::Errors::InvalidAuthToken
        raise CasacivilgoPermissoes::Errors::Unauthenticated
      end
    end

    def generate_auth_cookie(usuario)
      expiration_time = expire_in(usuario)
      if usuario.present?
        v_key = SecureRandom.hex
        cookies[:auth_private] =
          { value: criar_token(usuario, v_key), httponly: true, 
            expires: expiration_time, domain: request&.host }
        cookies.signed.encrypted[:auth_public] =
          { value: v_key, httponly: false, expires: expiration_time, 
            domain: request&.host }
      end
    end

    def validar_secret_key_jwt
      if CasacivilgoPermissoes.configuration.secret_key_jwt.blank?
        raise ::CasacivilgoPermissoes::Errors::ConfigConstantMissing,
              'CasacivilgoPermissoes.configuration.secret_key_jwt não encontrada'
      end
    end
  end
end

应用2的认证函数login_token_jwt_helper

def login_token_jwt_helper
  if ::CasacivilgoPermissoes.configuration.sgu_url.blank?
    raise CasacivilgoPermissoes::Errors::ConfigConstantMissing, 'CasacivilgoPermissoes.configuration.sgu_url não está configurada'
  end
  if ::CasacivilgoPermissoes.configuration.sistema_sigla.blank?
    raise CasacivilgoPermissoes::Errors::ConfigConstantMissing, 'CasacivilgoPermissoes.configuration.sistema_sigla não está configurada'
  end
  if params.dig(:params).dig(:tokenPrivado)&.strip.blank? || params.dig(:params).dig(:tokenPublico)&.strip.blank?
    raise CasacivilgoPermissoes::Errors::ConfigConstantMissing, 'Token de segurança não está configurado'
  end

  login = params.dig(:params).dig(:login)&.strip
  sistema = params.dig(:params).dig(:sistemaSigla)&.strip
  token_privado = params.dig(:params).dig(:tokenPrivado)&.strip
  token_publico = params.dig(:params).dig(:tokenPublico)&.strip

  token_autorizado = decodificar_token(token_privado, token_publico);

  if token_autorizado
    
    response = Net::HTTP.post_form(URI("http://localhost:3001/acesso_externo/api/v1/autenticacao/login_token_jwt"), 
      login: login, token: token_autorizado, sistema: sistema)
  else
    raise CasacivilgoPermissoes::Errors::Unauthenticated
  end  

  if response.code == '200'
    parsed_body = JSON.parse response.body
    novoUsuario = ::CasacivilgoPermissoes::Usuario.new parsed_body
    generate_auth_cookie(novoUsuario)

    { usuario: novoUsuario.to_hash, sucesso: true, code: 200 }
  else
    format_error(response)
  end
end

两个应用共享部分代码,应用2的前端被Portal调用时会自动触发上述login_token_jwt_helper函数。

内容的提问来源于stack exchange,提问作者Flávio Oliveira Neto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 18:59:54