美文网首页
.NET Core5.0 JWT鉴权SSO单点登录

.NET Core5.0 JWT鉴权SSO单点登录

作者: 包子wxl | 来源:发表于2021-07-18 01:09 被阅读0次

JWT

JWT全称“JSON Web Token”,是基于JSON的用户身份认证的令牌。可跨域身份认证,所以JWT很适合做分布式的鉴权,单点登录(SingleSign,SSO)。
jwt有三部分组成用符号"."隔开,
HEADER:token头,描述token是什么类型,加密方式是什么,把json内容转为base64
PAYLOAD:内容,是暴露出来的信息,不可存敏感信息,把json内容转为base64
SIGNATURE:签名,按token头的加密方式把HEADER和PAYLOAD的信息加密生成签名,下面是官网上面的介绍,地址:https://jwt.io/

jwt的token是不可以篡改的,虽然前两部分的内容可以base64解码之后就能看到明文,但由于第三部分签名是把前两部分内容用一个密钥加密的,验证的时候也是把前两部分内容再次加密和原来签名对比是否一致,若内容被篡改了,则两次签名不一致校验不通过。

image.png

SSO

问题一:同一个公司的系统 ,不如果每个系统都有一套自己的用户名密码,那用户记
得头都大了啊。所以这时产生了一个鉴权中心,全部系统用同一套用户信息,同一个地方登录。
问题二:用同一套用户信息可以了,但如果进每个系统都要输一次账户密码登录还是很麻烦的。所以这里还要一处登录 ,处处登录,登录了其中一个系统,进入其它系统的时候不需要登录

效果如下图所示


image.png

用户在sso中心登录后的token在站点A,站点B都能使用,并且站点A,站点B和sso中心不需要通讯也能自己鉴别token是否是有效的。
怎么做到站点和sso串联呢?具体的流程是用户打开站点A,发现未登录 ,那站点A会跳转到sso中心登录并且把自己的url带上,sso中心登录成功后,跳转回站点带过来的url并把token也带上。
那站点A登录成功了,站点B怎么共享这个Token呢,做法是,sso中心登录成功的时候同时存一份Token到cookie(或localstorage等地方),当用户进入站点B的时候,发现没登录,跳转到sso中心带上自己的url,sso中心发现cookie有token了,直接跳转回站点B的url并把token带上,这样站点B就能实现token共享和自动登录了。


image.png

代码实现

鉴权中心核心代码

新建一个AuthenticationCenter项目
新建一个AuthenticatinController控制器

 /// <summary>
    /// 鉴权相关
    /// </summary>
    public class AuthenticationController : Controller
    {
        /// <summary>
        /// 登录界面
        /// </summary>
        /// <param name="redirectUrl">登录成功后跳转页面</param>
        /// <returns></returns>
        [HttpGet]
        public IActionResult Login(string redirectUrl)
        {
            //取出登录token
            string token = string.Empty;
            //从cookie把token取出来
             Request.Cookies.TryGetValue("identity_token", out token);

            //实际还需验证token是否有效
            if(!string.IsNullOrEmpty(token))
            {
                //已经登录过,直接跳转回网站
                redirectUrl += $"?token={token}";
                return Redirect(redirectUrl);
            }

            ViewBag.redirectUrl = redirectUrl;
            //没登录过,展示登录界面
            return View();
        }
        [HttpPost]
        public IActionResult Login(string account, string password, string redirectUrl)
        {
            if (account == "admin" && password == "123456") //实际数据库校验
            {
                User user = new User()
                {
                    userId = 1000,
                    account = account,
                    userName = "张三",
                    age = 18,
                    email = "zhangsan@qq.com"
                };
                string token = new JWTService().GetJwtToken(user);
                //把token写入cookie
                Response.Cookies.Append("identity_token", token);
                //登录成功跳转到对应系统
                redirectUrl+= $"?token={token}";
                return Redirect(redirectUrl);
            }
            else
            {
                //登录失败
                return Content("登录失败");
            }
        }
        /// <summary>
        /// 退出登录
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public IActionResult LogOut()
        {
            //退出登录 
            HttpContext.Response.Cookies.Delete("identity_token");
            //回到登录页面
            return RedirectToAction("Login");
        }

    }

Login的view视图

<form action="/Authentication/Login" method="post">
    <input type="hidden" name="redirectUrl" value="@ViewBag.redirectUrl"/>
   账号: <input  name="account"/><br />
    密码:<input name="password" /><br />
    <input type="submit" value="登录" />
</form>

其它相关类

 /// <summary>
    /// jwt处理
    /// </summary>
    public class JWTService
    {
        public string GetJwtToken(User user)
        {
            var claims = new[]
            {
                   new Claim(ClaimTypes.Name, user.userId.ToString()),
                   new Claim("userName", user.userName),
                   new Claim("account", user.account),
                   new Claim("age", user.age.ToString()),
                   new Claim("email", user.email)
            };
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ConstOptions.SecurityKey));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            /**
             *  Claims (Payload)
                 Claims 部分包含了一些跟这个 token 有关的重要信息。 JWT 标准规定了一些字段,下面节选一些字段:

                 iss: The issuer of the token,token 是给谁的
                 sub: The subject of the token,token 主题
                 exp: Expiration Time。 token 过期时间,Unix 时间戳格式
                 iat: Issued At。 token 创建时间, Unix 时间戳格式
                 jti: JWT ID。针对当前 token 的唯一标识
                 除了规定的字段外,可以包含其他任何 JSON 兼容的字段。
             */
            var token = new JwtSecurityToken(
               issuer: ConstOptions.Issuer,
               audience: ConstOptions.Audience,
               claims: claims,
               expires: DateTime.Now.AddMinutes(60),//有效期
               notBefore: DateTime.Now,//开始有效时间,可以往后设置
               signingCredentials: creds);
            string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
            return returnToken;
        }
    }
 /// <summary>
    /// 常量
    /// </summary>
    public static class ConstOptions
    {
        //密钥
        public const string SecurityKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0afyoSDJT2o";
        public const string Issuer = "jwtIssuer"; //发送者
        public const string Audience = "jwtAudience";//签收者
        public const string identityToken = "identity_token";
    }
  public class User
    {
        public int userId { get; set; }
        public string account { get; set; }
        public string userName { get; set; }
        public int age { get; set; }
        public string email { get; set; }
    }

上面sso的登录功能就完成了,打开Login页面就能获取到Token了。

站点核心代码

新建一个站点A
修改startup.cs文件,在ConfigureServices方法里加上

 //jwt校验对称加密
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options => {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        //Audience,Issuer,SecurityKey的值要和sso的一致

                        //JWT有一些默认的属性,就是给鉴权时就可以筛选了
                        ValidateIssuer = true,//是否验证Issuer
                        ValidateAudience = true,//是否验证Audience
                        ValidateLifetime = true,//是否验证失效时间
                        ValidateIssuerSigningKey = true,//是否验证SecurityKey
                        ValidAudience = ConstOptions.Audience,//
                        ValidIssuer = ConstOptions.Issuer,//Issuer,这两项和前面签发jwt的设置一致
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ConstOptions.SecurityKey)),//拿到SecurityKey
                                                                                                                
                    };
                });

在Configure方法里加上

  app.UseAuthentication();//鉴权:解析信息--就是读取token,解密token

新建一个UserController

 public class UserController : Controller
    {
        /// <summary>
        /// 登录
        /// </summary>
        /// <returns></returns>
        [AllowAnonymous]
        public IActionResult Login()
        {
            //重定向到 sso登录
            return Redirect("http://localhost:5000/Authentication/Login?redirectUrl=http://localhost:27271/User/LoginSuccess");
        }
        [AllowAnonymous]
        public string LoginSuccess(string token)
        {
            return $"登录成功,token:{token}";
        }
        [Authorize]
        public IActionResult GetString()
        {
            return Content("ok");
        }
        [Authorize]
        public IActionResult GetName()
        {
            var str = string.Empty;
            var Claims = HttpContext.User.Identities.First().Claims;

            var name = Claims.Where(s => s.Type == "userName").First().Value;
            foreach (var item in Claims)
            {
                str += $"{item.Type}:{item.Value},";
            }
            return Content(str);
        }
    }

其它相关类

 public class ConstOptions
    {
        public const string SecurityKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0afyoSDJT2o";
        public const string Issuer = "jwtIssuer";
        public const string Audience = "jwtAudience";
    }

密钥要和sso中心的保持一致,上面的5000端口是sso的端口,27271端口是站点端口。

测试效果: 1.gif
一开始我输入的是端口为27271站点A的User/Login,然后直接跳转到端口5000的sso中心,然后登录完又回到27271的登录成功界面,第二次我再在地址上输入端口为27271站点A的User/Login,然后不需要输入密码就回到自己的登录成功页面上了,两次的Token一致,如果把A的代码复制一份变成站点B,也一样是不用登录的。

验证Token是否有效,获取用户信息

借助postman,站点A的/User/GetString方法头部加上 [Authorize]特性后表明要校验身份,这时候header没传token,会报401没鉴权 image.png
把刚才登录成功的token放到header里面的Authorization请求,就能看到成功返回ok,注意Token前面要加上Bearer和一个空格,这个是标准格式。 image.png
站点A如果读取Token的信息呢,上面说了Token的内容是中间的payload,只是用了base64编码了,直接截取base64解码就能拿到了,或者像上面的/User/GetName里面获取。 image.png

相关文章

网友评论

      本文标题:.NET Core5.0 JWT鉴权SSO单点登录

      本文链接:https://www.haomeiwen.com/subject/tepepltx.html