引言:
- 由于我第一次做这种
OAuth
,有人要我看官方文档,但是我发现我连基本流程都不知道,所以我认为应该从流程看起,再到官网文档。 - 我推荐的是看文章顺序是:
- 看完以上文章后,可以得到流程如下:
- 获取凭据:登录谷歌控制台,配置 Google Cloud 项目和应用程序,获取凭据(
ClienId
,ClientSecret
)。 - 获取授权URL并请求:携带参数get请求。
- 重定向到回调地址:进入谷歌授权界面,Google 提示用户同意,若正确授权后重定向到回调地址。
- 获取
Access_Token
:交换授权代码以刷新令牌和访问令牌。 - 获取用户资料:通过Access_Token调用谷歌api,请求访问或修改用户授权的资源。
- 查库?不注册:注册:查询gmail和sub字段的值是否存在数据库,若存在就注册用户再查询,若存在直接颁发JWT的token。
- 获取凭据:登录谷歌控制台,配置 Google Cloud 项目和应用程序,获取凭据(
思路实现:
- 其实到现在思路差不多按照流程出来了,谷歌文档的缺点就是在这:内容很全,但是需要你东拼西凑。所以前端需要做什么呢?后端又需要做什么呢?怎么应用到代码是最为关键的。
- 获取凭据:具体操作可参考此文 - OAuth 2.0(cnblogs.com),一直到下载json数据到本机为止。
- 所以前端要做的工作:
-
获取授权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认证页面
-
重定向到回调地址
点击授权同意后:
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 并且写入 //其他操作 重定向等等! } }
-
- 后端要做的工作:
-
获取到前端的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 }); } }
-