C# vue 实现Google Oauth2.0授权

47 阅读3分钟

引言:

  1. 由于我第一次做这种OAuth,有人要我看官方文档,但是我发现我连基本流程都不知道,所以我认为应该从流程看起,再到官网文档。
  2. 我推荐的是看文章顺序是:
  3. 看完以上文章后,可以得到流程如下:
    1. 获取凭据:登录谷歌控制台,配置 Google Cloud 项目和应用程序,获取凭据(ClienIdClientSecret)。
    2. 获取授权URL并请求:携带参数get请求。
    3. 重定向到回调地址:进入谷歌授权界面,Google 提示用户同意,若正确授权后重定向到回调地址。
    4. 获取Access_Token:交换授权代码以刷新令牌和访问令牌。
    5. 获取用户资料:通过Access_Token调用谷歌api,请求访问或修改用户授权的资源。
    6. 查库?不注册:注册:查询gmail和sub字段的值是否存在数据库,若存在就注册用户再查询,若存在直接颁发JWT的token。
    image.png

思路实现:

  1. 其实到现在思路差不多按照流程出来了,谷歌文档的缺点就是在这:内容很全,但是需要你东拼西凑。所以前端需要做什么呢?后端又需要做什么呢?怎么应用到代码是最为关键的。
  2. 获取凭据:具体操作可参考此文 - OAuth 2.0(cnblogs.com),一直到下载json数据到本机为止。
  3. 所以前端要做的工作:
    • 获取授权URL并请求.

      请求方法:Get 接口地址: https://accounts.google.com/o/oauth2/v2/auth
      必填参数:    
      client_id:google获取的client_id   
      redirect_uri:google获取的回调url 
      response_type:web授权固定填code (回调函数有code)   
      scope:后台配置的scope,如果有多个,要用空格隔开
      
      
      代码实现:
          const clientId ="xxxxxxxxxxxxxxxx.apps.googleusercontent.com";
          const redirectUri = "http://localhost:9527/login";
          const scope =
              "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email";
          const responseType = "code";
          const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=${responseType}`;  
          window.location.href = authUrl;  // 重定向到Google认证页面
      
    • 重定向到回调地址 image.png 点击授权同意后: image.png

      http://localhost:9527/login?
      code=GOOGLE_RESPONSE_CODE&scope=YOUR_SCOPE&authuser=0&prompt=consent
      code是谷歌生成的,用于换取token scope是你传的scope(所以只有code最重要)
      
      
    • 获取Access_Token

      请求方法:POST 接口地址:https://oauth2.googleapis.com/token 
      Content-Type: application/x-www-form-urlencoded
      
      请求参数:
      code:重定向到回调地址的code
      client_id:google获取的 client_id  
      client_secret:google获取的 client_secret
      redirect_uri:google获取的的回调地址   
      grant_type:authorization_code(固定)
      
      ---------------------------------------------------------------------
      代码实现:
      created(){
          const code = this.getParameterByName("code"); //
          if (code) {
              try {
                  const response = await axios.post(
                      "https://oauth2.googleapis.com/token", // 用自己的
                      {
                          code: code,
                          client_id:
                              "xxxxxxxxxxxxxxxx.com",
                          client_secret: "xxxxxxxxxxxxxxxx",
                          redirect_uri: "http://localhost:9527/login",
                          grant_type: "authorization_code",
                      }
                  )
              }
              } catch (error) {
                  // 处理错误
                  console.error("Error requesting Google Token:", error);
              }
          }
      },
      methods:{
         // 辅助函数,用于提取URL参数 code
          getParameterByName(name, url = window.location.href) {
              name = name.replace(/[\[\]]/g, "\\$&");
              const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
                  results = regex.exec(url);
              if (!results) return null;
              if (!results[2]) return "";
              return decodeURIComponent(results[2].replace(/\+/g, " "));
          }
      }
      
    • 将token传给后端, 交付后端验证获取用户资料。

      
                    if (response.data) {
                      const res = await getGoogleMessage({
                     // 将token传给后端, 交付后端验证获取用户资料。
                      RequestToken: response.data.access_token,
                      });
                      if (res.Success === false) {
                          this.$message({
                              type: "error",
                              message: res.Msg,
                          });
                      } else {
                          this.$message.success(this.$t("Common.Success"));
                          setToken(res.Data) // 接受后端jwt颁发的token 并且写入
                        //其他操作 重定向等等!
                      }
                  }
      
      
  4. 后端要做的工作:
    • 获取到前端的token,c#去调用谷歌的api

         // 接口层
      namespace Rc.IBusiness.Base_Manage
      {
          public interface IGoogleAuthBusiness
          {
              public Task<GooogleUserInfoResponse> ProcessGoogleCallback(string code); // 处理谷歌回调
          }
          public class GooogleUserInfoResponse
          {
              // 谷歌的 必须小写
              public string sub { get; set; }
              public string name { get; set; }
              public string given_name { get; set; }
              public string family_name { get; set; }
              public string picture { get; set; }
              public string email { get; set; }
              public bool email_verified { get; set; }
              public string locale { get; set; }
          }
      
      }
          ---------------------------------分割线----------------------------------
         // 接口实现层
        public async Task<GooogleUserInfoResponse> ProcessGoogleCallback(string access_token)
        {
            if (access_token != null)
            {
                using (var gClient = new HttpClient())
                {
                    gClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", access_token);
                    var gResponse = await gClient.GetAsync("https://www.googleapis.com/oauth2/v3/userinfo");
      
                    if (gResponse.IsSuccessStatusCode)
                    {
                        var gResult = await gResponse.Content.ReadAsStringAsync();
      
                        var userInfoData = System.Text.Json.JsonSerializer.Deserialize<GooogleUserInfoResponse>(gResult); 
      
                        return userInfoData;
                    }
                    else
                    {
                        // 请求失败的情况
                        var errorResponse = await gResponse.Content.ReadAsStringAsync();
                        // 抛出异常,由调用方处理
                        throw new Exception("Failed to retrieve Google user information. Details: " + errorResponse);
                    }
                }
            }
            return null;
        }
      
      
    • 查库?不注册:注册

         /// <summary>
       /// 谷歌登录
       /// </summary>
       /// <param name="request"></param>
       /// <returns></returns>
       [HttpPost]
       [AllowAnonymous]
       public async Task<IActionResult> SignInGoogle(GoogleCallbackRequest request)
       {
           try
           {
               GooogleUserInfoResponse userMsg = await _googleAuthBusiness.ProcessGoogleCallback(request.RequestToken); 
      
               // 查询userMsg的sub是否存在数据库 存在就直接授权jwt
               LoginGoogleInputDto input = new LoginGoogleInputDto
               {
                   UserName = userMsg.name,
                   Sub = userMsg.sub
               };
               var user = await _homeBus.SubmitLoginAsync(input);
      
               if (user == null)
               {
                   // 如果不存在数据库,就注册一个账号
                   await _baseUserBusiness.RegisterDataAsync(userMsg); // 谷歌注册 重载
      
                   // 重新查询用户
                   user = await _homeBus.SubmitLoginAsync(input);
               }
               if (user.EnableState == 0)
               {
                   //账号已禁用
                   throw new BusException("Account is disabled");
               }
               CurrentUserModel currentUser = new CurrentUserModel()
               {
                   Id = user.Id,
                   RealName = user.RealName
               };
               var claims = new[]
               {
                   new Claim("userId", user.Id),
                   new Claim("RealName", user.RealName)
               };
      
               //构造token
               var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.Secret));
               var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
               var jwtToken = new JwtSecurityToken(
                   string.Empty,
                   string.Empty,
                   claims,
                   expires: DateTime.Now.AddHours(_jwtOptions.AccessExpireHours),
                   signingCredentials: credentials);
      
               var token = new JwtSecurityTokenHandler().WriteToken(jwtToken);
      
               if (!string.IsNullOrEmpty(token))
               {
                   //自定义日志内容:用户登录
                   var logType = UserLogType.User.ToString();
                   var logContent = $"{currentUser.RealName}LOGIN IN CMS";
                   await _userLogBusiness.AddUserLogAsync(logType, logContent, currentUser);
                   return JsonContent(new AjaxResult<string>(token)
                   {
                       Data = token,
                       Success = true,
                       Msg = "验证成功",
                   }.ToJson());
               }
               else
               {
                   return JsonContent(new AjaxResult<string>("")
                   {
                       Success = false,
                       Msg = "颁发token失败",
                   }.ToJson());
               }
           }
           catch (Exception ex)
           {
               // 处理异常,根据实际情况返回适当的状态码或错误信息
               return BadRequest(new { Error = "Authentication failed", Details = ex.Message });
           }
       }