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