九零不老心 发布的文章

解决办法

logger.info('点击下翻月份')
xiayiye = self.browser.find_element_by_xpath(
    '//div[@class="mp-direct mp-direct-next" and @mp-role="nextMonthBtn" and @style="display: block;"]/a')
# 点击
# xiayiye.click()  # Message: element not interactable
self.browser.execute_script(
    "arguments[0].click();", xiayiye)

python监控网站证书有效期

import OpenSSL
import ssl
from datetime import datetime

'''
获取网站https证书有效期
'''

def main(domain):
    try:
        hostname = domain
        port = 443
        conn = ssl.create_connection((hostname, port))
        context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
        sock = context.wrap_socket(conn, server_hostname=hostname)
        cert = ssl.DER_cert_to_PEM_cert(sock.getpeercert(True))
        x509 = OpenSSL.crypto.load_certificate(
            OpenSSL.crypto.FILETYPE_PEM, cert)
        start_date = str(x509.get_notBefore(), encoding='utf-8')
        expire_date = str(x509.get_notAfter(), encoding='utf-8')
        # time 字符串转时间数组
        start_date = datetime.strptime(start_date, "%Y%m%d%H%M%SZ")
        # datetime 字符串转时间数组
        expire_date = datetime.strptime(expire_date, "%Y%m%d%H%M%SZ")
        # 剩余天数
        remain_days = (expire_date-datetime.now()).days
        print('{} ssl start_date,expire_date,remain_days is {},{},{}'.format(
            domain, start_date, expire_date, remain_days))
        return start_date, expire_date, remain_days
    except Exception as e:
        print('{} {}'.format(e.__traceback__.tb_lineno, e))
        return datetime.now(), datetime.now(), -999999999

if __name__ == "__main__":
    remain_days = main('www.guojingyi.cn')
    print(remain_days)

shell将多行内容的换行符替换为指定字符,然后进行拼接

grep ^pt_key ../env/all_cookies.txt | paste -s -d '&'

也可以使用tr进行替换,但是拼接末尾会多出指定字符,需要额外处理

  1. 卸载双系统中的linux
    windows中cmd以管理员身份运行——执行命令“bcdboot C:\Windows” (假设c 盘位系统盘,请根据实际情况操作)——重启只有windows了

  2. 首选确认该efi分区跟windows不是同一个efi分区(防止毁了windows正常启动)

  3. win+r执行命令diskpart

    Microsoft DiskPart 版本 10.0.19041.964
    
    Copyright (C) Microsoft Corporation.
    在计算机上: DESKTOP-VN503BD
    
    DISKPART> list disk
    
     磁盘 ###  状态           大小     可用     Dyn  Gpt
     --------  -------------  -------  -------  ---  ---
     磁盘 0    联机              223 GB  1024 KB        *
     磁盘 1    联机              111 GB  1024 KB        *
     磁盘 2    联机              465 GB   465 GB        *
     磁盘 3    联机              931 GB  2048 KB        *
    
    DISKPART> select disk 2
    
    磁盘 2 现在是所选磁盘。
    
    DISKPART> list partition
    
     分区 ###       类型              大小     偏移量
     -------------  ----------------  -------  -------
     分区      1    系统                 300 MB  1024 KB
    
    DISKPART> select partition 1
    
    分区 1 现在是所选分区。
    
    DISKPART> delete partition override
    
    DiskPart 成功地删除了所选分区。
    
    DISKPART>
  4. 打开windows的磁盘管理,发现该linux所在硬盘,全部分区、空间可操作使用了。

simplejwt token前后端验证详解

  1. 前后端分离场景:

    django

    djangorestframework

    djangorestframework-simplejwt

    element-ui

  2. 范例说明:

    后端部分代码:

    • serializers.py

      from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
      
      # Custom Token ObtainPairSerializer
      class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
          '''
          自定义jwt认证返回值
          '''
          def validate(self, attrs):
              data = super().validate(attrs)
              refresh = self.get_token(self.user)
              data['refresh'] = str(refresh)
              data['access'] = str(refresh.access_token)
              data['user_id'] = self.user.id
              data['user_name'] = self.user.username
              return data
      
    • views.py

      from backend.serializers import *
      from rest_framework_simplejwt.views import TokenObtainPairView
      
      # custom token view
      class CustomTokenObtainPairView(TokenObtainPairView):
          serializer_class = CustomTokenObtainPairSerializer
      
    • urls.py

      from django.contrib import admin
      from django.views.generic import TemplateView
      from django.urls import path, include
      from rest_framework_simplejwt.views import TokenRefreshView, TokenVerifyView
      from rest_framework.routers import DefaultRouter
      from backend import views
      
      router = DefaultRouter()
      router.register(r'users', views.UserViewSet)
      router.register(r'groups', views.GroupViewSet)
      
      # 使用自动url路由连接我们的API
      # 另外,我们还包括支持浏览器浏览API的登录URL
      app_name = 'backend'
      urlpatterns = [
          path('', TemplateView.as_view(template_name='index.html'), name='index'),
          path('admin/', admin.site.urls),  # django 管理员视图
          path('alterpassword/', views.AlterPassword.as_view()),
          path('search/', views.GlobalSearchAPIView.as_view(), name="search"),
          path('api/<version>/', include(router.urls)),  # 自定义rest api
          path('api-token-auth/', views.CustomTokenObtainPairView.as_view(),
               name='custom_token_obtain_pair'),  # rest_framework_simplejwt 生成
          path('api-token-refresh/', TokenRefreshView.as_view(),
               name='token_refresh'),  # rest_framework_simplejwt 刷新
          path('api-token-verify/', TokenVerifyView.as_view(),
               name='token_verify'),  # rest_framework_simplejwt 验证
          path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),  # rest framework api 可视化
      ]

    前端部分代码:

    • axios-filter.js(拦截器)

      import axios from 'axios'
      import { Message } from 'element-ui'
      import router from '@/router'
      
      // 读取cookie中的csrftoken
      axios.defaults.xsrfCookieName = 'csrftoken'
      // axios header设置X-CSRFTOKEN
      axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN'
      // 设置Content-Type
      axios.defaults.headers = {
        'Content-Type': 'application/json;charset=UTF-8'
      }
      // 设置超时时间
      axios.defaults.timeout = 50000
      
      // request拦截器
      axios.interceptors.request.use(
        config => {
          // sessionStorage 如果存在jwt token
          if (sessionStorage.token) {
            // 则JWT token认证格式,写入header
            config.headers.Authorization = 'JWt ' + sessionStorage.token
            // console.log(sessionStorage.token)
          }
          return config
        },
        error => {
          // console.log(error)
          return Promise.reject(error)
        }
      )
      // reponse自定义拦截器 start
      function customAxiosResponseInterceptor () {
        const interceptor = axios.interceptors.response.use(
          response => response,
          error => {
            // console.log(error)
            var config = error.config
            if (error.response.status === 401) {
              axios.interceptors.response.eject(interceptor)
              let data = { 'refresh': sessionStorage.getItem('refresh') }
              return axios.post('/api-token-refresh/', data).then(response => {
                sessionStorage.setItem('token', response.data.access)
                console.log('refresh access token success')
                // 重新发送请求
                return axios(config)
              }).catch(error => {
                Message({
                  showClose: true,
                  message: '登录超时,请重新登录!!!',
                  type: 'error'
                })
                redirectLoginWithQuery()
                console.log(error)
                return Promise.reject(error)
              }).finally(customAxiosResponseInterceptor)
            } else {
              Message({
                showClose: true,
                message: error.response.data,
                type: 'error'
              })
              return Promise.reject(error)
            }
          }
        )
      }
      customAxiosResponseInterceptor()
      // reponse自定义拦截器 end
      // login with redirect
      function redirectLoginWithQuery () {
        router.push({
          path: '/login',
          query: {
            redirect: router.currentRoute.fullPath
          }
        })
      }
      export default axios
      
    • login.vue

      <template>
        <el-form
          @keyup.enter.native="submitForm('loginForm')"
          :model="loginForm"
          status-icon
          :rules="rules"
          ref="loginForm"
          label-width="55px"
          class="login-container"
        >
          <h3 class="title">系统登录</h3>
          <el-form-item label="用户名" prop="name">
            <el-input type="text" v-model="loginForm.name" autocomplete="off" placeholder="用户名"></el-input>
          </el-form-item>
          <el-form-item label="密码" prop="pass">
            <label slot="label">密&nbsp;&nbsp;&nbsp;&nbsp;码</label>
            <el-input type="password" v-model="loginForm.pass" autocomplete="off" placeholder="密码"></el-input>
          </el-form-item>
          <el-checkbox v-model="checked" class="remember">记住密码</el-checkbox>
          <el-form-item>
            <el-button
              ref="button_login"
              v-loding="loading"
              type="primary"
              @click="submitForm('loginForm')"
            >登录</el-button>
            <el-button @click="resetForm('loginForm')">重置</el-button>
          </el-form-item>
        </el-form>
      </template>
      
      <script>
      export default {
        data () {
          var validateName = (rule, value, callback) => {
            if (!value) {
              callback(new Error('请输入用户名'))
            } else {
              callback()
            }
          }
          var validatePass = (rule, value, callback) => {
            if (!value) {
              callback(new Error('请输入密码'))
            } else {
              callback()
            }
          }
          return {
            checked: false,
            loginForm: {
              name: '',
              pass: ''
            },
            rules: {
              name: [
                { validator: validateName, trigger: 'blur' }
              ],
              pass: [
                { validator: validatePass, trigger: 'blur' }
              ]
            },
            loading: false
          }
        },
        methods: {
          submitForm (formName) {
            this.$refs[formName].validate((valid) => {
              if (valid) {
                var that = this
                this.$refs.button_login.loading = true
                this.$axios.request(
                  // 发送axios请求
                  {
                    url: '/api-token-auth/', // 请求路径
                    method: 'POST', // 请求方式
                    data: {
                      // 要发送的数据
                      username: this.loginForm.name,
                      password: this.loginForm.pass
                    },
                    responseType: 'json' // 期望返回的类型是json格式
                  }
                ).then(response => {
                  // 把返回的结果交给回调函数处理
                  console.log(response)
                  sessionStorage.setItem('token', response.data.access)
                  sessionStorage.setItem('refresh', response.data.refresh)
                  sessionStorage.setItem('user_id', response.data.user_id)
                  sessionStorage.setItem('user_name', response.data.user_name)
                  this.$message.success('登录成功')
                  let redirect = decodeURIComponent(this.$route.query.redirect || '/')
                  that.$router.push({ path: redirect })
                  // console.log(sessionStorage)
                }).catch(error => {
                  this.$message.error(error)
                  console.log(error)
                  this.$refs.button_login.loading = false
                })
              } else {
                console.log('请输入合法用户名和密码')
                this.$message.error('请输入合法用户名和密码')
                return false
              }
            })
          },
          resetForm (formName) {
            this.$refs[formName].resetFields()
          },
          keyupEnter () {
            const that = this
            if (window.event.keyCode === 13) {
              that.submitForm('loginForm')
            }
          }
        },
        mounted () {
          window.addEventListener('keyup', this.keyupEnter, false)
        },
        beforeDestroy () {
          window.removeEventListener('keyup', this.keyupEnter, false)
        }
      }
      </script>
      
      <style lang="scss" scoped>
      .login-container {
        -webkit-border-radius: 5px;
        border-radius: 5px;
        -moz-border-radius: 5px;
        background-clip: padding-box;
        margin: 180px auto;
        width: 350px;
        padding: 35px 35px 15px 35px;
        background: #fff;
        border: 1px solid #eaeaea;
        box-shadow: 0 0 25px #cac6c6;
        .title {
          margin: 0px auto 40px auto;
          text-align: center;
          color: #505458;
        }
        .remember {
          margin: 0px 0px 35px 0px;
        }
      }
      </style>
      
  1. token相关接口使用说明

    方法功能说明特点
    TokenObtainPairView用户第一次登录,返回access和refresh的两个tokenaccess的token应该设置较短有效时间,refresh的token应该设置较长的合理有效时间
    TokenRefreshView传递有效时间内的refresh-token,接口返回有效时间的新access-tokenrefresh-token过期返回的状态码与access-token过期返回的状态码都是401(vue拦截器对此处理需要一点小技巧,请参考axios-filter.js)
    TokenVerifyView验证提交的token,返回当前token状态信息
  2. settings.py中simplejwt相关设置参数

    SIMPLE_JWT = {
        'ACCESS_TOKEN_LIFETIME': timedelta(minutes=1),
        'REFRESH_TOKEN_LIFETIME': timedelta(minutes=2),
        'ROTATE_REFRESH_TOKENS': False,
        'BLACKLIST_AFTER_ROTATION': True,
        'UPDATE_LAST_LOGIN': False,
    
        'ALGORITHM': 'HS256',
        'SIGNING_KEY': SECRET_KEY,
        'VERIFYING_KEY': None,
        'AUDIENCE': None,
        'ISSUER': None,
    
        'AUTH_HEADER_TYPES': ('JWt',),
        'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
        'USER_ID_FIELD': 'id',
        'USER_ID_CLAIM': 'user_id',
        'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
    
        'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
        'TOKEN_TYPE_CLAIM': 'token_type',
    
        'JTI_CLAIM': 'jti',
    
        'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
        'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
        'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
    }

    完整参数请参考:simplejwt完整参数项

  3. 实际应用中:
    verify_jwt_token调用的频率可能比较少,
    如果对安全性没太大要求,可以只使用access的token,不启用refresh的token,access的token超时后,跳转登录页即可。
    当用户成功验证而应用程序不更新cookie时,这个时候就存在会话固定漏洞,攻击者可利用此漏洞发起会话劫持
    所以建议设置——ACCESS_TOKEN_LIFETIME为较短时间,设置REFRESH_TOKEN_LIFETIME为合适的较长时间,
    即——access为较短的有效期,refresh设置为合理的较长有效期,
    前端不断携带refresh token刷新接口,获取新的access token来进行业务交互。
  4. 简述一下djangorestframework-jwt和djangorestframework-simplejwt的区别

    1. 关于djangorestframework-jwt的模块,在使用obtain_jwt_token方法时,只会返回一个token,且该token只能在固定有效期时间内调用refresh_jwt_token,获取新的token
      (新的token有效期等于前面所有token有效期减去已消耗的时间,也就是t1+t2+t3+..........=t1(如果没有刷新token)=JWT_EXPIRATION_DELTA;

      Refresh with tokens can be repeated (token1 -> token2 -> token3), but this chain of token stores the time that the original token (obtained with username/password credentials), as orig_iat. You can only keep refreshing tokens up to JWT_REFRESH_EXPIRATION_DELTA.)

    2. 而如果使用djangorestframework-simplejwt模块,在使用TokenObtainPairView方法时,会直接返回两个(access和refresh)token,且设置不同有效期,携带refresh token访问TokenRefreshView,才能返回新的固定有效期的access token
  5. 参考链接:

    https://jpadilla.github.io/django-rest-framework-jwt
    https://django-rest-framework-simplejwt.readthedocs.io/en/latest/
    https://simpleisbetterthancomplex.com/tutorial/2018/12/19/how-to-use-jwt-authentication-with-django-rest-framework.html
    https://www.remoteinning.com/blog/how-to-use-jwt-authentication-with-django-rest-framework
    https://stackoverflow.com/questions/51646853/automating-access-token-refreshing-via-interceptors-in-axios