一个深漂近10年的程序员立志用未来10年时间去维护一个项目,这个项目的功能未定,用到什么就做什么,遇到什么就写什么。其主要目的有二,一为加深自己的技术深度,二为其他学习者提供参考。
感兴趣的可以跟着我一起做这个项目,不收费、不套路、人间自有真情在。
1、[《开源十年》项目源自于脑海中的一个想法](https://mp.weixin.qq.com/s?__biz=MjM5NTIxNjAwNQ==&mid=2448099718&idx=1&sn=e112ce9848c7ec72a162fe944f035be4&chksm=b2e4466c8593cf7aeb5c66587c747dc8f33926bc8e4779c5e85a332a53464eaf217e10505614&token=1699340681&lang=zh_CN#rd)
2、[项目准备工作](https://mp.weixin.qq.com/s?__biz=MjM5NTIxNjAwNQ==&mid=2448099725&idx=1&sn=82dd647e35bfc9700fdc06aba1298efb&chksm=b2e446678593cf71de957e83bd216f359752e2b7a530260bb72174a2c3812a3be411ba23ae06&token=1699340681&lang=zh_CN#rd)
3、[Blog项目骨架搭建---Go语言实战](https://mp.weixin.qq.com/s?__biz=MjM5NTIxNjAwNQ==&mid=2448099746&idx=1&sn=6d7b23613c3273935a7268615bd6ac9e&chksm=b2e446488593cf5ec1f56498e2a6dd07b455750bcda0af79e2c828584b94eb220d40f680936e&token=1699340681&lang=zh_CN#rd)
4、[Go语言下的Gin详解及Demo实践](https://mp.weixin.qq.com/s?__biz=MjM5NTIxNjAwNQ==&mid=2448099763&idx=1&sn=6a67a4be8567600f59919af2197993bd&chksm=b2e446598593cf4fbc6871163fdf294dc419cbddc811b4b2a22219f04c71c64b29d175cc44ed&token=1699340681&lang=zh_CN#rd)
5、[Go项目配置管理力荐Viper之Viper详解篇](https://mp.weixin.qq.com/s?__biz=MjM5NTIxNjAwNQ==&mid=2448099773&idx=1&sn=b6b1b3788897babb183e4452f96e249a&chksm=b2e446578593cf4150fcb4d178ebeb80ec9216e9c55d9de4114b6b6ed8ed5b60ce09df95324e&token=1699340681&lang=zh_CN#rd)
6、[Go语言中号称最友好的ORM之GORM详解篇](https://mp.weixin.qq.com/s?__biz=MjM5NTIxNjAwNQ==&mid=2448099784&idx=1&sn=e1a50d7cf4f5b5931b8c373c8b9c5e71&chksm=b2e446228593cf341f639ea22d06d658ba918c1994e8e8cdbecc384f4fcca20c0e5e4e469260&token=1699340681&lang=zh_CN#rd)
7、[一周总结,开源十年项目成立一周个人感悟(无代码,纯日记)](https://mp.weixin.qq.com/s?__biz=MjM5NTIxNjAwNQ==&mid=2448099790&idx=1&sn=382c8c2b7e589d4022ca9e60b120dda8&chksm=b2e446248593cf32cdcc95fbab2bf084d7444e057a9fecaab8815f645f63ec89260dad5543c0&token=1699340681&lang=zh_CN#rd)
备注:前面项目中用到的代码已经分享到GitHub中去了,并且以后所有项目中会出现的代码都会提交上去,欢迎查阅。
地址:https://github.com/kaiyuan10nian/kaiyuan10nian 感兴趣的可以点个star哦~开源十年项目的更新首发于公众号:**计算机自学平台**,有兴趣的小伙伴可以持续关注,并欢迎各位加我的微信(kaiyuan10nian)跟我一起完成并推动项目的发展。
---
## blog注册登录及验证权限项目实战之jwt-go的运用
第一阶段的知识点啃的差不多了,这周继续往下进行。因为我们目前要实现的是一个BLOG系统,那么我们肯定要考虑安全问题。不能说任何一个人上来就能随便发布博文,那我们这个blog系统最后就广告、垃圾横行了。所以我们只允许注册了的用户使用我们的系统,没有经过注册的坚决拒绝其使用。
在系统中怎么判断一个人是否是我们的用户应该怎么做呢?那就是用户拿自己的账号和密码给系统进行验证,系统看看他是不是已经注册的用户,如果账号和密码都能匹配上就允许访问,如果匹配不上就拒绝其访问。
但是在实际项目中,我们不可能在用户每次访问的时候都跟用户去要账号和密码,就算你这么去要也不安全呀。所以这个时候我们就需要采取一种加密手段。通过这种加密手段去处理用户的账号和密码,然后在用户每次访问我们的时候带上这一穿加密字符串就行了。
这样既解决了认证的问题还解决了安全的问题,这就是Token,在Go语言中有一个库已经解决了我们这个问题他就是:jwt-go.
安装go-jwt:go get -u github.com/dgrijalva/jwt-go
在开始写代码之前我们先回顾一下流程:
1)用户注册账号设置密码,成功后我们把其信息存入我们的库中。
2)用户登录,服务端先校验用户传入的账号和密码,准确无误后生成一个token给用户。
3)用户访问需要权限的接口时携带这个token即可。
下面开始编程,一探jwt-go的用法。
#### 1、指定加密秘钥
```go
var jwtKey = []byte("kai_yuan_shi_nian")
```
这就相当于是一把钥匙,自己保存好,造锁和开锁都是依托这把钥匙进行的。
#### 2、创建Claims结构体
```go
type Claims struct {
UserId uint
jwt.StandardClaims
}
```
这个结构体就是用来保存信息的,需要内嵌jwt.StandardClaims,这些信息会被保存在我们生成好的token当中。
#### 3、生成token
```go
func ReleaseToken(user model.User) (string,error){
expirationTime := time.Now().Add(7 * 24 * time.Hour)
claims := &Claims{
UserId : user.ID,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),//设置这个token的有效期
IssuedAt: time.Now().Unix(),//发放时间
Issuer: "kaiyuanshinian.tech",//发行方
Subject: "user token",//主题
},
}
//使用指定的签名方式创建签名对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256,claims)
//使用上面指定的钥匙(secret)签名并获取完整的签名后的字符串
tokenString,err := token.SignedString(jwtKey)
if err != nil{
return "",err
}
return tokenString,nil
}
```
在上面这段代码中我们分别设置了有效期和发布方信息。除了上面代码中用到的有效期、签发时间、签发人信息外,还有生效时间(NotBefore)、受众(Audience)、编号(JWTID)等等信息看需求你可以自己添加。
#### 4、解析token
```go
func ParseToken(tokenString string)(*jwt.Token,*Claims,error){
claims := &Claims{}
//用于解析鉴权声明
token,err := jwt.ParseWithClaims(tokenString,claims,func(token *jwt.Token)(i interface{},err error){
return jwtKey,nil
})
return token,claims,err
}
```
解析token就是在用户访问我们的时候,我们系统去解析他所携带的token,去验证它是否是我们正确的用户。我们可以直接根据token获取到它所携带的用户信息(上面的结构体)
#### 5、编写路由
上面完成后,我们开始写我们的业务功能,前面我们已经实现注册功能了,这里就不多说了。首先我们去实现登录功能,直接上代码分析:
```go
r.POST("/v1/account/login", controller.Login)
```
先写路由,指向Login去接收处理,那么我们再看看系统收到该接口访问时的处理。
其处理顺序是:
1)先获取用户访问接口是携带的参数
2)拿这些参数去校验我们的库是否准确(手机号、密码等信息)
3)准确的话发放token并返回登录成功
```go
//登录
func Login(ctx *gin.Context){
DB := common.GetDB()
//获取参数
mobile := ctx.PostForm("mobile")
password := ctx.PostForm("password")
//数据验证
if len(mobile) != 11 {
response.Response(ctx,http.StatusUnprocessableEntity,422,nil,"手机号必须为11位")
return
}
if len(password) < 6 {
response.Response(ctx,http.StatusUnprocessableEntity,422,nil,"密码不能少于6位")
return
}
//判断手机号是否存在
var user model.User
DB.Where("mobile = ?",mobile).First(&user)
if user.ID == 0 {
response.Response(ctx,http.StatusUnprocessableEntity,422,nil,"用户不存在")
return
}
//判断密码是否正确
if err := bcrypt.CompareHashAndPassword([]byte(user.Password),[]byte(password));err != nil{
response.Response(ctx,http.StatusUnprocessableEntity,400,nil,"密码错误")
return
}
//发放token
token,err := common.ReleaseToken(user)
if err != nil{
response.Response(ctx,http.StatusUnprocessableEntity,500,nil,"系统异常")
log.Printf("token generate error : %v",err)
return
}
//返回结果
fmt.Println(token)
response.Success(ctx,gin.H{"token":token},"登录成功")
}
```
在上面的代码中我们就用到了前面实现的token发放模块(ReleaseToken)。
到此,登录功能就已经实现了,那么这个token怎么使用呢?我们继续往下去实现一个可以获取用户信息的接口。
#### 6、token验证及使用
直接先上路由
```go
r.GET("/v1/account/info", common.AuthMiddleware(),controller.Info)
```
各位发现没有,这次注册路由的时候明显与前面两次不同了,多了一个common.AuthMiddleware()。它就是验证token必不可少的一环。
其AuthMiddleware()函数的主要内容如下:
```go
func AuthMiddleware() gin.HandlerFunc{
return func(ctx *gin.Context) {
//先从header中获取token
tokenString := ctx.GetHeader("Authorization")
//然后再去验证token不为空和它的类型
if tokenString == "" || !strings.HasPrefix(tokenString,"Bearer"){
ctx.JSON(http.StatusUnauthorized,gin.H{"code":401,"msg":"权限不足"})
ctx.Abort()
return
}
//抛去前面的7个字节不要(其主要内容就是一个bearer类型声明)
//token示例: ~~Bearer ~~ eyJhbGciOiJIUzI1NiIsInR...Ndafg,前面7位正好是:Bearer <-这里还有一个空格哦
tokenString = tokenString[7:]
token,claims,err := ParseToken(tokenString)
if err != nil || !token.Valid{
ctx.JSON(http.StatusUnauthorized,gin.H{"code":401,"msg":"权限不足"})
ctx.Abort()
return
}
//通过验证后获取claims中的userID
userId := claims.UserId
DB := GetDB()
var user model.User
DB.First(&user,userId)
//检查用户是否存在
if user.ID == 0{
ctx.JSON(http.StatusUnauthorized,gin.H{"code":401,"msg":"用户不存在"})
ctx.Abort()
return
}
//如果用户存在 将user信息存入上下文
ctx.Set("user",user)
ctx.Next()
}
}
```
这里主要做了几件事情:
1)校验token的有效性,有效go on ,无效 stop it.
2)校验完毕后取出token中的claims进行解析,根据上面我们的结构体可知,我们可以得到用户的userID。
3)拿该userID去我们的库中查询是否存在,存在go on ,不存在 stop it.
完事。
后面的就不用多说了,看下我们调用v1/account/info接口给我们返回了什么数据吧。
```json
{
"code": 200,
"data": {
"user": {
"name": "张三",
"telephone": "13523422342"
}
},
"msg": "请求成功"
}
```
目前我们仅仅用到了jwt-go的核心部分,其还有很多可扩展功能有待我们去开发学习,本节就先到这里吧。
ok just it ,see you next...
有疑问加站长微信联系(非本文作者))