如何修复 omniauth LinkedIn 抛出的 SSL 错误

问题描述

我正在尝试通过 LinkedIn 在 rails 5.2 应用程序中设置身份验证,同样我指的是 documentation given by devise 但我收到以下错误

ERROR -- omniauth: (linkedin) Authentication failure! Connection reset by peer: Faraday::SSLError,Connection reset by peer

我使用以下 gems 添加了这些配置

  1. 设计~> 4.8.0
  2. omniauth-linkedin-oauth2 ~> 1.0.0
  3. omniauth ~> 2.0.4

我什至尝试在包含有效 SSL 证书的生产服务器中的活动域上运行,但仍然抛出相同的错误

解决方法

为您提供有关 LinkedIn 的一些信息:

LinkedIn no longer supports the JavaScript SDK. The recommended approach is to use OAuth 2.0 and LinkedIn's Auth APIs.

还有:

LinkedIn does not support TLS 1.0. Support for TLS 1.1 has been marked for deprecation starting 02/01/2020. Please use TLS 1.2 when calling LinkedIn APIs. All API requests to api.linkedin.com must be made over HTTPS. Calls made over HTTP will fail.

Step 1:为 Javascript 库添加 Jquery,运行命令:

$ yarn add jquery

然后,设置config/webpack/environment.js的内容:

const { environment } = require('@rails/webpacker')
const webpack = require('webpack')
environment.plugins.prepend('Provide',new webpack.ProvidePlugin({
   $: 'jquery/src/jquery',jQuery: 'jquery/src/jquery'
 })
)
module.exports = environment

Step 2:通过添加 thin gem

创建 ssl 连接
gem 'thin'
$ bundle install

编辑 config/application.rb 并添加:

config.force_ssl = true

在项目命令行中,输入:

$ openssl genrsa 2048 > host.key
$ chmod 400 host.key
$ openssl req -new -x509 -nodes -sha256 -days 365 -key host.key -out host.cert

执行这些命令后会创建两个文件:host.keyhost.cert。然后运行:

$ thin start --ssl --ssl-key-file=./host.key --ssl-cert-file=./host.cert

它将在默认地址运行项目:https://0.0.0.0:3000。如果你想在 https://localhost:3000 上运行,只需输入:

$ thin start -a localhost --ssl --ssl-key-file=./host.key --ssl-cert-file=./host.cert

Step 3:创建 Linkedin oauth2 应用。

转到链接:https://www.linkedin.com/developers/

点击按钮Create app,然后填写信息到应用名称、LinkedIn页面(必须通过自定义页面完成)、应用徽标、条款复选框。然后点击“创建应用”以注册您的应用。

在“设置”选项卡中,设置您的应用程序的域,我使用 localhost 运行,因此我将设置 https://localhost:3000

在 Auth 选项卡中,将客户端 ID 和客户端密钥保存到 config/application.yml(记住在此之前运行命令 $ bundle exec figaro install),如下所示:

LINKEDIN_APP_ID: 86g3...sfjm
LINKEDIN_APP_SECRET: OKnb...jzSL

然后为您的应用编辑、键入并保存到部分授权重定向网址:

https://localhost:3000/auth/linkedin/callback

检查可在此页面中使用的范围!我的是r_emailaddress r_liteprofile

在产品选项卡上,选择 Sign In with LinkedIn,状态将更改为 Review in progress。刷新F5一段时间后这个状态消失就OK了!

Step 4:像我一样设置所有代码。使用简单的 config/routes.rb

Rails.application.routes.draw do
  devise_for :users,:controllers => { :omniauth_callbacks => "omniauth_callbacks" } 

  get '/auth/linkedin/callback',to: "linkedin#callback"
  post '/auth/linkedin/url',to: "linkedin#popup"
  post '/auth/linkedin/token',to: "linkedin#get_token"
  post '/auth/linkedin/info',to: "linkedin#get_info"
  post '/auth/linkedin/out',to: "linkedin#stop"
  root to: "linkedin#home"
end

用内容创建app/controllers/linkedin_controller.rb

class LinkedinController < ApplicationController
  # Lib to get token
  require "uri"
  require "net/http"
  # Data variables set/get
  attr_accessor :client_id,:client_secret,:redirect_uri,:scope,:raise_error
  # Class variable with 2@
  @@token = ""
  # Return view linkedins/home page
  def home
    render 'linkedins/home'
  end
  # Call back from popup login window of LinkedIn site
  def callback
    Rails.logger.debug "Callback called! Params:"
    Rails.logger.debug params
    @code = params[:code]
    @state = params[:state]
    @redirect = '/auth/linkedin/callback'
    # Get token
    url = URI("https://www.linkedin.com/oauth/v2/accessToken")
    https = Net::HTTP.new(url.host,url.port)
    https.use_ssl = true
    request = Net::HTTP::Post.new(url)
    request["Content-Type"] = "application/x-www-form-urlencoded"
    host_uri = ENV['HOST']+@redirect
    request.body = "grant_type=authorization_code&code=#{@code}&client_id=#{ENV['LINKEDIN_APP_ID']}&client_secret=#{ENV['LINKEDIN_APP_SECRET']}&redirect_uri=#{host_uri}"

    response = https.request(request)
    Rails.logger.debug "response.read_body:"
    # Rails.logger.debug response.read_body
    r = JSON.parse(response.read_body)
    Rails.logger.debug r["access_token"]
    @@token = r["access_token"]

    render 'linkedins/callback'
  end
  # Config init values
  def start
    @client_id = ENV['LINKEDIN_APP_ID']
    @client_secret = ENV['LINKEDIN_APP_SECRET']
    @raise_error = 'true'
    @redirect = '/auth/linkedin/callback'
    @redirect_uri = ENV['HOST']+@redirect
    @scope = 'r_emailaddress r_liteprofile'
    @state = generate_csrf_token
  end
  # Return popup url for sign in by LinkedIn,method = POST
  def popup
    self.start
    @url = "https://www.linkedin.com/uas/oauth2/authorization?client_id=#{@client_id}&raise_errors=#{@raise_error}&redirect_uri=#{@redirect_uri}&response_type=code&scope=#{@scope}&state=#{@state}"
    # return @url
    render json: { status: 'Success',message: 'Load url for popup finished!',link: @url},status: :ok
  end
  # Get token of current account Linkedin logged
  def get_token
    Rails.logger.debug 'From get_token,@@token cache:'
    Rails.logger.debug @@token
    render json: { status: 'Success',message: 'Load token finished!',token: @@token},status: :ok
  end
  # Get basic info
  def get_info
    Rails.logger.debug 'From get_info!'
    # Create custom api linking
    fields = ['id','firstName','lastName','profilePicture']
    link = "https://api.linkedin.com/v2/me?projection=(#{ fields.join(',') })"

    url = URI(link)
    https = Net::HTTP.new(url.host,url.port)
    https.use_ssl = true
    request = Net::HTTP::Get.new(url)
    request["Authorization"] = "Bearer #{@@token}"
    response = https.request(request)
    Rails.logger.debug "From get_info,variable response:"
    Rails.logger.debug response
    r = JSON.parse(response.read_body)
    # r = JSON.parse(response.body)
    first_name = r['firstName']['localized']['en_US'].to_s
    last_name = r['lastName']['localized']['en_US'].to_s
    full_name = first_name + " " + last_name
    render json: { status: 'Success',message: 'Load link basic info finished!',name: full_name},status: :ok
  end
  # For logout LinkedIn,by method revoke
  def stop
    link = 'https://www.linkedin.com/oauth/v2/revoke'
    url = URI(link)
    https = Net::HTTP.new(url.host,url.port)
    https.use_ssl = true
    request = Net::HTTP::Post.new(url)
    request["Content-Type"] = "application/x-www-form-urlencoded"
    request.body = "client_id=#{ENV['LINKEDIN_APP_ID']}&client_secret=#{ENV['LINKEDIN_APP_SECRET']}&token=#{@@token}"
    response = https.request(request)
    Rails.logger.debug "Test logout linkedin!"
    render json: { status: 'Success',message: 'Log out finished!'},status: :ok
  end
  # Genereate random state
  def generate_csrf_token
    SecureRandom.base64(32)
  end
end

注意安装这些 gems,我们不需要任何 oauth2 链接库:

gem 'uri'
gem 'net-http'
$ bundle install

我们将通过这个回调视图退出弹出的LinkedIn登录app/views/linkedins/callback.html.erb

<script>
  // Close this popup show from LinkedIn window open
  close();
</script>

创建此主视图 app/views/linkedins/home.html.erb

<p>Linkedin Login Home page</p>
<button id="linkedin-login" type="button">Login</button>
<p id="linkedin-informations">Token here!</p>

<button id="linkedin-logout" type="button">Logout</button>
<p id="linkedin-results">Results here!</p>

<script>
  $('#linkedin-login').on('click',function(e){
    // e.preventDefault()
    var url_popup = ""
    var ltoken = ""
    var lurl = ""
    $.post('/auth/linkedin/url',function(json) {
      console.log(json)
      url_popup = json.link
      if (url_popup != "") {
        console.log('Successful to load url popup!')
        const w = 600
        const h = 600
        const top = (screen.height - h) / 4,left = (screen.width - w) / 2
        
        var child = window.open(url_popup,"popupWindow",`width=${w},height=${h},top=${top},left=${left},scrollbars=yes`)
        
        function checkChild() {
          if (child.closed) {  
            clearInterval(timer);
            $.post('/auth/linkedin/token',function(json) {
              console.log('Load token link successful!')
              $('#linkedin-informations').html('Token is comming ...')
              ltoken = json.token
              console.log(json.token)
              $('#linkedin-informations').html(json.token)
            })
            $.post('/auth/linkedin/info',function(json) {
              console.log('Load info link successful!')
              $('#linkedin-results').html('Information is comming ...')
              console.log(json)
              $('#linkedin-results').html(`Your login account: ${json.name}`)
              
            }) 
          }
        }

        var timer = setInterval(checkChild,500);
      }
    })
    
    
  })

  $('#linkedin-logout').on('click',function(e){
    e.preventDefault()
    $.post('/auth/linkedin/out',function(json) {
      console.log('Log out successful!')
      $('#linkedin-results').html(`You logged out!`)
    })
  })

</script>

成功画面: enter image description here