Explorar el Código

😁优化OAuth2授权登录流程

zuohuaijun hace 2 años
padre
commit
8cab17c470

+ 9 - 15
Admin.NET/Admin.NET.Core/Enum/PlatformTypeEnum.cs

@@ -28,26 +28,20 @@ public enum PlatformTypeEnum
     微信小程序 = 2,
 
     /// <summary>
-    /// 支付宝小程序
+    /// QQ
     /// </summary>
-    [Description("支付宝小程序")]
-    支付宝小程序 = 3,
+    [Description("QQ")]
+    QQ = 3,
 
     /// <summary>
-    /// 微信APP快捷登陆
+    /// 支付宝
     /// </summary>
-    [Description("微信APP快捷登陆")]
-    微信APP快捷登陆 = 4,
+    [Description("支付宝")]
+    Alipay = 4,
 
     /// <summary>
-    /// QQ在APP中快捷登陆
+    /// Gitee
     /// </summary>
-    [Description("QQ在APP中快捷登陆")]
-    QQ在APP中快捷登陆 = 5,
-
-    /// <summary>
-    /// 头条系小程序
-    /// </summary>
-    [Description("头条系小程序")]
-    头条系小程序 = 6,
+    [Description("Gitee")]
+    Gitee = 5,
 }

+ 25 - 14
Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs

@@ -95,23 +95,11 @@ public class SysAuthService : IDynamicApiController, ITransient
         // 单用户登录
         await _sysOnlineUserService.SignleLogin(user.Id);
 
-        var tokenExpire = await _sysConfigService.GetTokenExpire();
-        var refreshTokenExpire = await _sysConfigService.GetRefreshTokenExpire();
-
         // 生成Token令牌
-        var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
-        {
-            { ClaimConst.UserId, user.Id },
-            { ClaimConst.TenantId, user.TenantId },
-            { ClaimConst.Account, user.Account },
-            { ClaimConst.RealName, user.RealName },
-            { ClaimConst.AccountType, user.AccountType },
-            { ClaimConst.OrgId, user.OrgId },
-            { ClaimConst.OrgName, user.SysOrg?.Name },
-            { ClaimConst.OrgType, user.SysOrg?.OrgType },
-        }, tokenExpire);
+        var accessToken = await CreateToken(user);
 
         // 生成刷新Token令牌
+        var refreshTokenExpire = await _sysConfigService.GetRefreshTokenExpire();
         var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, refreshTokenExpire);
 
         // 设置响应报文头
@@ -127,6 +115,29 @@ public class SysAuthService : IDynamicApiController, ITransient
         };
     }
 
+    /// <summary>
+    /// 生成Token令牌
+    /// </summary>
+    /// <param name="user"></param>
+    /// <returns></returns>
+    [ApiDescriptionSettings(false)]
+    public async Task<string> CreateToken(SysUser user)
+    {
+        var tokenExpire = await _sysConfigService.GetTokenExpire();
+
+        return JWTEncryption.Encrypt(new Dictionary<string, object>
+        {
+            { ClaimConst.UserId, user.Id },
+            { ClaimConst.TenantId, user.TenantId },
+            { ClaimConst.Account, user.Account },
+            { ClaimConst.RealName, user.RealName },
+            { ClaimConst.AccountType, user.AccountType },
+            { ClaimConst.OrgId, user.OrgId },
+            { ClaimConst.OrgName, user.SysOrg?.Name },
+            { ClaimConst.OrgType, user.SysOrg?.OrgType },
+        }, tokenExpire);
+    }
+
     /// <summary>
     /// 获取登录账号
     /// </summary>

+ 2 - 3
Admin.NET/Admin.NET.Core/Service/OAuth/OAuthClaim.cs

@@ -9,8 +9,7 @@
 
 namespace Admin.NET.Core.Service;
 
-public static class GiteeClaims
+public static class OAuthClaim
 {
-    public const string Name = "urn:gitee:name";
-    public const string AvatarUrl = "urn:gitee:avatar_url";
+    public const string GiteeAvatarUrl = "urn:gitee:avatar_url";
 }

+ 41 - 37
Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthService.cs

@@ -21,15 +21,12 @@ public class SysOAuthService : IDynamicApiController, ITransient
 {
     private readonly IHttpContextAccessor _httpContextAccessor;
     private readonly SqlSugarRepository<SysWechatUser> _sysWechatUserRep;
-    private readonly SysUserService _sysUserService;
 
     public SysOAuthService(IHttpContextAccessor httpContextAccessor,
-        SqlSugarRepository<SysWechatUser> sysWechatUserRep,
-        SysUserService sysUserService)
+        SqlSugarRepository<SysWechatUser> sysWechatUserRep)
     {
         _httpContextAccessor = httpContextAccessor;
         _sysWechatUserRep = sysWechatUserRep;
-        _sysUserService = sysUserService;
     }
 
     /// <summary>
@@ -73,44 +70,51 @@ public class SysOAuthService : IDynamicApiController, ITransient
         if (openIdClaim == null || string.IsNullOrWhiteSpace(openIdClaim.Value))
             throw Oops.Oh("授权失败");
 
-        if (provider == "Weixin")
+        var name = authenticateResult.Principal.FindFirst(ClaimTypes.Name)?.Value;
+        var email = authenticateResult.Principal.FindFirst(ClaimTypes.Email)?.Value;
+        var mobilePhone = authenticateResult.Principal.FindFirst(ClaimTypes.MobilePhone)?.Value;
+        var dateOfBirth = authenticateResult.Principal.FindFirst(ClaimTypes.DateOfBirth)?.Value;
+        var gender = authenticateResult.Principal.FindFirst(ClaimTypes.Gender)?.Value;
+        var avatarUrl = "";
+
+        var platformType = PlatformTypeEnum.微信公众号;
+        if (provider == "Gitee")
         {
+            platformType = PlatformTypeEnum.Gitee;
+            avatarUrl = authenticateResult.Principal.FindFirst(OAuthClaim.GiteeAvatarUrl)?.Value;
         }
-        else if (provider == "Gitee")
+
+        // 若账号不存在则新建
+        var wechatUser = await _sysWechatUserRep.AsQueryable().Includes(u => u.SysUser).Filter(null, true).FirstAsync(u => u.OpenId == openIdClaim.Value);
+        if (wechatUser == null)
         {
-            string email = authenticateResult.Principal.FindFirst(ClaimTypes.Email)?.Value;
-            string name = authenticateResult.Principal.FindFirst(ClaimTypes.Name)?.Value;
-            string giteeName = authenticateResult.Principal.FindFirst(GiteeClaims.Name)?.Value;
-            string avatarUrl = authenticateResult.Principal.FindFirst(GiteeClaims.AvatarUrl)?.Value;
-
-            // 若账号不存在则新建
-            var user = await _sysWechatUserRep.GetFirstAsync(u => u.OpenId == openIdClaim.Value);
-            if (user == null)
+            var userId = await App.GetRequiredService<SysUserService>().AddUser(new AddUserInput()
             {
-                var userId = await _sysUserService.AddUser(new AddUserInput()
-                {
-                    Account = name,
-                    RealName = name,
-                    NickName = name,
-                    Email = email,
-                    Avatar = avatarUrl,
-                    Phone = "",
-                });
-
-                user = await _sysWechatUserRep.InsertReturnEntityAsync(new SysWechatUser()
-                {
-                    UserId = userId,
-                    OpenId = openIdClaim.Value,
-                    Avatar = avatarUrl,
-                    NickName = name,
-                });
-            }
-
-            // 构建登录Token
-
-
+                Account = name,
+                RealName = name,
+                NickName = name,
+                Email = email,
+                Avatar = avatarUrl,
+                Phone = mobilePhone,
+                OrgId = 1300000000101, // 根组织架构
+                RoleIdList = new List<long> { 1300000000104 } // 仅本人数据角色
+            });
+
+            await _sysWechatUserRep.InsertAsync(new SysWechatUser()
+            {
+                UserId = userId,
+                OpenId = openIdClaim.Value,
+                Avatar = avatarUrl,
+                NickName = name,
+                PlatformType = platformType
+            });
+
+            wechatUser = await _sysWechatUserRep.AsQueryable().Includes(u => u.SysUser).Filter(null, true).FirstAsync(u => u.OpenId == openIdClaim.Value);
         }
 
-        return new RedirectResult($"{redirectUrl}?openId={openIdClaim.Value}");
+        // 构建Token令牌
+        var accessToken = await App.GetRequiredService<SysAuthService>().CreateToken(wechatUser.SysUser);
+
+        return new RedirectResult($"{redirectUrl}/#/login?token={accessToken}");
     }
 }

+ 11 - 7
Admin.NET/Admin.NET.Web.Core/Startup.cs

@@ -92,18 +92,22 @@ public class Startup : AppStartup
                 options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
             })
-            .AddCookie()
+            .AddCookie(options =>
+            {
+                options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
+                options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;
+            })
             .AddWeixin(options =>
             {
-                options.ClientId = authOpt.Weixin.ClientId;
-                options.ClientSecret = authOpt.Weixin.ClientSecret;
+                options.ClientId = authOpt.Weixin?.ClientId;
+                options.ClientSecret = authOpt.Weixin?.ClientSecret;
             })
             .AddGitee(options =>
             {
-                options.ClientId = authOpt.Gitee.ClientId;
-                options.ClientSecret = authOpt.Gitee.ClientSecret;
-                options.ClaimActions.MapJsonKey(GiteeClaims.Name, "name");
-                options.ClaimActions.MapJsonKey(GiteeClaims.AvatarUrl, "avatar_url");
+                options.ClientId = authOpt.Gitee?.ClientId;
+                options.ClientSecret = authOpt.Gitee?.ClientSecret;
+
+                options.ClaimActions.MapJsonKey(OAuthClaim.GiteeAvatarUrl, "avatar_url");
             });
 
         // ElasticSearch

+ 28 - 7
Web/src/views/login/component/account.vue

@@ -53,6 +53,7 @@
 			</el-button>
 		</el-form-item>
 		<div class="font12 mt30 login-animation4 login-msg">{{ $t('message.mobile.msgText') }}</div>
+		<!-- <el-button type="primary" round v-waves @click="weixinSignIn" :loading="state.loading.signIn"></el-button> -->
 	</el-form>
 
 	<div class="dialog-header">
@@ -77,11 +78,11 @@ import { useRoute, useRouter } from 'vue-router';
 import { ElMessage, InputInstance } from 'element-plus';
 import { useI18n } from 'vue-i18n';
 import { initBackEndControlRoutes } from '/@/router/backEnd';
-import { Session } from '/@/utils/storage';
+import { Local, Session } from '/@/utils/storage';
 import { formatAxis } from '/@/utils/formatTime';
 import { NextLoading } from '/@/utils/loading';
 
-import { clearTokens, feature, getAPI } from '/@/utils/axios-utils';
+import { accessTokenKey, clearTokens, feature, getAPI } from '/@/utils/axios-utils';
 import { SysAuthApi } from '/@/api-services/api';
 
 // 旋转图片滑块组件
@@ -123,6 +124,12 @@ const state = reactive({
 	isPassRotate: false,
 });
 onMounted(async () => {
+	// 若URL带有Token参数(第三方登录)
+	var accessToken = route.query.token;
+	if (accessToken != null && accessToken != undefined) {
+		await saveTokenAndInitRoutes(accessToken);
+	}
+
 	// 获取登录配置
 	var res1 = await getAPI(SysAuthApi).apiSysAuthLoginConfigGet();
 	state.secondVerEnabled = res1.data.result.secondVerEnabled ?? true;
@@ -159,16 +166,25 @@ const onSignIn = async () => {
 				ElMessage.error('登录失败,请检查账号!');
 				return;
 			}
-
-			Session.set('token', res.data.result?.accessToken); // 缓存token
-			// 添加完动态路由再进行router跳转,否则可能报错 No match found for location with path "/"
-			const isNoPower = await initBackEndControlRoutes();
-			signInSuccess(isNoPower); // 再执行 signInSuccess
+			await saveTokenAndInitRoutes(res.data.result?.accessToken);
 		} finally {
 			state.loading.signIn = false;
 		}
 	});
 };
+
+// 保持Token并初始化路由
+const saveTokenAndInitRoutes = async (accessToken: string | any) => {
+	// 缓存token
+	Local.set(accessTokenKey, accessToken);
+	// Local.set(refreshAccessTokenKey, refreshAccessToken);
+	Session.set('token', accessToken);
+
+	// 添加完动态路由再进行router跳转,否则可能报错 No match found for location with path "/"
+	const isNoPower = await initBackEndControlRoutes();
+	signInSuccess(isNoPower); // 再执行 signInSuccess
+};
+
 // 登录成功后的跳转
 const signInSuccess = (isNoPower: boolean | undefined) => {
 	if (isNoPower) {
@@ -219,6 +235,11 @@ const handleSignIn = () => {
 		state.secondVerEnabled ? openRotateVerify() : onSignIn();
 	}
 };
+
+// // 微信登录
+// const weixinSignIn = () => {
+// 	window.open('http://localhost:5005/api/sysoauth/signin?provider=Gitee&redirectUrl=http://localhost:8888');
+// };
 </script>
 
 <style lang="scss" scoped>

+ 5 - 5
Web/src/views/system/weChatUser/index.vue

@@ -25,11 +25,11 @@
 				<el-table-column prop="platformType" label="平台类型" width="110" align="center" show-overflow-tooltip>
 					<template #default="scope">
 						<el-tag v-if="scope.row.platformType === 1"> 微信公众号 </el-tag>
-						<el-tag v-if="scope.row.platformType === 2"> 微信小程序 </el-tag>
-						<el-tag v-if="scope.row.platformType === 3"> 支付宝小程序 </el-tag>
-						<el-tag v-if="scope.row.platformType === 4"> 微信APP快捷登陆 </el-tag>
-						<el-tag v-if="scope.row.platformType === 5"> QQ在APP中快捷登陆 </el-tag>
-						<el-tag v-if="scope.row.platformType === 6"> 头条系小程序 </el-tag>
+						<el-tag v-else-if="scope.row.platformType === 2"> 微信小程序 </el-tag>
+						<el-tag v-else-if="scope.row.platformType === 3"> QQ </el-tag>
+						<el-tag v-else-if="scope.row.platformType === 4"> Alipay </el-tag>
+						<el-tag v-else-if="scope.row.platformType === 5"> Gitee </el-tag>
+						<el-tag v-else> 未知 </el-tag>
 					</template>
 				</el-table-column>
 				<el-table-column prop="nickName" label="昵称" align="center" show-overflow-tooltip />