NewLife/X

调整OAuth架构,拆分多提供商实现,便于扩展
大石头 编写于 2018-01-29 11:50:42
共计: 修改12个文件,增加304行、删除173行。
修改 +1 -1
Doc
修改 +5 -0
增加 +33 -0
增加 +50 -0
增加 +40 -0
增加 +29 -0
增加 +21 -0
修改 +64 -162
修改 +3 -3
修改 +8 -6
修改 +12 -1
修改 +38 -0
修改 +1 -1
Doc
diff --git a/Doc b/Doc
index 52676ae..17ccb49 160000
--- a/Doc
+++ b/Doc
@@ -1 +1 @@
-Subproject commit 52676ae66cb6e586d34b3b6445aedc084ff41bb8
+Subproject commit 17ccb49b512980b880c162926eeed5ff677a57ac
修改 +5 -0
diff --git a/NewLife.Core/NewLife.Core.csproj b/NewLife.Core/NewLife.Core.csproj
index cd7e8dd..871dc87 100644
--- a/NewLife.Core/NewLife.Core.csproj
+++ b/NewLife.Core/NewLife.Core.csproj
@@ -290,6 +290,11 @@
     <Compile Include="Threading\ThreadPoolX.cs" />
     <Compile Include="Threading\TimerX.cs" />
     <Compile Include="Threading\TimerScheduler.cs" />
+    <Compile Include="Web\OAuth\GithubClient.cs" />
+    <Compile Include="Web\OAuth\TaobaoClient.cs" />
+    <Compile Include="Web\OAuth\WeixinClient.cs" />
+    <Compile Include="Web\OAuth\BaiduClient.cs" />
+    <Compile Include="Web\OAuth\QQClient.cs" />
     <Compile Include="Web\OAuthServer.cs" />
     <Compile Include="Web\OAuthConfig.cs" />
     <Compile Include="Yun\AMap.cs" />
增加 +33 -0
diff --git a/NewLife.Core/Web/OAuth/BaiduClient.cs b/NewLife.Core/Web/OAuth/BaiduClient.cs
new file mode 100644
index 0000000..7ddd742
--- /dev/null
+++ b/NewLife.Core/Web/OAuth/BaiduClient.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+
+namespace NewLife.Web.OAuth
+{
+    /// <summary>身份验证提供者</summary>
+    public class BaiduClient : OAuthClient
+    {
+        /// <summary>实例化</summary>
+        public BaiduClient()
+        {
+            var url = "https://openapi.baidu.com/oauth/2.0/";
+
+            AuthUrl = url + "authorize?response_type={response_type}&client_id={key}&redirect_uri={redirect}&state={state}&scope={scope}";
+            AccessUrl = url + "token?grant_type=authorization_code&client_id={key}&client_secret={secret}&code={code}&state={state}&redirect_uri={redirect}";
+            UserUrl = "https://openapi.baidu.com/rest/2.0/passport/users/getLoggedInUser?access_token={token}";
+        }
+
+        /// <summary>从响应数据中获取信息</summary>
+        /// <param name="dic"></param>
+        protected override void OnGetInfo(IDictionary<String, String> dic)
+        {
+            base.OnGetInfo(dic);
+
+            if (dic.ContainsKey("uid")) UserID = dic["uid"].Trim().ToLong();
+            if (dic.ContainsKey("uname")) UserName = dic["uname"].Trim();
+
+            // small image: http://tb.himg.baidu.com/sys/portraitn/item/{$portrait}
+            // large image: http://tb.himg.baidu.com/sys/portrait/item/{$portrait}
+            if (dic.ContainsKey("portrait")) Avatar = "http://tb.himg.baidu.com/sys/portrait/item/" + dic["portrait"].Trim();
+        }
+    }
+}
\ No newline at end of file
增加 +50 -0
diff --git a/NewLife.Core/Web/OAuth/GithubClient.cs b/NewLife.Core/Web/OAuth/GithubClient.cs
new file mode 100644
index 0000000..9926b56
--- /dev/null
+++ b/NewLife.Core/Web/OAuth/GithubClient.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace NewLife.Web.OAuth
+{
+    /// <summary>身份验证提供者</summary>
+    public class GithubClient : OAuthClient
+    {
+        /// <summary>实例化</summary>
+        public GithubClient()
+        {
+            var url = "https://github.com/login/oauth/";
+
+            AuthUrl = url + "authorize?response_type={response_type}&client_id={key}&redirect_uri={redirect}&state={state}&scope={scope}";
+            AccessUrl = url + "access_token?grant_type=authorization_code&client_id={key}&client_secret={secret}&code={code}&state={state}&redirect_uri={redirect}";
+            UserUrl = "https://api.github.com/user?access_token={token}";
+        }
+
+        /// <summary>从响应数据中获取信息</summary>
+        /// <param name="dic"></param>
+        protected override void OnGetInfo(IDictionary<String, String> dic)
+        {
+            base.OnGetInfo(dic);
+
+            if (dic.ContainsKey("id")) UserID = dic["id"].Trim('\"').ToLong();
+            if (dic.ContainsKey("login")) UserName = dic["login"].Trim();
+            if (dic.ContainsKey("name")) NickName = dic["name"].Trim();
+            if (dic.ContainsKey("avatar_url")) Avatar = dic["avatar_url"].Trim();
+        }
+
+        private WebClientX _Client;
+
+        /// <summary>创建客户端</summary>
+        /// <param name="url">路径</param>
+        /// <returns></returns>
+        protected override async Task<String> Request(String url)
+        {
+            if (_Client == null)
+            {
+                // 允许宽松头部
+                WebClientX.SetAllowUnsafeHeaderParsing(true);
+
+                // 必须指定中文编码
+                _Client = new WebClientX(true, true);
+            }
+            return LastHtml = await _Client.DownloadStringAsync(url);
+        }
+    }
+}
\ No newline at end of file
增加 +40 -0
diff --git a/NewLife.Core/Web/OAuth/QQClient.cs b/NewLife.Core/Web/OAuth/QQClient.cs
new file mode 100644
index 0000000..3eec096
--- /dev/null
+++ b/NewLife.Core/Web/OAuth/QQClient.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+
+namespace NewLife.Web.OAuth
+{
+    /// <summary>身份验证提供者</summary>
+    public class QQClient : OAuthClient
+    {
+        /// <summary>实例化</summary>
+        public QQClient()
+        {
+            var url = "https://graph.qq.com/oauth2.0/";
+
+            AuthUrl = url + "authorize?response_type={response_type}&client_id={key}&redirect_uri={redirect}&state={state}&scope={scope}";
+            AccessUrl = url + "token?grant_type=authorization_code&client_id={key}&client_secret={secret}&code={code}&state={state}&redirect_uri={redirect}";
+            OpenIDUrl = url + "me?access_token={token}";
+            UserUrl = "https://graph.qq.com/user/get_user_info?access_token={token}&oauth_consumer_key={key}&openid={openid}";
+        }
+
+        /// <summary>从响应数据中获取信息</summary>
+        /// <param name="dic"></param>
+        protected override void OnGetInfo(IDictionary<String, String> dic)
+        {
+            base.OnGetInfo(dic);
+
+            if (dic.ContainsKey("nickname")) NickName = dic["nickname"].Trim();
+
+            // 从大到小找头像
+            var avs = "figureurl_qq_2,figureurl_qq_1,figureurl_2,figureurl_1,figureurl".Split(",");
+            foreach (var item in avs)
+            {
+                if (dic.TryGetValue(item, out var av) && !av.IsNullOrEmpty())
+                {
+                    Avatar = av.Trim();
+                    break;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
增加 +29 -0
diff --git a/NewLife.Core/Web/OAuth/TaobaoClient.cs b/NewLife.Core/Web/OAuth/TaobaoClient.cs
new file mode 100644
index 0000000..136b526
--- /dev/null
+++ b/NewLife.Core/Web/OAuth/TaobaoClient.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+
+namespace NewLife.Web.OAuth
+{
+    /// <summary>身份验证提供者</summary>
+    public class TaobaoClient : OAuthClient
+    {
+        /// <summary>实例化</summary>
+        public TaobaoClient()
+        {
+            var url = "https://oauth.taobao.com/";
+
+            AuthUrl = url + "authorize?response_type={response_type}&client_id={key}&redirect_uri={redirect}&state={state}&scope={scope}";
+            AccessUrl = url + "token?grant_type=authorization_code&client_id={key}&client_secret={secret}&code={code}&state={state}&redirect_uri={redirect}";
+            //UserUrl = "https://openapi.baidu.com/rest/2.0/passport/users/getLoggedInUser?access_token={token}";
+        }
+
+        /// <summary>从响应数据中获取信息</summary>
+        /// <param name="dic"></param>
+        protected override void OnGetInfo(IDictionary<String, String> dic)
+        {
+            base.OnGetInfo(dic);
+
+            if (dic.ContainsKey("taobao_user_id")) UserID = dic["taobao_user_id"].Trim('\"').ToLong();
+            if (dic.ContainsKey("taobao_user_nick")) UserName = dic["taobao_user_nick"].Trim();
+        }
+    }
+}
\ No newline at end of file
增加 +21 -0
diff --git a/NewLife.Core/Web/OAuth/WeixinClient.cs b/NewLife.Core/Web/OAuth/WeixinClient.cs
new file mode 100644
index 0000000..101c450
--- /dev/null
+++ b/NewLife.Core/Web/OAuth/WeixinClient.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+
+namespace NewLife.Web.OAuth
+{
+    /// <summary>身份验证提供者</summary>
+    public class WeixinClient : OAuthClient
+    {
+        /// <summary>实例化</summary>
+        public WeixinClient()
+        {
+        }
+
+        /// <summary>从响应数据中获取信息</summary>
+        /// <param name="dic"></param>
+        protected override void OnGetInfo(IDictionary<String, String> dic)
+        {
+            base.OnGetInfo(dic);
+        }
+    }
+}
\ No newline at end of file
修改 +64 -162
diff --git a/NewLife.Core/Web/OAuthClient.cs b/NewLife.Core/Web/OAuthClient.cs
index 19eff2d..320ef55 100644
--- a/NewLife.Core/Web/OAuthClient.cs
+++ b/NewLife.Core/Web/OAuthClient.cs
@@ -4,12 +4,13 @@ using System.Linq;
 using System.Threading.Tasks;
 using System.Web;
 using NewLife.Log;
+using NewLife.Reflection;
 using NewLife.Security;
 using NewLife.Serialization;
 
 namespace NewLife.Web
 {
-    /// <summary>OAuth 2.0</summary>
+    /// <summary>OAuth 2.0 客户端</summary>
     public class OAuthClient
     {
         #region 属性
@@ -28,18 +29,6 @@ namespace NewLife.Web
         /// <summary>访问令牌地址</summary>
         public String AccessUrl { get; set; }
 
-        /// <summary>基础地址。用于相对路径生成完整绝对路径</summary>
-        /// <remarks>
-        /// 为了解决反向代理问题,可调用WebHelper.GetRawUrl取得原始访问地址作为基础地址。
-        /// </remarks>
-        public String BaseUrl { get; set; }
-
-        /// <summary>重定向地址</summary>
-        /// <remarks>
-        /// 某些提供商(如百度)会在获取AccessToken时要求传递与前面一致的重定向地址
-        /// </remarks>
-        public String RedirectUri { get; set; }
-
         /// <summary>响应类型</summary>
         /// <remarks>
         /// 验证服务器跳转回来子系统时的类型,默认code,此时还需要子系统服务端请求验证服务器换取AccessToken;
@@ -71,11 +60,48 @@ namespace NewLife.Web
         public IDictionary<String, String> Items { get; private set; }
         #endregion
 
+        #region 构造
+        /// <summary>实例化</summary>
+        public OAuthClient()
+        {
+            Name = GetType().Name.TrimEnd("Client");
+        }
+        #endregion
+
+        #region 静态创建
+        private static IDictionary<String, Type> _map;
+        /// <summary>根据名称创建客户端</summary>
+        /// <param name="name"></param>
+        /// <returns></returns>
+        public static OAuthClient Create(String name)
+        {
+            // 初始化映射表
+            if (_map == null)
+            {
+                var dic = new Dictionary<String, Type>(StringComparer.OrdinalIgnoreCase);
+                foreach (var item in typeof(OAuthClient).GetAllSubclasses(true))
+                {
+                    var key = item.Name.TrimEnd("Client");
+                    dic[key] = item;
+                }
+
+                _map = dic;
+            }
+
+            if (!_map.TryGetValue(name, out var type)) throw new Exception($"找不到[{name}]的OAuth客户端");
+
+            var client = type.CreateInstance() as OAuthClient;
+            //client.Name = name;
+            client.Apply(name);
+
+            return client;
+        }
+        #endregion
+
         #region 方法
         /// <summary>应用参数设置</summary>
         /// <param name="name"></param>
-        /// <param name="baseUrl"></param>
-        public void Apply(String name, String baseUrl = null)
+        public void Apply(String name)
         {
             var set = OAuthConfig.Current;
             var ms = set.Items;
@@ -86,10 +112,9 @@ namespace NewLife.Web
             if (name.IsNullOrEmpty()) mi = ms.FirstOrDefault(e => e.Enable);
             if (mi == null) throw new InvalidOperationException($"未找到有效的OAuth服务端设置[{name}]");
 
-            name = mi.Name;
+            Name = mi.Name;
 
             if (set.Debug) Log = XTrace.Log;
-            BaseUrl = baseUrl;
 
             Apply(mi);
         }
@@ -102,140 +127,18 @@ namespace NewLife.Web
             Key = mi.AppID;
             Secret = mi.Secret;
             Scope = mi.Scope;
-
-            switch (mi.Name.ToLower())
-            {
-                case "qq": SetQQ(); break;
-                case "baidu": SetBaidu(); break;
-                case "taobao": SetTaobao(); break;
-                case "github": SetGithub(); break;
-            }
-        }
-        #endregion
-
-        #region 默认提供者
-        /// <summary>设置为QQ专属地址</summary>
-        public void SetQQ()
-        {
-            var set = OAuthConfig.Current;
-            var mi = set.GetOrAdd("QQ");
-            mi.Enable = true;
-
-            var url = "https://graph.qq.com/oauth2.0/";
-            if (!mi.Server.IsNullOrEmpty()) url = mi.Server.EnsureEnd("/");
-
-            AuthUrl = url + "authorize?response_type={response_type}&client_id={key}&redirect_uri={redirect}&state={state}&scope={scope}";
-            AccessUrl = url + "token?grant_type=authorization_code&client_id={key}&client_secret={secret}&code={code}&state={state}&redirect_uri={redirect}";
-            OpenIDUrl = url + "me?access_token={token}";
-            UserUrl = "https://graph.qq.com/user/get_user_info?access_token={token}&oauth_consumer_key={key}&openid={openid}";
-
-            _OnGetUserInfo = dic =>
-            {
-                if (dic.ContainsKey("nickname")) NickName = dic["nickname"].Trim();
-
-                // 从大到小找头像
-                var avs = "figureurl_qq_2,figureurl_qq_1,figureurl_2,figureurl_1,figureurl".Split(",");
-                foreach (var item in avs)
-                {
-                    if (dic.TryGetValue(item, out var av) && !av.IsNullOrEmpty())
-                    {
-                        Avatar = av.Trim();
-                        break;
-                    }
-                }
-            };
-
-            set.SaveAsync();
-        }
-
-        /// <summary>设置百度</summary>
-        public void SetBaidu()
-        {
-            var set = OAuthConfig.Current;
-            var mi = set.GetOrAdd("Baidu");
-            mi.Enable = true;
-
-            var url = "https://openapi.baidu.com/oauth/2.0/";
-            if (!mi.Server.IsNullOrEmpty()) url = mi.Server.EnsureEnd("/");
-
-            AuthUrl = url + "authorize?response_type={response_type}&client_id={key}&redirect_uri={redirect}&state={state}&scope={scope}";
-            AccessUrl = url + "token?grant_type=authorization_code&client_id={key}&client_secret={secret}&code={code}&state={state}&redirect_uri={redirect}";
-            UserUrl = "https://openapi.baidu.com/rest/2.0/passport/users/getLoggedInUser?access_token={token}";
-
-            _OnGetUserInfo = dic =>
-            {
-                if (dic.ContainsKey("uid")) UserID = dic["uid"].Trim().ToLong();
-                if (dic.ContainsKey("uname")) UserName = dic["uname"].Trim();
-
-                // small image: http://tb.himg.baidu.com/sys/portraitn/item/{$portrait}
-                // large image: http://tb.himg.baidu.com/sys/portrait/item/{$portrait}
-                if (dic.ContainsKey("portrait")) Avatar = "http://tb.himg.baidu.com/sys/portrait/item/" + dic["portrait"].Trim();
-            };
-
-            set.SaveAsync();
-        }
-
-        /// <summary>设置淘宝</summary>
-        public void SetTaobao()
-        {
-            var set = OAuthConfig.Current;
-            var mi = set.GetOrAdd("Taobao");
-            mi.Enable = true;
-
-            var url = "https://oauth.taobao.com/";
-            if (!mi.Server.IsNullOrEmpty()) url = mi.Server.EnsureEnd("/");
-
-            AuthUrl = url + "authorize?response_type={response_type}&client_id={key}&redirect_uri={redirect}&state={state}&scope={scope}";
-            AccessUrl = url + "token?grant_type=authorization_code&client_id={key}&client_secret={secret}&code={code}&state={state}&redirect_uri={redirect}";
-            //UserUrl = "https://openapi.baidu.com/rest/2.0/passport/users/getLoggedInUser?access_token={token}";
-
-            _OnGetUserInfo = dic =>
-            {
-                if (dic.ContainsKey("taobao_user_id")) UserID = dic["taobao_user_id"].Trim('\"').ToLong();
-                if (dic.ContainsKey("taobao_user_nick")) UserName = dic["taobao_user_nick"].Trim();
-            };
-
-            set.SaveAsync();
-        }
-
-        /// <summary>设置Github</summary>
-        public void SetGithub()
-        {
-            var set = OAuthConfig.Current;
-            var mi = set.GetOrAdd("Github");
-            mi.Enable = true;
-
-            var url = "https://github.com/login/oauth/";
-            if (!mi.Server.IsNullOrEmpty()) url = mi.Server.EnsureEnd("/");
-
-            AuthUrl = url + "authorize?response_type={response_type}&client_id={key}&redirect_uri={redirect}&state={state}&scope={scope}";
-            AccessUrl = url + "access_token?grant_type=authorization_code&client_id={key}&client_secret={secret}&code={code}&state={state}&redirect_uri={redirect}";
-            UserUrl = "https://api.github.com/user?access_token={token}";
-
-            _OnGetUserInfo = dic =>
-            {
-                if (dic.ContainsKey("id")) UserID = dic["id"].Trim('\"').ToLong();
-                if (dic.ContainsKey("login")) UserName = dic["login"].Trim();
-                if (dic.ContainsKey("name")) NickName = dic["name"].Trim();
-                if (dic.ContainsKey("avatar_url")) Avatar = dic["avatar_url"].Trim();
-            };
-
-            set.SaveAsync();
-
-            // 允许宽松头部
-            WebClientX.SetAllowUnsafeHeaderParsing(true);
-
-            if (_Client == null) _Client = new WebClientX(true, true);
         }
         #endregion
 
         #region 1-跳转验证
+        private String _redirect;
         private String _state;
 
         /// <summary>跳转验证</summary>
         /// <param name="redirect">验证完成后调整的目标地址</param>
         /// <param name="state">用户状态数据</param>
-        public virtual String Authorize(String redirect, String state = null)
+        /// <param name="baseUri">相对地址的基地址</param>
+        public virtual String Authorize(String redirect, String state = null, Uri baseUri = null)
         {
             if (redirect.IsNullOrEmpty()) throw new ArgumentNullException(nameof(redirect));
 
@@ -249,19 +152,13 @@ namespace NewLife.Web
             if (redirect.StartsWith("~/")) redirect = HttpRuntime.AppDomainAppVirtualPath.EnsureEnd("/") + redirect.Substring(2);
             if (redirect.StartsWith("/"))
             {
-                var burl = BaseUrl;
-                if (burl.IsNullOrEmpty()) throw new ArgumentNullException(nameof(BaseUrl), "使用相对跳转地址时,需要设置BaseUrl");
-                // 从Http请求头中取出原始主机名和端口
-                //var request = HttpContext.Current.Request;
-                //var uri = request.GetRawUrl();
+                if (baseUri == null) throw new ArgumentNullException(nameof(baseUri), "使用相对跳转地址时,需要设置BaseUrl");
 
-                var uri = new Uri(new Uri(BaseUrl), redirect);
+                var uri = new Uri(baseUri, redirect);
                 redirect = uri.ToString();
             }
-
-            //if (redirect.Contains("/")) redirect = HttpUtility.UrlEncode(redirect);
 #endif
-            RedirectUri = redirect;
+            _redirect = redirect;
             _state = state;
 
             var url = GetUrl(AuthUrl);
@@ -301,7 +198,8 @@ namespace NewLife.Web
                 if (dic.ContainsKey("refresh_token")) RefreshToken = dic["refresh_token"].Trim();
                 if (dic.ContainsKey("openid")) OpenID = dic["openid"].Trim();
 
-                _OnGetUserInfo?.Invoke(dic);
+                //_OnGetUserInfo?.Invoke(dic);
+                OnGetInfo(dic);
             }
             Items = dic;
 
@@ -334,7 +232,8 @@ namespace NewLife.Web
                 if (dic.ContainsKey("expires_in")) Expire = DateTime.Now.AddSeconds(dic["expires_in"].Trim().ToInt());
                 if (dic.ContainsKey("openid")) OpenID = dic["openid"].Trim();
 
-                _OnGetUserInfo?.Invoke(dic);
+                //_OnGetUserInfo?.Invoke(dic);
+                OnGetInfo(dic);
             }
             Items = dic;
 
@@ -358,8 +257,6 @@ namespace NewLife.Web
         /// <summary>头像</summary>
         public String Avatar { get; set; }
 
-        private Action<IDictionary<String, String>> _OnGetUserInfo;
-
         /// <summary>获取用户信息</summary>
         /// <returns></returns>
         public virtual async Task<String> GetUserInfo()
@@ -382,12 +279,14 @@ namespace NewLife.Web
                 if (dic.ContainsKey("uid")) UserID = dic["uid"].Trim().ToLong();
                 if (dic.ContainsKey("userid")) UserID = dic["userid"].Trim().ToLong();
                 if (dic.ContainsKey("user_id")) UserID = dic["user_id"].Trim().ToLong();
+                if (dic.ContainsKey("name")) UserName = dic["name"].Trim();
                 if (dic.ContainsKey("username")) UserName = dic["username"].Trim();
                 if (dic.ContainsKey("user_name")) UserName = dic["user_name"].Trim();
                 if (dic.ContainsKey("nickname")) NickName = dic["nickname"].Trim();
-                if (dic.ContainsKey("openid")) OpenID = dic["openid"].Trim();
 
-                _OnGetUserInfo?.Invoke(dic);
+                //_OnGetUserInfo?.Invoke(dic);
+                OnGetInfo(dic);
+
                 //Items = dic;
                 // 合并字典
                 if (Items == null)
@@ -418,7 +317,7 @@ namespace NewLife.Web
                .Replace("{token}", AccessToken)
                .Replace("{code}", Code)
                .Replace("{openid}", OpenID)
-               .Replace("{redirect}", HttpUtility.UrlEncode(RedirectUri + ""))
+               .Replace("{redirect}", HttpUtility.UrlEncode(_redirect + ""))
                .Replace("{scope}", Scope)
                .Replace("{state}", _state);
 
@@ -453,17 +352,20 @@ namespace NewLife.Web
         /// <summary>最后一次请求的响应内容</summary>
         public String LastHtml { get; set; }
 
-        private WebClientX _Client;
-
         /// <summary>创建客户端</summary>
         /// <param name="url">路径</param>
         /// <returns></returns>
         protected virtual async Task<String> Request(String url)
         {
-            if (_Client != null) return LastHtml = await _Client.DownloadStringAsync(url);
-
             return LastHtml = await WebClientX.GetStringAsync(url);
         }
+
+        /// <summary>从响应数据中获取信息</summary>
+        /// <param name="dic"></param>
+        protected virtual void OnGetInfo(IDictionary<String, String> dic)
+        {
+            if (dic.ContainsKey("openid")) OpenID = dic["openid"].Trim();
+        }
         #endregion
 
         #region 日志
修改 +3 -3
diff --git a/NewLife.Core/Web/OAuthConfig.cs b/NewLife.Core/Web/OAuthConfig.cs
index e2b2da5..43c52c3 100644
--- a/NewLife.Core/Web/OAuthConfig.cs
+++ b/NewLife.Core/Web/OAuthConfig.cs
@@ -115,9 +115,9 @@ namespace NewLife.Web
         [XmlAttribute]
         public Boolean Enable { get; set; }
 
-        /// <summary>服务地址</summary>
-        [XmlAttribute]
-        public String Server { get; set; }
+        ///// <summary>服务地址</summary>
+        //[XmlAttribute]
+        //public String Server { get; set; }
 
         /// <summary>应用标识</summary>
         [XmlAttribute]
修改 +8 -6
diff --git a/NewLife.Cube/Controllers/SsoController.cs b/NewLife.Cube/Controllers/SsoController.cs
index 6d9200b..5f82844 100644
--- a/NewLife.Cube/Controllers/SsoController.cs
+++ b/NewLife.Cube/Controllers/SsoController.cs
@@ -65,10 +65,10 @@ namespace NewLife.Cube.Controllers
         /// <returns></returns>
         protected virtual OAuthClient GetClient(String name)
         {
-            var sso = new OAuthClient();
-            sso.Apply(name, Request.GetRawUrl() + "");
+            var client = OAuthClient.Create(name);
+            //client.Apply(name, Request.GetRawUrl() + "");
 
-            return sso;
+            return client;
         }
 
         /// <summary>第三方登录</summary>
@@ -82,14 +82,15 @@ namespace NewLife.Cube.Controllers
 
             var redirect = "~/Sso/LoginInfo";
 
+            var buri = Request.GetRawUrl();
             if (!returnUrl.IsNullOrEmpty() && returnUrl.StartsWithIgnoreCase("http"))
             {
                 var uri = new Uri(returnUrl);
-                if (uri != null && uri.Host.EqualIgnoreCase(Request.Url.Host)) returnUrl = uri.PathAndQuery;
+                if (uri != null && uri.Host.EqualIgnoreCase(buri.Host)) returnUrl = uri.PathAndQuery;
             }
 
             redirect = OAuthHelper.GetUrl(redirect, returnUrl);
-            var url = client.Authorize(redirect, client.Name);
+            var url = client.Authorize(redirect, client.Name, buri);
 
             return Redirect(url);
         }
@@ -107,9 +108,10 @@ namespace NewLife.Cube.Controllers
 
             var client = GetClient(state + "");
 
+            var buri = Request.GetRawUrl();
             var redirect = "~/Sso/LoginInfo";
             redirect = OAuthHelper.GetUrl(redirect, returnUrl);
-            client.Authorize(redirect, client.Name);
+            client.Authorize(redirect, client.Name, buri);
 
             Task.Run(() => client.GetAccessToken(code)).Wait();
 
修改 +12 -1
diff --git a/NewLife.Cube/NewLife.Cube.csproj b/NewLife.Cube/NewLife.Cube.csproj
index 953efed..f602b51 100644
--- a/NewLife.Cube/NewLife.Cube.csproj
+++ b/NewLife.Cube/NewLife.Cube.csproj
@@ -25,6 +25,8 @@
     <UpgradeBackupLocation>
     </UpgradeBackupLocation>
     <OldToolsVersion>15.0</OldToolsVersion>
+    <Use64BitIISExpress />
+    <UseGlobalApplicationHostFile />
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
@@ -906,7 +908,16 @@
     <VisualStudio>
       <FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
         <WebProjectProperties>
-          <SaveServerSettingsInUserFile>True</SaveServerSettingsInUserFile>
+          <UseIIS>True</UseIIS>
+          <AutoAssignPort>True</AutoAssignPort>
+          <DevelopmentServerPort>0</DevelopmentServerPort>
+          <DevelopmentServerVPath>/</DevelopmentServerVPath>
+          <IISUrl>http://localhost:1080/</IISUrl>
+          <NTLMAuthentication>False</NTLMAuthentication>
+          <UseCustomServer>False</UseCustomServer>
+          <CustomServerUrl>
+          </CustomServerUrl>
+          <SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
         </WebProjectProperties>
       </FlavorProperties>
     </VisualStudio>
修改 +38 -0
diff --git a/NewLife.Cube/Web/OAuthHelper.cs b/NewLife.Cube/Web/OAuthHelper.cs
index 6340155..aaf83e1 100644
--- a/NewLife.Cube/Web/OAuthHelper.cs
+++ b/NewLife.Cube/Web/OAuthHelper.cs
@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Web;
+using NewLife.Web;
 
 namespace NewLife.Cube.Web
 {
@@ -44,5 +45,42 @@ namespace NewLife.Cube.Web
 
             return url;
         }
+
+        //public static String GetRedirectUri(this HttpRequestBase req, String redirect)
+        //{
+        //    var buri = req.GetRawUrl();
+
+        //    var returnUrl = req["returnUrl"];
+        //    if (!returnUrl.IsNullOrEmpty() && returnUrl.StartsWithIgnoreCase("http"))
+        //    {
+        //        var uri = new Uri(returnUrl);
+        //        if (uri != null && uri.Host.EqualIgnoreCase(buri.Host)) returnUrl = uri.PathAndQuery;
+        //    }
+
+        //    if (!returnUrl.IsNullOrEmpty())
+        //    {
+        //        if (redirect.Contains("?"))
+        //            redirect += "&";
+        //        else
+        //            redirect += "?";
+
+        //        redirect += "returnUrl=" + HttpUtility.UrlEncode(returnUrl);
+        //    }
+
+        //    // 如果是相对路径,自动加上前缀。需要考虑反向代理的可能,不能直接使用Request.Url
+        //    if (redirect.StartsWith("~/")) redirect = HttpRuntime.AppDomainAppVirtualPath.EnsureEnd("/") + redirect.Substring(2);
+        //    if (redirect.StartsWith("/"))
+        //    {
+        //        //if (burl.IsNullOrEmpty()) throw new ArgumentNullException(nameof(BaseUrl), "使用相对跳转地址时,需要设置BaseUrl");
+        //        // 从Http请求头中取出原始主机名和端口
+        //        //var request = HttpContext.Current.Request;
+        //        //var uri = request.GetRawUrl();
+
+        //        var uri = new Uri(buri, redirect);
+        //        redirect = uri.ToString();
+        //    }
+
+        //    return redirect;
+        //}
     }
 }
\ No newline at end of file