From 045b8f7655924577687496a8f28955ce24345f72 Mon Sep 17 00:00:00 2001 From: jingquan huang Date: Mon, 11 Mar 2019 13:54:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=95=E7=82=B9=E7=99=BB=E5=BD=95=E5=88=9D?= =?UTF-8?q?=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/assets/javascripts/oauth.js.coffee | 3 + app/assets/stylesheets/oauth.css.scss | 3 + app/controllers/oauth_controller.rb | 167 ++++++++++++++++++ app/helpers/oauth_helper.rb | 2 + app/models/oauth.rb | 53 ++++++ app/models/oauth_config.rb | 3 + config/routes.rb | 9 +- ...0190228060206_add_forbid_copy_to_shixun.rb | 2 +- db/migrate/20190311030508_create_oauths.rb | 44 +++++ spec/controllers/oauth_controller_spec.rb | 5 + spec/factories/oauth_configs.rb | 5 + spec/factories/oauths.rb | 5 + spec/models/oauth_config_spec.rb | 5 + spec/models/oauths_spec.rb | 5 + 14 files changed, 309 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/oauth.js.coffee create mode 100644 app/assets/stylesheets/oauth.css.scss create mode 100644 app/controllers/oauth_controller.rb create mode 100644 app/helpers/oauth_helper.rb create mode 100644 app/models/oauth.rb create mode 100644 app/models/oauth_config.rb create mode 100644 db/migrate/20190311030508_create_oauths.rb create mode 100644 spec/controllers/oauth_controller_spec.rb create mode 100644 spec/factories/oauth_configs.rb create mode 100644 spec/factories/oauths.rb create mode 100644 spec/models/oauth_config_spec.rb create mode 100644 spec/models/oauths_spec.rb diff --git a/app/assets/javascripts/oauth.js.coffee b/app/assets/javascripts/oauth.js.coffee new file mode 100644 index 00000000..76156794 --- /dev/null +++ b/app/assets/javascripts/oauth.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/stylesheets/oauth.css.scss b/app/assets/stylesheets/oauth.css.scss new file mode 100644 index 00000000..e1c1a010 --- /dev/null +++ b/app/assets/stylesheets/oauth.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the oauth controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/oauth_controller.rb b/app/controllers/oauth_controller.rb new file mode 100644 index 00000000..bcc12856 --- /dev/null +++ b/app/controllers/oauth_controller.rb @@ -0,0 +1,167 @@ +#encoding: utf-8 +class OauthController < ApplicationController + + include ApplicationHelper + + before_filter :user_setup + before_filter :require_login, only: [:authorize] + + + skip_before_filter :verify_authenticity_token, only: [:token] + + + def index + render 'oauth/index', layout: false + end + + + # 客户端申请认证的URI,包含以下参数: + # + # response_type:表示授权类型,必选项,此处的值固定为”code” + # client_id:表示客户端的ID,必选项 + # redirect_uri:表示重定向URI,可选项 + # scope:表示申请的权限范围,可选项 + # state:表示客户端的当前状态,可以指定任意值(最好是随机字符串),认证服务器会原封不动地返回这个值,可防止CSRF攻击 + # + # 这个页显示授权页,如果授权成功,返回redirect_uri+code + # + # + # 服务器回应客户端的URI,包含以下参数: + # + # code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次, 否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。 + # state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。 + def authorize + begin + + #参数检查 + raise "response_type只能为code" unless params["response_type"] == "code" + raise "client_id为必传项" unless params["client_id"].present? + raise "redirect_uri为必传项" unless params["redirect_uri"].present? + + + config = OauthConfig.where(client_id: params["client_id"], redirect_uri: params["redirect_uri"]).first + raise "client_id或redirect_uri不正确" unless config + + + @data = params + + if params[:gen_code] + ## 检查通过,生成code + oauth = Oauth.create!(client_id: config.client_id, + client_secret: config.client_secret, + redirect_uri: config.redirect_uri, + user_id: User.current.id + ) + code = oauth.gen_code + + redirect_to params["redirect_uri"] + "?code=#{code}&state=#{params[:state]}" + else + render 'oauth/authorize', :layout => 'forge' + end + + rescue => e + logger.error e + render :text => e.message + end + + end + + def test_callback + # 申请 token + # + client_id = "88d893c5a345313e7b8c6fcf23d3d024ee08d5e41ce120c3448b6eea77d8de30" + client_secret = "e9240cc5fc913741db5aea93f2986a8ea0631bb67f7c00e41e491b95d9619e64" + redirect_uri = "http://localhost:3000/oauth/cb" + url = "http://127.0.0.1:3000/oauth/token?grant_type=authorization_code&code=#{params['code']}&redirect_uri=#{redirect_uri}&client_id=#{client_id}&client_secret=#{client_secret}" + + render text: url + end + + + # 客户端向认证服务器申请令牌的HTTP请求,包含以下参数: + # + # grant_type:表示使用的授权模式,必选项,此处的值固定为”authorization_code”。 + # code:表示上一步获得的授权码,必选项。 + # redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。 + # client_id:表示客户端ID,必选项。 + # client_secret: 表示客户端密钥,必选项。 + # + # + # 认证服务器核对了授权码和”重定向URI”,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。 + # + # 认证服务器发送的HTTP回复,包含以下内容: + # + # access_token:表示访问令牌,必选项。 + # token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。 + # expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 + # refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。 + # scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。 + def token + begin + res = {} + if params[:grant_type] == 'authorization_code' + + raise "code必传" unless params["code"] + raise "client_id必传" unless params["client_id"] + raise "client_secret必传" unless params["client_secret"] + + raise "code错误或已超时" unless Oauth.code_valid?(params["code"]) + + oauth = Oauth.auth_code(params["code"], params["client_id"], params["client_secret"]) + raise "认证不通过" unless oauth + + ## 生成 token + # + oauth.gen_token + + oauth.reload + + res = { + access_token: oauth.access_token, + token_type: 'bearer', + expires_in: oauth.token_expires_in, + refresh_token: oauth.refresh_token + } + + end + + render json: res.to_json + + rescue => e + logger.error e + render text: e.message + end + end + + + def get_userinfo + user = Oauth.auth(params["access_token"]) + + user_info = {} + if user + user_info = { + token: user.id, + login: user.login, + avatar_url: "https://openi.org.cn/images/" + url_to_avatar(user), + name: user.show_name, + email: user.mail, + allow: (user.login == "guange"||user.phone=='15607313899') ? 1 : 0 + } + end + + render json: user_info.to_json + end + + + private + + def require_login + require "base64" + if !User.current.logged? + redirect_to '/login?back_url64=' + Base64.urlsafe_encode64(request.original_url) + end + end + + include Trustie::Http + +end \ No newline at end of file diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb new file mode 100644 index 00000000..010cf9f5 --- /dev/null +++ b/app/helpers/oauth_helper.rb @@ -0,0 +1,2 @@ +module OauthHelper +end diff --git a/app/models/oauth.rb b/app/models/oauth.rb new file mode 100644 index 00000000..075c1ef0 --- /dev/null +++ b/app/models/oauth.rb @@ -0,0 +1,53 @@ +require 'base64' + +class Oauth < ActiveRecord::Base + attr_accessible :client_id, :client_secret, :redirect_uri, :access_token, + :refresh_token, :token_created_at,:token_expires_in, :user_id + + belongs_to :user + + def gen_code + code = Base64.urlsafe_encode64 Digest::MD5.hexdigest "#{Time.now}-#{Random.new_seed}" + update_column(:code, code) + code + end + + def gen_token + access_token = Digest::MD5.hexdigest "#{Time.now}-#{Random.new_seed}" + refresh_token = Digest::MD5.hexdigest "#{Random.new_seed}-#{Time.now}-#{Random.new_seed}" + + self.update_attributes(access_token: access_token, + refresh_token: refresh_token, + token_created_at: Time.now.to_i, + token_expires_in: Time.now.to_i + 24*60*60, + ) + end + + + def self.code_valid?(code) + # 1. 是否存在 + oauth = Oauth.where(code: code).order("ID desc").first + return false unless oauth + + # 2. 是否超过10分钟 + return false if Time.now.to_i - oauth.created_at.to_i > 10*60 + + # 3. 是否有使用过 + return false if oauth.access_token.present? + + return true + end + + + def self.auth_code(code, client_id, client_secret) + Oauth.where(code: code, client_id: client_id, client_secret: client_secret).order('id desc').first + end + + def self.auth(access_token) + oauth = self.find_by_access_token(access_token) + return nil unless oauth + oauth.user + end + + +end diff --git a/app/models/oauth_config.rb b/app/models/oauth_config.rb new file mode 100644 index 00000000..15bfcd82 --- /dev/null +++ b/app/models/oauth_config.rb @@ -0,0 +1,3 @@ +class OauthConfig < ActiveRecord::Base + attr_accessible :client_id, :client_secret, :redirect_uri, :scope +end diff --git a/config/routes.rb b/config/routes.rb index c0651b7a..ff0b4ebc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -25,7 +25,14 @@ # 所有的 act: :act 变成 :act => :act # Example: :via => :get ====> :via => :get -RedmineApp::Application.routes.draw do +RedmineApp::Application.routes.draw do ## oauth相关 + match 'oauth', to: 'oauth#index' + match 'oauth/authorize', to: 'oauth#authorize', :via => [:get, :post] + match 'oauth/token', to: 'oauth#token', :via => :post + match 'oauth/cb', to: 'oauth#test_callback', :via => :get + match 'oauth/userinfo', to: 'oauth#get_userinfo', :via => :get + + resources :ec_course_evaluations do member do match 'import_score', :via => [:post] diff --git a/db/migrate/20190228060206_add_forbid_copy_to_shixun.rb b/db/migrate/20190228060206_add_forbid_copy_to_shixun.rb index 716e1e1d..b159f276 100644 --- a/db/migrate/20190228060206_add_forbid_copy_to_shixun.rb +++ b/db/migrate/20190228060206_add_forbid_copy_to_shixun.rb @@ -1,5 +1,5 @@ class AddForbidCopyToShixun < ActiveRecord::Migration def change - add_column :shixuns, :forbid_copy, :boolean, :default => 0 + # add_column :shixuns, :forbid_copy, :boolean, :default => 0 end end diff --git a/db/migrate/20190311030508_create_oauths.rb b/db/migrate/20190311030508_create_oauths.rb new file mode 100644 index 00000000..955c0dcf --- /dev/null +++ b/db/migrate/20190311030508_create_oauths.rb @@ -0,0 +1,44 @@ +class CreateOauths < ActiveRecord::Migration + def change + + create_table :oauths do |t| + t.string :client_id + t.string :client_secret + t.string :code + t.string :redirect_uri + t.string :scope + + t.string :access_token + t.string :refresh_token + t.integer :token_created_at + t.integer :token_expires_in #过期时间 + + t.timestamps + end + + + add_column :oauths, :user_id, :integer, default: 0 + + add_index :oauths, :user_id + + + create_table :oauth_configs do |t| + t.string :client_id + t.string :client_secret + t.string :redirect_uri + t.string :scope + + t.timestamps + end + + + OauthConfig.create( + client_id: '88d893c5a345313e7b8c6fcf23d3d024ee08d5e41ce120c3448b6eea77d8de30', + client_secret: 'e9240cc5fc913741db5aea93f2986a8ea0631bb67f7c00e41e491b95d9619e64', + redirect_uri: 'http://localhost:3000/oschina/login_cb', + scope: '' + ) + + end +end + diff --git a/spec/controllers/oauth_controller_spec.rb b/spec/controllers/oauth_controller_spec.rb new file mode 100644 index 00000000..cc1d9ffc --- /dev/null +++ b/spec/controllers/oauth_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe OauthController, :type => :controller do + +end diff --git a/spec/factories/oauth_configs.rb b/spec/factories/oauth_configs.rb new file mode 100644 index 00000000..f22e2f9a --- /dev/null +++ b/spec/factories/oauth_configs.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :oauth_config do + + end +end diff --git a/spec/factories/oauths.rb b/spec/factories/oauths.rb new file mode 100644 index 00000000..014dce7f --- /dev/null +++ b/spec/factories/oauths.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :oauth, class: 'Oauths' do + + end +end diff --git a/spec/models/oauth_config_spec.rb b/spec/models/oauth_config_spec.rb new file mode 100644 index 00000000..7aeac40e --- /dev/null +++ b/spec/models/oauth_config_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe OauthConfig, :type => :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/oauths_spec.rb b/spec/models/oauths_spec.rb new file mode 100644 index 00000000..5550ea78 --- /dev/null +++ b/spec/models/oauths_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Oauths, :type => :model do + pending "add some examples to (or delete) #{__FILE__}" +end