二.3.token认证,jwt认证,前端框架

一.token:

铺垫:  

之前用的是通过最基本的用户名密码登录我的运维平台http://127.0.0.1:8000/---这种用的是form表单,但是这种对于前后端分离的不适合。前后端分离,应该通过http的url地址去登录,登录完之后获取一个token,我下次请求只需要带着这个token去获取数据即可。

查看django-drf认证官方配置:

如下图中可以看到它中有几个认证方式,它默认的认证是BasicAuthentication和SessionAuthentication认证。

1.默认的认证就走的session认证,只要前后端没有分离的情况下,所有认证全部走的session,也就是你登录完后,后端会向你的浏览器cookie中写入一数据,此数据代表了你的身份。但这个数据它实际上跟session有一定的关联,如下进它的数据库查看session表:

当用户登录之后发现有如下一条记录,而session_key是放在浏览器上的,session_data就是它的value,这种方式就是session认证。

 

 如下图:session_key就是在浏览器的application下的cookie中---sessionid:

 这是最原始的方式,通过session登录。!

2.django-drf默认用session和basic,这两个默认我们要写上,如果不想用默认的session,那把session关掉即可那就用用户名登录不了了,那怎样配置,怎样知道它默认用session登录?如下图,它的官方配置默认就支持session和basic,所以如果我想用别的方式就把这个代码配置拷贝到我项目settings.py中的drf中,并覆盖掉它默认值即可--建议线上把session关掉,basic留着:

token:

  经查看它的源码authentication.py中可知它有提供TokenAuthentication方法(上图1中有),所以我把此方法复制并配置到我项目settings.py中即可如下:

1.配置token:

REST_FRAMEWORK = {
 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    'PAGE_SIZE':10,
    'DEFAULT_PAGINATION_CLASS':'users.pagination.Pagination',
    'DEFAULT_FILTER_BACKENDS': (
        'django_filters.rest_framework.DjangoFilterBackend',
    ),
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
        # 'devops.permissions.Permissions',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',     <<<这里我配置同时支持session和token
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ],
}

2.配置installed_apps:

  就像上述所说的,如果用session的话,数据库中有session表,那使用token它也有一张表,我要想同步表结构,必须要把token放在installed_app中,

.....
'rest_framework.authtoken',
'rest_framework',
.....

3.同步数据库:

(python36env) [vagrant@CentOS devops]$ python manage.py makemigrations

(python36env) [vagrant@CentOS devops]$ python manage.py migrate

(python36env) [vagrant@CentOS devops]$ python manage.py dbshell

MariaDB [devops]> show tables;如下已经有token表了

 authtoken_token 

4.配置url地址:在项目urls.py中加入如下代码:

from rest_framework.authtoken import views
urlpatterns += [
    url(r'^api-token-auth/', views.obtain_auth_token)
]

(python36env) [vagrant@CentOS devops]$ python manage.py runserver 0.0.00:8000启动服务

5.测试:用curl命令用户登录,要先登录所以走的是post,-d参数走json数据,-H告诉它用json去解析

(python36env) [vagrant@CentOS devops]$ curl -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"123456"}' http://127.0.0.1:8000/api-token-auth/

结果如下拿到token了
{"token":"780ba1a240a974b958731bb446c5a7574976df4e"}

 去数据库看如下,也有了,告诉我们创建时间created,但是发现没有过期时间,也就是说申请了这个token,我再次去请求,拿到的还是这个token,永远不过期。那怎样让它过期,手动删除此token即可。

MariaDB [devops]> select * from authtoken_tokenG;
key: 780ba1a240a974b958731bb446c5a7574976df4e
created: 2020-06-26 15:00:56.118433
user_id: 2

6.获取数据:

(1)给我的运维平台所有api加上访问权限--批量配置(只要登录就可访问):

settings.py:

REST_FRAMEWORK = {
 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    'PAGE_SIZE':10,
    'DEFAULT_PAGINATION_CLASS':'users.pagination.Pagination',
    'DEFAULT_FILTER_BACKENDS': (
        'django_filters.rest_framework.DjangoFilterBackend',
    ),
    'DEFAULT_PERMISSION_CLASSES': [
        # 'rest_framework.permissions.AllowAny',
        'devops.permissions.Permissions',    <<<<打开模型权限,此时所有的api就需要登录权限
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ],
}

(2)怎样使用我上述拿到的token去访问数据:

(python36env) [vagrant@CentOS devops]$ curl -X GET http://127.0.0.1:8000/users/
{"detail":"Authentication credentials were not provided."}    <<<<它告诉我需要权限,如下我加上我的token后就拿到数据了
(python36env) [vagrant@CentOS devops]$ curl -X GET http://127.0.0.1:8000/users/ -H 'Authorization: Token 780ba1a240a974b958731bb446c5a7574976df4e' {"count":98,"next":"http://127.0.0.1:8000/users/?page=2","previous":null,"results":[{"id":1,"username":"devops","email":"123@qq.com"}...
格式化一下如下:就显示标准json结果了
(python36env) [vagrant@CentOS devops]$ curl -X GET http://127.0.0.1:8000/users/ -H 'Authorization: Token 780ba1a240a974b958731bb446c5a7574976df4e'|python -m json.tool
{ "count": 98, "next": "http://127.0.0.1:8000/users/?page=2", "previous": null, "results": [ { "id": 1, "username": "devops", "email": "123@qq.com" }, { "id": 2, "username": "admin", "email": "admin@51reboot.com" },
.........

  拿到的token永远不过期,要使过期就手动删除它。因为表中token直接跟user_id关联上,跟某user_id关联上就代表此用户已经登录了,如果这个用户是超级管理员,你只要拿到这个token,那所有的权限信息你也会拥有,那这样就很危险了,因为它永远不过期,不变。除非你每天晚上刷一次,把token表清空。

二.jwt

前后端分离之JWT用户认证https://www.jianshu.com/p/180a870a308a

参考https://github.com/jpadilla/django-rest-framework-jwt

(python36env) [vagrant@CentOS devops]$ pip install djangorestframework-jwt  安装

1.配置权限验证--我这里是基于模型的

REST_FRAMEWORK = {
.....
    'DEFAULT_PERMISSION_CLASSES': [
        # 'rest_framework.permissions.AllowAny',
        'devops.permissions.Permissions',
    ],
......

2.配置认证后端类

REST_FRAMEWORK = {
.....
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ],
...

3.配置url---devops/urls.py:

from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    url(r'^', include(route.urls)),
    url(r'^aip-auth', include("rest_framework.urls",namespace="reset_framework")),
    url(r'^docs/', include_docs_urls("lizhihua运维平台接口文档")),
    url(r'^api-token-auth/', obtain_jwt_token)
]

(python36env) [vagrant@CentOS devops]$ python manage.py runserver 0.0.00:8000启动服务

4.测试:

(python36env) [vagrant@CentOS devops]$ curl -X POST -d "username=admin&password=123456" http://localhost:8000/api-token-auth/   拿到token
{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTkzMjI0NDI2LCJlbWFpbCI6ImFkbWluQDUxcmVib290LmNvbSJ9.SZhNkN6WgkKyMEZKora7rZRzHgbMQyTRrFP7OFxg0Ts"}

5.获取数据

(python36env) [vagrant@CentOS devops]$ curl -H "Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTkzMjI0MzU1LCJlbWFpbCI6ImFkbWluQDUxcmVib290LmNvbSJ9.3cn__Tc3ddUhl4_PhST83vRZBcNASs0UBKdLvhgupt4" http://localhost:8000/users/|python -m json.tool

{
"count": 98,
"next": "http://localhost:8000/users/?page=2",
"previous": null,
"results": [
{
"id": 1,
"username": "devops",
"email": "123@qq.com"
},
{
"id": 2,
"username": "admin",
"email": "admin@51reboot.com"
},
.....

   jwt默认它的过期时间是300s,如果想改的话在项目settings.py中如下位置drf下改即可:

import datetime
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=3000),   <<<<秒
}

三.前端框架

 参考https://juejin.im/post/59097cd7a22b9d0065fb61d2

 1.需要在windows装node环境----https://nodejs.org/en/下载想要的版本即可

C:UsersAdministrator>node -v
v12.18.1

2.git clone 想要的vue模版并npm run dev

3.修改项目主题---vueAdmin-template/index.html中修改title

4.配置用户登录

(1)登录页面配置src/views/login/index.vue

<template>
  <div class="login-container">
    <el-form autoComplete="on" :model="loginForm" :rules="loginRules" ref="loginForm" label-position="left" label-width="0px"
             class="card-box login-form">
      <h3 class="title">lizhihua运维平台</h3>
      <el-form-item prop="username">
        <span class="svg-container svg-container_login">
          <svg-icon icon-class="user" />
        </span>
        <el-input type="text" v-model="loginForm.username" autoComplete="off" placeholder="请输入用户名" />
      </el-form-item>
      <el-form-item prop="password">
        <span class="svg-container">
          <svg-icon icon-class="password"></svg-icon>
        </span>
        <el-input :type="pwdType" @keyup.enter.native="handleLogin" v-model="loginForm.password" autoComplete="off"
                  placeholder="请输入密码"></el-input>
        <span class="show-pwd" @click="showPwd"><svg-icon icon-class="eye" /></span>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" style="100%;" :loading="loading" @click.native.prevent="handleLogin">
          登录
        </el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
  // import { isvalidUsername } from '@/utils/validate'
  export default {
    name: 'login',
    data() {
      return {
        loginForm: {
          username: '',
          password: ''
        },
        loginRules: {
          username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
          password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
        },
        loading: false,
        pwdType: 'password'
      }
    },
    methods: {
      showPwd() {
        if (this.pwdType === 'password') {
          this.pwdType = ''
        } else {
          this.pwdType = 'password'
        }
      },
      handleLogin() {
        this.$refs.loginForm.validate(valid => {
          if (valid) {
            this.loading = true
            this.$store.dispatch('Login', this.loginForm).then(() => {
              this.loading = false
              this.$router.push({ path: '/' })
            }).catch(() => {
              this.loading = false
            })
          } else {
            console.log('error submit!!')
            return false
          }
        })
      }
    }
  }
</script>

<style rel="stylesheet/scss" lang="scss">
  $bg:#2d3a4b;
  $dark_gray:#889aa4;
  $light_gray:#eee;

  .login-container {
    position: fixed;
    height: 100%;
    100%;
    background-color: $bg;
    input:-webkit-autofill {
      -webkit-box-shadow: 0 0 0px 1000px #293444 inset !important;
      -webkit-text-fill-color: #fff !important;
    }
    input {
      background: transparent;
      border: 0px;
      -webkit-appearance: none;
      border-radius: 0px;
      padding: 12px 5px 12px 15px;
      color: $light_gray;
      height: 47px;
    }
    .el-input {
      display: inline-block;
      height: 47px;
       85%;
    }
    .tips {
      font-size: 14px;
      color: #fff;
      margin-bottom: 10px;
    }
    .svg-container {
      padding: 6px 5px 6px 15px;
      color: $dark_gray;
      vertical-align: middle;
       30px;
      display: inline-block;
      &_login {
        font-size: 20px;
      }
    }
    .title {
      font-size: 26px;
      font-weight: 400;
      color: $light_gray;
      margin: 0px auto 40px auto;
      text-align: center;
      font-weight: bold;
    }
    .login-form {
      position: absolute;
      left: 0;
      right: 0;
       400px;
      padding: 35px 35px 15px 35px;
      margin: 120px auto;
    }
    .el-form-item {
      border: 1px solid rgba(255, 255, 255, 0.1);
      background: rgba(0, 0, 0, 0.1);
      border-radius: 5px;
      color: #454545;
    }
    .show-pwd {
      position: absolute;
      right: 10px;
      top: 7px;
      font-size: 16px;
      color: $dark_gray;
      cursor: pointer;
      user-select:none;
    }
    .thirdparty-button{
      position: absolute;
      right: 35px;
      bottom: 28px;
    }
  }
</style>

(3)config/dev.env.js

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  BASE_API: '"http://127.0.0.1:8000"',
})

(4)src/api/login.js

export function login(username, password) {
  return request({
    url: '/api-token-auth/',
    method: 'post',
    data: {
      username,
      password
    }
  })
}

F:devopsdatawebvueAdmin-template>npm run dev    重启服务

(5)src/utils/request.js:修改配置request拦截器

 配置成每次请求把token带上,这里用jwt

import axios from 'axios'
import { Message } from 'element-ui'
import store from '../store'
import { getToken } from '@/utils/auth'

// 创建axios实例
const service = axios.create({
  baseURL: process.env.BASE_API, // api的base_url
  timeout: 5000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(config => {
  if (store.getters.token) {
    config.headers['Authorization'] = 'JWT ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  }
  return config
}, error => {
  // Do something with request error
  console.log(error) // for debug
  Promise.reject(error)
})

// respone拦截器
service.interceptors.response.use(
  response => {
    /**
     * code为非20000是抛错 可结合自己业务进行修改
     */
    console.log(response)
    return response.data
  },
  error => {
    console.log('err' + error)
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service

(6)src/store/modules/user.js:  配置把token写到浏览器cookie中

.....  
actions: {
    // 登录
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim()
      return new Promise((resolve, reject) => {
        login(username, userInfo.password).then(response => {
          setToken(response.token)
          commit('SET_TOKEN', response.token)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },
........

(7)src/utils/auth.js:

....
const TokenKey = 'Token'
....

(8)src/permission.js: 配置登录成功则跳转到首页

import router from './router'
import store from './store'
import NProgress from 'nprogress' // Progress 进度条
import 'nprogress/nprogress.css'// Progress 进度条样式
import { Message } from 'element-ui'
import { getToken } from '@/utils/auth' // 验权

const whiteList = ['/login'] // 不重定向白名单
router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      if (store.getters.name === '') {
        store.dispatch('GetInfo').then(res => { // 拉取用户信息
          next({ ...to, replace: true })
        }).catch((err) => {
          store.dispatch('FedLogOut').then(() => {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/' })
          })
        })
      } else {
        next()
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      next('/login')
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  NProgress.done() // 结束Progress
})

(9)src/views/dashboad/index.vue :

<template>
  <div class="dashboard-container">
    dashboard
  </div>
</template>

<script>
export default {
  name: 'dashboard'
}
</script>

这样就能登录后跳转到首页了!

若登录后跳转并如下报错:原因是没获取到userinfo对象

GET http://127.0.0.1:8000/UserInfo/ 404 (Not Found)
[Vue warn]: data functions should return an object:

 解决:导入userinfo即可

from django.conf.urls import url, include
from django.contrib import admin
from rest_framework.routers import  DefaultRouter
from rest_framework.documentation import include_docs_urls
from rest_framework_jwt.views import obtain_jwt_token
from idcs.views import IdcViewset
from users.views import UserViewset, DashboardStatusViewset, UserInfoViewset
from cabinet.views import CabinetViewset
from manufacturer.views import ManufacturerViewset, ProductModelViewset
from servers.views import ServerAutoReportViewset, NetworkDeviceViewset, IPViewset, ServerViewset

route = DefaultRouter()
route.register("idcs", IdcViewset, basename="idcs")
route.register("users", UserViewset, basename="users")
route.register("UserInfo", UserInfoViewset, basename="UserInfo")
route.register("cabinet", CabinetViewset, basename="cabinet")
route.register("Manufacturer", ManufacturerViewset, basename="Manufacturer")
route.register("ProductModel", ProductModelViewset, basename="ProductModel")
route.register("ServerAutoReport", ServerAutoReportViewset, basename="ServerAutoReport")
route.register("Servers", ServerViewset, basename="Servers")
route.register("NetworkDevice", NetworkDeviceViewset, basename="NetworkDevice")
route.register("IP", IPViewset, basename="IP")
route.register("dashboardStatus", DashboardStatusViewset, basename="dashboardStatus")

urlpatterns = [
    url(r'^', include(route.urls)),
    url(r'^api-auth', include("rest_framework.urls", namespace="rest_framework")),
    url(r'^docs/', include_docs_urls("51reboot运维平台接口文档")),
    url(r'^api-token-auth/', obtain_jwt_token),
]

11

22

原文地址:https://www.cnblogs.com/dbslinux/p/13196264.html