Vue.js结合ASP.NET Core构建用户登录与权限验证系统

news2025/1/6 19:26:27

  • 1. 环境准备
  • 2. 创建项目
  • 3. Vue配置
    • 步骤一: 安装包
    • 步骤二: 配置文件
    • 步骤三: 页面文件
  • 4. 后台配置

在本教程中,我将利用Visual Studio 2022的强大集成开发环境,结合Vue.js前端框架和ASP.NET Core后端框架,从头开始创建一个具备用户登录与权限验证功能的Web应用程序。我们将充分利用Visual Studio的内置工具和模板来简化开发流程。

1. 环境准备

Visual Studio 2022,Vue3

2. 创建项目

打开Visual Studio 2022,选择“创建新项目”。在项目模板搜索框中输入“Vue and ASP.NET Core”,选择模板后点击“下一步”。
按照图中配置:
在这里插入图片描述
生成目录如下:
在这里插入图片描述

3. Vue配置

步骤一: 安装包

右键npm->安装新的npm包

  • element-plus UI包
  • @element-plus/icons-vue UI图标包
  • axios 发送请求的包
  • qs 发送请求时序列化的包
  • vue-router vue路由
  • jwt-decode 令牌解码

步骤二: 配置文件

  • 封装axios
    新建axios.js文件,内容如下:

    // axios.js
    
    import axios from 'axios';
    import PLATFROM_CONFIG from '../public/config';
    
    
    const instance = axios.create({
        baseURL: PLATFROM_CONFIG.baseURL, // 替换为实际的 API 地址
        timeout: 10000,
    });
    
    instance.defaults.headers.post['Content-Type'] = 'application/json';
    
    // 添加请求拦截器
    axios.interceptors.request.use((config) => {
        // 在发送请求之前做些什么
        return config;
    }, function (error) {
        // 对请求错误做些什么
        return Promise.reject(error);
    });
    
    // 添加响应拦截器
    axios.interceptors.response.use(function (response) {
        // 对响应数据做点什么
        if (response.status === 200) {
            return Promise.resolve(response);
        } else {
            return Promise.reject(response);
        }
    }, function (error) {
        // 对响应错误做点什么
        return Promise.reject(error);
    });
    
    
    export const get = (url, params) => {
        return instance.get(url, { params });
    };
    
    export const post = (url, data) => {
        // data = QS.stringify(data);
        return instance.post(url, data);
    };
    
    
  • 创建路由
    新建router文件夹,并新建router.js文件

import { createRouter, createWebHashHistory } from 'vue-router'

import qs from 'qs';
import { ElMessage } from 'element-plus'

import { post } from '../axios';

import Home from '../components/Home.vue'
import Setting from '../components/Setting.vue'
import Login from '../components/Login.vue'
import LoginOut from '../components/LoginOut.vue'

// 路由配置
const routes = [
    { path: '/', component: Home },
    { path: '/Login', component: Login },
    { path: '/Setting', component: Setting, meta: { requiresAuth: true, role: 'ShortcutManage;' } },
    { path: '/LoginOut', component: LoginOut },
]

const router = createRouter({
    history: createWebHashHistory(),
    routes,
})
// 路由守卫,在这里创建验证的流程
router.beforeEach((to, from, next) => {
    const accessToken = localStorage.getItem('accessToken');
    if (to.meta.requiresAuth && !accessToken) {
        // 如果需要认证并且没有令牌,则重定向到登录页
        next('/Login');
    } else {
        if (to.meta.requiresAuth) {
            // 如果有令牌判断令牌是否过期
            //判断令牌是否过期
            const decodedToken = jwtDecode(accessToken);
            const expirationTime = decodedToken.exp * 1000;
            const isTokenExpired = expirationTime < Date.now();
            // 已经过期
            if (isTokenExpired) {
                next('/LoginOut');
            } else { // 没有过期
                // 判断是否需要指定权限
                if (typeof (to.meta.role) !== 'undefined' && to.meta.role != null) {
                    let USER_INFO = qs.parse(localStorage.getItem("userInfo"))
                    post("Login/ValidPerm", { username: USER_INFO.ad, userPowr: to.meta.role }).then(res => {
                        next();
                    }).catch(err => {
                        console.log(err)
                        ElMessage({
                            message: '您没有权限,请联系管理员!',
                            type: 'warning',
                        })
                    })
                } else {
                    next();
                }
            }
        } else {
            next();
        }


    }
});

export default router
  • 配置main.js
import './assets/main.css'

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import { ElMessage} from 'element-plus'
import 'element-plus/dist/index.css'
import router from './router/router'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { get, post } from './axios';
import App from './App.vue'
import * as utils from './utils';
import qs from 'qs'

import ELHeader from './components/custom/ElHeader.vue'
import ElAside from './components/custom/ElAside.vue'

const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}

Object.entries(utils).forEach(([key, value]) => {
    app.config.globalProperties[`$${key}`] = value;
});


app.config.globalProperties.$get = get;
app.config.globalProperties.$qs = qs;
app.config.globalProperties.$post = post;
app.config.globalProperties.$message = ElMessage;

app.config.globalProperties.$USER_INFO = qs.parse(localStorage.getItem("userInfo"))

app.use(ElementPlus)
app.use(router)

app.component('ELHeader', ELHeader).component('ELAside',ElAside); 


app.mount('#app')

步骤三: 页面文件

  • 登陆页面Login.vue
<template>
    <el-form :model="loginForm" ref="loginForm" :inline="false" size="large">
        <el-form-item prop="Username" :rules="{
    required: true,
    message: 'Username can not be null',
    trigger: 'blur',
}">
            <el-input v-model="loginForm.Username" placeholder="Okta Account">
                <template #prepend><el-icon>
                        <User />
                    </el-icon></template>
            </el-input>
        </el-form-item>
        <el-form-item prop="Password" :rules="{
    required: true,
    message: 'Password can not be null',
    trigger: 'blur',
}">
            <el-input type="password" v-model="loginForm.Password" placeholder="Okta Password">
                <template #prepend><el-icon>
                        <Lock />
                    </el-icon></template>
            </el-input>
        </el-form-item>
        <el-form-item>
            <el-button class="login-btn" type="primary" @click="loginOn">登陆</el-button>
        </el-form-item>
    </el-form>
</template>
<script lang="js">
import { defineComponent } from 'vue';
export default defineComponent({
    data() {
        return {
            loginForm: {
                Username: '',
                Password: '',
                auth: "ShortcutLinks"
            }
        }
    },
    methods: {
        loginOn() {
            this.$refs.loginForm.validate((valid) => {
                if (valid) {
                    let that = this
                    this.$post("/Login/LoginVerify", this.loginForm).then(res => {
                        if (res.data.success) {
                        	// 检测是否有Token
                            localStorage.setItem('accessToken', res.data.data.token)
                            let userInfo = res.data.data
                            userInfo.token = null
                            localStorage.setItem('userInfo', this.$qs.stringify(userInfo))
                            that.$router.push('/')
                        } else {
                            this.$message({
                                showClose: true,
                                message: res.data.message,
                                type: 'warning',
                            })
                        }
                    }).catch(err => {
                        console.error(err)
                    })
                } else {
                    console.log('error submit!')
                    return false
                }
            })

        }
    },mounted(){
        this.Yaer = new Date().getFullYear()
    }
})
</script>
  • 注销界面LoginOut.vue
<template>
    <div>退出登陆成功!</div>
</template>

<script lang="js">
import { defineComponent } from 'vue';

export default defineComponent({
    data() {
        return {

        }
    }, mounted() {
        localStorage.removeItem('userInfo');
        localStorage.removeItem('accessToken');  
        this.$router.push('/Login');
    }
})
</script>
  • 修改App.vue

<template>
   <router-view></router-view>
</template>

<style scoped>

</style>

4. 后台配置

创建一个生成Token的工具类TokenService

    public class TokenService
    {
        private readonly string _secretKey;
        private readonly string _issuer;
        private readonly string _audience;

        public TokenService(string secretKey, string issuer, string audience)
        {
            _secretKey = secretKey;
            _issuer = issuer;
            _audience = audience;
        }

        public string GenerateToken(string username)
        {
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secretKey));
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            var claims = new[]
            {
            new Claim(ClaimTypes.Name, username),
            // Add additional claims as needed
        };

            var token = new JwtSecurityToken(
                _issuer,
                _audience,
                claims,
                expires: DateTime.Now.AddMonths(1), // Token expiration time
                signingCredentials: credentials
            );

            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }

配置跨域Program.cs`
在Buidl()之前增加:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins, policy =>
    {
        policy.WithOrigins("*").AllowAnyOrigin()
        .AllowAnyHeader().AllowAnyMethod();
    });
});

登陆验证
LoginController.cs

 [HttpPost("LoginVerify")]
 public async Task<Result<LoginInfo>> LoginVerify([FromBody] LoginModel loginModel)
 {
     if (string.IsNullOrEmpty(loginModel.Username) || string.IsNullOrEmpty(loginModel.Password))
     {
         return Result<LoginInfo>.Fail("用户名或密码不能为空!");
     }
     if (string.IsNullOrEmpty(loginModel.Auth) || !"ShortcutLinks".Equals(loginModel.Auth))
     {
         return Result<LoginInfo>.Fail("令牌识别错误!");
     }
     string responseContent = await IsValidUser(loginModel.Username, loginModel.Password);
     if ("Unauthorized".Equals(responseContent))
     {
         return Result<LoginInfo>.Fail("验证失败!");
     }
     if ("The user name or password is incorrect.".Equals(responseContent))
     {
         return Result<LoginInfo>.Fail("用户名或密码错误!");
     }

     try
     {
         // 加密秘钥,可以自定义
         string key = "Ns9XoAdW7Pb3Cv9Fm2Zq4t6w8y/B?E(H+MbQeThWmZq4t7w9z$C&F)J@NcRfUjXn2r5u8x/A%D*G-KaPdSgVkYp3s6v9y";
         // 我自己的登陆验证方法,根据实际情况可以修改
         LoginInfo loginInfo = JsonConvert.DeserializeObject<LoginInfo>(responseContent);
         // 生成验证的Token
         var tokenService = new TokenService(key, "ShortcutLinksServer", "ShortcutLinksClient");
         // 为Token添加一个标识,我这里使用的用户的AD
         var token = tokenService.GenerateToken(loginInfo.AD);
         loginInfo.token = token;
         // 生成的信息返回前天
         return Result<LoginInfo>.Suc(loginInfo);

     }
     catch (Exception)
     {

         return Result<LoginInfo>.Fail("登陆失败!");
     }
 }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1814341.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SwiftUI 中的自定义Shape

在SwiftUI中&#xff0c;Shape协议允许开发者定义可重用的图形&#xff0c;这些图形可以用于绘制界面元素&#xff0c;如按钮、背景、边框等。通过实现 Shape 协议&#xff0c;可以创建完全自定义的图形&#xff0c;并控制其绘制方式。本文将详细介绍如何在 SwiftUI 中创建自定…

Qt-Advanced-Docking-System的学习

Qt-Advanced-Docking-System使用说明_cdockmanager-CSDN博客 示例1&#xff1a; #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include "DockManager.h" #include "QtDock/DockAreaWidget.h" namespace Ui { class MainWind…

计算机组成原理之运算方法和运算器

文章目录 数据与文字的表示方法定点表示法机器码&#xff08;机器数&#xff09;原码 反码补码移码 浮点表示法尾数规格化 数据与文字的表示方法 定点表示法 机器码&#xff08;机器数&#xff09; 正数的原码、反码、补码一样&#xff0c;负数的原码、反码、补码的符号位均为…

迅狐矩阵系统:智能化多平台内容管理与发布

迅狐矩阵系统是一套专为提高数字内容管理和发布效率而设计的综合性解决方案。它通过一系列智能化功能&#xff0c;帮助用户实现多平台内容的高效管理和发布&#xff0c;以下是系统的几大核心优势&#xff1a; 多平台绑定发布 迅狐矩阵系统支持用户绑定多个平台的多个账号&…

linux驱动学习(十一)之内核时钟

需要板子一起学习的可以这里购买&#xff08;含资料&#xff09;&#xff1a;点击跳转 一、内核时钟 1、内核时钟 内核时钟&#xff08;Kernel Clock&#xff09;&#xff0c;也称为系统时钟&#xff08;System Clock&#xff09;或滴答时钟&#xff08;Tick Timer&#xff0…

使用LANGCHAIN和GEMINI构建AI应用程序

借助这些先进技术&#xff0c;您可以生成文本、分析图像并实现多模态 AI 交互。 LangChain 和 Google 的 Gemini API 是什么&#xff1f; LangChain&#xff1a;构建 AI 应用程序的弹性框架 LangChain 是一个强大且灵活的框架&#xff0c;可以简化 AI 应用程序的开发。它提供…

绝对新惊喜:4款王者级别的办公软件,免费又实用

工作里有那么多规矩&#xff0c;但效率绝对是重中之重&#xff0c;选对了好软件&#xff0c;工作就能更高效&#xff0c;下班也能更早回家。 要是你也想做个“时间管理小能手”&#xff0c;下面这4款超好用的办公软件&#xff0c;你可千万别错过&#xff01; Gitmind 现在很…

奇绩创坛 2024 年春季创业营路演

奇绩创坛 2024 年春季创业营路演 奇绩创坛在北京中关村举办的 2024 年春季创业营路演 奇绩创坛在北京中关村举办的 2024 年春季创业营路演 RWKV 元始智能的COO罗璇在会议上详细分享了RWKV的创新模型架构、最新进展以及当前的研究方向。 目前&#xff0c;RWKV架构已经推出了最…

五、LVS原理

目录 5.1 LVS 相关原理 5.1.1 LVS集群的体系结构以及特点 5.1.1.1 LVS简介 5.1.1.2 LVS体系结构 5.1.1.3 LVS相关术语 5.1.1.4 LVS工作模式 5.1.1.5 LVS调度算法 5.1.2 LVS-DR集群介绍 5.1.2.1 LVS-DR模式工作原理 5.1.2.2 LVS-DR模式应用特点 5.1.2.3 LVS-DR模式ARP抑制 5.1…

使用Transformer进行抄袭检测

动机 在许多行业中&#xff0c;尤其是在学术界&#xff0c;抄袭是一个重大问题。随着互联网和开放信息的兴起&#xff0c;这种现象甚至变得更加严重&#xff0c;任何人都可以通过点击访问特定主题的任何信息。 基于这一观察&#xff0c;研究人员一直在尝试使用不同的文本分析…

生成高保真度3D数字人化身:打造你的专属虚拟形象

在数字化时代,我们的虚拟形象正变得越来越重要。现在,一项前沿技术正将这一领域推向新的高度——生成高保真度的3D数字人化身。这项技术不仅可以将你的形象以3D形式呈现,更能赋予它生命,让你的虚拟形象拥有丰富的表情和动作。 一、技术简介 这项技术就像是一个高级的3D照…

最简单的方法,连续打印多个空格

1、大家都知道&#xff0c;c语言中&#xff0c;我们打印语句时&#xff0c;如果使用\t来控制打印间隔&#xff0c;可能会出现排版错乱问题&#xff0c;所以一般都会使用空格来控制间隔&#xff0c;这样不管在哪个工具上面显示&#xff0c;打印的信息都不会错乱。 2、控制间隔的…

fdtd(时域有限差分)仿真

FDTD Solutions 是一款非常好用的微纳光学设计工具。该软件提供了丰富的设计功能&#xff0c;支持 CMOS 图像传感器&#xff0c;OLED 和液晶&#xff0c;表面计量&#xff0c;表面等离子体&#xff0c;石墨烯&#xff0c;太阳能电池&#xff0c;集成光子组件&#xff0c;超材 料…

Offline :Adversarially Trained Actor Critic for Offline Reinforcement Learning

ICML 2022 paper code 基于Stackelberg游戏博弈形式&#xff0c;对抗的学习actor与critic Intro Method 将离线RL的Stackelberg博弈表述为一个双层优化问题&#xff0c;学习者策略π∈Π为领导者&#xff0c;批评家f∈F为跟随者: π ^ ∗ ∈ argmax ⁡ π ∈ I I L μ ( π…

JVM如何确定方法调用

方法调用并不等同于方法执行&#xff0c;方法调用阶段唯一的任务就是确定调用哪一个方法&#xff0c;不涉及方法内部的具体运行过程。在程序运行时&#xff0c;进行方法调用是最普遍、最频繁的操作&#xff0c;但Class文件的编译过程中不包含传统编译中的连接步骤&#xff0c;一…

破解动态网页:如何用JavaScript获取自动消失的联想词

前几天在做数据分析时&#xff0c;我尝试获取某网站上输入搜索词后的联想词&#xff0c;输入搜索词后会弹出一个显示联想词的框。有趣的是&#xff0c;当我尝试通过按F12定位这个弹框在HTML中的位置时&#xff0c;输入框失去焦点后&#xff0c;联想词弹框就自动消失了。我观察到…

UnityAPI学习之Animator的基本使用

动画与动画控制器 示例1&#xff1a; 创建Animator对动画控制器进行统一管理&#xff0c;在Gris中创建Animator组件&#xff0c;并对其中的Controller属性进行赋值 在进行动画创作前&#xff0c;需先将图片的Texture Type属性改为Sprite(2D and UI) 再将一系列图片拖入Gris物…

nss刷题(4)

1、[SWPUCTF 2021 新生赛]easyrce <?php error_reporting(0); highlight_file(__FILE__); if(isset($_GET[url])) { eval($_GET[url]); } ?> if(isset($_GET[url])) isset函数用来检测url变量是否存在&#xff1b;$_GET函数获取变量数据 eval($_GET[url]); eval函数用…

基于Java+Swing+mysql幼儿园信息管理系统V2

博主介绍&#xff1a; 大家好&#xff0c;本人精通Java、Python、C#、C、C编程语言&#xff0c;同时也熟练掌握微信小程序、Php和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我有丰富的成品Java、Python、C#毕设项目经验&#xff0c;能够为学生提供各类…

和鲸101领航北中医:助力健康医疗AI实验室建设,培养交叉数据人才

2024 年 3 月开学季&#xff0c;北京中医药大学&#xff08;简称“北中医”&#xff09;的健康医疗人工智能实验室迎来了正式投入使用后的第一堂课。除了配备全新的桌椅和尖端的硬件服务器外&#xff0c;实验室还引入了先进的人工智能实训平台&#xff0c;为大数据管理与应用专…