oauth qq and wechat

dev_oauth
p31729568 5 years ago
parent 1fcd4283d9
commit cd717f1e1a

@ -93,3 +93,7 @@ gem 'bulk_insert'
gem 'searchkick'
gem 'aasm'
# oauth2
gem 'omniauth', '~> 1.9.0'
gem 'omniauth-oauth2', '~> 1.6.0'

@ -110,7 +110,7 @@ GEM
grape-entity (0.7.1)
activesupport (>= 4.0)
multi_json (>= 1.3.2)
hashie (3.6.0)
hashie (3.5.7)
htmlentities (4.3.4)
httparty (0.16.2)
multi_xml (>= 0.5.2)
@ -164,6 +164,12 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3)
omniauth-oauth2 (1.6.0)
oauth2 (~> 1.1)
omniauth (~> 1.9)
pdfkit (0.8.4.1)
public_suffix (3.0.2)
puma (3.12.0)
@ -348,6 +354,8 @@ DEPENDENCIES
listen (>= 3.0.5, < 3.2)
mysql2 (>= 0.4.4, < 0.6.0)
oauth2
omniauth (~> 1.9.0)
omniauth-oauth2 (~> 1.6.0)
pdfkit
puma (~> 3.11)
rack-cors

@ -114,35 +114,6 @@ class AccountsController < ApplicationController
end
end
def successful_authentication(user)
uid_logger("Successful authentication start: '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}")
# Valid user
self.logged_user = user
# generate a key and set cookie if autologin
set_autologin_cookie(user)
UserAction.create(:action_id => user.try(:id), :action_type => "Login", :user_id => user.try(:id), :ip => request.remote_ip)
user.update_column(:last_login_on, Time.now)
# 注册完成后有一天的试用申请(先去掉)
# UserDayCertification.create(user_id: user.id, status: 1)
end
def set_autologin_cookie(user)
token = Token.get_or_create_permanent_login_token(user, "autologin")
cookie_options = {
:value => token.value,
:expires => 1.month.from_now,
:path => '/',
:secure => false,
:httponly => true
}
if edu_setting('cookie_domain').present?
cookie_options = cookie_options.merge(domain: edu_setting('cookie_domain'))
end
cookies[autologin_cookie_name] = cookie_options
logger.info("cookies is #{cookies}")
end
def logout
UserAction.create(action_id: User.current.id, action_type: "Logout", user_id: User.current.id, :ip => request.remote_ip)
session[:user_id] = nil
@ -183,20 +154,6 @@ class AccountsController < ApplicationController
end
private
def autologin_cookie_name
edu_setting('autologin_cookie_name') || 'autologin'
end
def logout_user
if User.current.logged?
if autologin = cookies.delete(autologin_cookie_name)
User.current.delete_autologin_token(autologin)
end
User.current.delete_session_token(session[:tk])
self.logged_user = nil
end
session[:user_id] = nil
end
# type 事件类型 1用户注册 2忘记密码 3: 绑定手机 4: 绑定邮箱, 5: 验证手机号是否有效 # 如果有新的继续后面加
# login_type 1手机类型 2邮箱类型

@ -7,6 +7,7 @@ class ApplicationController < ActionController::Base
include ControllerRescueHandler
include GitHelper
include LoggerHelper
include LoginHelper
protect_from_forgery prepend: true, unless: -> { request.format.json? }
@ -225,12 +226,6 @@ class ApplicationController < ActionController::Base
# end
end
def start_user_session(user)
session[:user_id] = user.id
session[:ctime] = Time.now.utc.to_i
session[:atime] = Time.now.utc.to_i
end
def user_setup
# reacct静态资源加载不需要走这一步
return if params[:controller] == "main"
@ -256,17 +251,6 @@ class ApplicationController < ActionController::Base
# User.current = User.find 81403
end
# Sets the logged in user
def logged_user=(user)
reset_session
if user && user.is_a?(User)
User.current = user
start_user_session(user)
else
User.current = User.anonymous
end
end
# Returns the current user or nil if no user is logged in
# and starts a session if needed
def find_current_user

@ -0,0 +1,69 @@
module LoginHelper
extend ActiveSupport::Concern
def edu_setting(name)
EduSetting.get(name)
end
def autologin_cookie_name
edu_setting('autologin_cookie_name') || 'autologin'
end
def set_autologin_cookie(user)
token = Token.get_or_create_permanent_login_token(user, "autologin")
cookie_options = {
:value => token.value,
:expires => 1.month.from_now,
:path => '/',
:secure => false,
:httponly => true
}
if edu_setting('cookie_domain').present?
cookie_options = cookie_options.merge(domain: edu_setting('cookie_domain'))
end
cookies[autologin_cookie_name] = cookie_options
Rails.logger.info("cookies is #{cookies}")
end
def successful_authentication(user)
Rails.logger.info("id: #{user&.id} Successful authentication start: '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}")
# Valid user
self.logged_user = user
# generate a key and set cookie if autologin
set_autologin_cookie(user)
UserAction.create(action_id: user&.id, action_type: 'Login', user_id: user&.id, ip: request.remote_ip)
user.update_column(:last_login_on, Time.now)
# 注册完成后有一天的试用申请(先去掉)
# UserDayCertification.create(user_id: user.id, status: 1)
end
def logout_user
if User.current.logged?
if autologin = cookies.delete(autologin_cookie_name)
User.current.delete_autologin_token(autologin)
end
User.current.delete_session_token(session[:tk])
self.logged_user = nil
end
session[:user_id] = nil
end
# Sets the logged in user
def logged_user=(user)
reset_session
if user && user.is_a?(User)
User.current = user
start_user_session(user)
else
User.current = User.anonymous
end
end
def start_user_session(user)
session[:user_id] = user.id
session[:ctime] = Time.now.utc.to_i
session[:atime] = Time.now.utc.to_i
end
end

@ -0,0 +1,20 @@
class Oauth::BaseController < ActionController::Base
include RenderHelper
include LoginHelper
skip_before_action :verify_authenticity_token
private
def session_user_id
session[:user_id]
end
def current_user
@_current_user ||= User.find_by(id: session_user_id)
end
def auth_hash
request.env['omniauth.auth']
end
end

@ -0,0 +1,9 @@
class Oauth::QQController < Oauth::BaseController
def create
user = Oauth::CreateOrFindQqAccountService.call(current_user, auth_hash)
successful_authentication(user)
render_ok
end
end

@ -0,0 +1,11 @@
class WechatController < Oauth::BaseController
def create
user = Oauth::CreateOrFindWechatAccountService.call(current_user ,params)
successful_authentication(user)
render_ok
rescue Oauth::CreateOrFindWechatAccountService::Error => ex
render_error(ex.message)
end
end

@ -0,0 +1,50 @@
module OmniAuth
module Strategies
class QQ < OmniAuth::Strategies::OAuth2
option :client_options, {
site: 'https://graph.qq.com',
authorize_url: '/oauth2.0/authorize',
token_url: '/oauth2.0/token'
}
def request_phase
super
end
def authorize_params
super.tap do |params|
%w[scope client_options].each do |v|
if request.params[v]
params[v.to_sym] = request.params[v]
end
end
end
end
uid { raw_info['openid'].to_s }
info do
{
name: user_info['nickname'],
nickname: user_info['nickname'],
image: user_info['figureurl_qq_1']
}
end
extra do
{ raw_info: user_info }
end
def raw_info
access_token.options[:mode] = :query
@raw_info ||= access_token.get('/oauth2.0/me').parsed
end
def user_info
access_token.options[:mode] = :query
params = { oauth_consumer_key: options.client_id, openid: raw_info['openid'], format: 'json' }
@user_info ||= access_token.get('/user/get_user_info', params: params)
end
end
end
end

@ -0,0 +1,13 @@
module WechatOauth
class << self
attr_accessor :appid, :secret, :scope, :base_url
def logger
@_logger ||= STDOUT
end
def logger=(l)
@_logger = l
end
end
end

@ -0,0 +1,14 @@
class WechatOauth::Error < StandardError
attr_reader :code
def initialize(code, msg)
super(msg)
@code = code
end
def message
I18n.t("oauth.wechat.#{code}")
rescue I18n::MissingTranslationData
super
end
end

@ -0,0 +1,61 @@
module WechatOauth::Service
module_function
def request(method, url, params)
WechatOauth.logger.info("[WechatOauth] [#{method.to_s.upcase}] #{url} || #{params}")
client = Faraday.new(url: WechatOauth.base_url)
response = client.public_send(method, url, params)
result = JSON.parse(response.body)
WechatOauth.logger.info("[WechatOauth] [#{response.status}] #{result}")
if result['errcode'].present? && result['errcode'].to_s != '0'
raise WechatOauth::Error.new(result['errcode'], result['errmsg'])
end
result
end
# https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
# response:
# {
# "access_token":"ACCESS_TOKEN",
# "expires_in":7200,
# "refresh_token":"REFRESH_TOKEN",
# "openid":"OPENID",
# "scope":"SCOPE",
# "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
# }
def access_token(code)
params = {
appid: WechatOauth.appid,
secret: WechatOauth.secret,
code: code,
grant_type: 'authorization_code'
}
request(:get, '/sns/oauth2/access_token', params)
end
# https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Authorized_Interface_Calling_UnionID.html
# response:
# {
# "openid":"OPENID",
# "nickname":"NICKNAME",
# "sex":1,
# "province":"PROVINCE",
# "city":"CITY",
# "country":"COUNTRY",
# "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
# "privilege":[
# "PRIVILEGE1",
# "PRIVILEGE2"
# ],
# "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
#
# }
def user_info(access_token, openid)
request(:get, '/sns/userinfo', access_token: access_token, openid: openid)
end
end

@ -0,0 +1,5 @@
class OpenUser < ApplicationRecord
belongs_to :user
validates :uid, presence: true, uniqueness: { scope: :type }
end

@ -0,0 +1,3 @@
class OpenUsers::QQ < OpenUser
end

@ -0,0 +1,3 @@
class OpenUsers::Wechat < OpenUser
end

@ -26,6 +26,8 @@ class User < ApplicationRecord
MIX_PASSWORD_LIMIT = 8
LOGIN_CHARS = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z).freeze
has_one :user_extension, dependent: :destroy
accepts_nested_attributes_for :user_extension, update_only: true
@ -603,6 +605,15 @@ class User < ApplicationRecord
admin? || business?
end
def self.generate_login(prefix)
login = prefix + LOGIN_CHARS.sample(8).join('')
while User.exists?(login: login)
login = prefix + LOGIN_CHARS.sample(8).join('')
end
login
end
protected
def validate_password_length
# 管理员的初始密码是5位

@ -0,0 +1,34 @@
class Oauth::CreateOrFindQqAccountService < ApplicationService
attr_reader :user, :params
def initialize(user, params)
@user = user
@params = params
end
def call
# 存在该用户
open_user = OpenUsers::QQ.find_by(uid: params['uid'])
return open_user.user if open_user.present?
if user.blank? || !user.logged?
# 新用户
login = User.generate_login('q')
@user = User.new(login: login, nickname: params.dig('info', 'nickname'))
end
ActiveRecord::Base.transaction do
if user.new_record?
user.save!
gender = params.dig('extra', 'raw_info', 'gender') == '女' ? 1 : 0
user.create_user_extension!(gender: gender)
end
OpenUsers::QQ.create!(user: user, uid: params['uid'])
end
user
end
end

@ -0,0 +1,48 @@
class Oauth::CreateOrFindWechatAccountService < ApplicationService
Error = Class.new(StandardError)
attr_reader :user, :params
def initialize(user, params)
@user = user
@params = params
end
def call
code = params['code'].to_s.strip
raise Error, 'Code不能为空' if code.blank?
result = WechatOauth::Service.access_token(code)
# 存在该用户
open_user = OpenUsers::Wechat.find_by(uid: result['unionid'])
return open_user.user if open_user.present?
if user.blank? || !user.logged?
# 新用户
login = User.generate_login('w')
@user = User.new(login: login, nickname: result['nickname'])
end
ActiveRecord::Base.transaction do
if user.new_record?
user.save!
gender = result['sex'].to_i == 1 ? 0 : 1
user.create_user_extension!(gender: gender)
end
OpenUsers::Wechat.create!(user: user, uid: result['unionid'])
end
user
rescue WechatOauth::Error => ex
raise Error, ex.message
end
private
def code
params[:code].to_s.strip
end
end

@ -0,0 +1,19 @@
defaults: &defaults
oauth:
qq:
appid: 'test'
secret: 'test123456'
wechat:
appid: 'test'
secret: 'test'
scope: 'snsapi_login'
base_url: 'https://api.weixin.qq.com'
development:
<<: *defaults
test:
<<: *defaults
production:
<<: *defaults

@ -14,3 +14,8 @@
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym 'RESTful'
# end
ActiveSupport::Inflector.inflections do |inflect|
inflect.acronym 'QQ'
inflect.acronym 'OmniAuth'
end

@ -0,0 +1,17 @@
OmniAuth.config.add_camelization 'qq', 'QQ'
oauth_config = {}
begin
config = Rails.application.config_for(:configuration)
oauth_config = config.dig('oauth', 'qq')
raise 'oauth qq config missing' if oauth_config.blank?
rescue => ex
raise ex if Rails.env.production?
puts %Q{\033[33m [warning] qq oauth config or configuration.yml missing,
please add it or execute 'cp config/configuration.yml.example config/configuration.yml' \033[0m}
end
Rails.application.config.middleware.use OmniAuth::Builder do
provider :qq, oauth_config['appid'], oauth_config['secret']
end

@ -0,0 +1,17 @@
oauth_config = {}
begin
config = Rails.application.config_for(:configuration)
oauth_config = config.dig('oauth', 'wechat')
raise 'oauth wechat config missing' if oauth_config.blank?
rescue => ex
raise ex if Rails.env.production?
puts %Q{\033[33m [warning] wechat oauth config or configuration.yml missing,
please add it or execute 'cp config/configuration.yml.example config/configuration.yml' \033[0m}
end
WechatOauth.appid = oauth_config['appid']
WechatOauth.secret = oauth_config['secret']
WechatOauth.scope = oauth_config['scope']
WechatOauth.base_url = oauth_config['base_url']
WechatOauth.logger = Rails.logger

@ -0,0 +1,4 @@
'zh-CN':
oauth:
wechat:
'40029': '授权已失效,请重新授权'

@ -7,6 +7,8 @@ Rails.application.routes.draw do
get 'attachments/download/:id', to: 'attachments#show'
get 'attachments/download/:id/:filename', to: 'attachments#show'
get '/auth/qq/callback', to: 'oauth/qq#create'
resources :edu_settings
resources :admin
scope '/api' do

@ -0,0 +1,14 @@
class CreateOpenUsers < ActiveRecord::Migration[5.2]
def change
create_table :open_users do |t|
t.references :user
t.string :type
t.string :uid
t.timestamps
t.index [:type, :uid], unique: true
end
end
end
Loading…
Cancel
Save