学习笔记5

  1. Linux多线程同步机制 - 信号量

    信号量函数定义如下:
    include <sys/sem.h>
    int semctl(int sem_id, int sem_num, int command, ...);
    int semget(key_t key, int num_sems, int sem_flags);
    int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

    事实上,为了获得我们特定操作所需要的#define定义,我们需要在包含sys/sem.h文件之前通常需要包含sys/types.h与sys/ipc.h文件。而在某些情况下,这并不是必须的。

    因为我们会依次了解每一个函数,记住,这些函数的设计是用于操作信号量值数组的,从而会使用其操作向比单个信号量所需要的操作更为复杂。

    注意,key的作用类似于一个文件名,因为他表示程序也许会使用或是合作所用的资源。相类似的,由semget所返回的并且为其他的共享内存函数所用的标 识符与由fopen函数所返回 的FILE *十分相似,因为他被进程用来访问共享文件。而且与文件类似,不同的进程会有不同的信号量标识符,尽管他们指向相同的信号量。key与标识符的用法对于在 这里所讨论的所有IPC程序都是通用的,尽管每一个程序会使用独立的key与标识符。

    semget

    semget函数创建一个新的信号量或是获得一个已存在的信号量键值。

    int semget(key_t key, int num_sems, int sem_flags);

    第一个参数key是一个用来允许不相关的进程访问相同信号量的整数值。所有的信号量是为不同的程序通过提供一个key来间接访问的,对于每一个信号量系统 生成一个信号量标识符。信号量键值只可以由semget获得,所有其他的信号量函数所用的信号量标识符都是由semget所返回的。

    还有一个特殊的信号量key值,IPC_PRIVATE(通常为0),其作用是创建一个只有创建进程可以访问的信号量。这通常并没有有用的目的,而幸运的是,因为在某些Linux系统上,手册页将IPC_PRIVATE并没有阻止其他的进程访问信号量作为一个bug列出。

    num_sems参数是所需要的信号量数目。这个值通常总是1。

    sem_flags参数是一个标记集合,与open函数的标记十分类似。低九位是信号的权限,其作用与文件权限类似。另外,这些标记可以与 IPC_CREAT进行或操作来创建新的信号量。设置IPC_CREAT标记并且指定一个已经存在的信号量键值并不是一个错误。如果不需 要,IPC_CREAT标记只是被简单的忽略。我们可以使用IPC_CREAT与IPC_EXCL的组合来保证我们可以获得一个新的,唯一的信号量。如果 这个信号量已经存在,则会返回一个错误。

    如果成功,semget函数会返回一个正数;这是用于其他信号量函数的标识符。如果失败,则会返回-1。

    semop

    函数semop用来改变信号量的值:

    int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

    第一个参数,sem_id,是由semget函数所返回的信号量标识符。第二个参数,sem_ops,是一个指向结构数组的指针,其中的每一个结构至少包含下列成员:

    struct sembuf {
    ​ short sem_num;
    ​ short sem_op;
    ​ short sem_flg;
    }

    第一个成员,sem_num,是信号量数目,通常为0,除非我们正在使用一个信号量数组。sem_op成员是信号量的变化量值。(我们可以以任何量改变信 号量值,而不只是1)通常情况下中使用两个值,-1是我们的P操作,用来等待一个信号量变得可用,而+1是我们的V操作,用来通知一个信号量可用。

    最后一个成员,sem_flg,通常设置为SEM_UNDO。这会使得操作系统跟踪当前进程对信号量所做的改变,而且如果进程终止而没有释放这个信号量, 如果信号量为这个进程所占有,这个标记可以使得操作系统自动释放这个信号量。将sem_flg设置为SEM_UNDO是一个好习惯,除非我们需要不同的行 为。如果我们确实变我们需要一个不同的值而不是SEM_UNDO,一致性是十分重要的,否则我们就会变得十分迷惑,当我们的进程退出时,内核是否会尝试清 理我们的信号量。

    semop的所用动作会同时作用,从而避免多个信号量的使用所引起的竞争条件。我们可以在手册页中了解关于semop处理更为详细的信息。

    semctl

    semctl函数允许信号量信息的直接控制:

    int semctl(int sem_id, int sem_num, int command, ...);

    第一个参数,sem_id,是由semget所获得的信号量标识符。sem_num参数是信号量数目。当我们使用信号量数组时会用到这个参数。通常,如果 这是第一个且是唯一的一个信号量,这个值为0。command参数是要执行的动作,而如果提供了额外的参数,则是union semun,根据X/OPEN规范,这个参数至少包括下列参数:

    union semun {
    ​ int val;
    ​ struct semid_ds *buf;
    ​ unsigned short *array;
    }

    许多版本的Linux在头文件(通常为sem.h)中定义了semun联合,尽管X/Open确认说我们必须定义我们自己的联合。如果我们发现我们确实需 要定义我们自己的联合,我们可以查看semctl手册页了解定义。如果有这样的情况,建议使用手册页中提供的定义,尽管这个定义与上面的有区别。

    有多个不同的command值可以用于semctl。在这里我们描述两个会经常用到的值。要了解semctl功能的详细信息,我们应该查看手册页。

    这两个通常的command值为:

    SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
    IPC_RMID:当信号量不再需要时用于删除一个信号量标识。

    semctl函数依据command参数会返回不同的值。对于SETVAL与IPC_RMID,如果成功则会返回0,否则会返回-1。

  2. 线程操作

    踩坑:由于pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a,所以在使用pthread_create()创建线程,以及调用 pthread_atfork()函数建立fork处理程序时,在编译中要加 -lpthread参数。

    例如:gcc thread.c -o thread -lpthread

    头文件:pthread.h

    • pthread_create()

    功能:创建线程

    int pthread_create(pthread_t *restrict tidp, //返回线程的ID
                        const pthread_attr_t *restrict attr, //线程属性,默认为NULL
                        void *(*start_rtn)(void), //线程函数入口地址
                        void *restrict arg); //参数
    
    • pthread_join()

    功能:以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果进程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。

    int pthread_join(pthread_t thread, void **retval);
    
    

    参数:
    thread: 线程标识符,即线程ID,标识唯一线程。
    retval: 用户定义的指针,用来存储被等待线程的返回值。
    返回值 : 0代表成功,失败返回的则是错误号。

  3. flask-mail

    • 安装 Flask-Mail

      pip install Flask-Mail

      Flask-Mail 使用标准的 Flask 配置 API 进行配置。

      • MAIL_SERVER : 默认为 ‘localhost’
      • MAIL_PORT : 默认为 25
      • MAIL_USE_TLS : 默认为 False
      • MAIL_USE_SSL : 默认为 False
      • MAIL_DEBUG : 默认为 app.debug
      • MAIL_USERNAME : 默认为 None
      • MAIL_PASSWORD : 默认为 None
      • MAIL_DEFAULT_SENDER : 默认为 None
      • MAIL_MAX_EMAILS : 默认为 None
      • MAIL_SUPPRESS_SEND : 默认为 app.testing
      • MAIL_ASCII_ATTACHMENTS : 默认为 False
  • Mail 实例进行管理
 ```
    from flask import Flask
    from flask_mail import Mail

    app = Flask(__name__)
    mail = Mail(app)
 ```

    或者你也可以在应用程序配置的时候设置你的 Mail 实例,通过使用 init_app 方法:

    ```
    mail = Mail()

    app = Flask(__name__)
    mail.init_app(app)
    ```
  • 发送邮件

    为了能够发送邮件,首先需要创建一个 Message 实例:

    from flask_mail import Message
    
    @app.route("/")
    def index():
    
        msg = Message("Hello",
                      sender="from@example.com",
                      recipients=["to@example.com"])
    
    

    你能够设置一个或者多个收件人:

    msg.recipients = ["you@example.com"]
    msg.add_recipient("somebodyelse@example.com")
    
    

    如果你设置了 MAIL_DEFAULT_SENDER,就不必再次填写发件人,默认情况下将会使用配置项的发件人:

    msg = Message("Hello",
                  recipients=["to@example.com"])
    
    

    如果 sender 是一个二元组,它将会被分成姓名和邮件地址:

    msg = Message("Hello",
                  sender=("Me", "me@example.com"))
    
    assert msg.sender == "Me <me@example.com>"
    
    

    邮件内容可以包含主体以及/或者 HTML:

    msg.body = "testing"
    msg.html = "<b>testing</b>"
    
    

    最后,发送邮件的时候请使用 Flask 应用设置的 Mail 实例:

    mail.send(msg)
    
  • 大量邮件

    通常在一个 Web 应用中每一个请求会同时发送一封或者两封邮件。在某些特定的场景下,有可能会发送数十或者数百封邮件,不过这种发送工作会给交离线任务或者脚本执行。

    在这种情况下发送邮件的代码会有些不同:

    with mail.connect() as conn:
        for user in users:
            message = '...'
            subject = "hello, %s" % user.name
            msg = Message(recipients=[user.email],
                          body=message,
                          subject=subject)
    
            conn.send(msg)
    
    

    与电子邮件服务器的连接会一直保持活动状态直到所有的邮件都已经发送完成后才会关闭(断开)。

    有些邮件服务器会限制一次连接中的发送邮件的上限。你可以设置重连前的发送邮件的最大数,通过配置 MAIL_MAX_EMAILS

  • 附件

    在邮件中添加附件同样非常简单:

    with app.open_resource("image.png") as fp:
        msg.attach("image.png", "image/png", fp.read())
    
    

    具体细节请参看 API

    如果 MAIL_ASCII_ATTACHMENTS 设置成 True 的话,文件名将会转换成 ASCII 的。 当文件名是以 UTF-8 编码的时候,使用邮件转发的时候会修改邮件内容并且混淆 Content-Disposition 描述,这个时候 MAIL_ASCII_ATTACHMENTS 配置项是十分有用的。转换成 ASCII 的基本方式就是对 non-ASCII 字符的去除。任何一个 unicode 字符能够被 NFKD 分解成一个或者多个 ASCII 字符。

  1. SMTP发送邮件

    SMTP是发送邮件的协议,Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。

    Python对SMTP支持有smtplibemail两个模块,email负责构造邮件,smtplib负责发送邮件。

    首先,我们来构造一个最简单的纯文本邮件:

    from email.mime.text import MIMEText
    msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
    
    

    注意到构造MIMEText对象时,第一个参数就是邮件正文,第二个参数是MIME的subtype,传入'plain',最终的MIME就是'text/plain',最后一定要用utf-8编码保证多语言兼容性。

    然后,通过SMTP发出去:

    # 输入Email地址和口令:
    from_addr = raw_input('From: ')
    password = raw_input('Password: ')
    # 输入SMTP服务器地址:
    smtp_server = raw_input('SMTP server: ')
    # 输入收件人地址:
    to_addr = raw_input('To: ')
    
    import smtplib
    server = smtplib.SMTP(smtp_server, 25) # SMTP协议默认端口是25
    server.set_debuglevel(1)
    server.login(from_addr, password)
    server.sendmail(from_addr, [to_addr], msg.as_string())
    server.quit()
    
    

    我们用set_debuglevel(1)就可以打印出和SMTP服务器交互的所有信息。SMTP协议就是简单的文本命令和响应。login()方法用来登录SMTP服务器,sendmail()方法就是发邮件,由于可以一次发给多个人,所以传入一个list,邮件正文是一个stras_string()MIMEText对象变成str

    如果一切顺利,就可以在收件人信箱中收到我们刚发送的Email:

    send-mail

    仔细观察,发现如下问题:

    1. 邮件没有主题;
    2. 收件人的名字没有显示为友好的名字,比如Mr Green <green@example.com>
    3. 明明收到了邮件,却提示不在收件人中。

    这是因为邮件主题、如何显示发件人、收件人等信息并不是通过SMTP协议发给MTA,而是包含在发给MTA的文本中的,所以,我们必须把FromToSubject添加到MIMEText中,才是一封完整的邮件:

    # -*- coding: utf-8 -*-
    
    from email import encoders
    from email.header import Header
    from email.mime.text import MIMEText
    from email.utils import parseaddr, formataddr
    import smtplib
    
    def _format_addr(s):
        name, addr = parseaddr(s)
        return formataddr(( \
            Header(name, 'utf-8').encode(), \
            addr.encode('utf-8') if isinstance(addr, unicode) else addr))
    
    from_addr = raw_input('From: ')
    password = raw_input('Password: ')
    to_addr = raw_input('To: ')
    smtp_server = raw_input('SMTP server: ')
    
    msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
    msg['From'] = _format_addr(u'Python爱好者 <%s>' % from_addr)
    msg['To'] = _format_addr(u'管理员 <%s>' % to_addr)
    msg['Subject'] = Header(u'来自SMTP的问候……', 'utf-8').encode()
    
    server = smtplib.SMTP(smtp_server, 25)
    server.set_debuglevel(1)
    server.login(from_addr, password)
    server.sendmail(from_addr, [to_addr], msg.as_string())
    server.quit()
    
    

    我们编写了一个函数_format_addr()来格式化一个邮件地址。注意不能简单地传入name <addr@example.com>,因为如果包含中文,需要通过Header对象进行编码。

    msg['To']接收的是字符串而不是list,如果有多个邮件地址,用,分隔即可。

    再发送一遍邮件,就可以在收件人邮箱中看到正确的标题、发件人和收件人:

    mail-with-header

    你看到的收件人的名字很可能不是我们传入的管理员,因为很多邮件服务商在显示邮件时,会把收件人名字自动替换为用户注册的名字,但是其他收件人名字的显示不受影响。

    如果我们查看Email的原始内容,可以看到如下经过编码的邮件头:

    From: =?utf-8?b?UHl0aG9u54ix5aW96ICF?= <xxxxxx@163.com>
    To: =?utf-8?b?566h55CG5ZGY?= <xxxxxx@qq.com>
    Subject: =?utf-8?b?5p2l6IeqU01UUOeahOmXruWAmeKApuKApg==?=
    
    

    这就是经过Header对象编码的文本,包含utf-8编码信息和Base64编码的文本。如果我们自己来手动构造这样的编码文本,显然比较复杂。

    发送HTML邮件

    如果我们要发送HTML邮件,而不是普通的纯文本文件怎么办?方法很简单,在构造MIMEText对象时,把HTML字符串传进去,再把第二个参数由plain变为html就可以了:

    msg = MIMEText('<html><body><h1>Hello</h1>' +
        '<p>send by <a href="http://www.python.org">Python</a>...</p>' +
        '</body></html>', 'html', 'utf-8')
    
    

    再发送一遍邮件,你将看到以HTML显示的邮件:

    html-mail

    发送附件

    如果Email中要加上附件怎么办?带附件的邮件可以看做包含若干部分的邮件:文本和各个附件本身,所以,可以构造一个MIMEMultipart对象代表邮件本身,然后往里面加上一个MIMEText作为邮件正文,再继续往里面加上表示附件的MIMEBase对象即可:

    # 邮件对象:
    msg = MIMEMultipart()
    msg['From'] = _format_addr(u'Python爱好者 <%s>' % from_addr)
    msg['To'] = _format_addr(u'管理员 <%s>' % to_addr)
    msg['Subject'] = Header(u'来自SMTP的问候……', 'utf-8').encode()
    
    # 邮件正文是MIMEText:
    msg.attach(MIMEText('send with file...', 'plain', 'utf-8'))
    
    # 添加附件就是加上一个MIMEBase,从本地读取一个图片:
    with open('/Users/michael/Downloads/test.png', 'rb') as f:
        # 设置附件的MIME和文件名,这里是png类型:
        mime = MIMEBase('image', 'png', filename='test.png')
        # 加上必要的头信息:
        mime.add_header('Content-Disposition', 'attachment', filename='test.png')
        mime.add_header('Content-ID', '<0>')
        mime.add_header('X-Attachment-Id', '0')
        # 把附件的内容读进来:
        mime.set_payload(f.read())
        # 用Base64编码:
        encoders.encode_base64(mime)
        # 添加到MIMEMultipart:
        msg.attach(mime)
    
    

    然后,按正常发送流程把msg(注意类型已变为MIMEMultipart)发送出去,就可以收到如下带附件的邮件:

    mimemultipart

    发送图片

    如果要把一个图片嵌入到邮件正文中怎么做?直接在HTML邮件中链接图片地址行不行?答案是,大部分邮件服务商都会自动屏蔽带有外链的图片,因为不知道这些链接是否指向恶意网站。

    要把图片嵌入到邮件正文中,我们只需按照发送附件的方式,先把邮件作为附件添加进去,然后,在HTML中通过引用src="cid:0"就可以把附件作为图片嵌入了。如果有多个图片,给它们依次编号,然后引用不同的cid:x即可。

    把上面代码加入MIMEMultipartMIMETextplain改为html,然后在适当的位置引用图片:

    msg.attach(MIMEText('<html><body><h1>Hello</h1>' +
        '<p><img src="cid:0"></p>' +
        '</body></html>', 'html', 'utf-8'))
    
    

    再次发送,就可以看到图片直接嵌入到邮件正文的效果:

    email-inline-image

    同时支持HTML和Plain格式

    如果我们发送HTML邮件,收件人通过浏览器或者Outlook之类的软件是可以正常浏览邮件内容的,但是,如果收件人使用的设备太古老,查看不了HTML邮件怎么办?

    办法是在发送HTML的同时再附加一个纯文本,如果收件人无法查看HTML格式的邮件,就可以自动降级查看纯文本邮件。

    利用MIMEMultipart就可以组合一个HTML和Plain,要注意指定subtype是alternative

    msg = MIMEMultipart('alternative')
    msg['From'] = ...
    msg['To'] = ...
    msg['Subject'] = ...
    
    msg.attach(MIMEText('hello', 'plain', 'utf-8'))
    msg.attach(MIMEText('<html><body><h1>Hello</h1></body></html>', 'html', 'utf-8'))
    # 正常发送msg对象...
    
    

    加密SMTP

    使用标准的25端口连接SMTP服务器时,使用的是明文传输,发送邮件的整个过程可能会被窃听。要更安全地发送邮件,可以加密SMTP会话,实际上就是先创建SSL安全连接,然后再使用SMTP协议发送邮件。

    某些邮件服务商,例如Gmail,提供的SMTP服务必须要加密传输。我们来看看如何通过Gmail提供的安全SMTP发送邮件。

    必须知道,Gmail的SMTP端口是587,因此,修改代码如下:

    smtp_server = 'smtp.gmail.com'
    smtp_port = 587
    server = smtplib.SMTP(smtp_server, smtp_port)
    server.starttls()
    # 剩下的代码和前面的一模一样:
    server.set_debuglevel(1)
    ...
    

    只需要在创建SMTP对象后,立刻调用starttls()方法,就创建了安全连接。后面的代码和前面的发送邮件代码完全一样。

    如果因为网络问题无法连接Gmail的SMTP服务器,请相信我们的代码是没有问题的,你需要对你的网络设置做必要的调整。

  2. jwt

    JWT是Auth0提出的通过对JSON进行加密签名来实现授权验证的方案,编码之后的JWT看起来是这样的一串字符:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    

    . 分为三段,通过解码可以得到:

    1. 头部(Header)
    // 包括类别(typ)、加密算法(alg);
    {
      "alg": "HS256",
      "typ": "JWT"
    }
    

    jwt的头部包含两部分信息:

    • 声明类型,这里是jwt
    • 声明加密的算法 通常直接使用 HMAC SHA256

    然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分。

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
    
    1. 载荷(payload)

    载荷就是存放有效信息的地方。这些有效信息包含三个部分:

    • 标准中注册声明
    • 公共的声名
    • 私有的声明

    公共的声明 :
    公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密。

    私有的声明 :
    私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

    下面是一个例子:

    // 包括需要传递的用户信息;
    { "iss": "Online JWT Builder", 
      "iat": 1416797419, 
      "exp": 1448333419, 
      "aud": "www.gusibi.com", 
      "sub": "uid", 
      "nickname": "goodspeed", 
      "username": "goodspeed", 
      "scopes": [ "admin", "user" ] 
    }
    
    • iss: 该JWT的签发者,是否使用是可选的;
    • sub: 该JWT所面向的用户,是否使用是可选的;
    • aud: 接收该JWT的一方,是否使用是可选的;
    • exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的;
    • iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的;

    其他还有:

    • nbf (Not Before):如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;,是否使用是可选的;
    • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

    将上面的JSON对象进行base64编码可以得到下面的字符串。这个字符串我们将它称作JWT的Payload(载荷)。

    eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0MTY3OTc0MTksImV4cCI6MTQ0ODMzMzQxOSwiYXVkIjoid3d3Lmd1c2liaS5jb20iLCJzdWIiOiIwMTIzNDU2Nzg5Iiwibmlja25hbWUiOiJnb29kc3BlZWQiLCJ1c2VybmFtZSI6Imdvb2RzcGVlZCIsInNjb3BlcyI6WyJhZG1pbiIsInVzZXIiXX0
    

    信息会暴露:由于这里用的是可逆的base64 编码,所以第二部分的数据实际上是明文的。我们应该避免在这里存放不能公开的隐私信息。

    1. 签名(signature)
    // 根据alg算法与私有秘钥进行加密得到的签名字串;
    // 这一段是最重要的敏感信息,只能在服务端解密;
    HMACSHA256(  
        base64UrlEncode(header) + "." +
        base64UrlEncode(payload),
        SECREATE_KEY
    )
    

    jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

    • header (base64后的)
    • payload (base64后的)
    • secret

    将上面的两个编码后的字符串都用句号.连接在一起(头部在前),就形成了:

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9
    

    最后,我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret)。如果我们用 secret 作为密钥的话,那么就可以得到我们加密后的内容:

    pq5IDv-yaktw6XEa5GEv07SzS9ehe6AcVSdTj0Ini4o
    

    将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0MTY3OTc0MTksImV4cCI6MTQ0ODMzMzQxOSwiYXVkIjoid3d3Lmd1c2liaS5jb20iLCJzdWIiOiIwMTIzNDU2Nzg5Iiwibmlja25hbWUiOiJnb29kc3BlZWQiLCJ1c2VybmFtZSI6Imdvb2RzcGVlZCIsInNjb3BlcyI6WyJhZG1pbiIsInVzZXIiXX0.pq5IDv-yaktw6XEa5GEv07SzS9ehe6AcVSdTj0Ini4o
    

    签名的目的:签名实际上是对头部以及载荷内容进行签名。所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不知道服务器加密的时候用的密钥的话,得出来的签名也一定会是不一样的。
    这样就能保证token不会被篡改。

    token 生成好之后,接下来就可以用token来和服务器进行通讯了。

    下图是client 使用 JWT 与server 交互过程:

    [图片上传失败...(image-4fb30f-1525878931041)]

    这里在第三步我们得到 JWT 之后,需要将JWT存放在 client,之后的每次需要认证的请求都要把JWT发送过来。(请求时可以放到 header 的 Authorization )

    1. JWT 使用场景

    JWT的主要优势在于使用无状态、可扩展的方式处理应用中的用户会话。服务端可以通过内嵌的声明信息,很容易地获取用户的会话信息,而不需要去访问用户或会话的数据库。在一个分布式的面向服务的框架中,这一点非常有用。

    但是,如果系统中需要使用黑名单实现长期有效的token刷新机制,这种无状态的优势就不明显了。

    优点:

    • 快速开发
    • 不需要cookie
    • JSON在移动端的广泛应用
    • 不依赖于社交登录
    • 相对简单的概念理解

    缺点:

    • Token有长度限制
    • Token不能撤销
    • 需要token有失效时间限制(exp)
  3. pop3收取邮件

    python的poplib也针对这些命令分别提供了对应的方法,上面在第二列里已经标出来。

    收取邮件的过程一般是:

    1. 连接pop3服务器 (poplib.POP3.init 或者poplib.POP3_SSL.init)
    2. 发送用户名和密码进行验证 (poplib.POP3.user poplib.POP3.pass_)
    3. 获取邮箱中信件信息 (poplib.POP3.stat)
    4. 收取邮件 (poplib.POP3.retr)
    5. 删除邮件 (poplib.POP3.dele)
    6. 退出 (poplib.POP3.quit)

    官方文档:

    • POP3.set_debuglevel(level)

      Set the instance’s debugging level. This controls the amount of debugging output printed. The default, 0, produces no debugging output. A value of1 produces a moderate amount of debugging output, generally a single line per request. A value of 2 or higher produces the maximum amount of debugging output, logging each line sent and received on the control connection.

  • POP3.getwelcome()

    Returns the greeting string sent by the POP3 server.

  • POP3.user(username)

    Send user command, response should indicate that a password is required.

  • POP3.pass_(password)

    Send password, response includes message count and mailbox size. Note: the mailbox on the server is locked until quit() is called.

  • POP3.apop(user, secret)

    Use the more secure APOP authentication to log into the POP3 server.

  • POP3.rpop(user)

    Use RPOP authentication (similar to UNIX r-commands) to log into POP3 server.

  • POP3.stat()

    Get mailbox status. The result is a tuple of 2 integers: (message count, mailbox size).

  • POP3.list([which])

    Request message list, result is in the form (response, ['mesg_num octets', ...], octets). If which is set, it is the message to list.

  • POP3.retr(which)

    Retrieve whole message number which, and set its seen flag. Result is in form (response, ['line', ...], octets).

  • POP3.dele(which)

    Flag message number which for deletion. On most servers deletions are not actually performed until QUIT (the major exception is Eudora QPOP, which deliberately violates the RFCs by doing pending deletes on any disconnect).

  • POP3.rset()

    Remove any deletion marks for the mailbox.

  • POP3.noop()

    Do nothing. Might be used as a keep-alive.

  • POP3.quit()

    Signoff: commit changes, unlock mailbox, drop connection.

  • POP3.top(which, howmuch)

    Retrieves the message header plus howmuch lines of the message after the header of message number which. Result is in form (response,['line', ...], octets).The POP3 TOP command this method uses, unlike the RETR command, doesn’t set the message’s seen flag; unfortunately, TOP is poorly specified in the RFCs and is frequently broken in off-brand servers. Test this method by hand against the POP3 servers you will use before trusting it.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容