新建项目:

安装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. 基于Verilog HDL的二进制转BCD码实现

    在项目设计中,经常需要显示一些数值,比如温湿度,时间等等.在数字电路中数据都是用二进制的形式存储,要想显示就需要进行转换,对于一个两位的数值,对10取除可以得到其十位的数值,对10取余可以得到个位的数 ...

  2. cheerio笔记

    不会的东西,再简单都是难:会的东西,再难都是简单. 给自己写的,写的通俗易懂. cheerio:在node服务端,解析网页,是jquery的核心,去除了DOM中不一致的地方. 先获得网页的源码,再通过 ...

  3. hibernate 延迟加载深入分析(persistentSet的延迟加载)

    Hibernae 的延迟加载是一个非常常用的技术,实体的集合属性默认会被延迟加载,实体所关联的实体默认也会被延迟加载.Hibernate 通过这种延迟加载来降低系统的内存开销,从而保证 Hiberna ...

  4. LINUX中关于SIGNAL的定义

    /* Signals. */ #define SIGHUP 1 /* Hangup (POSIX). */ #define SIGINT 2 /* Interrupt (ANSI). */ #defi ...

  5. api接口签名相关文章

    http://www.cnblogs.com/hnsongbiao/p/5478645.htmlhttp://www.cnblogs.com/codeon/p/5900914.html?from=ti ...

  6. Oracle Client 连接 Server 并通过代码测试连接

    Oracle客户端配置 步骤一: 步骤二: 步骤三: 步骤四: 步骤五: 最后测试成功   注: 如果是客户端配置可以不用添加 程序同样可以进行连接,如果是服务器则需要配置. 程序连接 namespa ...

  7. VS2017集成FastReport.Net并将模板保存到数据库

    本着开发与实施分离的思想,设计一个通用的报表设计窗体显得尤为重要(下图为图一): 要求与优点: I.报表设计窗体支持所有单据调用,一种单据支持多个打印模板. II.报表模板存储在数据库中.一是支持客户 ...

  8. C++引用和指针的区别

    一.引用和指针的定义 引用:它是给另一个变量取一个别名,不会再次分配空间(可以带来程序的优化) 指针:它是一个实体,需要分配空间 引用在定义的时候必须进行初始化,并且空间不能够改变.   指针在定义的 ...

  9. for循环、for in整理

    for循环 作用:按照一定的规律,重复去做某件事情,此时我们就需要使用循环来处理了 例子1:倒着输出每一项 <script type="text/javascript"> ...

  10. 「BZOJ1095」[ZJOI2007] Hide 捉迷藏

    题目描述 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩捉迷藏游戏.他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条 ...