文档章节

OAuth2.0授权码模式理论+实践

李朝强
 李朝强
发布于 2017/08/15 20:13
字数 2758
阅读 288
收藏 2

OAuth是一个关于授权(Authorization)的开放网络标准,目前的版本是2.0版。注意是Authorization(授权),而不是Authentication(认证)。用来做Authentication(认证)的标准叫做openid connect

一、OAuth2.0理论普及

1、OAuth2.0中的角色说明:

资源拥有者(resource owner:能授权访问受保护资源的一个实体,可以是一个人,那我们称之为最终用户;如新浪微博用户zhangsan;

资源服务器(resource server:存储受保护资源,客户端通过access token请求资源,资源服务器响应受保护资源给客户端;存储着用户zhangsan的微博等信息。

授权服务器(authorization server:成功验证资源拥有者并获取授权之后,授权服务器颁发授权令牌(Access Token)给客户端。

客户端(client:如新浪微博客户端weico、微格等第三方应用,也可以是它自己的官方应用;其本身不存储资源,而是资源拥有者授权通过后,使用它的授权(授权令牌)访问受保护资源,然后客户端把相应的数据展示出来/提交到服务器。“客户端”术语不代表任何特定实现(如应用运行在一台服务器、桌面、手机或其他设备)。

2、OAuth2.0客户端的授权模式:

2.1、Oautho2.0为客户端定义了4种授权模式:

1)授权码模式

2)简化模式

3)密码模式

4)客户端模式

 2.2、授权码模式:

授权码模式是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。

授权码模式的认证流程:

(A)用户访问客户端,后者将前者导向认证服务器。

(B)用户选择是否给予客户端授权。

(C)假设用户给予授权,认证服务器首先生成一个授权码,并返回给用户,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。

(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。

(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。

注意:(C)和(D)中两个重定向URI是不一样的,(C)中的重定向URI是用来核对的,这个是服务器事先指定并保存在数据库里面。而(D)中的重定向URI指的是生成access_token的url。

3、选择合适的OAuth模式打造自己的webApi认证服务

场景:你自己实现了一套webApi,想供自己的客户端调用,又想做认证。

这种场景下你应该选择模式3或者4,特别是当你的的客户端是js+html应该选择3,当你的客户端是移动端(ios应用之类)可以选择3,也可以选择4。

密码模式(resource owner password credentials)的流程:

oauth2.0

我在另一篇博客中

深入聊聊微服务架构的身份认证问题》,详细介绍了各种微服务身份认证的技术方案。

如果觉得以上理论信息意犹未尽的话,请继续关注鄙人博客,或者搜索相关的资料,做进一步的研究。

下面就以授权码授权模式为例,进行代码的实践。

二、OAuth2.0实践

  这里是以Asp.Net mvc5为例,具体步骤如下:

 首先引用Owin OAuth相关的类库。

Microsoft.AspNet.Identity;
Microsoft.Owin;
Microsoft.Owin.Security.Cookies;
Microsoft.Owin.Security.Infrastructure;
Microsoft.Owin.Security.OAuth;

添加Owin启动类,代码如下:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Infrastructure;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System.Security.Claims;
using System.Collections.Concurrent;

/*=================================================================================================
*
* Title:XXXXXXXX
* Author:李朝强
* Description:模块描述
* CreatedBy:lichaoqiang.com
* CreatedOn:2017-8-4 11:16:42
* ModifyBy:暂无...
* ModifyOn:2017-8-4 11:16:42
* Blog:http://www.lichaoqiang.com
* Mark:
*
*================================================================================================*/
[assembly: OwinStartup(typeof(OAuthCode.Startup))]
namespace OAuthCode
{
    /// <summary>
    /// 应用程序启动类
    /// </summary>
    public class Startup
    {

        /// <summary>
        /// 用来存放临时授权码 线程安全
        /// </summary>
        private readonly ConcurrentDictionary<string, string> _authenticationCodes = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);

        /// <summary>
        /// 配置授权
        /// </summary>
        /// <param name="app"></param>
        public void Configuration(IAppBuilder app)
        {
            //创建OAuth授权服务器
            app.UseOAuthAuthorizationServer(new Microsoft.Owin.Security.OAuth.OAuthAuthorizationServerOptions()
            {
                AllowInsecureHttp = true,//开启
                AuthenticationType = "Bearer",
                AuthorizeEndpointPath = new PathString("/OAuth/Authorize"),
                TokenEndpointPath = new PathString("/OAuth/Token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
                Provider = new OAuthAuthorizationServerProvider()
                {
                    //授权码 authorization_code
                    OnGrantAuthorizationCode = ctx =>
                    {
                        if (ctx.Ticket != null &&
                            ctx.Ticket.Identity != null &&
                            ctx.Ticket.Identity.IsAuthenticated)
                        {
                            ctx.Validated(ctx.Ticket.Identity);//
                        }
                        return Task.FromResult(0);
                    },
                    OnGrantRefreshToken = ctx =>
                    {
                        if (ctx.Ticket != null &&
                            ctx.Ticket.Identity != null &&
                            ctx.Ticket.Identity.IsAuthenticated)
                        {
                            ctx.Validated();
                        }
                        return Task.FromResult(0);
                    },
                    //OnGrantResourceOwnerCredentials = (context) =>
                    //{
                    //    context.Validated(context.Ticket.Identity);
                    //    return Task.FromResult(0);
                    //},
                    OnValidateAuthorizeRequest = ctx =>
                    {
                        ctx.Validated();
                        return Task.FromResult(ctx);
                    },
                    //验证redirect_uri是否合法
                    OnValidateClientRedirectUri = context =>
                    {
                        context.Validated(redirectUri: context.RedirectUri);
                        return Task.FromResult(context);
                    },
                    //用来验证请求中的client_id和client_secret
                    OnValidateClientAuthentication = context =>
                    {
                        string clientId;
                        string clientSecret;
                        //这是通过Basic或form的方式,获取client_id和client_secret
                        if (context.TryGetBasicCredentials(out clientId, out clientSecret) ||
                            context.TryGetFormCredentials(out clientId, out clientSecret))
                        {
                            context.Validated(clientId);
                        }
                        return Task.FromResult(context);
                    },
                    OnAuthorizeEndpoint = context =>
                    {
                        return Task.FromResult(context);
                    },
                    OnTokenEndpoint = (context) =>
                    {
                        return Task.FromResult(context);
                    },
                    //OnGrantClientCredentials = (context) =>
                    //{
                    //    context.Validated();
                    //    return Task.FromResult(context);
                    //}
                },
                //Code授权
                AuthorizationCodeProvider = new AuthenticationTokenProvider()
                {

                    OnCreate = context =>
                    {
                        context.SetToken(DateTime.Now.Ticks.ToString());
                        string token = context.Token;
                        string ticket = context.SerializeTicket();
                        var redirect_uri = context.Request.Query["redirect_uri"];
                        context.Response.Redirect(string.Format("{0}?code={1}&state=1", redirect_uri, token));
                        _authenticationCodes[token] = ticket;//这里存放授权码
                    },
                    //当接收到code时
                    OnReceive = context =>
                    {
                        string token = context.Token;
                        string ticket;
                        if (_authenticationCodes.TryRemove(token, out ticket))
                        {
                            context.DeserializeTicket(ticket);
                        }

                    },
                },
                //(可选)访问令牌
                AccessTokenProvider = new AuthenticationTokenProvider()
                {
                    //创建访问令牌
                    OnCreate = (context) =>
                    {
                        string token = context.SerializeTicket();
                        context.SetToken(token);
                    },
                    //接收
                    OnReceive = (context) =>
                    {
                        context.DeserializeTicket(context.Token);
                    },
                },
                //刷新令牌
                RefreshTokenProvider = new AuthenticationTokenProvider()
                {
                    OnCreate = context =>
                    {
                        context.SetToken(context.SerializeTicket());
                    },
                    OnReceive = context =>
                    {
                        context.DeserializeTicket(context.Token);
                    },
                }
            });

            //本地Cookie身份认证
            app.UseCookieAuthentication(new CookieAuthenticationOptions()
            {
                LoginPath = new PathString("/Account/Login"),
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie
            });

        }
    }
}

以上是启动类的所有代码,你也可以在码云中获取。

接下来,我们需要一个登录授权页面,这里有两个控制器

AccountController及OAuthController,分别负责登录及认证授权。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Microsoft.Owin.Security;
using OAuthCode.Models;


namespace OAuthCode.Controllers
{
    public class AccountController : Controller
    {

        /// <summary>
        /// 
        /// </summary>
        public IAuthenticationManager AuthenticationManager
        {
            get { return HttpContext.GetOwinContext().Authentication; }
        }

        //
        // GET: /Account/
        public ActionResult Index()
        {
            return View();
        }


        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public ActionResult Login(string returnUrl)
        {
            ViewBag.returnUrl = Uri.EscapeDataString(returnUrl);
            return View();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="model"></param>
        /// <param name="returnUrl"></param>
        /// <returns></returns>
        [HttpPost]
        public ActionResult Login(LoginViewModel model, string returnUrl)
        {
            string userId = "1";
            //可以在这里将用户所属的role或者Claim添加到此
            ClaimsIdentity claims = new ClaimsIdentity(new[] {
                new Claim(ClaimTypes.Name, model.account)
                ,new Claim(ClaimTypes.NameIdentifier,userId)
                ,new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",userId)},
                DefaultAuthenticationTypes.ApplicationCookie);

            AuthenticationProperties properties = new AuthenticationProperties
            {
                IsPersistent = true
            };

            ClaimsPrincipal principal = new ClaimsPrincipal(claims);
            //System.Threading.Thread.CurrentPrincipal = principal;
            this.AuthenticationManager.SignIn(properties, new[] { claims });

            return Redirect(returnUrl);
        }
    }
}

以上是登录控制器有关的代码。

接下来,我们编写下OAuthController控制器相关的Action.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Microsoft.Owin.Security;
using System.Threading.Tasks;
using DotNetOpenAuth.OAuth2;
/******************************************************************************************************************
* 
* 
* 说 明: (版本:Version1.0.0)
* 作 者:李朝强
* 日 期:2015/05/19
* 修 改:
* 参 考:http://my.oschina.net/lichaoqiang/
* 备 注:暂无...
* 
* 
* ***************************************************************************************************************/
namespace OAuthCode.Controllers
{
    /// <summary>
    /// <![CDATA[验证授权]]>
    /// </summary>
    public class OAuthController : Controller
    {
        /// <summary>
        /// <!--授权-->
        /// </summary>
        /// <returns></returns>
        public ActionResult Authorize()
        {
            //验证是否登录,如果没有,
            IAuthenticationManager authentication = HttpContext.GetOwinContext().Authentication;
            AuthenticateResult ticket = authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie).Result;
            ClaimsIdentity identity = ticket == null ? null : ticket.Identity;

            if (identity == null)
            {
                //如果没有验证通过,则必须先通过身份验证,跳转到验证方法
                authentication.Challenge();
                return new HttpUnauthorizedResult();
            }

            identity = new ClaimsIdentity(identity.Claims, "Bearer");
            //hardcode添加一些Claim,正常是从数据库中根据用户ID来查找添加
            identity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
            identity.AddClaim(new Claim(ClaimTypes.Role, "Normal"));
            identity.AddClaim(new Claim("MyType", "MyValue"));

            authentication.SignIn(new AuthenticationProperties() { IsPersistent = true }, identity);

            return new EmptyResult();
        }




        /// <summary>
        ///<![CDATA[获取访问令牌]]>
        /// </summary>
        /// <returns></returns>
        public async Task<ActionResult> GetAccessToken()
        {

            #region 使用DotNetOpenOAuth获取访问令牌
            //var authServer = new AuthorizationServerDescription
            //{
            //    AuthorizationEndpoint = new Uri("http://localhost:3335/OAuth/Authorize"),
            //    TokenEndpoint = new Uri("http://localhost:3335/OAuth/Token"),

            //};

            //var autoServerClient = new DotNetOpenAuth.OAuth2.WebServerClient(authServer, clientIdentifier: "fNm0EDIXbfuuDowUpAoq5GTEiywV8eg0TpiIVnV8", clientSecret: "clientSecret");
            //var authorizationState = autoServerClient.ProcessUserAuthorization();
            //if (authorizationState != null)
            //{
            //    if (!string.IsNullOrEmpty(authorizationState.AccessToken))
            //    {
            //        var token = authorizationState.AccessToken;
            //    }
            //} 
            #endregion 使用DotNetOpenOAuth获取访问令牌

            #region 根据授权码,获取访问令牌 模拟第三方回调地址 redirect_uri
            string strCode = Request.QueryString["code"];//访问令牌

            if (string.IsNullOrEmpty(strCode) == false)
            {
                HttpClient client = new HttpClient();
                HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, "http://localhost:3335/OAuth/Token");

                Dictionary<string, string> dict = new Dictionary<string, string>();
                dict["grant_type"] = "authorization_code";
                dict["client_id"] = "fNm0EDIXbfuuDowUpAoq5GTEiywV8eg0TpiIVnV8";
                dict["client_secret"] = "111111";
                dict["code"] = strCode;
                dict["redirect_uri"] = "http://localhost:3335/OAuth/GetAccessToken";
                dict["scope"] = "scope1";
                message.Content = new FormUrlEncodedContent(dict);

                var response = await client.SendAsync(message);
                string strResponseText = await response.Content.ReadAsStringAsync();
                return Content(strResponseText, "text/javascript");
            }
            #endregion 根据授权码,获取访问令牌

            return Content("invalid code!");
        }
    }
}

其中包含了认证及回调获取令牌的处理逻辑。

以上步骤中,注意的事项比较多,

认证终结点:/OAuth/Authorize

下面是我本地演示的认证地址:

http://localhost:3335//OAuth/Authorize?redirect_uri=http://localhost:3335/OAuth/GetAccessToken&client_id=fNm0EDIXbfuuDowUpAoq5GTEiywV8eg0TpiIVnV8&client_secret=111111&response_type=code&scope=scope1

授权码模式下需要注意以下事项:

注意:

认证的时候,response_type必须为code,scope可选参数。

授权(获取令牌的)的时候:grant_type必须为authorization_code

完成以上工作,接下来让我们进行尝试,首先,打开请求认证授权的地址,

http://localhost:3335//OAuth/Authorize?redirect_uri=http://localhost:3335/OAuth/GetAccessToken&client_id=fNm0EDIXbfuuDowUpAoq5GTEiywV8eg0TpiIVnV8&client_secret=111111&response_type=code&scope=scope1

它请求的是认证终结点,也就是颁发授权码的入口。

第一次,由于没有登录,于是会出现登录授权的界面。

这个过程就像,第三方登录,如QQ,点击QQ登录,会出现QQ的授权页面,这里只是省略了,可以根据实际情况进行定制。

我们点击登录授权

返回结果如下:

{"access_token":"p8Jd6YwYmBgDkyTt4zTBMWNzTRbZRAM30vO3gfOiqzEw_8dCft-emDrbCC4o6_DGHW2zX0HuQus_4GJ1mYio6meCGeNP4tyEz_la4_zP8vJPsWG0TyXIwzyZth0ioWJ9JJc453MXNMH7EPMevrRsYyQpPG387gEaQFia1Q3EL7EOV7_LIkpmmMyfHGuxaTbevCbekWqR8YJdpigFd4WSwOOlode_PwL23qtneu-ezE3YitFoRIicD4rLk62lCme5pc9gFHBo2d0hRjyu7sHbqiwotWISDm290ddkhlhGlS2cPNJKYJZeCXMb7EPOdTuWMWBoOO1tpFZUsWZDVsbsu2tf42O5SNvQwzNw_o-oDW3riDVwle6aW5IqwFDk2cBIXVU3_ewbNhx13r4HfoeyhzFqUBmOjmUcB2qaER5UaEDsNVf8d0-KukUPEW-MHwl42flCTB_qqFn7ZjiKOIbjZJbVlbVj8vDvzTYjrc3msjc","token_type":"bearer","expires_in":1799,"refresh_token":"bR4OmKey_ex9JJApDP8-3O1gFZ0X9yefaXGS95At5q8NDt_v8CIM825jJklg0hrMd-yvpK_9ZG_ev8jViK78G7XN6jmy882bZPZAgcKT4tf879rKtMR_m7v4SJQAy7Jf1WnDr-U_Ty5s8bAnTCZFj99kK-S0mSoeBbgyepk1Cvez0fsw60jovxH8q_DPJPFfFETGQKYmDWQ34T1MeBllgtfZ2_Ayp5Dd4RBewDQTb_c1-cgmXy4rE_rcHz751aRsYSvhHU07QnzpjtFO6oo0i3bjWD84SCxhoevXEm-5TgBLNeX_WGv1raazwgAoV_7lpbvbGgsVbEcjyAkx05j81wp9RU3NnaKxQOpstOFW3C7ER-jx9niGplwpFS_As7t2L3Z0_ww2XwS1LHyMDXEYU3UPSP3EA7aW12qoNpxfe1ep0Ky-4kc1tFf3qq9syIvTgmXhXjGqxD8m3PvZsxlpHV89RVqFrrbCkTqIH3gm9fw"}

 

项目源码:https://gitee.com/lichaoqiang/RichCodeBox

RichCodeBox其中包含了代码JWT、客户端模式、授权码模式等。

另外,我们也可以自己定制OAuthAuthorizationServerProvider。我在JWT中有用到,以下代码只做了解代码如下:

using Microsoft.Owin.Security;
using Microsoft.Owin.Security.DataHandler.Encoder;
using Microsoft.Owin.Security.OAuth;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;

namespace WebApplication3.OAuth
{
    /// <summary>
    /// 
    /// </summary>
    public class CustomAuthorizationServerProvider : OAuthAuthorizationServerProvider
    {

        /// <summary>
        /// 
        /// </summary>
        public CustomAuthorizationServerProvider()
        {

        }

        /// <summary>
        /// 
        /// </summary>
        private TokenValidationParameters _validationParameters;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="validationParameters"></param>
        public CustomAuthorizationServerProvider(TokenValidationParameters validationParameters)
        {
            _validationParameters = validationParameters;
        }

        /// <summary>
        /// <![CDATA[授权资源拥有者证书]]>
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            var secret = TextEncodings.Base64Url.Decode("IxrAjDoa2FqElO7IhrSrUJELhUckePEPVpaePlS_Xaw");//秘钥
            var username = context.UserName;
            var password = context.Password;
            string userid;
            if (!CheckCredential(username, password, out userid))
            {
                context.SetError("invalid_grant", "The user name or password is incorrect");
                context.Rejected();//拒绝访问
                return Task.FromResult<object>(context);
            }

            var ticket = new AuthenticationTicket(SetClaimsIdentity(context, userid, username), new AuthenticationProperties());
            context.Validated(ticket);

            return Task.FromResult<object>(context);
        }

        /// <summary>
        /// <![CDATA[验证客户端授权]]>
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            string client_id;
            string client_secret;
            context.TryGetFormCredentials(out client_id, out client_secret);
            //验证clientid
            if (string.IsNullOrEmpty(context.ClientId) ||
                context.ClientId != Constants.Const.OAuth2.CLIENT_ID)
            {
                context.SetError("ClientId is incorrect!");
                context.Rejected();
            }
            //正常
            else
            {
                context.Validated();
            }
            return Task.FromResult<object>(context);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
        {
            return Task.FromResult<object>(context);
        }

        /// <summary>
        /// 验证客户端重定向URL
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
        {
            if (string.IsNullOrEmpty(context.ClientId) ||
                context.ClientId != Constants.Const.OAuth2.CLIENT_ID)
            {
                context.SetError("client_id is incorrect!");
                context.Rejected();
            }
            if (string.IsNullOrEmpty(context.RedirectUri))
            {
                context.SetError("redirect_uri is null!");
                context.Rejected();
            }
            else
            {
                context.Validated(context.RedirectUri);
            }
            return Task.FromResult<object>(0);
        }

        /// <summary>
        /// 验证令牌
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task ValidateTokenRequest(OAuthValidateTokenRequestContext context)
        {
            context.Validated();
            return Task.FromResult<object>(context);
        }

        #region 私有方法
        /// <summary>
        /// 设置声明
        /// </summary>
        /// <param name="context"></param>
        /// <param name="userid"></param>
        /// <param name="usercode"></param>
        /// <returns></returns>
        private static ClaimsIdentity SetClaimsIdentity(OAuthGrantResourceOwnerCredentialsContext context, string userid, string usercode)
        {
            var identity = new ClaimsIdentity("JWT");
            identity.AddClaim(new Claim(JwtRegisteredClaimNames.Sub, userid));
            identity.AddClaim(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
            identity.AddClaim(new Claim(ClaimTypes.Name, usercode));
            identity.AddClaim(new Claim(ClaimTypes.Role, "1"));
            identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, usercode));
            return identity;
        }

        /// <summary>
        /// 检测用户凭证
        /// </summary>
        /// <param name="usernme"></param>
        /// <param name="password"></param>
        /// <param name="userid"></param>
        /// <returns></returns>
        private static bool CheckCredential(string usernme, string password, out string userid)
        {
            var success = false;
            // 用户名和密码验证
            if (usernme == "admin" && password == "admin")
            {
                userid = "1";
                success = true;
            }
            else
            {
                userid = string.Empty;
            }
            return success;
        }
        #endregion 私有方法
    }
}

另外,建议在使用Microsoft.Owin.Security.OAuth默认的AccessToken生成类时,在

配置文件中,添加machineKey的有配置,关于machineKey的生成工具,不在讨论范围。

© 著作权归作者所有

李朝强
粉丝 91
博文 297
码字总数 149962
作品 0
郑州
产品经理
私信 提问
OAuth2.0认证流程是如何实现的?

导读 大家也许都有过这样的体验,我们登录一些不是特别常用的软件或网站的时候可以使用QQ、微信或者微博等账号进行授权登陆。例如我们登陆豆瓣网的时候,如果不想单独注册豆瓣网账号的话,就...

无敌码农
05/06
0
0
ios应用在企业内部分发遇到到问题(OAuth2.0授权码模式)

问题 最近遇到在企业内部分发iOS软件的时候,plist文件和ipa文件都不能下载安装的问题。安装苹果的官方文档:《通过网页服务器分发企业内部应用》。搭建了让企业内部员工下载的iOS安装包的服...

亚林瓜子
07/04
17
0
如何在移动端开发中正确地使用OAuth协议:常见错误剖析

作者在之前的文章中曾经介绍过 OAuth2.0 协议,并将其与OpenID和SAML性对比。然而,在理论上设计协议是一回事,在工程中实现协议是另一回事,由于很多开发人员没有真正理解OAuth2.0的设计意图...

登高且赋
2017/12/22
0
0
从前端安全出发理解OAuth2.0

OAuth2.0是一套非常经典、流行的授权框架,但是我们在学习和使用它的过程中会觉得它的授权认证流程非常繁琐,造成学习和使用OAuth2.0的时间成本很高。所以今天我将从前端安全的角度出发,使用...

罗辑思维前端开发团队
07/05
0
0
ASP.NET MVC使用Oauth2.0实现身份验证

 随着软件的不断发展,出现了更多的身份验证使用场景,除了典型的服务器与客户端之间的身份验证外还有,如服务与服务之间的(如微服务架构)、服务器与多种客户端的(如PC、移动、Web等),甚至...

CSharpKit
2017/12/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

mysql-connector-java升级到8.0后保存时间到数据库出现了时差

在一个新项目中用到了新版的mysql jdbc 驱动 <dependency>     <groupId>mysql</groupId>     <artifactId>mysql-connector-java</artifactId>     <version>8.0.18</version> ......

ValSong
今天
5
0
Spring Boot 如何部署到 Linux 中的服务

打包完成后的 Spring Boot 程序如何部署到 Linux 上的服务? 你可以参考官方的有关部署 Spring Boot 为 Linux 服务的文档。 文档链接如下: https://docs.ossez.com/spring-boot-docs/docs/r...

honeymoose
今天
6
0
Spring Boot 2 实战:使用 Spring Boot Admin 监控你的应用

1. 前言 生产上对 Web 应用 的监控是十分必要的。我们可以近乎实时来对应用的健康、性能等其他指标进行监控来及时应对一些突发情况。避免一些故障的发生。对于 Spring Boot 应用来说我们可以...

码农小胖哥
今天
6
0
ZetCode 教程翻译计划正式启动 | ApacheCN

原文:ZetCode 协议:CC BY-NC-SA 4.0 欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远。 ApacheCN 学习资源 贡献指南 本项目需要校对,欢迎大家提交 Pull Request。 ...

ApacheCN_飞龙
今天
4
0
CSS定位

CSS定位 relative相对定位 absolute绝对定位 fixed和sticky及zIndex relative相对定位 position特性:css position属性用于指定一个元素在文档中的定位方式。top、right、bottom、left属性则...

studywin
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部