新建项目:

安装DotNetOpenAuth:

新增OAuthController:

代码如下:

public class OAuthController : Controller
    {
        private readonly AuthorizationServer authorizationServer = new AuthorizationServer(new OAuth2AuthorizationServer());

        public async Task<ActionResult> Token()
        {
            var request = await this.authorizationServer.HandleTokenRequestAsync(this.Request, this.Response.ClientDisconnectedToken);
            Response.ContentType = request.Content.Headers.ContentType.ToString();
            return request.AsActionResult();
        }

        public async Task<ActionResult> Authorize()
        {
            var pendingRequest = await this.authorizationServer.ReadAuthorizationRequestAsync(Request, Response.ClientDisconnectedToken);
            if (pendingRequest == null)
            {
                throw new HttpException((int)HttpStatusCode.BadRequest, "Missing authorization request.");
            }

            var requestingClient = MvcApplication.DataContext.Clients.First(c => c.ClientIdentifier == pendingRequest.ClientIdentifier);

            // Consider auto-approving if safe to do so.
            if (((OAuth2AuthorizationServer)this.authorizationServer.AuthorizationServerServices).CanBeAutoApproved(pendingRequest))
            {
                var approval = this.authorizationServer.PrepareApproveAuthorizationRequest(pendingRequest, HttpContext.User.Identity.Name);
                var response = await this.authorizationServer.Channel.PrepareResponseAsync(approval, Response.ClientDisconnectedToken);
                Response.ContentType = response.Content.Headers.ContentType.ToString();
                return response.AsActionResult();
            }

            var model = new AccountAuthorizeModel
            {
                ClientApp = requestingClient.Name,
                Scope = pendingRequest.Scope,
                AuthorizationRequest = pendingRequest,
            };

            return View(model);
        }

        public async Task<ActionResult> AuthorizeResponse(bool isApproved)
        {
            var pendingRequest = await this.authorizationServer.ReadAuthorizationRequestAsync(Request, Response.ClientDisconnectedToken);
            if (pendingRequest == null)
            {
                throw new HttpException((int)HttpStatusCode.BadRequest, "Missing authorization request.");
            }

            IDirectedProtocolMessage response;
            if (isApproved)
            {
                // The authorization we file in our database lasts until the user explicitly revokes it.
                // You can cause the authorization to expire by setting the ExpirationDateUTC
                // property in the below created ClientAuthorization.
                var client = MvcApplication.DataContext.Clients.First(c => c.ClientIdentifier == pendingRequest.ClientIdentifier);
                client.ClientAuthorizations.Add(
                    new ClientAuthorization
                    {
                        Scope = OAuthUtilities.JoinScopes(pendingRequest.Scope),
                        User = MvcApplication.LoggedInUser,
                        CreatedOnUtc = DateTime.UtcNow,
                    });
                MvcApplication.DataContext.SaveChanges(); // submit now so that this new row can be retrieved later in this same HTTP request

                // In this simple sample, the user either agrees to the entire scope requested by the client or none of it.
                // But in a real app, you could grant a reduced scope of access to the client by passing a scope parameter to this method.
                response = this.authorizationServer.PrepareApproveAuthorizationRequest(pendingRequest, User.Identity.Name);
            }
            else
            {
                response = this.authorizationServer.PrepareRejectAuthorizationRequest(pendingRequest);
            }

            var preparedResponse = await this.authorizationServer.Channel.PrepareResponseAsync(response, Response.ClientDisconnectedToken);
            Response.ContentType = preparedResponse.Content.Headers.ContentType.ToString();
            return preparedResponse.AsActionResult();
        }
    }

添加Code目录,写入这些类:

Client:需实现IClientDescription接口

public class Client:IClientDescription
    {
        public int ClientId { get; set; }
        [Required]
        [MaxLength()]
        public string ClientIdentifier { get; set; }
        [MaxLength()]
        public string ClientSecret { get; set; }
        [MaxLength()]
        public string Callback { get; set; }
        [Required]
        [MaxLength()]
        public string Name { get; set; }
        public int ClientType { get; set; }
        public virtual ICollection<ClientAuthorization> ClientAuthorizations { get; set; }

        public Uri DefaultCallback
        {
            get { return string.IsNullOrEmpty(this.Callback) ? null : new Uri(this.Callback); }
        }

        ClientType IClientDescription.ClientType
        {
            get { return (ClientType)this.ClientType; }
        }

        public bool HasNonEmptySecret
        {
            get { return !string.IsNullOrEmpty(this.ClientSecret); }
        }

        public bool IsCallbackAllowed(Uri callback)
        {
            if (string.IsNullOrEmpty(this.Callback))
            {
                return true;
            }

            // In this sample, it's enough of a callback URL match if the scheme and host match.
            // In a production app, it is advisable to require a match on the path as well.
            Uri acceptableCallbackPattern = new Uri(this.Callback);
            if (string.Equals(acceptableCallbackPattern.GetLeftPart(UriPartial.Authority), callback.GetLeftPart(UriPartial.Authority), StringComparison.Ordinal))
            {
                return true;
            }

            return false;
        }

        public bool IsValidClientSecret(string secret)
        {
            return MessagingUtilities.EqualsConstantTime(secret, this.ClientSecret);
        }
    }

User:

public class User
    {
        public int UserId { get; set; }
        [Required]
        [MaxLength()]
        public string OpenIDClaimedIdentifier { get; set; }
        [MaxLength()]
        public string OpenIDFriendlyIdentifier { get; set; }
        public virtual ICollection<ClientAuthorization> ClientAuthorizations { get; set; }
    }

ClientAuthorization:

public class ClientAuthorization
    {
        [Key]
        public int AuthorizationId { get; set; }
        [Required]
        public DateTime CreatedOnUtc { get; set; }
        [Required]
        public int ClientId { get; set; }
        public int UserId { get; set; }
        [Required]
        public string Scope { get; set; }
        public DateTime? ExpirationDateUtc { get; set; }
        public virtual Client Client { get; set; }
        public virtual User User { get; set; }
    }

Nonce:

public class Nonce
    {
        [Key]
        [Column(Order = )]
        [MaxLength()]
        public string Context { get; set; }
        [Key]
        [Column(Order = )]
        [MaxLength()]
        public string Code { get; set; }
        [Key]
        [Column(Order = )]
        public DateTime Timestamp { get; set; }
    }

SymmetricCryptoKey:

public class SymmetricCryptoKey
    {
        [Key]
        [Column(Order = )]
        [MaxLength()]
        public string Bucket { get; set; }
        [Key]
        [Column(Order = )]
        [MaxLength()]
        public string Handle { get; set; }
        public DateTime ExpiresUtc { get; set; }
        [Required]
        public byte[] Secret { get; set; }
    }

这里需要注意后面两个类的主键长度,如果不指定长度的话,EF会自动生成128位的长度,这样在后面的插入数据过程中就会报错,可以看到:

因为项目中有两个context,所以用迁移的时候需要指定名字:

会有警告,说SqlServer的索引长度不能超过900字节,这个可以忽略。

上面这5个类有数据库里的实际对应表。

DatabaseKeyNonceStore:

public class DatabaseKeyNonceStore : INonceStore, ICryptoKeyStore
    {
        public CryptoKey GetKey(string bucket, string handle)
        {
            var result = MvcApplication.DataContext.SymmetricCryptoKeys
                .Where(x => x.Bucket == bucket && x.Handle == handle).AsEnumerable();
            var matches = result.Select(key => new CryptoKey(key.Secret, key.ExpiresUtc.AsUtc()));
            return matches.FirstOrDefault();
        }

        public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket)
        {
            var keys = MvcApplication.DataContext.SymmetricCryptoKeys.
                OrderByDescending(x => x.ExpiresUtc).
                Where(x => x.Bucket == bucket).ToList();
            var result = keys.Select(x => new KeyValuePair<string, CryptoKey>(x.Handle, new CryptoKey(x.Secret, x.ExpiresUtc.AsUtc())));
            return result;
        }

        public void RemoveKey(string bucket, string handle)
        {
            var match = MvcApplication.DataContext.SymmetricCryptoKeys.FirstOrDefault(k => k.Bucket == bucket && k.Handle == handle);
            if (match != null)
            {
                MvcApplication.DataContext.SymmetricCryptoKeys.Remove(match);
                MvcApplication.DataContext.SaveChanges();
            }
        }

        public void StoreKey(string bucket, string handle, CryptoKey key)
        {
            var keyRow = new SymmetricCryptoKey()
            {
                Bucket = bucket,
                Handle = handle,
                Secret = key.Key,
                ExpiresUtc = key.ExpiresUtc,
            };

            MvcApplication.DataContext.SymmetricCryptoKeys.Add(keyRow);
            MvcApplication.DataContext.SaveChanges();
        }

        public bool StoreNonce(string context, string nonce, DateTime timestampUtc)
        {
            MvcApplication.DataContext.Nonces.Add(new Nonce { Context = context, Code = nonce, Timestamp = timestampUtc });
            try
            {
                MvcApplication.DataContext.SaveChanges();
                return true;
            }
            catch (DuplicateKeyException)
            {
                return false;
            }
            catch (SqlException)
            {
                return false;
            }
        }
    }

这里需要添加System.Data.Linq的引用,这个类需要实现INonceStore, ICryptoKeyStore,这里还需要注意修改下面两个方法,否则会报如下错误:

public CryptoKey GetKey(string bucket, string handle)
        {
            var result = MvcApplication.DataContext.SymmetricCryptoKeys
                .Where(x => x.Bucket == bucket && x.Handle == handle).AsEnumerable();
            var matches = result.Select(key => new CryptoKey(key.Secret, key.ExpiresUtc.AsUtc()));
            return matches.FirstOrDefault();
        }

        public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket)
        {
            var keys = MvcApplication.DataContext.SymmetricCryptoKeys.
                OrderByDescending(x => x.ExpiresUtc).
                Where(x => x.Bucket == bucket).ToList();
            var result = keys.Select(x => new KeyValuePair<string, CryptoKey>(x.Handle, new CryptoKey(x.Secret, x.ExpiresUtc.AsUtc())));
            return result;
        }

Utilities:

internal static class Utilities
    {
        internal static DateTime AsUtc(this DateTime value)
        {
            if (value.Kind == DateTimeKind.Unspecified)
            {
                return new DateTime(value.Ticks, DateTimeKind.Utc);
            }

            return value.ToUniversalTime();
        }
    }

DataContext:

public class DataContext:DbContext
    {
        public DataContext(): base("name=DefaultConnection")
        {

        }

        public DbSet<ClientAuthorization> ClientAuthorizations { get; set; }
        public DbSet<Client> Clients { get; set; }
        public DbSet<User> Users { get; set; }
        public DbSet<Nonce> Nonces { get; set; }
        public DbSet<SymmetricCryptoKey> SymmetricCryptoKeys { get; set; }
    }

OAuth2AuthorizationServer:

public class OAuth2AuthorizationServer : IAuthorizationServerHost
    {
        public ICryptoKeyStore CryptoKeyStore
        {
            get { return MvcApplication.KeyNonceStore; }
        }

        public INonceStore NonceStore
        {
            get { return MvcApplication.KeyNonceStore; }
        }

        public AutomatedAuthorizationCheckResponse CheckAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest)
        {
            throw new NotImplementedException();
        }

        public AutomatedUserAuthorizationCheckResponse CheckAuthorizeResourceOwnerCredentialGrant(string userName, string password, IAccessTokenRequest accessRequest)
        {
            //用户名、密码验证
            throw new NotImplementedException();
        }

        public AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage)
        {
            var accessToken = new AuthorizationServerAccessToken();

            accessToken.Lifetime = TimeSpan.FromMinutes();

            // Also take into account the remaining life of the authorization and artificially shorten the access token's lifetime
            // to account for that if necessary.
            //// TODO: code here

            accessToken.ResourceServerEncryptionKey = new RSACryptoServiceProvider();
            accessToken.ResourceServerEncryptionKey.ImportParameters(ResourceServerEncryptionPublicKey);

            accessToken.AccessTokenSigningKey = CreateRSA();

            var result = new AccessTokenResult(accessToken);
            return result;
        }

        private static RSACryptoServiceProvider CreateRSA()
        {
            var rsa = new RSACryptoServiceProvider();
            rsa.ImportParameters(CreateAuthorizationServerSigningKey());
            return rsa;
        }

#if DEBUG
        /// <summary>
        /// This is the FOR SAMPLE ONLY hard-coded public key of the complementary OAuthResourceServer sample.
        /// </summary>
        /// <remarks>
        /// In a real app, the authorization server would need to determine which resource server the access token needs to be encoded for
        /// based on the authorization request.  It would then need to look up the public key for that resource server and use that in
        /// preparing the access token for the client to use against that resource server.
        /// </remarks>
        private static readonly RSAParameters ResourceServerEncryptionPublicKey = new RSAParameters {
            Exponent = , ,  },
            Modulus = , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,  },
        };
#else
        [Obsolete("You must use a real key for a real app.", true)]
        private static readonly RSAParameters ResourceServerEncryptionPublicKey;
#endif

        private static RSAParameters CreateAuthorizationServerSigningKey()
        {
#if DEBUG
            // Since the sample authorization server and the sample resource server must work together,
            // we hard-code a FOR SAMPLE USE ONLY key pair.  The matching public key information is hard-coded into the OAuthResourceServer sample.
            // In a real app, the RSA parameters would typically come from a certificate that may already exist.  It may simply be the HTTPS certificate for the auth server.
            return new RSAParameters {
                Exponent = , ,  },
                Modulus = , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,  },
                P = , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,  },
                Q = , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,  },
                DP = , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,  },
                DQ = , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,  },
                InverseQ = , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,  },
                D = , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,  },
            };
#else
            // This is how you could generate your own public/private key pair.
            // As we generate a new random key, we need to set the UseMachineKeyStore flag so that this doesn't
            // crash on IIS. For more information:
            // http://social.msdn.microsoft.com/Forums/en-US/clr/thread/7ea48fd0-8d6b-43ed-b272-1a0249ae490f?prof=required
            var cspParameters = new CspParameters();
            cspParameters.Flags = CspProviderFlags.UseArchivableKey | CspProviderFlags.UseMachineKeyStore;
            var keyPair = new RSACryptoServiceProvider(cspParameters);

            // After exporting the private/public key information, read the information out and store it somewhere
            var privateKey = keyPair.ExportParameters(true);
            var publicKey = keyPair.ExportParameters(false);

            // Ultimately the private key information must be what is returned through the AccessTokenSigningPrivateKey property.
            return privateKey;
#endif
        }

        public IClientDescription GetClient(string clientIdentifier)
        {
            var consumerRow = MvcApplication.DataContext.Clients.SingleOrDefault(
                consumerCandidate => consumerCandidate.ClientIdentifier == clientIdentifier);
            if (consumerRow == null)
            {
                throw new ArgumentOutOfRangeException("clientIdentifier");
            }

            return consumerRow;
        }

        public bool IsAuthorizationValid(IAuthorizationDescription authorization)
        {
            return this.IsAuthorizationValid(authorization.Scope, authorization.ClientIdentifier, authorization.UtcIssued, authorization.User);
        }

        private bool IsAuthorizationValid(HashSet<string> requestedScopes, string clientIdentifier, DateTime issuedUtc, string username)
        {
            issuedUtc += TimeSpan.FromSeconds();
            var grantedScopeStrings = from auth in MvcApplication.DataContext.ClientAuthorizations
                                      where
                                          auth.Client.ClientIdentifier == clientIdentifier &&
                                          auth.CreatedOnUtc <= issuedUtc &&
                                          (!auth.ExpirationDateUtc.HasValue || auth.ExpirationDateUtc.Value >= DateTime.UtcNow) &&
                                          auth.User.OpenIDClaimedIdentifier == username
                                      select auth.Scope;

            if (!grantedScopeStrings.Any())
            {
                return false;
            }

            var grantedScopes = new HashSet<string>(OAuthUtilities.ScopeStringComparer);
            foreach (string scope in grantedScopeStrings)
            {
                grantedScopes.UnionWith(OAuthUtilities.SplitScopes(scope));
            }

            return requestedScopes.IsSubsetOf(grantedScopes);
        }

        public bool CanBeAutoApproved(EndUserAuthorizationRequest authorizationRequest)
        {
            if (authorizationRequest == null)
            {
                throw new ArgumentNullException("authorizationRequest");
            }

            // NEVER issue an auto-approval to a client that would end up getting an access token immediately
            // (without a client secret), as that would allow arbitrary clients to masquarade as an approved client
            // and obtain unauthorized access to user data.
            if (authorizationRequest.ResponseType == EndUserAuthorizationResponseType.AuthorizationCode)
            {
                // Never issue auto-approval if the client secret is blank, since that too makes it easy to spoof
                // a client's identity and obtain unauthorized access.
                var requestingClient = MvcApplication.DataContext.Clients.First(c => c.ClientIdentifier == authorizationRequest.ClientIdentifier);
                if (!string.IsNullOrEmpty(requestingClient.ClientSecret))
                {
                    return this.IsAuthorizationValid(
                        authorizationRequest.Scope,
                        authorizationRequest.ClientIdentifier,
                        DateTime.UtcNow,
                        HttpContext.Current.User.Identity.Name);
                }
            }

            // Default to not auto-approving.
            return false;
        }
    }

在Models目录中添加AccountAuthorizeModel:

public class AccountAuthorizeModel
    {
        public string ClientApp { get; set; }

        public HashSet<string> Scope { get; set; }

        public EndUserAuthorizationRequest AuthorizationRequest { get; set; }
    }

在Global.asax中加入:

public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            KeyNonceStore = new DatabaseKeyNonceStore();
        }
        /// <summary>
        /// 连接数据库.
        /// </summary>
        public static DataContext DataContext
        {
            get
            {
                DataContext dataContext = DataContextSimple;
                if (dataContext == null)
                {
                    dataContext = new DataContext();
                    DataContextSimple = dataContext;
                }
                return dataContext;
            }
        }

        public static User LoggedInUser
        {
            get { return DataContext.Users.SingleOrDefault(user => user.OpenIDClaimedIdentifier == HttpContext.Current.User.Identity.Name); }
        }
        public static DatabaseKeyNonceStore KeyNonceStore { get; set; }

        protected void Application_EndRequest(object sender, EventArgs e)
        {
            CommitAndCloseDatabaseIfNecessary();
        }
        private static DataContext DataContextSimple
        {
            get
            {
                if (HttpContext.Current != null)
                {
                    return HttpContext.Current.Items["DataContext"] as DataContext;
                }
                else
                {
                    throw new InvalidOperationException();
                }
            }

            set
            {
                if (HttpContext.Current != null)
                {
                    HttpContext.Current.Items["DataContext"] = value;
                }
                else
                {
                    throw new InvalidOperationException();
                }
            }
        }
        private static void CommitAndCloseDatabaseIfNecessary()
        {
            var dataContext = DataContextSimple;
            if (dataContext != null)
            {
                dataContext.SaveChanges();
            }
        }
    }

在HomeController中添加方法:

[HttpPost]
        public ActionResult CreateDatabase()
        {
            MvcApplication.DataContext.Clients.Add(new Client
            {
                ClientIdentifier = "sampleconsumer",
                ClientSecret = "samplesecret",
                Name = "Some sample client",
            });
            MvcApplication.DataContext.Clients.Add(new Client
            {
                ClientIdentifier = "sampleImplicitConsumer",
                Name = "Some sample client used for implicit grants (no secret)",
                Callback = "http://localhost:59722/",
            });
            try
            {
                MvcApplication.DataContext.SaveChanges();
                ViewBag.Success = true;
            }
            catch (Exception ex)
            {
                ViewBag.Error = ex.Message;
            }
            return View();
        }

修改Home/Index页面添加一个按钮:

<div class="jumbotron">
    <h1>ASP.NET</h1>
    <p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
    <p><a href="http://asp.net" class="btn btn-primary btn-lg">Learn more &raquo;</a></p>
    @using (Html.BeginForm("CreateDatabase", "home"))
    {
        <input type="submit" class="btn btn-primary btn-lg" value="创建数据库" />
    }

</div>

添加CreateDatabase页面:

@{
    ViewBag.Title = "CreateDatabase";
}

<h2>CreateDatabase</h2>
<div class="jumbotron">
    @if (ViewBag.Success)
    {
        <p>数据库创建成功。</p>
    }
    else
    {
        <p>数据库创建失败:@ViewBag.Error</p>
    }
</div>

运行项目,点击创建数据库,可以看到新增的表和数据:

接下来就可以用上一篇的客户端来进行测试了:

首先修改下客户端的代码:

private static AuthorizationServerDescription authServerDescription = new AuthorizationServerDescription
        {
            TokenEndpoint = new Uri("http://localhost:26259/OAuth/Token"),
            AuthorizationEndpoint = new Uri("http://localhost:26259/OAuth/Authorize"),
        };

然后启动服务端和客户端,访问客户端:http://localhost:18180/Authorize,发现报错了:

这个错误很熟悉了吧,修改服务端的配置:

<messaging  relaxSslRequirements="true">
            <untrustedWebRequest>
                <whitelistHosts>
                    <!-- Uncomment to enable communication with localhost (should generally not activate in production!) -->
                    <!--<add name="localhost" />-->
                </whitelistHosts>
            </untrustedWebRequest>
        </messaging>

再次启动,发现找不到页面:

因为此时我们还没有添加这个页面,接下来添加这个页面:

@using MvcAuth2Server.Models
@using DotNetOpenAuth.OAuth2
@model AccountAuthorizeModel
@{
    ViewBag.Title = "Authorize";
}

<h2>Authorize</h2>

<div style="background-color: Yellow">
    <b>Warning</b>: Never give your login credentials to another web site or application.
</div>
<p>
    The
    @Html.Encode(Model.ClientApp)
    application is requesting to access the private data in your account here. Is that
    alright with you?
</p>
<p>
    <b>Requested access: </b>
    @Html.Encode(String.Join(" ", Model.Scope.ToArray()))
</p>
<p>
    If you grant access now, you can revoke it at any time by returning to
    @Html.ActionLink("your account page", "Edit")
</p>
    @using (Html.BeginForm("AuthorizeResponse", "OAuth")) {
        @Html.AntiForgeryToken()
        @Html.Hidden("IsApproved")
        @Html.Hidden("client_id", Model.AuthorizationRequest.ClientIdentifier)
        @Html.Hidden("redirect_uri", Model.AuthorizationRequest.Callback)
        @Html.Hidden("state", Model.AuthorizationRequest.ClientState)
        @Html.Hidden("scope", OAuthUtilities.JoinScopes(Model.AuthorizationRequest.Scope))
        @Html.Hidden("response_type", Model.AuthorizationRequest.ResponseType == DotNetOpenAuth.OAuth2.Messages.EndUserAuthorizationResponseType.AccessToken ? "token" : "code")
<div style="display: none" id="responseButtonsDiv">
    <input type="submit" value="Yes" onclick="document.getElementsByName('IsApproved')[0].value = true; return true;" />
    <input type="submit" value="No" onclick="document.getElementsByName('IsApproved')[0].value = false; return true;" />
</div>
<div id="javascriptDisabled">
    <b>Javascript appears to be disabled in your browser. </b>This page requires Javascript
    to be enabled to better protect your security.
</div>
<script language="javascript" type="text/javascript">
            //<![CDATA[
            // we use HTML to hide the action buttons and Javascript to show them
            // to protect against click-jacking in an iframe whose javascript is disabled.
            document.getElementById('responseButtonsDiv').style.display = 'block';
            document.getElementById('javascriptDisabled').style.display = 'none';

            // Frame busting code (to protect us from being hosted in an iframe).
            // This protects us from click-jacking.
            if (document.location !== window.top.location) {
                window.top.location = document.location;
            }
            //]]>
</script>
     } 

再次运行,就看到下面的页面了:

这里就有问题了,还没授权登录呢,怎么就过了呢,原因在这里:

[Authorize, AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
        public async Task<ActionResult> Authorize()

更新OAuth里的两个方法:

[Authorize, AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
        [HttpHeader("x-frame-options", "SAMEORIGIN")] // mitigates clickjacking
        public async Task<ActionResult> Authorize()
[Authorize, HttpPost, ValidateAntiForgeryToken]
        public async Task<ActionResult> AuthorizeResponse(bool isApproved)

这里有个HttpHeader,在Code目录下添加此类:

public class HttpHeaderAttribute : ActionFilterAttribute
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="HttpHeaderAttribute"/> class.
        /// </summary>
        /// <param name="name">The HTTP header name.</param>
        /// <param name="value">The HTTP header value.</param>
        public HttpHeaderAttribute(string name, string value)
        {
            this.Name = name;
            this.Value = value;
        }

        /// <summary>
        /// Gets or sets the name of the HTTP Header.
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the value of the HTTP Header.
        /// </summary>
        public string Value { get; set; }

        /// <summary>
        /// Called by the MVC framework after the action result executes.
        /// </summary>
        /// <param name="filterContext">The filter context.</param>
        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            filterContext.HttpContext.Response.AppendHeader(this.Name, this.Value);
            base.OnResultExecuted(filterContext);
        }
    }

再次运行,就会跳转到登录页面了:

这里我们想使用OpenID登录,所以使用另外一个登录页面:

在Models目录下的AccountViewModels中添加:

public class LogOnModel
    {
        [Required]
        [Display(Name="OpenID")]
        public string UserSuppliedIdentifier { get; set; }

        [Display(Name="Remember me?")]
        public bool RememberMe { get; set; }
    }

在AccountController中添加:

[HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Login(LogOnModel model, string returnUrl)
        {
            if (ModelState.IsValid)
            {
                var rp = new OpenIdRelyingParty();
                var request = await rp.CreateRequestAsync(model.UserSuppliedIdentifier, Realm.AutoDetect, new Uri(Request.Url, Url.Action("Authenticate")));
                if (request != null)
                {
                    if (returnUrl != null)
                    {
                        request.AddCallbackArguments("returnUrl", returnUrl);
                    }

                    var response = await request.GetRedirectingResponseAsync();
                    Response.ContentType = response.Content.Headers.ContentType.ToString();
                    return response.AsActionResult();
                }
                else
                {
                    ModelState.AddModelError(string.Empty, "The identifier you supplied is not recognized as a valid OpenID Identifier.");
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }
        [AllowAnonymous]
        public async Task<ActionResult> Authenticate(string returnUrl)
        {
            var rp = new OpenIdRelyingParty();
            var response = await rp.GetResponseAsync(Request);
            if (response != null)
            {
                switch (response.Status)
                {
                    case AuthenticationStatus.Authenticated:
                        // Make sure we have a user account for this guy.
                        string identifier = response.ClaimedIdentifier; // convert to string so LinqToSQL expression parsing works.
                        if (MvcApplication.DataContext.Users.FirstOrDefault(u => u.OpenIDClaimedIdentifier == identifier) == null)
                        {
                            MvcApplication.DataContext.Users.Add(new User
                            {
                                OpenIDFriendlyIdentifier = response.FriendlyIdentifierForDisplay,
                                OpenIDClaimedIdentifier = response.ClaimedIdentifier,
                            });
                        }

                        FormsAuthentication.SetAuthCookie(response.ClaimedIdentifier, false);
                        return this.Redirect(returnUrl ?? Url.Action("Index", "Home"));
                    default:
                        ModelState.AddModelError(string.Empty, "An error occurred during login.");
                        break;
                }
            }

            return this.View("LogOn");
        }

这里的[AllowAnonymous]很重要,没有这个的话无法正常提交请求

运行网站

跳转到登录页面:

这里需要有一个OpenID的提供者,此时我们使用客户端来提供OpenID服务(假设服务端和客户端都是内网的,直接用客户端的登录授权)

下面需要改造客户端,这里坑比较多,需要注意:

首先修改HomeController:

public ActionResult Index()
        {
            if (Request.AcceptTypes.Contains("application/xrds+xml"))
            {
                ViewData["OPIdentifier"] = true;
                return View("Xrds");
            }
            return View();
        }

        public ActionResult Xrds()
        {
            ViewData["OPIdentifier"] = true;
            return View();
        }

加入Xrds页面:

<?xml version="1.0" encoding="UTF-8" ?>
<xrds:XRDS xmlns:xrds="xri://$xrds"
           xmlns:openid="http://openid.net/xmlns/1.0"
           xmlns="xri://$xrd*($v*2.0)">
    <XRD>
        <Service priority=">
            @if (ViewData["OPIdentifier"] != null)
            {
                <Type>http://specs.openid.net/auth/2.0/server</Type>
            }
            else
            {
                <Type>http://specs.openid.net/auth/2.0/signon</Type>
            }
            <Type>http://openid.net/extensions/sreg/1.1</Type>
            <Type>http://axschema.org/contact/email</Type>

            @*Add these types when and if the Provider supports the respective aspects of the UI extension.
                <Type>http://specs.openid.net/extensions/ui/1.0/mode/popup</Type>
                <Type>http://specs.openid.net/extensions/ui/1.0/lang-pref</Type>
                <Type>http://specs.openid.net/extensions/ui/1.0/icon</Type>*@
            <URI>@(new Uri(Request.Url, Response.ApplyAppPathModifier("~/OpenId/Provider")))</URI>
        </Service>
        @if (ViewData["OPIdentifier"] == null)
        {
            <Service priority=">
                <Type>http://openid.net/signon/1.0</Type>
                <Type>http://openid.net/extensions/sreg/1.1</Type>
                <Type>http://axschema.org/contact/email</Type>
                <URI>@(new Uri(Request.Url, Response.ApplyAppPathModifier("~/OpenId/Provider")))</URI>
            </Service>
        }
    </XRD>
</xrds:XRDS>
@{ Layout = null;}
@{ Response.ContentType = "application/xrds+xml"; }

新增OpenIdController:

[ValidateInput(false)]
        public async Task<ActionResult> Provider()
        {
            return View();
        }

OpenId的Provider页面:

@{
    ViewBag.Title = "Provider";
}

<h2>Provider</h2>

<p>
    This page expects to receive OpenID authentication messages to allow users to log
    into other web sites.
</p>

稍后我们要对这两处进行修改。

先关注Xrds页面:

这里要注意最后两行,而且一定要把这两行放到最后,否则生成的xml就会出现换行,在服务端验证就会报错

跟源码发现问题在这里:

修改后发现生成的xml一直有第一行的空白,元凶在这里:

把这个第4行删掉,再次运行就看到如下页面了:

接下来实现OpenId。

修改OpenIdController:

public class OpenIdController : Controller
    {
        internal static OpenIdProvider OpenIdProvider = new OpenIdProvider();
        public IFormsAuthentication FormsAuth { get; private set; }
        [ValidateInput(false)]
        public async Task<ActionResult> Provider()
        {
            IRequest request = await OpenIdProvider.GetRequestAsync(this.Request, this.Response.ClientDisconnectedToken);
            if (request != null)
            {
                // Some requests are automatically handled by DotNetOpenAuth.  If this is one, go ahead and let it go.
                if (request.IsResponseReady)
                {
                    var response = await OpenIdProvider.PrepareResponseAsync(request, this.Response.ClientDisconnectedToken);
                    Response.ContentType = response.Content.Headers.ContentType.ToString();
                    return response.AsActionResult();
                }

                // This is apparently one that the host (the web site itself) has to respond to.
                ProviderEndpoint.PendingRequest = (IHostProcessedRequest)request;

                // If PAPE requires that the user has logged in recently, we may be required to challenge the user to log in.
                var papeRequest = ProviderEndpoint.PendingRequest.GetExtension<PolicyRequest>();
                if (papeRequest != null && papeRequest.MaximumAuthenticationAge.HasValue)
                {
                    TimeSpan timeSinceLogin = DateTime.UtcNow - this.FormsAuth.SignedInTimestampUtc.Value;
                    if (timeSinceLogin > papeRequest.MaximumAuthenticationAge.Value)
                    {
                        // The RP wants the user to have logged in more recently than he has.
                        // We'll have to redirect the user to a login screen.
                        return this.RedirectToAction("LogOn", "Account", new { returnUrl = this.Url.Action("ProcessAuthRequest") });
                    }
                }

                return await this.ProcessAuthRequest();
            }
            else
            {
                // No OpenID request was recognized.  This may be a user that stumbled on the OP Endpoint.
                return this.View();
            }
        }

        public async Task<ActionResult> ProcessAuthRequest()
        {
            if (ProviderEndpoint.PendingRequest == null)
            {
                return this.RedirectToAction("Index", "Home");
            }

            // Try responding immediately if possible.
            ActionResult response = await this.AutoRespondIfPossibleAsync();
            if (response != null)
            {
                return response;
            }

            // We can't respond immediately with a positive result.  But if we still have to respond immediately...
            if (ProviderEndpoint.PendingRequest.Immediate)
            {
                // We can't stop to prompt the user -- we must just return a negative response.
                return await this.SendAssertion();
            }

            return this.RedirectToAction("AskUser");
        }

        /// <summary>
        /// Displays a confirmation page.
        /// </summary>
        /// <returns>The response for the user agent.</returns>
        [Authorize]
        public async Task<ActionResult> AskUser()
        {
            if (ProviderEndpoint.PendingRequest == null)
            {
                // Oops... precious little we can confirm without a pending OpenID request.
                return this.RedirectToAction("Index", "Home");
            }

            // The user MAY have just logged in.  Try again to respond automatically to the RP if appropriate.
            ActionResult response = await this.AutoRespondIfPossibleAsync();
            if (response != null)
            {
                return response;
            }

            if (!ProviderEndpoint.PendingAuthenticationRequest.IsDirectedIdentity &&
                !this.UserControlsIdentifier(ProviderEndpoint.PendingAuthenticationRequest))
            {
                return this.Redirect(this.Url.Action("LogOn", "Account", new { returnUrl = this.Request.Url }));
            }

            this.ViewData["Realm"] = ProviderEndpoint.PendingRequest.Realm;

            return this.View();
        }

        [HttpPost, Authorize, ValidateAntiForgeryToken]
        public async Task<ActionResult> AskUserResponse(bool confirmed)
        {
            if (!ProviderEndpoint.PendingAuthenticationRequest.IsDirectedIdentity &&
                !this.UserControlsIdentifier(ProviderEndpoint.PendingAuthenticationRequest))
            {
                // The user shouldn't have gotten this far without controlling the identifier we'd send an assertion for.
                return new HttpStatusCodeResult((int)HttpStatusCode.BadRequest);
            }

            if (ProviderEndpoint.PendingAnonymousRequest != null)
            {
                ProviderEndpoint.PendingAnonymousRequest.IsApproved = confirmed;
            }
            else if (ProviderEndpoint.PendingAuthenticationRequest != null)
            {
                ProviderEndpoint.PendingAuthenticationRequest.IsAuthenticated = confirmed;
            }
            else
            {
                throw new InvalidOperationException("There's no pending authentication request!");
            }

            return await this.SendAssertion();
        }

        /// <summary>
        /// Attempts to formulate an automatic response to the RP if the user's profile allows it.
        /// </summary>
        /// <returns>The ActionResult for the caller to return, or <c>null</c> if no automatic response can be made.</returns>
        private async Task<ActionResult> AutoRespondIfPossibleAsync()
        {
            // If the odds are good we can respond to this one immediately (without prompting the user)...
            if (await ProviderEndpoint.PendingRequest.IsReturnUrlDiscoverableAsync(OpenIdProvider.Channel.HostFactories, this.Response.ClientDisconnectedToken) == RelyingPartyDiscoveryResult.Success
                && User.Identity.IsAuthenticated
                && this.HasUserAuthorizedAutoLogin(ProviderEndpoint.PendingRequest))
            {
                // Is this is an identity authentication request? (as opposed to an anonymous request)...
                if (ProviderEndpoint.PendingAuthenticationRequest != null)
                {
                    // If this is directed identity, or if the claimed identifier being checked is controlled by the current user...
                    if (ProviderEndpoint.PendingAuthenticationRequest.IsDirectedIdentity
                        || this.UserControlsIdentifier(ProviderEndpoint.PendingAuthenticationRequest))
                    {
                        ProviderEndpoint.PendingAuthenticationRequest.IsAuthenticated = true;
                        return await this.SendAssertion();
                    }
                }

                // If this is an anonymous request, we can respond to that too.
                if (ProviderEndpoint.PendingAnonymousRequest != null)
                {
                    ProviderEndpoint.PendingAnonymousRequest.IsApproved = true;
                    return await this.SendAssertion();
                }
            }

            return null;
        }

        /// <summary>
        /// Sends a positive or a negative assertion, based on how the pending request is currently marked.
        /// </summary>
        /// <returns>An MVC redirect result.</returns>
        public async Task<ActionResult> SendAssertion()
        {
            var pendingRequest = ProviderEndpoint.PendingRequest;
            var authReq = pendingRequest as IAuthenticationRequest;
            var anonReq = pendingRequest as IAnonymousRequest;
            ProviderEndpoint.PendingRequest = null; // clear session static so we don't do this again
            if (pendingRequest == null)
            {
                throw new InvalidOperationException("There's no pending authentication request!");
            }

            // Set safe defaults if somehow the user ended up (perhaps through XSRF) here before electing to send data to the RP.
            if (anonReq != null && !anonReq.IsApproved.HasValue)
            {
                anonReq.IsApproved = false;
            }

            if (authReq != null && !authReq.IsAuthenticated.HasValue)
            {
                authReq.IsAuthenticated = false;
            }

            if (authReq != null && authReq.IsAuthenticated.Value)
            {
                if (authReq.IsDirectedIdentity)
                {
                    authReq.LocalIdentifier = Models.User.GetClaimedIdentifierForUser(User.Identity.Name);
                }

                if (!authReq.IsDelegatedIdentifier)
                {
                    authReq.ClaimedIdentifier = authReq.LocalIdentifier;
                }
            }

            // Respond to AX/sreg extension requests only on a positive result.
            if ((authReq != null && authReq.IsAuthenticated.Value) ||
                (anonReq != null && anonReq.IsApproved.Value))
            {
                // Look for a Simple Registration request.  When the AXFetchAsSregTransform behavior is turned on
                // in the web.config file as it is in this sample, AX requests will come in as SReg requests.
                var claimsRequest = pendingRequest.GetExtension<ClaimsRequest>();
                if (claimsRequest != null)
                {
                    var claimsResponse = claimsRequest.CreateResponse();

                    // This simple respond to a request check may be enhanced to only respond to an individual attribute
                    // request if the user consents to it explicitly, in which case this response extension creation can take
                    // place in the confirmation page action rather than here.
                    if (claimsRequest.Email != DemandLevel.NoRequest)
                    {
                        claimsResponse.Email = User.Identity.Name + "@dotnetopenauth.net";
                    }

                    pendingRequest.AddResponseExtension(claimsResponse);
                }

                // Look for PAPE requests.
                var papeRequest = pendingRequest.GetExtension<PolicyRequest>();
                if (papeRequest != null)
                {
                    var papeResponse = new PolicyResponse();
                    if (papeRequest.MaximumAuthenticationAge.HasValue)
                    {
                        papeResponse.AuthenticationTimeUtc = this.FormsAuth.SignedInTimestampUtc;
                    }

                    pendingRequest.AddResponseExtension(papeResponse);
                }
            }

            var response = await OpenIdProvider.PrepareResponseAsync(pendingRequest, this.Response.ClientDisconnectedToken);
            Response.ContentType = response.Content.Headers.ContentType.ToString();
            return response.AsActionResult();
        }
        /// <summary>
        /// Determines whether the currently logged in user has authorized auto login to the requesting relying party.
        /// </summary>
        /// <param name="request">The incoming request.</param>
        /// <returns>
        ///     <c>true</c> if it is safe to respond affirmatively to this request and all extensions
        ///     without further user confirmation; otherwise, <c>false</c>.
        /// </returns>
        private bool HasUserAuthorizedAutoLogin(IHostProcessedRequest request)
        {
            // TODO: host should implement this method meaningfully, consulting their user database.
            // Make sure the user likes the RP
            if (true/*User.UserLikesRP(request.Realm))*/)
            {
                // And make sure the RP is only asking for information about the user that the user has granted before.
                if (true/*User.HasGrantedExtensions(request)*/)
                {
                    // For now for the purposes of the sample, we'll disallow auto-logins when an sreg request is present.
                    if (request.GetExtension<ClaimsRequest>() != null)
                    {
                        return false;
                    }

                    return true;
                }
            }

            // If we aren't sure the user likes this site and is willing to disclose the requested info, return false
            // so the user has the opportunity to explicity choose whether to share his/her info.
            return false;
        }

        /// <summary>
        /// Checks whether the logged in user controls the OP local identifier in the given authentication request.
        /// </summary>
        /// <param name="authReq">The authentication request.</param>
        /// <returns><c>true</c> if the user controls the identifier; <c>false</c> otherwise.</returns>
        private bool UserControlsIdentifier(IAuthenticationRequest authReq)
        {
            if (authReq == null)
            {
                throw new ArgumentNullException("authReq");
            }

            if (User == null || User.Identity == null)
            {
                return false;
            }

            Uri userLocalIdentifier = Models.User.GetClaimedIdentifierForUser(User.Identity.Name);

            // Assuming the URLs on the web server are not case sensitive (on Windows servers they almost never are),
            // and usernames aren't either, compare the identifiers without case sensitivity.
            // No reason to do this for the PPID identifiers though, since they *can* be case sensitive and are highly
            // unlikely to be typed in by the user anyway.
            return string.Equals(authReq.LocalIdentifier.ToString(), userLocalIdentifier.ToString(), StringComparison.OrdinalIgnoreCase) ||
                authReq.LocalIdentifier == PpidGeneration.PpidIdentifierProvider.GetIdentifier(userLocalIdentifier, authReq.Realm);
        }
    }

新增Code文件夹和两个类:

public interface IFormsAuthentication
    {
        string SignedInUsername { get; }

        DateTime? SignedInTimestampUtc { get; }

        void SignIn(string userName, bool createPersistentCookie);

        void SignOut();
    }
internal static class Util
    {
        internal static Uri GetAppPathRootedUri(string value)
        {
            string appPath = HttpContext.Current.Request.ApplicationPath.ToLowerInvariant();
            if (!appPath.EndsWith("/"))
            {
                appPath += "/";
            }

            return new Uri(HttpContext.Current.Request.Url, appPath + value);
        }
    }

添加引用:

在Models文件夹中添加User:

internal class User
    {
        internal static Uri ClaimedIdentifierBaseUri
        {
            get { return Util.GetAppPathRootedUri("user/"); }
        }

        internal static Uri GetClaimedIdentifierForUser(string username)
        {
            if (string.IsNullOrEmpty(username))
            {
                throw new ArgumentNullException("username");
            }

            return new Uri(ClaimedIdentifierBaseUri, username.ToLowerInvariant());
        }

        internal static string GetUserFromClaimedIdentifier(Uri claimedIdentifier)
        {
            Regex regex = new Regex(@"/user/([^/\?]+)");
            Match m = regex.Match(claimedIdentifier.AbsoluteUri);
            if (!m.Success)
            {
                throw new ArgumentException();
            }

            ].Value;
        }

        internal static Uri GetNormalizedClaimedIdentifier(Uri uri)
        {
            return GetClaimedIdentifierForUser(GetUserFromClaimedIdentifier(uri));
        }
    }

添加UserController,因为后面还会调用User里的Xrds,可以看到这里的Xrds做了两次验证,把原来Views/home 下的Xrds.cshtml移到Shared目录下,这样就可以共享

public class UserController : Controller
    {
        /// <summary>
        /// Identities the specified id.
        /// </summary>
        /// <param name="id">The username or anonymous identifier.</param>
        /// <param name="anon">if set to <c>true</c> then <paramref name="id"/> represents an anonymous identifier rather than a username.</param>
        /// <returns>The view to display.</returns>
        public ActionResult Identity(string id, bool anon)
        {
            if (!anon)
            {
                var redirect = this.RedirectIfNotNormalizedRequestUri(id);
                if (redirect != null)
                {
                    return redirect;
                }
            }

            if (Request.AcceptTypes != null && Request.AcceptTypes.Contains("application/xrds+xml"))
            {
                return View("Xrds");
            }

            if (!anon)
            {
                this.ViewData["username"] = id;
            }

            return View();
        }

        public ActionResult Xrds(string id)
        {
            return View();
        }

        private ActionResult RedirectIfNotNormalizedRequestUri(string user)
        {
            Uri normalized = Models.User.GetClaimedIdentifierForUser(user);
            if (Request.Url != normalized)
            {
                return Redirect(normalized.AbsoluteUri);
            }

            return null;
        }
    }

修改RegisterRoutes:

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            routes.MapRoute(
                name: "User identities",
                url: "user/{id}/{action}",
                defaults: new { controller = "User", action = "Identity", id = string.Empty, anon = false });
            routes.MapRoute(
                name: "PPID identifiers",
                url: "anon",
                defaults: new { controller = "User", action = "Identity", id = string.Empty, anon = true });
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }

这里需要注意的是这里的配置,因为这里我们的用户是邮箱注册的,回调的时候是这样的:http://localhost:18180/user/123@123.com

直接访问这个地址会发现找不到页面:

需要在web.config中修改配置:

<system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="FormsAuthentication" />
    </modules>
  </system.webServer>

这里一定要注意添加localhost到whitelistHosts

<messaging  relaxSslRequirements="true">
            <untrustedWebRequest>
                <whitelistHosts>
                    <!-- Uncomment to enable communication with localhost (should generally not activate in production!) -->
                    <add name="localhost" />
                </whitelistHosts>
            </untrustedWebRequest>
        </messaging>

否则会报这个错误,

"The URL 'http://localhost:18180/' is rated unsafe and cannot be requested this way."

当然这是跟源码才能得到的错误,不跟源码的话只有一个错误页面:

修改后运行跳转到登录页面:

登录后跳转:

这里又需要注意了,比如在客户端我们是用1@1.com来登录的,那么在客户端的Identity.Name就是1@1.com,服务端用默认的登录会直接取这个1@1.com,那么在这里就不匹配了:

public static User LoggedInUser
        {
            get { return DataContext.Users.SingleOrDefault(user => user.OpenIDClaimedIdentifier == HttpContext.Current.User.Identity.Name); }
        }

接下来修改这些地方:

1.删除ManagerConroller

2.Startup中注销外部登录

public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            //ConfigureAuth(app);
        }
    }

3.web.config中修改configSections、system.web.authentication、system.webServer.modules、dotNetOpenAuth如下:

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=301880
  -->
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core">
            <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" />
            <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" />
        <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" />
    <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" />
  <sectionGroup name="oauth2" type="DotNetOpenAuth.Configuration.OAuth2SectionGroup, DotNetOpenAuth.OAuth2">
                <section name="authorizationServer" type="DotNetOpenAuth.Configuration.OAuth2AuthorizationServerSection, DotNetOpenAuth.OAuth2.AuthorizationServer"
                    requirePermission="false" allowLocation="true"/>
            </sectionGroup>
  </sectionGroup></configSections>
  <connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\MvcAuth2Server-2016.mdf;Initial Catalog=MvcAuth2Server-2016;Integrated Security=True" providerName="System.Data.SqlClient" />
  </connectionStrings>
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  </appSettings>
  <system.web>
    <authentication mode="Forms">
            <forms loginUrl="/>
        </authentication>
    <compilation debug="true" targetFramework="4.5.2" />
    <httpRuntime targetFramework="4.5.2" />
  </system.web>
  <system.webServer>
    <!--<modules>
      <remove name="FormsAuthentication" />
    </modules>-->
  <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed" />
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  <!-- This prevents the Windows Event Log ,  and  references relink
             to MVC  so libraries such  will work with it.
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <dependentAssembly>
                <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
                <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
            </dependentAssembly>
        </assemblyBinding>
         --></runtime>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="mssqllocaldb" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
  <system.codedom>
    <compilers>
      <compiler language=" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" />
      <compiler language=" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+" />
    </compilers>
  </system.codedom>
  <system.net>
        <defaultProxy enabled="true" />
        <settings>
            <!-- This setting causes .NET to check certificate revocation lists (CRL)
                 before trusting HTTPS certificates.  But this setting tends to not
                 be allowed in shared hosting environments. -->
            <!--<servicePointManager checkCertificateRevocationList="true"/>-->
        </settings>
    </system.net>

  <dotNetOpenAuth>
        <messaging  relaxSslRequirements="true">
            <untrustedWebRequest>
                <whitelistHosts>
                    <!-- Uncomment to enable communication with localhost (should generally not activate in production!) -->
                    <add name="localhost" />
                </whitelistHosts>
            </untrustedWebRequest>
        </messaging>
        <!-- Allow DotNetOpenAuth to publish usage statistics to library authors to improve the library. -->
        <reporting enabled="true" />
    <oauth2>
            <authorizationServer>
            </authorizationServer>
        </oauth2>
  </dotNetOpenAuth>

  <uri>
        <!-- The uri section is necessary to turn on .NET 3.5 support for IDN (international domain names),
             which is necessary for OpenID urls with unicode characters in the domain/host name.
             It  escaping mode, which OpenID and OAuth require. -->
        <idn enabled="All" />
        <iriParsing enabled="true" />
    </uri></configuration>

这样就会使用DotNetOpenAuth的认证登录了:

确定后就看到授权页面了:

此时再看数据库记录:

到这里基本就OK了。

DotNetOpenAuth 服务端搭建的更多相关文章

  1. Centos 6.5 pptpd服务端搭建过程

    首先检测有没有启用ppp和tun cat /dev/ppp cat /dev/net/tun 如果显示是这样的 cat: /dev/ppp: No such device or address cat ...

  2. Apereo CAS Server服务端搭建教程

    不说废话了,直接看搭建过程吧. 首先到下载源码,https://github.com/apereo/cas-overlay-template/tree/4.2 附上地址,本次版本为4.2,下载源码后如 ...

  3. react 项目实战(一)创建项目 及 服务端搭建

    1.安装 React社区提供了众多的脚手架,这里我们使用官方推荐的create-react-app. //安装脚手架 npm install -g create-react-app //生成并运行项目 ...

  4. FTP服务端 FTP服务端搭建教程

    FTP服务端搭建教程如下:一.需要准备以下工具:1.微型FTP服务端.2.服务器管理工具二.操作步骤:1.下载微型FTP服务端.(站长工具包可下载:http://zzgjb.iis7.com/ )2. ...

  5. node服务端搭建学习笔记

    咳咳,终于迈出这一步了...这篇文章将是边学边写的真正笔记...用于mark下学习过程中的点滴~ 开篇先把我学习参考的文章来源给出,以表示对前人的尊敬: https://github.com/nswb ...

  6. centos6.5 svn服务端搭建

    一.前言 Subversion是一个免费的开源的版本管理系统,它是作为CVS(Concurrent Versions System)的取代品出现的.本文简单介绍了Subversion在centos上的 ...

  7. git分布式的理解----简单服务端搭建

    Git是分布式的,并没有服务端跟客户端之分,所谓的服务端安装的其实也是git.Git支持四种协议,file,ssh,git,http.ssh是使用较多的,下面使用ssh搭建一个免密码登录的服务端. 1 ...

  8. CAS单点登录学习(一):服务端搭建

    下载先在网上下载cas-server-3.5.2,将里面的cas-server-webapp-3.5.2.war放到tomcat的webapps目录下. https设置cas单点登默认使用的是http ...

  9. 菜鸟之webservice(一) 服务端搭建

    首先说一下,为什么取名叫菜鸟之webservice,由于本人技术真的不咋滴,写博客仅仅是为了对所学知识的总结.webservice对于我来说一直都是高大上的感觉,一个java web和javase都没 ...

随机推荐

  1. 事务不起作用 Closing non transactional SqlSession

    In proxy mode (which is the default), only external method calls coming in through the proxy are int ...

  2. Node.js是什么[译]

    当我向人们介绍Node.js的时候,一般会有两种反应:多数立刻表示“哦,这样啊”,另外的则会感到困惑. 如果你是第二种的话,我会试着这样解释node: 这是一个命令行工具.你可以下载一个tar包,然后 ...

  3. C++ 引用 指针 使用举例

    1. 请看下程序 inline void CScanLineFill::removeOldNodeAET(AET* &aetList, const float yCurrent) { AET* ...

  4. POJ 2462 / HDU 1154 Cutting a Polygon

    就这样莫名其妙的过了,不过可以确定之前都是被精度卡死了.真心受不了精度问题了. 题意:一条直线在一个不规则多边形内的长度,包括边重合部分. 首先计算出所有交点,然后按想x,y的大小进行二级排序. 然后 ...

  5. java实现自动生成四则运算

    Github项目链接:https://github.com/shoulder01/Fouroperation.git 一.项目相关要求 1. 使用 -n 参数控制生成题目的个数(实现) 2.使用 -r ...

  6. Sharepoint/Project Server 看不到“安全性”菜单以及子菜单

    在Sharepoint/Project Server 构建后,左侧看不到看不到“服务器设置”菜单,在设置菜单后左侧出现“服务器设置”菜单,但是依然在右侧看不到“安全性”菜单以及子菜单. (这个图是借的 ...

  7. SQL Server Extended Events 进阶 2:使用UI创建基本的事件会话

    第一阶中我们描述了如何在Profiler中自定义一个Trace,并且让它运行在服务器端来创建一个Trace文件.然后我们通过Jonathan Kehayias的 sp_SQLskills_Conver ...

  8. redis 任务队列

    使用Redis实现任务队列 说到队列很自然就能想到Redis的列表类型,3.4.2节介绍了使用LPUSH和RPOP命令实现队列的概念.如果要实现任务队列,只需要让生产者将任务使用LPUSH命令加入到某 ...

  9. 修改jenkins启动的默认用户

    # 背景 通过yum命令安装的jenkins,通过service jenkins去启动jenkins的话,默认的用户是jenkins,但jenkins这个用户是无法通过su切换过去的 ,在某些环节可能 ...

  10. Asp.net WebForm 中无法引用App_Code文件夹下的类

    在VS2013中新建asp.net webform应用程序,手动添加"APP_Code"文件夹并新建类文件,发现这些类无法在APP_Code文件夹以外被引用. 解决办法: 选中类文 ...