ASP.Net Core Web API 应用程序。
控制器:
[Route("[controller]/[action]")]
[ApiController]
public class AuthController : Controller
{
public AuthController(IAesSecurity aesSecurity, ITokenHelper tokenHelper, ISqLiteHelper sqLiteHelper)
{
AesSecurity = aesSecurity;
TokenHelper = tokenHelper;
SqLiteHelper = sqLiteHelper;
}
private ISqLiteHelper SqLiteHelper { get; } // linqTodb sqlte 数据库访问
private IAesSecurity AesSecurity { get; }//Aes加解密工具类
private ITokenHelper TokenHelper { get; }//Token生成工具类
//供Vue axios 获取Jwt Token
public IActionResult GetToken(string username, string password, string sms)
{
//获取Token:密码登录或短信登录
if (string.IsNullOrEmpty(username))
return null;
if (string.IsNullOrEmpty(password))
return Ok(CheckSms(username, sms));
return Ok(CheckPassword(username, password));
}
private TokenResult CheckPassword(string username, string password)
{
//用户名手机号,在数据库中加密存储
var scMobile = AesSecurity.Encrypt(username);
var scPass = AesSecurity.Md5($"{username}{password}");
return TokenHelper.TokenByPass(scMobile, scPass);
}
private TokenResult CheckSms(string username, string sms)
{
var scMobile = AesSecurity.Encrypt(username);
return TokenHelper.TokenBySms(scMobile, sms);
}
[TokenFilter]
[Authorize]
public IActionResult GetValue1(string id)
{
//JWT Token授权测试
return Ok(new ResultModel
{
Code = 1,
Data = "return Test GetValue1!",
Message = "Test GetValue 1-" + id
});
}
[TokenFilter]
public IActionResult GetValue2(string id)
{//普通测试
return Ok(new ResultModel
{
Code = 1,
Data = "return Test GetValue 2 !",
Message = "AllowAnonymous Test GetValue 2-" + id
});
}
}
ResultModel
public class ResultModel
{
///
/// 返回码
///
public int Code { get; set; }
///
/// 消息
///
public string Message { get; set; }
///
/// 关联地址
///
public string Url { get; set; }
}
public class ResultModel : ResultModel
{
///
/// 数据
///
public virtual T Data { get; set; }
}
public class ResultArray : ResultModel>
{
}
JwtConfig
public class JwtConfig
{
///
/// 签发人
///
public string Issuer { get; set; }
///
/// 主题
///
public string Subject { get; set; }
///
/// 受众(接受者)
///
public string Audience { get; set; }
///
/// 多用户登录
///
public bool MultipleLogin { get; set; }
///
/// 过期时间:分钟
///
public int ExpiresMinutes { get; set; }
///
/// 密匙:HMACSHA256
///
public string SecretKey { get; set; }
///
/// 缓冲过期时间 默认5分钟 有效时间 = 过期时间 + 缓冲过期时间
///
public int ClockSkew { get; set; }
///
/// 无权限指定路由
///
public string DeniedAction { get; set; }
///
/// 错误指定路由
///
public string ErrorAction { get; set; }
///
/// 忽略验证的路由
///
public List IgnoreUrls { get; set; }
}
ITokenHelper 、TokenHelper
public interface ITokenHelper
{
TokenResult TokenByPass(string username, string password);
TokenResult TokenBySms(string username, string sms);
}
public class TokenHelper : ITokenHelper
{
public const string Domain = "http://localhost:5000";
private readonly IOptions options;
private readonly ISqLiteHelper sqLiteHelper;
public TokenHelper(IOptions options, ISqLiteHelper sqLiteHelper)
{
this.options = options;
this.sqLiteHelper = sqLiteHelper;
}
public TokenResult TokenBySms(string username, string sms)
{
var claims = new List();
var audience = options.Value.Audience;
var loginTime = DateTime.Now;
using (var dc = new YysxProductDB(sqLiteHelper.Option))
{
var user = dc.Users.FirstOrDefault(f => f.ScMobile == username && f.CheckSms == sms);
if (user != null)
{
claims.Add(new Claim(ClaimTypes.Name, username));
//claims.Add(new Claim("ErrorAction", options.Value.ErrorAction));
//claims.Add(new Claim("DeniedAction", options.Value.DeniedAction));
user.LoginTime = loginTime;
user.ExpirationTime = loginTime.AddHours(options.Value.ExpiresMinutes);
dc.Update(user);
audience = options.Value.Audience; // $"{username}{loginTime.Ticks}";
}
}
return CreateTokenString(claims, audience, loginTime);
}
public TokenResult TokenByPass(string username, string password)
{
var claims = new List();
var audience = options.Value.Audience;
var loginTime = DateTime.Now;
using (var dc = new YysxProductDB(sqLiteHelper.Option))
{
var user = dc.Users.FirstOrDefault(f => f.ScMobile == username && f.Password == password);
if (user != null)
{
claims.Add(new Claim(ClaimTypes.Name, username));
//claims.Add(new Claim("ErrorAction", options.Value.ErrorAction));
//claims.Add(new Claim("DeniedAction", options.Value.DeniedAction));
user.LoginTime = loginTime;
user.ExpirationTime = loginTime.AddHours(options.Value.ExpiresMinutes);
dc.Update(user);
audience = options.Value.Audience; // $"{username}{loginTime.Ticks}";
}
}
return CreateTokenString(claims, audience, loginTime);
}
///
/// 生成token
///
/// List的 Claim对象
///
///
///
private TokenResult CreateTokenString(List claims, string audience, DateTime loginTime)
{
var secret = Encoding.UTF8.GetBytes(TestToken.Secret);
var key = new SymmetricSecurityKey(secret);
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
TestToken.Issurer,
TestToken.Audience,
claims,
expires: loginTime.AddMinutes(30),
signingCredentials: creds);
var expires = loginTime.AddMinutes(options.Value.ExpiresMinutes);
var s = new JwtSecurityTokenHandler().WriteToken(token);
return new TokenResult {Token = s, Expires = expires};
}
}
Startup ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton();
services.AddSingleton();
services.AddTransient();
//添加配置
services.Configure(Configuration.GetSection("TokenConfig"));
//设置跨域中间件:读取 appsetting.json的 AllowedHosts
//否则axios 跨域访问时会出现两次请求,第一次尝试跨域options Method,通过后再请求Get Method。options 不能通过认证,导致get无法继续访问。
services.AddCors(options =>
{
// CorsPolicy 是自訂的 Policy 名稱
options.AddPolicy("CorsPolicy", policy =>
{
policy.SetPreflightMaxAge(TimeSpan.FromSeconds(1800L));//update by jason
var origins = Configuration.GetSection("AllowedHosts").Get();
if (origins == null || origins.Contains("*"))
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
}
else
{
policy.WithOrigins(origins)
.AllowAnyHeader()
.AllowAnyMethod();
}
});
});
//配置认证服务
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
//是否验证发行人
ValidateIssuer = true,
ValidIssuer = TestToken.Issurer,//发行人
//是否验证受众人
ValidateAudience = true,
ValidAudience = TestToken.Audience,//受众人
//是否验证密钥
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(TestToken.Secret)),
ValidateLifetime = true, //验证生命周期
RequireExpirationTime = true, //过期时间
};
});
services.AddControllers();
}
Startup Configure
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
app.UseHttpsRedirection();
app.UseRouting();
//0、启用跨域中间件
app.UseCors("CorsPolicy");
//1.先开启认证
app.UseAuthentication();
//2.再开启授权
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
appsetting.json 配置
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"JwtConfig": {
"Issuer": "eyysx.net", //签发人
"Subject": null, //主题
"Audience": "Audience", //受众(接受者)
"MultipleLogin": false,
"ExpiresMinutes": 20, //过期时间:分钟
"IssuedAt": null, //签发时间
"SecretKey": "MIGfMA0GCSqGSIb", // 必须是 16 位密码
"ClockSkew": 1, //缓冲过期时间 默认5分钟 有效时间 = 过期时间 + 缓冲过期时间
"DeniedAction": "/api/NoPermission", //无权限路由
"ErrorAction": "/api/ErrorPage", //错误的路由
"IgnoreUrls": [ "/Auth/GetToken" ] //忽略验证的url
},
"AesKey": {
"Key": "eettWpsixCi1aaacbaEoHtm/nISpONLqAVJYFRbSR8=",
"IV": "DKWZFQ7sMI1sdasdsVDYmLsw=="
},
"SQLiteDbFilePath": "E:\\ChengYunSu\\bin\\Debug\\netcoreapp3.1\\YysxProduct.db"
}
TestToken 提供固定值
public class TestToken
{
public static string Issurer = "JWTBearer.Auth"; //发行人
public static string Audience = "api.auth"; //受众人
public static string Secret = "q2xiARx$4x3TKqBJ"; //密钥
}
Axios 封装,用$http访问
AxiosPlugin.js
// 引入一次就行
import axios from 'axios'
axios.defaults.baseURL = 'http://localhost:55555'
axios.defaults.timeout = 2000
axios.interceptors.request.use(
(config) => {
var tiken = localStorage.getItem('JWT_TOKEN')
if (tiken) {
config.headers.Authorization = `bearer ${
tiken}`
}
return config
},
(error) => {
return Promise.reject(error)
},
)
axios.interceptors.response.use(
(res) => {
// 对响应数据做些事
if (res.status !== 200) {
// alert(res.statusText)
return Promise.reject(res)
}
return res
},
(error) => {
if (error.response.status === 401) {
// 401 说明 token 验证失败
// 可以直接跳转到登录页面,重新登录获取 token
location.href = '/login'
}
// 返回 response 里的错误信息
return Promise.reject(error.response.data)
},
)
// 将 Axios 实例添加到Vue的原型对象上
export default {
install(Vue) {
Object.defineProperty(Vue.prototype, '$http', {
value: axios })
},
}
main.js
main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
//css 不能直接引用,变相引用,有好办法请指教。
import './assets/bootstrap-css/bootstrap.min.css'
import './assets/bootstrap-vue-css/bootstrap-vue.min.css'
//引用bootstrap-vue
import {
BootstrapVue, IconsPlugin } from 'bootstrap-vue'
//导入axios 封装文件
import AxiosPlugin from './libs/AxiosPlugin'
// Install AxiosPlugin
Vue.use(AxiosPlugin)
// Install BootstrapVue
Vue.use(BootstrapVue)
// Optionally install the BootstrapVue icon components plugin
Vue.use(IconsPlugin)
Vue.config.productionTip = false
// eslint-disable-next-line no-new
new Vue({
el: '#app',
router,
template: ' ',
components: {
App },
})
main.js
获取jwt tioken 并保存,测试授权访问和无授权访问。
App.Vue
<template>
<div id="app">
<b-button variant="danger" @click="getVal1()">getVal1</b-button>
<b-button variant="success" @click="getVal2()">getVal2</b-button>
<b-button variant="outline-primary" @click="clear()">Clear V1,V2</b-button>
<br />
tokon:{
{
token }}
<br />
Val1: {
{
val1 }}
<br />
Val2:{
{
val2 }}
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
token: 'App.Vue',
val1: '',
val2: '',
}
},
mounted() {
this.$http
.get('/auth/GetToken', {
params: {
username: '12312345678',
password: '111111',
sms: '123456',
},
})
.then((res) => {
this.token = res.data.token
localStorage.setItem('JWT_TOKEN', this.token)
})
.catch(function () {
// 请求失败处理
this.token = 'error'
})
},
methods: {
getVal1: function () {
this.$http
.get('/auth/GetValue1', {
data: {
id: '*************',
},
})
.then((res) => (this.val1 = res.data))
.catch(function () {
// 请求失败处理
// this.val1 = 'GetValue1 Error Bearer'
})
},
getVal2: function () {
this.$http
.get('/auth/GetValue2', {
params: {
id: '$$$$$$$$$$',
},
})
.then((res) => {
this.val2 = res.data
this.token = localStorage.getItem('JWT_TOKEN')
})
.catch(function () {
// 请求失败处理
this.val2 = 'GetValue2 Error'
})
},
clear: function () {
this.val2 = ''
this.val1 = ''
this.token = ''
},
},
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Vue axios 前端,core webapi做后台 ,JwtBearer认证 ,JwtBearer 中间件配置、axios 封装访问都没有问题,必须添加跨域控制中间件,来解决前端两次请求导致的错误。