认证与授权
- 认证(authentication,authn):验证某个用户是否具有访问系统的权限。
- 授权(authorization,authz):验证某个用户是否具有访问某个资源的权限。
即认证证明了用户是谁,授权决定了用户能够干什么。
认证的四种方式
常见的认证方式有四种,Basic,Digest,OAuth,Bearer。 在设计系统的时候,要遵循:不要在请求参数中使用明文密码,也不要在任何存储中保存明文密码。
Basic
这是最简单的认证方式,将用户名:密码进行Base64编码后,放入到HTTP Authorization Header中。HTTP请求到达后端,服务就会解码获得用户名和密码,并与数据库中的值进行比较来认证用户。
Basic需要与SSL配置来保证安全性。
Digest
摘要认证。修复了基本认证的严重缺陷。
- 绝不用明文方式在网络上发送密码。
- 可以有效防止重放攻击。
- 有选择地防止对报文内容的篡改。
摘要认证过程如下:
- 客户端请求服务端资源
- 服务端认证失败,返回401 Unauthorized并返返回WWW-Authenticate头,里面包含了认证需要的信息。
- 客户端根据WWW-Authenticate头中的信息,选择加密算法,使用密码随机数nonce,计算出密码摘要response,并再次请求服务端。
- 服务端将客户端提供的密码摘要与服务器内部计算的摘要进行对比,匹配则表明认证通过,然后返回一些与授权会话相关的附加信息,放在Authorization-Info中。
下面是WWW-Authenticate中携带的信息:
使用摘要避免了明文发送密码,同时nonce的随机数每次请求都会变化,可以防止重放攻击。
OAuth
允许第三方用户访问该用户在某个Web服务上存储的私密资源,而无需将用户名和密码提供给第三方应用。
OAuth2.0的授权方式有四种:密码式、隐藏式、拼接式、授权码模式。
1. 密码式
直接将用户名和密码告诉第三方应用,然后第三方应用通过用户名和密码换取令牌。 除非其它授权方式无法使用,并且高度信任第三方应用,否则不要使用这种方法。
流程如下:
- 网站A向用户发出获取用户名和密码的请求。
- 用户同意后,网站A拼接用户名和密码向网站B获取令牌。
- 网站B验证用户身份,给出网站A令牌,网站A凭借令牌可以访问网站B对应权限的资源。
2. 隐藏式
适用于前端,认证流程如下:
- A网站提供一个跳转到B网站的链接,用户点击跳转到B网站,并向用户请求授权。
- 用户登录B网站,同意授权后,跳转回A网站指定的重定向的redirect_url地址,并携带B网站返回的令牌,用户在B网站的数据给A使用。 这种方式存在中间人攻击的风险,只适合一些安全性不高的场景,并且令牌有效时间要非常短。
3. 凭借式
在命令行中请求,适用于没有前端的命令行应用。认证流程如下:
- 应用A在命令行向应用B请求授权,A需要携带B提前颁发的secretID和secretKey,其中secretKey出于安全性考虑,需要在后端发送。
- B收到secretID和secretKey,进行身份验证,通过后返回给A令牌。
4. 授权码式
第三方应用提前申请一个授权码,然后使用授权码来获取令牌。认证流程如下:

- A提供一个跳转到B的链接+redirect_url,用户点击之后跳转到B。
- 用户携带向B网站提前申请的client_id,向B发起身份验证请求。
- 用户登录B网站,通过验证,授权A网站权限,然后网站跳转回redirect_url,其中会有B网站通过验证后的授权码附在url后面。
- 网站A携带授权码向B网站请求令牌,网站B验证授权码后,返回令牌即access_token。
Bearer
也成为令牌认证,核心是bearer token。这是一个加密字符串,通常由服务端根据密钥生成。
客户端请求服务端的时候,必须在请求头中包含Authorization: Bearer <token>。服务端校验token来进行认证。
token最流行的方式是JSON Web Token(JWT)。就通过JWT来描述Bearer认证原理。
在一次登录之后,产生一个有一定有效期的token,每个请求都携带这个token,后端对这个token进行验证。 可以利用文件、缓存、数据库将token存放下来,也可以利用更简单的方式,使用密钥来签发Token,可以省略存储。
认证流程如下:
- 客户端使用用户名和密码登录。
- 服务端验证用户名和密码,通过后,就签发一个token给客户端。
- 客户端将token缓存起来,比如cookie或者localStorage。之后每次请求都会携带token。
- 服务端收到请求之后,会验证token,通过才处理请求。
JWT的格式由三部分组成:
Header和Payload都是存放一些不包含敏感信息的声明,Signature签名则是将base64编码后的Header和Payload通过服务端的密钥进行加密得到的。
可以了解到服务端的密钥对于安全性至关重要。
认证实现
每一个认证当作一个策略,使用策略模式实现。 定义一个策略的接口
// AuthStrategy 定义了用于资源认证的方法集合
type AuthStrategy interface {
AuthFunc() gin.HandlerFunc
}然后定义一个调用策略的执行者
// AuthOperator 用于不同的认证策略之间的切换
type AuthOperator struct {
strategy AuthStrategy
}
// SetStrategy 用来设置成指定的认证策略
func (operator *AuthOperator) SetStrategy(strategy AuthStrategy) {
operator.strategy = strategy
}
// AuthFunc 执行资源认证
func (operator *AuthOperator) AuthFunc() gin.HandlerFunc {
return operator.strategy.AuthFunc()
}这样就只需要为对应的策略实现AuthStrategy接口即可。 下面实现了四种策略
basic策略
实现Basic认证。用来验证用户名和密码。然后为其实现AuthFunc方法即可。
type BasicStrategy struct {
compare func(username string, password string) bool
}jwt策略
利用gin-jwt包来实现。其内部的GinJWTMiddleware包含了认证相关函数。
type JWTStrategy struct {
ginjwt.GinJWTMiddleware
}将需要认证的API加载到需要认证的API路由上,来实现API认证。
jwtStrategy, _ := newJWTAuth().(auth.JWTStrategy)
g.POST("/login", jwtStrategy.LoginHandler)
g.POST("/logout", jwtStrategy.LogoutHandler)
// Refresh time can be longer than token timeout
g.POST("/refresh", jwtStrategy.RefreshHandler)其中jwtStrategy提供的了一些认证函数:
- LoginHandler: 实现了Basic认证,完成登录认证。
- RefreshHandler:刷新Token的过期时间。
- LogoutHandler:用户注销时调用,清楚认证相关信息。
auto策略
根据HTTP头Authorization: Basic xxx和Authorization: Bearer xxx来自动选择使用Basic还是Bearer认证。
tags: 权限认证