登录功能
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
<!-- Email & MIME Libraries -->
|
<!-- Email & MIME Libraries -->
|
||||||
<PackageVersion Include="FreeSql" Version="3.5.304" />
|
<PackageVersion Include="FreeSql" Version="3.5.304" />
|
||||||
<PackageVersion Include="MailKit" Version="4.14.1" />
|
<PackageVersion Include="MailKit" Version="4.14.1" />
|
||||||
|
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.1" />
|
||||||
<PackageVersion Include="MimeKit" Version="4.14.0" />
|
<PackageVersion Include="MimeKit" Version="4.14.0" />
|
||||||
<!-- Dependency Injection & Configuration -->
|
<!-- Dependency Injection & Configuration -->
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
|
||||||
|
|||||||
6
Service/AppSettingModel/AuthSettings.cs
Normal file
6
Service/AppSettingModel/AuthSettings.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Service.AppSettingModel;
|
||||||
|
|
||||||
|
public class AuthSettings
|
||||||
|
{
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
9
Service/AppSettingModel/JwtSettings.cs
Normal file
9
Service/AppSettingModel/JwtSettings.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Service.AppSettingModel;
|
||||||
|
|
||||||
|
public class JwtSettings
|
||||||
|
{
|
||||||
|
public string SecretKey { get; set; } = string.Empty;
|
||||||
|
public string Issuer { get; set; } = string.Empty;
|
||||||
|
public string Audience { get; set; } = string.Empty;
|
||||||
|
public int ExpirationHours { get; set; }
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<van-config-provider :theme="theme">
|
<van-config-provider :theme="theme">
|
||||||
<RouterView />
|
<RouterView />
|
||||||
<van-tabbar v-model="active">
|
<van-tabbar v-model="active" v-show="showTabbar">
|
||||||
<van-tabbar-item icon="notes-o" to="/calendar">
|
<van-tabbar-item icon="notes-o" to="/calendar">
|
||||||
日历
|
日历
|
||||||
</van-tabbar-item>
|
</van-tabbar-item>
|
||||||
@@ -20,10 +20,15 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { RouterView, useRoute } from 'vue-router'
|
import { RouterView, useRoute } from 'vue-router'
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
// 根据路由判断是否显示Tabbar
|
||||||
|
const showTabbar = computed(() => {
|
||||||
|
return route.path !== '/login'
|
||||||
|
})
|
||||||
|
|
||||||
const active = ref(0)
|
const active = ref(0)
|
||||||
const theme = ref('light')
|
const theme = ref('light')
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { showToast } from 'vant'
|
import { showToast } from 'vant'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import router from '@/router'
|
||||||
|
|
||||||
// 创建 axios 实例
|
// 创建 axios 实例
|
||||||
const request = axios.create({
|
const request = axios.create({
|
||||||
@@ -13,11 +15,11 @@ const request = axios.create({
|
|||||||
// 请求拦截器
|
// 请求拦截器
|
||||||
request.interceptors.request.use(
|
request.interceptors.request.use(
|
||||||
config => {
|
config => {
|
||||||
// 可以在这里添加 token 等认证信息
|
// 添加 token 认证信息
|
||||||
// const token = localStorage.getItem('token')
|
const authStore = useAuthStore()
|
||||||
// if (token) {
|
if (authStore.token) {
|
||||||
// config.headers.Authorization = `Bearer ${token}`
|
config.headers.Authorization = `Bearer ${authStore.token}`
|
||||||
// }
|
}
|
||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
@@ -53,6 +55,10 @@ request.interceptors.response.use(
|
|||||||
break
|
break
|
||||||
case 401:
|
case 401:
|
||||||
message = '未授权,请重新登录'
|
message = '未授权,请重新登录'
|
||||||
|
// 清除登录状态并跳转到登录页
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
authStore.logout()
|
||||||
|
router.push({ name: 'login', query: { redirect: router.currentRoute.value.fullPath } })
|
||||||
break
|
break
|
||||||
case 403:
|
case 403:
|
||||||
message = '拒绝访问'
|
message = '拒绝访问'
|
||||||
|
|||||||
@@ -1,29 +1,57 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes: [
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: () => import('../views/LoginView.vue'),
|
||||||
|
meta: { requiresAuth: false },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'transactions',
|
name: 'transactions',
|
||||||
component: () => import('../views/TransactionsRecord.vue'),
|
component: () => import('../views/TransactionsRecord.vue'),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/email',
|
path: '/email',
|
||||||
name: 'email',
|
name: 'email',
|
||||||
component: () => import('../views/EmailRecord.vue'),
|
component: () => import('../views/EmailRecord.vue'),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/setting',
|
path: '/setting',
|
||||||
name: 'setting',
|
name: 'setting',
|
||||||
component: () => import('../views/SettingView.vue'),
|
component: () => import('../views/SettingView.vue'),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/calendar',
|
path: '/calendar',
|
||||||
name: 'calendar',
|
name: 'calendar',
|
||||||
component: () => import('../views/CalendarView.vue'),
|
component: () => import('../views/CalendarView.vue'),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 路由守卫
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const requiresAuth = to.meta.requiresAuth !== false // 默认需要认证
|
||||||
|
|
||||||
|
if (requiresAuth && !authStore.isAuthenticated) {
|
||||||
|
// 需要认证但未登录,跳转到登录页
|
||||||
|
next({ name: 'login', query: { redirect: to.fullPath } })
|
||||||
|
} else if (to.name === 'login' && authStore.isAuthenticated) {
|
||||||
|
// 已登录用户访问登录页,跳转到首页
|
||||||
|
next({ name: 'transactions' })
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
||||||
|
|||||||
49
Web/src/stores/auth.js
Normal file
49
Web/src/stores/auth.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import request from '@/api/request'
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
|
const token = ref(localStorage.getItem('token') || '')
|
||||||
|
const expiresAt = ref(localStorage.getItem('expiresAt') || '')
|
||||||
|
|
||||||
|
const isAuthenticated = computed(() => {
|
||||||
|
if (!token.value || !expiresAt.value) return false
|
||||||
|
// 检查token是否过期
|
||||||
|
return new Date(expiresAt.value) > new Date()
|
||||||
|
})
|
||||||
|
|
||||||
|
const login = async (password) => {
|
||||||
|
try {
|
||||||
|
const response = await request.post('/Auth/Login', { password })
|
||||||
|
const { token: newToken, expiresAt: newExpiresAt } = response.data
|
||||||
|
|
||||||
|
token.value = newToken
|
||||||
|
expiresAt.value = newExpiresAt
|
||||||
|
|
||||||
|
localStorage.setItem('token', newToken)
|
||||||
|
localStorage.setItem('expiresAt', newExpiresAt)
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
throw new Error('密码错误')
|
||||||
|
}
|
||||||
|
throw new Error('登录失败,请稍后重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
token.value = ''
|
||||||
|
expiresAt.value = ''
|
||||||
|
localStorage.removeItem('token')
|
||||||
|
localStorage.removeItem('expiresAt')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
expiresAt,
|
||||||
|
isAuthenticated,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
}
|
||||||
|
})
|
||||||
107
Web/src/views/LoginView.vue
Normal file
107
Web/src/views/LoginView.vue
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="login-box">
|
||||||
|
<h1 class="login-title">账单</h1>
|
||||||
|
<div class="login-form">
|
||||||
|
<van-field
|
||||||
|
v-model="password"
|
||||||
|
type="password"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
clearable
|
||||||
|
:border="true"
|
||||||
|
@keyup.enter="handleLogin"
|
||||||
|
>
|
||||||
|
<template #left-icon>
|
||||||
|
<van-icon name="lock" />
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
|
||||||
|
<van-button
|
||||||
|
type="primary"
|
||||||
|
block
|
||||||
|
round
|
||||||
|
:loading="loading"
|
||||||
|
@click="handleLogin"
|
||||||
|
class="login-button"
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { showToast } from 'vant'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
const password = ref('')
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const handleLogin = async () => {
|
||||||
|
if (!password.value) {
|
||||||
|
showToast('请输入密码')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
await authStore.login(password.value)
|
||||||
|
showToast({ type: 'success', message: '登录成功' })
|
||||||
|
router.push('/')
|
||||||
|
} catch (error) {
|
||||||
|
showToast({ type: 'fail', message: error.message || '登录失败' })
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login-container {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-box {
|
||||||
|
width: 90%;
|
||||||
|
max-width: 400px;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 40px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-button {
|
||||||
|
margin-top: 10px;
|
||||||
|
height: 44px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.van-field) {
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.van-field__control) {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -18,14 +18,25 @@
|
|||||||
<van-cell-group inset>
|
<van-cell-group inset>
|
||||||
<van-cell title="智能分类" is-link />
|
<van-cell title="智能分类" is-link />
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
|
|
||||||
|
<div class="detail-header">
|
||||||
|
<p>账户</p>
|
||||||
|
</div>
|
||||||
|
<van-cell-group inset>
|
||||||
|
<van-cell title="退出登录" is-link @click="handleLogout" />
|
||||||
|
</van-cell-group>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { showLoadingToast, showSuccessToast, showToast, closeToast } from 'vant'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { showLoadingToast, showSuccessToast, showToast, closeToast, showConfirmDialog } from 'vant'
|
||||||
import { uploadBillFile } from '@/api/billImport'
|
import { uploadBillFile } from '@/api/billImport'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const authStore = useAuthStore()
|
||||||
const fileInputRef = ref(null)
|
const fileInputRef = ref(null)
|
||||||
const currentType = ref('')
|
const currentType = ref('')
|
||||||
|
|
||||||
@@ -89,6 +100,25 @@ const handleFileChange = async (event) => {
|
|||||||
event.target.value = ''
|
event.target.value = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理退出登录
|
||||||
|
*/
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
await showConfirmDialog({
|
||||||
|
title: '提示',
|
||||||
|
message: '确定要退出登录吗?',
|
||||||
|
})
|
||||||
|
|
||||||
|
authStore.logout()
|
||||||
|
showSuccessToast('已退出登录')
|
||||||
|
router.push({ name: 'login' })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('取消退出登录:', error)
|
||||||
|
showToast('已取消退出登录')
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
86
WebApi/Controllers/AuthController.cs
Normal file
86
WebApi/Controllers/AuthController.cs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Service.AppSettingModel;
|
||||||
|
|
||||||
|
namespace WebApi.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]/[action]")]
|
||||||
|
public class AuthController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AuthSettings _authSettings;
|
||||||
|
private readonly JwtSettings _jwtSettings;
|
||||||
|
private readonly ILogger<AuthController> _logger;
|
||||||
|
|
||||||
|
public AuthController(
|
||||||
|
IOptions<AuthSettings> authSettings,
|
||||||
|
IOptions<JwtSettings> jwtSettings,
|
||||||
|
ILogger<AuthController> logger)
|
||||||
|
{
|
||||||
|
_authSettings = authSettings.Value;
|
||||||
|
_jwtSettings = jwtSettings.Value;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户登录
|
||||||
|
/// </summary>
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpPost]
|
||||||
|
public BaseResponse<LoginResponse> Login([FromBody] LoginRequest request)
|
||||||
|
{
|
||||||
|
// 验证密码
|
||||||
|
if (string.IsNullOrEmpty(request.Password) || request.Password != _authSettings.Password)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("登录失败: 密码错误");
|
||||||
|
return new BaseResponse<LoginResponse>
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "密码错误"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成JWT Token
|
||||||
|
var token = GenerateJwtToken();
|
||||||
|
var expiresAt = DateTime.UtcNow.AddHours(_jwtSettings.ExpirationHours);
|
||||||
|
|
||||||
|
_logger.LogInformation("用户登录成功");
|
||||||
|
|
||||||
|
return new BaseResponse<LoginResponse>
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Data = new LoginResponse
|
||||||
|
{
|
||||||
|
Token = token,
|
||||||
|
ExpiresAt = expiresAt
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateJwtToken()
|
||||||
|
{
|
||||||
|
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey));
|
||||||
|
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
|
||||||
|
|
||||||
|
var claims = new[]
|
||||||
|
{
|
||||||
|
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
||||||
|
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()),
|
||||||
|
new Claim("auth", "password-auth")
|
||||||
|
};
|
||||||
|
|
||||||
|
var token = new JwtSecurityToken(
|
||||||
|
issuer: _jwtSettings.Issuer,
|
||||||
|
audience: _jwtSettings.Audience,
|
||||||
|
claims: claims,
|
||||||
|
expires: DateTime.UtcNow.AddHours(_jwtSettings.ExpirationHours),
|
||||||
|
signingCredentials: credentials
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
WebApi/Controllers/Dto/LoginRequest.cs
Normal file
6
WebApi/Controllers/Dto/LoginRequest.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace WebApi.Controllers.Dto;
|
||||||
|
|
||||||
|
public class LoginRequest
|
||||||
|
{
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
7
WebApi/Controllers/Dto/LoginResponse.cs
Normal file
7
WebApi/Controllers/Dto/LoginResponse.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace WebApi.Controllers.Dto;
|
||||||
|
|
||||||
|
public class LoginResponse
|
||||||
|
{
|
||||||
|
public string Token { get; set; } = string.Empty;
|
||||||
|
public DateTime ExpiresAt { get; set; }
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
|
using System.Text;
|
||||||
using FreeSql;
|
using FreeSql;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Scalar.AspNetCore;
|
using Scalar.AspNetCore;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Service.AppSettingModel;
|
using Service.AppSettingModel;
|
||||||
@@ -35,6 +38,35 @@ builder.Services.AddCors(options =>
|
|||||||
// 绑定配置
|
// 绑定配置
|
||||||
builder.Services.Configure<EmailSettings>(builder.Configuration.GetSection("EmailSettings"));
|
builder.Services.Configure<EmailSettings>(builder.Configuration.GetSection("EmailSettings"));
|
||||||
builder.Services.Configure<AISettings>(builder.Configuration.GetSection("OpenAI"));
|
builder.Services.Configure<AISettings>(builder.Configuration.GetSection("OpenAI"));
|
||||||
|
builder.Services.Configure<JwtSettings>(builder.Configuration.GetSection("JwtSettings"));
|
||||||
|
builder.Services.Configure<AuthSettings>(builder.Configuration.GetSection("AuthSettings"));
|
||||||
|
|
||||||
|
// 配置JWT认证
|
||||||
|
var jwtSettings = builder.Configuration.GetSection("JwtSettings");
|
||||||
|
var secretKey = jwtSettings["SecretKey"]!;
|
||||||
|
var key = Encoding.UTF8.GetBytes(secretKey);
|
||||||
|
|
||||||
|
builder.Services.AddAuthentication(options =>
|
||||||
|
{
|
||||||
|
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
})
|
||||||
|
.AddJwtBearer(options =>
|
||||||
|
{
|
||||||
|
options.TokenValidationParameters = new TokenValidationParameters
|
||||||
|
{
|
||||||
|
ValidateIssuer = true,
|
||||||
|
ValidateAudience = true,
|
||||||
|
ValidateLifetime = true,
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
ValidIssuer = jwtSettings["Issuer"],
|
||||||
|
ValidAudience = jwtSettings["Audience"],
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(key),
|
||||||
|
ClockSkew = TimeSpan.Zero
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddAuthorization();
|
||||||
|
|
||||||
// 配置 FreeSql + SQLite
|
// 配置 FreeSql + SQLite
|
||||||
var dbPath = Path.Combine(AppContext.BaseDirectory, "database");
|
var dbPath = Path.Combine(AppContext.BaseDirectory, "database");
|
||||||
@@ -81,6 +113,10 @@ app.UseStaticFiles();
|
|||||||
// 启用 CORS
|
// 启用 CORS
|
||||||
app.UseCors();
|
app.UseCors();
|
||||||
|
|
||||||
|
// 启用认证和授权
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
// 添加 SPA 回退路由(用于前端路由)
|
// 添加 SPA 回退路由(用于前端路由)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
|
||||||
<PackageReference Include="Scalar.AspNetCore" />
|
<PackageReference Include="Scalar.AspNetCore" />
|
||||||
<PackageReference Include="FreeSql.Provider.Sqlite" />
|
<PackageReference Include="FreeSql.Provider.Sqlite" />
|
||||||
|
|||||||
@@ -48,5 +48,14 @@
|
|||||||
"95555@message.cmbchina.com",
|
"95555@message.cmbchina.com",
|
||||||
"ccsvc@message.cmbchina.com"
|
"ccsvc@message.cmbchina.com"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"JwtSettings": {
|
||||||
|
"SecretKey": "6CA57F7D-B73F-AABC-007C-D2DF98E319DF-07802A80-1982-64CD-1CFE-466728053850",
|
||||||
|
"Issuer": "EmailBillApi",
|
||||||
|
"Audience": "EmailBillWeb",
|
||||||
|
"ExpirationHours": 7200
|
||||||
|
},
|
||||||
|
"AuthSettings": {
|
||||||
|
"Password": "SCsunch940622"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
networks:
|
networks:
|
||||||
- all_in
|
- all_in
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 14904:8080
|
||||||
environment:
|
environment:
|
||||||
- ASPNETCORE_ENVIRONMENT=Production
|
- ASPNETCORE_ENVIRONMENT=Production
|
||||||
- ASPNETCORE_URLS=http://+:8080
|
- ASPNETCORE_URLS=http://+:8080
|
||||||
@@ -16,20 +16,6 @@
|
|||||||
- /wd/apps/vols/emailbill/database:/app/database
|
- /wd/apps/vols/emailbill/database:/app/database
|
||||||
- /wd/apps/vols/emailbill/logs:/app/logs
|
- /wd/apps/vols/emailbill/logs:/app/logs
|
||||||
|
|
||||||
nas_robot_proxy:
|
|
||||||
image: beevelop/nginx-basic-auth:v2023.10.1
|
|
||||||
container_name: emailbill_proxy
|
|
||||||
restart: always
|
|
||||||
networks:
|
|
||||||
- all_in
|
|
||||||
ports:
|
|
||||||
- 14904:80 # 开放端口
|
|
||||||
environment:
|
|
||||||
- TZ=Asia/Shanghai
|
|
||||||
- HTPASSWD=suncheng:$$apr1$$2QX32QHP$$HIGAbCuTt8jxdc4uDzNLI1
|
|
||||||
- FORWARD_PORT=8080
|
|
||||||
- FORWARD_HOST=emailbill
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
all_in:
|
all_in:
|
||||||
external: true
|
external: true
|
||||||
Reference in New Issue
Block a user