Rails 8中Devise+Omniauth实现微软SSO与独立离线授权的问题
Rails 8 中分离 Devise SSO 与独立微软 OAuth 授权的解决方案
核心思路
把Devise 集成的微软SSO和已登录用户的独立微软应用授权拆成两个完全独立的 Omniauth 策略实例——哪怕都是微软 OAuth,也要用不同的策略名称区分,彻底避免 Devise 初始化与单独 Omniauth 配置的冲突问题,同时实现权限隔离(SSO 只用基础权限,独立授权按需申请离线访问及高级 Graph 权限)。
步骤实现
1. 注册两个独立的微软应用
- SSO专用应用:仅申请基础 OpenID 权限(
openid,profile,email),不需要offline_access,用于用户快速登录。 - 高级授权专用应用:申请业务所需的 Graph 权限 +
offline_access,专门给已登录用户单独授权获取离线访问能力。
2. 配置双 Omniauth 策略
在config/initializers/omniauth.rb中分别配置两个策略,用自定义名称区分高级授权的策略:
Rails.application.config.middleware.use OmniAuth::Builder do # Devise SSO 用的微软策略 provider :microsoft_graph, ENV['MICROSOFT_SSO_CLIENT_ID'], ENV['MICROSOFT_SSO_CLIENT_SECRET'], scope: 'openid profile email', redirect_uri: ENV['MICROSOFT_SSO_REDIRECT_URI'] # 独立授权用的高级微软策略,自定义名称避免冲突 provider :microsoft_advanced, ENV['MICROSOFT_ADVANCED_CLIENT_ID'], ENV['MICROSOFT_ADVANCED_CLIENT_SECRET'], scope: 'offline_access Calendars.ReadWrite Mail.Send', # 按需替换为你的权限 redirect_uri: ENV['MICROSOFT_ADVANCED_REDIRECT_URI'], strategy_class: OmniAuth::Strategies::MicrosoftGraph # 复用原策略类 end
3. 调整 Devise 配置
在config/initializers/devise.rb中,只让 Devise 关联 SSO 专用的策略,不干涉独立授权流程:
config.omniauth :microsoft_graph, ENV['MICROSOFT_SSO_CLIENT_ID'], ENV['MICROSOFT_SSO_CLIENT_SECRET'], scope: 'openid profile email', redirect_uri: ENV['MICROSOFT_SSO_REDIRECT_URI']
4. 手动实现独立授权的路由与控制器
由于devise :omniauthable会拦截 Omniauth 默认路由,需要手动触发授权请求:
- 路由配置(
config/routes.rb):
get '/connect-microsoft', to: 'microsoft_advanced#initiate_auth' get '/auth/microsoft_advanced/callback', to: 'microsoft_advanced#callback'
- 控制器实现(
app/controllers/microsoft_advanced_controller.rb):
class MicrosoftAdvancedController < ApplicationController before_action :authenticate_user! # 确保只有已登录用户能发起授权 def initiate_auth # 手动构建策略实例,绕过 Devise 拦截 auth_strategy = OmniAuth::Strategies::MicrosoftGraph.new( Rails.application.config.middleware, ENV['MICROSOFT_ADVANCED_CLIENT_ID'], ENV['MICROSOFT_ADVANCED_CLIENT_SECRET'], scope: 'offline_access Calendars.ReadWrite Mail.Send', redirect_uri: ENV['MICROSOFT_ADVANCED_REDIRECT_URI'] ) redirect_to auth_strategy.authorize_url end def callback auth_hash = request.env['omniauth.auth'] # 将授权信息存储到用户关联的模型中(建议单独建表,不混在 User 表) current_user.create_or_update_user_microsoft_auth( refresh_token: auth_hash.credentials.refresh_token, access_token: auth_hash.credentials.token, expires_at: Time.at(auth_hash.credentials.expires_at) ) redirect_to dashboard_path, notice: '微软账户授权成功' end end
5. 数据存储优化
创建独立模型存储高级授权的 Token 信息,避免与 SSO 用户数据混淆:
# app/models/user.rb has_one :user_microsoft_auth, dependent: :destroy # app/models/user_microsoft_auth.rb belongs_to :user validates :user_id, uniqueness: true attribute :refresh_token, :string attribute :access_token, :string attribute :expires_at, :datetime
关键注意事项
- 两个微软应用的
redirect_uri必须与配置完全一致(包括协议、域名、路径),否则微软会拒绝回调。 - 独立授权流程必须通过
before_action :authenticate_user!限制已登录用户访问,避免未注册用户发起授权。 - 离线访问需妥善保存
refresh_token,并在access_token过期时用其刷新,注意微软的 Refresh Token 轮换规则。 - 严格遵循最小权限原则:SSO 只申请必要的登录权限,独立授权按需申请业务所需权限,降低用户授权门槛。
内容的提问来源于stack exchange,提问作者David Claiborne
相关产品推荐
相关产品推荐

