#676 转载:管理者的四种不同授权风格

2021-09-23

什么是授权?

所谓授权是指管理者把由他全权负责的一项或多项任务委派给下属员工,使下属拥有相当的自主权和行动权。

授权的6个误区

  1. 授权不是全程参与,授权后管理者不要过多干涉员工的工作;
  2. 授权不是弃权,授权是将任务、权利分配下去,再做适当的监督、帮助和支持;
  3. 授权不是授责,虽然把权利、工作内容和资源分配给下属,但是责任还是在主管身上;
  4. 授权不是代理职务,并不意味着下属拥有管理者的所有职权;
  5. 授权不是分工,分工在岗位说明中已经设定,但授权工作有可能在岗位职责之外;
  6. 授权不是助理,不是让下属打杂,而是要他独立完成一项工作。

管理者的四种不同授权风格

  1. 操控型:喜欢主动监督工作的进展,经常不说明理由,只是下令照办,并且严密监督工作的进行。适合于团队经验不足。
  2. 教练型:会密切监督下属工作的进行,但通常不会详细指点下属该如何进行,只会跟下属说明所交付的任务,逐步引导。当有必要或需要时,才会提供建议或支援。适合于新参加工作的员工。
  3. 顾问型:一般会给予执行工作的人更多的主导权,对于所交付的任务通常只是做大致上的描述,并且会征询对方的意见和观点,最终取得共识,最终接受任务的个人和团队投下更多的心力。这类管理者通常告知下属,只要表明需求,就会得到协助。
  4. 协调型:只给原则性的指示和注意事项,并表达进行协助的意愿,而做事的程序和方法全由下属自己决定。在授权时,会视工作执行的能力而给予相对的权限,日后再根据工作的进度进行调整。

下面举几个《三国演义》的例子

  1. 刘备谋赖孔明,武赖关张。孔明出山第一仗前,刘备命人取了佩剑和印信,代表了所有的权利都交给了孔明。孔明出计火烧博望坡时,张飞和关羽开始并不配合,刘备在旁边使眼色,关张二人不得不听令。
    刘备在给孔明授权时表现了怎样的授权风格呢?
  2. 火烧博望坡之战,孔明给诸将下令:让关羽埋伏于豫山,让曹军先头部队过去,等南面火起,放火烧曹军粮草;让张飞埋伏于安林,等南面火起,烧曹军博望城之前屯粮的地方;让赵云在博望坡北负责诱敌深入。
    孔明给关张赵三人授权时表现了怎样的授权风格呢?
  3. 赤壁之战火烧连营的真正实施者是黄盖,苦肉计前一天晚上,周瑜见到黄盖,首先询问黄盖的意见,黄盖自己提出用火攻。周瑜又引导说火攻实施的人很重要。这时候黄盖自告奋勇,愿意用苦肉计骗取信任,然后从曹军内部火攻。
    周瑜给黄盖授权实施火烧连营的计策是怎样的授权风格呢?
  4. 东吴使美人计,实则想讨要荆州。孔明授权赵云保护刘备,并给了他三个锦囊,嘱咐他在紧急时刻依次拆看。
    孔明给赵云授权时表现了怎样的授权风格呢?

答案

  1. 协调型
  2. 操控型
  3. 顾问型
  4. 教练型

#674 正当防卫与互殴

2021-09-21

抖音上看到有人穿警服说,如果有人打你,你只要还手就是互殴,无关正当防卫。那普通人面对被打的情况该怎么正确处理?

#673 JWT 认证

2021-09-20

JWT 介绍

一句话介绍 JWT:就是通过非对称加密算法签名的方式,将部分用户信息存在客户端。

全名 JSON Web Token,简称 JWT。
内容 = 头 Headers (JSON) + 负载 Payload (JSON) + 签名 Signature
分别 Base64 编码之后,用 . 连接。

示例

JSON 实现

import base64

def jsonify(obj):
    return json.dumps(obj, ensure_ascii=False, separators=',:')

def base64urlEncoding(s):
    return base64.urlsafe_b64encode(s).rstrip(b'=').decode('ascii')

def sign(key, msg):
    return base64urlEncoding(hmac.new(key, msg, hashlib.sha256).digest())

# def verify(key, msg, sig):
#     return key.verify(msg, base64.urlsafe_b64decode(sig))

def e(obj):
    return base64urlEncoding(jsonify(header).encode('utf-8'))

def encode(key, header, payload):
    s = e(header) + '.' + e(payload)
    return s + '.' + sign(key, s)

def decode(token):
    header_str, payload_str, sig = token.split('.')
    header = json.loads(base64.urlsafe_b64decode(header_str).decode('utf-8'))
    payload = json.loads(base64.urlsafe_b64decode(payload_str).decode('utf-8'))
    sig = base64.urlsafe_b64decode(sig)

header = {
  "alg": "HS256",
  "typ": "JWT"
}
payload = {
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

signature = HMAC_SHA256(
    secret,
    base64urlEncoding(header) + '.' +
    base64urlEncoding(payload)
)
const token = base64urlEncoding(header) + '.' + base64urlEncoding(payload) + '.' + base64urlEncoding(signature)

标准字段

Header 字段

Code Name Description
typ Type Token 类型,通常为 JWT,用于声明整个对象的类型。
cty Content Type 内容类型,用于声明 Payload 中嵌套对象的类型;嵌套 JWT 时常使用 JWT。
alg Algorithm 签名或加密所使用的算法,例如 HS256、RS256、ES256。
kid Key ID 密钥标识,用于帮助接收方选择对应的验证密钥。
x5c X.509 Certificate Chain X.509 证书链,以 Base64 编码的 DER 证书数组表示。
x5u X.509 URL 指向 X.509 公钥证书或证书链的 URL。
x5t X.509 SHA-1 Thumbprint X.509 证书的 SHA-1 指纹。
x5t#S256 X.509 SHA-256 Thumbprint X.509 证书的 SHA-256 指纹。
jku JWK Set URL 指向 JSON Web Key Set(JWKS)的 URL,用于获取验证所需的公钥。
jwk JSON Web Key 直接在 Header 中携带用于验证签名的 JSON Web Key。

Claim 字段(Payload 字段)

Code Name Description
iss Issuer Token 的签发者。
sub Subject Token 所代表的主体,通常用于表示用户或资源的唯一标识。
aud Audience Token 的受众,即允许使用该 Token 的服务或服务集合。
exp Expiration Time Token 的过期时间,使用 Unix 时间戳。
nbf Not Before Token 开始生效的时间,使用 Unix 时间戳。
iat Issued At Token 的签发时间,使用 Unix 时间戳。
jti JWT ID JWT 的唯一标识,可用于防重放、Token 撤销和审计追踪。

关于 JWT 的看法

  1. 相比传统 Session,JWT 可以减少认证状态存储带来的 DB/Redis 查询。

    • Session 通常需要服务端维护 Session 状态,请求到达后需要查询 Session Store。
    • JWT 将部分认证信息放在 Token 中,服务端验证签名即可完成认证,不需要每次查询 Session Store。
    • 但 JWT 并不意味着完全不需要 DB/Redis。业务数据、权限数据、Token 撤销状态等仍然可能需要查询。
  2. JWT 的主要优势是无状态认证,但“注销 / 踢下线”会重新引入状态。

    • JWT 一旦签发,在 exp 到期之前通常都有效。
    • 如果需要立即注销某个 Token,可以维护黑名单,例如:

      revoked_jti → expiration_time
      
    • 网关或认证服务可以定期同步黑名单,也可以通过 Redis 等共享存储实时查询。

    • 如果对“立即注销”的一致性要求不高,可以接受一定同步延迟。
    • 如果认证系统发生故障,可以根据业务要求选择继续验证 JWT或拒绝认证,但不能简单地认为“去掉黑名单检查就是降级”;这实际上是安全策略的改变。
  3. 相比加密 Cookie,JWT 不依赖 HTTP Cookie,适用场景更加广泛。

    • Cookie 是 HTTP 状态管理机制,浏览器会自动携带。
    • JWT 本质上只是一个 Token,可以放在:

      • Authorization: Bearer <token>
      • Cookie
      • WebSocket 握手
      • 移动端请求
      • 服务间调用
    • 因此 JWT 与 Cookie 实际上不是完全互斥的概念:JWT 也可以放在 Cookie 中。

  4. JWT 应尽可能保持短小。

    • JWT 会随着请求发送,因此 Payload 越大,请求开销越大。
    • 通常只需要保存少量认证相关信息,例如:

      {
          "sub": "123456",
          "iss": "auth.example.com",
          "iat": 1780000000,
          "exp": 1780003600
      }
      
    • 不建议把用户资料、权限列表、大量业务数据直接塞进 JWT。

    • JWT 更适合表达“这个 Token 是谁签发的、代表谁、给谁使用、什么时候有效”,而不是作为一个用户数据容器。
    • 如果需要携带大量信息,应考虑通过 sub 等标识再查询业务数据。
  5. JWT 很适合在 API Gateway / BFF 层统一完成认证校验。

    • 网关可以统一完成:

      • Token 格式检查
      • 签名验证
      • exp / nbf 校验
      • iss / aud 校验
      • 黑名单检查
    • 校验通过后,可以将用户身份信息传递给后端服务,例如:

      X-User-ID: 123456
      X-User-Roles: admin
      
    • 这样业务服务无需重复实现 JWT 验证逻辑。

    • 但认证和授权不是一回事。网关完成身份认证后,具体业务服务仍然应该根据业务资源执行授权判断。
  6. JWT 对跨域 API 场景比较友好,但它本身并没有“解决跨域”。

    • 跨域主要由浏览器的 CORS 机制决定。
    • 如果使用:

      Authorization: Bearer <JWT>
      

      前端可以主动携带 Token,并通过服务端 CORS 配置允许 Authorization 请求头。

    • 相比依赖 Cookie 的认证方式,Bearer Token 不需要依赖浏览器的 Cookie 自动携带机制,因此在前后端分离、移动端、第三方 API 等场景下通常更加灵活。

    • 如果 JWT 放在 Cookie 中,那么仍然需要处理 SameSite、Secure、HttpOnly、CORS 等 Cookie 相关问题。
  7. JWT 最大的价值不是“比 Session 更高级”,而是把认证状态的一部分从服务端 Session Store 转移到了客户端 Token,从而实现无状态认证。

    代价也很明确:

    方案 服务端状态 服务端注销 请求大小 横向扩展
    Session 有 简单 小 依赖共享 Session
    JWT 无 不支持 较大 简单
    JWT + 黑名单 少量 简单 较大 需要共享黑名单

    传统单体 Web 应用使用 Session 更简单,多服务、API、网关认证等场景适合 JWT。

#672 GitHub 上那些徽章是怎么弄出来的?

2021-09-19

比如:Tornado 框架的 stars 数量:、,再比如说这种:

外国人真是会玩,总有这样的好点子,在有限的环境(Markdown)中也能弄的丰富多彩的。

上面都是用的 shields.io 提供的服务,它内置了很多种类的图标,GitHub 关注数、Fork 数、Star 数、协议,Twitter 关注数、NPM 包大小、PyPI 下载数量等等,这些看他们官网提供的示例,照着用就是了。

还有一种,就是用户定制接口,格式:

  1. Static:URLPath https://img.shields.io/badge/<LABEL>-<MESSAGE>-<COLOR>
  2. Static:QueryString https://img.shields.io/static/v1?label=<LABEL>&message=<MESSAGE>&color=<COLOR>
  3. Endpoint https://img.shields.io/endpoint?url=<URL>&style<STYLE>
  4. Dynamic https://img.shields.io/badge/dynamic/json?url=<URL>&label=<LABEL>&query=<$.DATA.SUBDATA>&color=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX>

还有好多更加细致的规则,用来定制自己的图标,就不细说了,自己看文档就行。

其实我用的多的还是上面的第一种接口,因为它比较简单,只要把 label 和 message 修改就可以了。

https://img.shields.io/badge/site-markjour.com-brightgreen.svg?style=plastic&logo=nginx

https://img.shields.io/badge/-doing-green

参考资料与拓展阅读

#669 不简单的绝对值

2021-09-17

这篇文章讲到了绝对值计算的问题:One does not simply calculate the absolute value。

IEEE 754

三个特殊值:

  1. 如果指数是0并且尾数的小数部分是0,这个数 ±0(和符号位相关)
  2. 如果指数 = 2^e - 1 并且尾数的小数部分是 0,这个数是 ±∞(同样和符号位相关)
  3. 如果指数 = 2^e - 1 并且尾数的小数部分非 0,这个数表示为非数(NaN)。

abs 的实现

class Test {
    public static double abs(double value) {
        if (value < 0) {
            return -value;
        }
        return value;
    }
    public static void main(String[] args) {
        double x = -0.0;
        if (1 / abs(x) < 0) {
            System.out.println("oops");
        }
    }
}

if 中加上条件:value == -0.0 是行不通的,因为 +0.0 == -0.0,可以使用 JDK 中的 Double.compare:

public static double abs(double value) {
    if (value < 0 || Double.compare(value, -0.0) == 0) {
        return -value;
    }
    return value;
}

这样确实有效,不过效率上可能会受到影响,abs 的复杂性就上了一个台阶。

JDK 17 中的实现

java/lang/Double.java

public static int compare(double d1, double d2) {
    if (d1 < d2)
        return -1;           // Neither val is NaN, thisVal is smaller
    if (d1 > d2)
        return 1;            // Neither val is NaN, thisVal is larger

    // Cannot use doubleToRawLongBits because of possibility of NaNs.
    long thisBits    = Double.doubleToLongBits(d1);
    long anotherBits = Double.doubleToLongBits(d2);

    return (thisBits == anotherBits ?  0 : // Values are equal
            (thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
             1));                          // (0.0, -0.0) or (NaN, !NaN)
}

重新实现

参考 JDK 中的实现,重写 abs:

private static final long MINUS_ZERO_LONG_BITS = Double.doubleToLongBits(-0.0);
public static double abs(double value) {
    if (value < 0 || Double.doubleToLongBits(value) == MINUS_ZERO_LONG_BITS) {
        return -value;
    }
    return value;
}

新的问题:NaN 的处理,处理方法:把 doubleToLongBits 改成 doubleToRawLongBits。

private static final long MINUS_ZERO_LONG_BITS = Double.doubleToRawLongBits(-0.0);
public static double abs(double value) {
    if (value < 0 || Double.doubleToRawLongBits(value) == MINUS_ZERO_LONG_BITS) {
        return -value;
    }
    return value;
}

JVM 的 JIT 会替换这次调用为底层的 CPU 寄存器操作,效率非常可观。

PS:如果可以省去这个分支的判断逻辑,JVM 可以给我们更好的性能优化?

  1. 中间涉及 CPU 分支预测(branch predictor),如果预测错误,可能会付出相对昂贵的代码。

    We know that branches are bad. If the CPU branch predictor guesses incorrectly, they can be very expensive.

  2. 有传言说,这个调用(doubleToRawLongBits)会导致浮点数寄存器转换到通用集成器。

    Although there are rumors saying that this call may still lead to a transfer from a floating-point register to a general-purpose register. Still it's very fast.

进一步优化

采用 0 减负数等于正数,并且 0 - -0 = 0 的规则:

public static double abs(double value) {
    if (value <= 0) {
        return 0.0 - value;
    }
    return value;
}

这就是长期以来(直到最新的 Java 17),JDK 使用的方法(return (a <= 0.0D) ? 0.0D - a : a;)。

参考:JDK 17 中的的实现:java/lang/Math.java

再进一步

有人提出了意见,认为目前官方的实现 too slow(6506405: Math.abs(float) is slow #4711)。

这就是 jdk-18+6 中引入的新方案(java/lang/Math.java#L1600~L1604):

public static double abs(double a) {
    return Double.longBitsToDouble(Double.doubleToRawLongBits(a) & DoubleConsts.MAG_BIT_MASK);
}

DoubleConsts.MAG_BIT_MASK 就是 0x7fffffffffffffffL, 0 + 63 个 1。

原理就是,通过位运算,清除符号位(使之为 0)。

参考资料与拓展阅读

#668 《科技爱好者周刊(第 176 期)》阅读笔记

2021-09-17

视觉错觉

这么明显的视觉错觉,让人觉得好神奇!
我下次要拍一张这样的图片,拼在一起看看。
如果拍好了,我就更新在这个下面。

绝对值问题

由于有 +0 和 -0 的问题 (IEEE 754),问题变的复杂起来。

由于需要粘贴大量代码,我另起一篇吧(不简单的绝对值)!

手机学习网页开发

PS: 周刊中提供的链接打不开,我自己在 LinkedIn 上找到了这个用户,拿到了嵌入代码:

一个尼泊尔人教他弟弟学习 Web 开发的故事,由于没有电脑可用,就通过一部手机搭建了开发环境。
然后,经过两个月的学习,这个人成功的教他弟弟初步掌握 HTML、CSS 和 JS。

图片中采用的是什么 App 或者网站,我看不出来。但是阮一峰在上面提到了一个可以通过手机浏览器使用的 Web 开发环境 http://www.webden.dev,我试了一下,感觉还不错,如果接上鼠标键盘,确实可以用来学习。

宇宙飞船可以飞多远?

如果你走得足够远,其实就再也没办法回去了。当你回到故乡,原来的一切都已经消失了。
不过,这也不算什么,归根结底,旅程才是你的故事中最重要的部分。
-- 《如果有无尽的推动力,宇宙飞船可以飞多远?》

这段话让我想起 《三体》 中的一句话:“真正进入太空的人,再也不是人了”。
不过前者指的是时间流速不一致导致的错过,后者是社会意义上的,由于失去故土家园导致的,放弃之前所有的认同,包括国家、民族、价值观、甚至人的身份。