软件清单:
https://www.cnblogs.com/sybil-hxl/p/16004080.html
1、web的登录状态保持方式:JWT
解决问题:账号安全验证
登录之后的状态保持,后台核心数据需要校验权限等。一系列涉及账号安全问题都需要我们进行-**签名验签,保持账号信息的可靠性**
状态保持:因为http是一种无状态协议,浏览器请求服务器是无状态的
无状态:指一次用户请求时,浏览器、服务器无法知道之前这个用户做过什么,每次请求都是一次新的请求
无状态原因:浏览器与服务器之间使用socket套接字进行通讯,服务器将请求结果返回给浏览器后,会关闭当前的socket连接,而且服务器也会在处理页面完毕之后销毁页面对象
web应用状态保持的几种方式:Cookie,Session,Token(JWT)
实现授权的方式有:cookie、session、token、OAuth
常见的前后端鉴权方式:Session-Cookie,Token 验证(包括 JWT,SSO),OAuth2.0(开放授权)
认证方式:基于服务器的验证,基于Token的验证
JWT,是最流行的跨域认证解决方案,是一种认证授权机制。
参考:
https://juejin.cn/post/6844904034181070861
Session
实现代码
Cookie
实现代码
1. Token和JWT
原理:服务端根据规范生成一个令牌(token),并且发放给客户端。此时客户端请求服务端的时候就可以携带者令牌,以令牌来证明自己的身份信息。
1.1 Token
1.1.1 代码实现:自定义token
在我的子应用中创建一个utils模块,里面定义了两个方法,一个用来随机生成token,一个用来校验token;
token的生成和验证,都需要“用户id”和“密钥”。
//随机生成token:根据用户id(源数据)和密钥(盐, 不同项目不同盐值),生成token
func get_token(id string) string {
salt := iniConfigYaml() //从config文件读出salt值
s := []byte(salt)
// 方法一:通过Sum传参
// data := []byte(id)
// s := fmt.Sprintf("%x", md5.Sum(data))
// fmt.Println(s)
// 方法二:通过Write传参
i := []byte(id)
h := md5.New()
h.Write(i)
// 若要加盐的话,在第二种方式的基础上再把salt給write进去,如下所示:
h.Write(s)
return hex.EncodeToString(h.Sum(nil)) + "|" + id
}
//校验token
func check_token(token string) bool {
salt := iniConfigYaml() //从config文件读出salt值
s := []byte(salt)
strs := bytes.Split([]byte(token), []byte("|"))
result := strs[0] // 加密后的
i := strs[1] //id
h := md5.New()
h.Write(i)
h.Write(s)
newResult := hex.EncodeToString(h.Sum(nil))
fmt.Println(string(result), newResult)
if string(result) == newResult {
return true
} else {
return false
}
}
使用:
func main() {
token := get_token("sybilId")
fmt.Println(token)
fmt.Println(check_token(token))
}
//运行结果:
b29b4c6954ee4b08085d9d5cfd6bdf43|sybilId
true
至于具体如何实现账号安全验证,具体的前后台交互代码,参考:
https://blog.csdn.net/xiaodsadwwq/article/details/107763004【python实现的】
1.2 JWT
1.2.1 JWT定义
Json Web Token,是基于Json的一个公开规范。JWT是一种Token的实现【?】。
1.2.2 组成
JWT 标准的 Token 有三个部分,header(头部), playload(负载), signature(签名)。
三部分都使用 Base64 编码,用.分隔开,JWT标准的Token形式如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
1.2.2.1 Header
header 部分主要是两部分内容,一个是 Token 的类型,另一个是使用的算法,比如下面类型就是 JWT,使用的算法是 HS256。
{
"typ": "JWT",
"alg": "HS256"
}
上面的内容要用 Base64 的形式编码一下,所以就变成这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
签名算法(JWA)
JSON Web Algorithms (JWA)
JWT 最常见的几种JWA:HS256(HMAC-SHA256) 、RS256(RSA-SHA256) 还有 ES256(ECDSA-SHA256)。
这三种算法都是一种消息签名算法,得到的都只是一段无法还原的签名。区别在于消息签名与签名验证需要的「key」不同:
1 HS256 使用同一个「secret_key」进行签名与验证(对称加密)。一旦 secret_key 泄漏,就毫无安全性可言了。
因此 HS256 只适合集中式认证,签名和验证都必须由可信方进行。
传统的`单体应用`广泛使用这种算法,但是请不要在任何分布式的架构中使用它!
2 RS256 是使用 RSA 私钥进行签名,使用 RSA 公钥进行验证。公钥即使泄漏也毫无影响,只要确保私钥安全就行。
RS256 可以将验证委托给其他应用,只要将公钥给他们就行。
3 ES256 和 RS256 一样,都使用私钥签名,公钥验证。算法速度上差距也不大,但是它的签名长度相对短很多(省流量),并且算法强度和 RS256 差不多。
适用:
对于单体应用而言,HS256 和 RS256 的安全性没有多大差别。
而对于需要进行多方验证的微服务架构而言,显然只有 RS256/ES256 才能提供足够的安全性。
具体算法,参考:
https://www.cnblogs.com/kirito-c/p/12402066.html 【算法】
1.2.2.2 Payload
Payload 是 Token 的详细内容,这些内容里面有一些是标准字段,你也可以添加其它需要的内容。下面是标准字段:
iss:Issuer,发行者
sub:Subject,主题/用户信息
aud:Audience,观众/接收者
exp:Expiration time,过期时间
nbf:Not before
iat:Issued at,发行时间
jti:JWT ID
比如下面这个 Payload ,用到了 iss 发行人,还有 exp 过期时间。另外还有两个自定义的字段,一个是 name ,还有一个是 admin 。
{
"iss": "ninghao.net",
"exp": "1438955445",
"name": "wanghao",
"admin": true
}
使用 Base64 编码以后就变成了这个样子:
eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ
1.2.2.3 Signature
JWT 的最后一部分是 Signature ,这部分内容有三个部分,先是用 Base64 编码的 header.payload ,再用加密算法加密一下,加密的时候要放进去一个 Secret ,这个相当于是一个密码,这个密码秘密地存储在服务端。
header
payload
secret
var encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);
最终token = HMACSHA256(encodedString, '密钥secret');
处理完成以后看起来像这样:
SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
最后这个在服务端生成并且要发送给客户端的 Token 看起来像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
客户端收到这个 Token 以后把它存储下来,下回向服务端发送请求的时候就带着这个 Token 。服务端收到这个 Token ,然后进行验证,通过以后就会返回给客户端想要的资源。
参考:
https://www.cnblogs.com/fps2tao/p/8662301.html
载体验证
截图中,左侧是载体验证,右侧是签名算法JWA。
iss(Issuser):如果签发的时候这个claim的值是“a.com”,验证的时候如果这个claim的值不是“a.com”就属于验证失败
sub(Subject):如果签发的时候这个claim的值是“liuyunzhuge”,验证的时候如果这个claim的值不是“liuyunzhuge”就属于验证失败
aud(Audience):如果签发的时候这个claim的值是“['b.com','c.com']”,验证的时候这个claim的值至少要包含b.com,c.com的其中一个才能验证通过
exp(Expiration time):如果验证的时候超过了这个claim指定的时间,就属于验证失败;nbf(Not Before):如果验证的时候小于这个claim指定的时间,就属于验证失败
iat(Issued at):它可以用来做一些maxAge之类的验证,假如验证时间与这个claim指定的时间相差的时间大于通过maxAge指定的一个值,就属于验证失败
jti(JWT ID):如果签发的时候这个claim的值是“1”,验证的时候如果这个claim的值不是“1”就属于验证失败
注意:在验证一个JWT的时候,签名认证是每个实现库都会自动做的,但是payload的认证是由使用者来决定的。因为JWT里面可能不会包含任何一个标准的claim,所以它不会自动去验证这些claim。
以登录认证来说,在签发JWT的时候,完全可以只用sub跟exp两个claim,用sub存储用户的id,用exp存储它本次登录之后的过期时间,然后在验证的时候仅验证exp这个claim,以实现会话的有效期管理。
参考:
https://www.jianshu.com/p/e34a579c63a0
1.2.3 原理
它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过密钥验证token的正确性,判断是否通过验证。
1.2.4 使用场景
使用JWT在用户和服务器之间传递安全可靠的信息,他的两大使用场景是认证和数据交换。
1.2.5 代码实现:go使用JWT
参考:
https://jwt.io/libraries【JWT测试网站】
1.3 JWT vs Token
观点1:
Token是无状态协议中认证用户的一种形式,不受域名限制,而JWT只是Token的一种实现形式,通过在客户端存储payload来降低服务端压力。
观点2:
jwt和token区别主要体现在接收的信息是否需要进入数据库查询信息。
- 服务端验证客户端发来的
token信息要进行数据的查询操作; - 而JWT验证客户端发来的token信息
使用密钥校验,不用数据库的查询。
参考:
http://www.ujiuye.com/wenda/2021/70163.html
2、单点登录(SSO)
SSO,single sign-on,单点登录
单点登陆:在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统;
多点登陆:多个端同时登录一个帐号,同时收发消息;
消息漫游:在任何一个终端的任何一个实例登录qq,都能够拉取到所有历史聊天消息,这个就是消息漫游;
SSO应用场景:单点登录在大型网站使用非常频繁,例如阿里巴巴网站,在网站的背后是成白上千的子系统,用户的一次操作可能涉及到几十个子系统的协作,如果每个子系统都需要用户验证,不仅用户会疯掉,各系统也会为这种重复授权搞疯。
1. 解决方案
需要解决的两点:解决如何产生和存储信任,系统如何验证这个信任的有效性(1.存储信任 2.验证信任)
1.1 以Cookie作为凭证媒介,通过JSONP实现
用户登录父应用系统后,跟Session匹配的Cookie会存到客户端中,当用户登录子应用系统时,授权应用访问父应用提供的JSONP接口,并在请求中带上父应用系统域名下Cookie,父应用接收到请求,验证用户的登录状态,
如果登录中则返回加密信息,子应用通过解析返回加密信息验证,验证通过则登录成功子应用系统
如果不是登录中则返回重新登录页面
优点:简单方便
缺点:信任全部压在Cookie加密中,如果加密算法泄露了,攻击者可以在本地建立一个实现了登录接口的假冒父应用,通过绑定host来把子应用发起的请求指向本地假冒的父应用并作出回应通过验证,登录特定用户
3. 单点登录 vs 状态保持
单点登录:多个子系统,只需登录一次。
状态保持:针对过期时间。
一个服务器:
cookie+session:服务端创建session,并声称一个cookie字符串,把sessionID放到cookie里并返回给浏览器。下次请求时,浏览器携带cookie请求服务器,服务器识别sessionID,直接读取session中的用户信息。
多个服务器、跨域:
cookie+session:需要一个统一的session数据库来保存会话数据实现共享,这样负载均衡下每个服务器才能正确验证用户身份。
JWT
参考:
https://blog.csdn.net/sky138421/article/details/2299059
3、Go读取各种配置文件
1. 读取json
2. 直接读取yaml
代码:go直接读取yaml
config.yaml,配置文件
# demo Global Configuration
# http configuration
http:
token:
salt: 'HXL'
s: 'ddd'
config/config.go,配置文件结构体
package config
type Http struct {
Token Token `mapstructure:"token" yaml:"token"`
}
type Token struct {
Salt string `mapstructure:"salt" yaml:"salt"`
S string `mapstructure:"s" yaml:"s"`
}
type Config struct {
Http Http `mapstructure:"http" yaml:"http"`
}
global/global.go,全局配置
package global
import "demo/config"
var (
CONFIG = new(config.Config)
)
config_test/test_yaml.go-函数iniConfigYaml(),读yaml文件,调用gopkg.in/yaml.v2包中的yaml.Unmarshal(),返回配置文件中的数值
package config_test
import (
"demo/global"
"io/ioutil"
"gopkg.in/yaml.v2"
)
//从config文件中读出salt值
func IniConfigYaml() string {
file, err := ioutil.ReadFile("config.yaml")
if err != nil {
panic(err)
}
err = yaml.Unmarshal(file, global.CONFIG)
if err != nil {
panic(err)
}
return global.CONFIG.Http.Token.Salt
}
3. Viper读取yaml
yaml.v2只能读取yaml格式的文件,而Viper可以处理多种格式的配置,目前已支持从JSON、TOML、YAML、HCL、INI和Java properties文件中读取配置数据。
Viper还能监视配置文件的变动、重新读取配置文件。在项目中可以用作热加载。
3.1 安装依赖
go get github.com/spf13/viper 或 go get gopkg.in/yaml.v2
3.2 代码:go使用viper读取config
config_test/test_viper.go
package config_test
import (
"fmt"
"os"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
)
//用viper读取config
func main() {
//获取项目的执行路径
path, err := os.Getwd()
if err != nil {
panic(err)
}
fmt.Println(path)
v := viper.New()
// 方法1
// v.AddConfigPath(path) //设置读取的文件路径
// v.SetConfigName("config") //设置读取的文件名
// v.SetConfigType("yaml") //设置文件的类型
// 方法2
v.SetConfigFile(path + "/config.yaml")
if err := v.ReadInConfig(); err != nil {
panic(err)
}
fmt.Println(v.Get("http.token.salt"))
fmt.Println(v.Get("http.token.s"))
// go基于viper实现配置文件热更新
v.WatchConfig()
v.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("config file changed:", e.Name)
if err := v.ReadInConfig(); err != nil {
panic(err)
}
})
}
//输出:
D:\hxl_landspace\go_learn\demo
HXL
ddd
设置未见路径时:直接设置文件路径v.AddConfigPath(".") 报错,好像是因为反斜杠的原因找不到位置,很奇怪,只好通过os获取项目路径。下面是报错:
// v.AddConfigPath(".") // panic: Config File "config" Not Found in "[]"
// panic: Config File "config.yml" Not Found in "[D:\\hxl_landspace\\go_learn\\demo\\config_test]"
参考:
https://www.cnblogs.com/zhangyafei/p/12863384.html
https://www.qb5200.com/article/222931.html【这个好,获取绝对path】
4、CORS跨域访问实现:gin的cors中间件
参考:
https://github.com/gin-contrib/cors【gin支持CORS的中间件】
5、数据库与实体映射的orm框架:GORM
数据库关系模型与实体域模型映射的框架。
GVA_DB
导入包
import "github.com/jinzhu/gorm"
go mod vendor //安装包
(1)
(6)gorm查询数据如何判空
GORM V2 只有在你使用 First、Last、Take 这些预期会返回结果的方法查询记录时,才会返回 ErrRecordNotFound,我们还移除了 RecordNotFound 方法,请使用 errors.Is 来检查错误,例如:
import "errors"
import "github.com/jinzhu/gorm"
err := db.First(&user).Error
errors.Is(err, gorm.ErrRecordNotFound)
参考:
https://oitboy.com/detail?id=40
gorm v2中文文档:
gorm官方API 英文:https://pkg.go.dev/github.com/jinzhu/gorm
6、casbin
casbin:go的权限管理库。
7、生成接口文档:swagger
swagger是一款可以根据resutful风格生成的生成的接口开发文档,并且支持做测试的一款中间软件。
参考:
https://zhuanlan.zhihu.com/p/98560871
8、Zap
zap是uber开源的Go高性能日志库
10、一些词
UUID:指Universally Unique Identifier,翻译为中文是通用唯一识别码,UUID 的目的是让分布式系统中的所有元素都能有唯一的识别信息。如此一来,每个人都可以创建不与其它人冲突的 UUID,就不需考虑数据库创建时的名称重复问题。
Avatar:用户头像。在大众媒体中,“avatar”一词最早出现在1992年Neal Stephenson所写的科学小说《Snow Crash》中,意为在虚拟世界中虚构另一个我,又叫“虚拟实境”。在IT领域,`“avatar”是对计算机用户自身形式的描述。
作者:西伯尔
出处:http://www.cnblogs.com/sybil-hxl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
浙公网安备 33010602011771号