Vue axios 前端 ASP.Net Core WebAPI 后台JWT Token 认证

Vue axios 前端 ASP.Net Core WebAPI 后台JWT Token 认证

  • 1、ASP.Net Core WebAPI JWT Token 认证
  • 2、Vue axios 访问
  • 3、总结

1、ASP.Net Core WebAPI JWT Token 认证

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";   //密钥
    }

2、Vue axios 访问

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>

3、总结

Vue axios 前端,core webapi做后台 ,JwtBearer认证 ,JwtBearer 中间件配置、axios 封装访问都没有问题,必须添加跨域控制中间件,来解决前端两次请求导致的错误。

你可能感兴趣的:(c#,vue,c#)