parent
1fcd4283d9
commit
cd717f1e1a
@ -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
|
@ -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
|
@ -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': '授权已失效,请重新授权'
|
@ -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…
Reference in new issue