简介
谈起web应用,登录鉴权是必不可少的一步。beego应用当然也需要鉴权。今天我结合我目前在做的项目谈一下jwt鉴权。
首先大家应该要知道JWT是什么
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
使用场景
Authorization (授权) :这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
Information Exchange (信息交换) :对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWTs可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。
工具包
GO语言使用JWT,比较常用的是JWT-go 和JWT-Auth
其下载命令分别如下:
go get github.com/dgrijalva/jwt-go
go get github.com/adam-hanna/jwt-auth
因为我是利用jwt-go鉴权的,所以也接下来只讲诉jwt-go的应用,所有代码copy后就能使用。
代码
首先创建一个tokenUtil.go 文件,主要是处理一些token的操作。这里面有三个函数。
创建token
这一步主要是在用户登录的时候,如果用户名密码正确,调用此方法,生成相应的token,返回给用户
func CreateToken(Phone string) string {
token := jwt.New(jwt.SigningMethodHS256)
claims :=make(jwt.MapClaims)
tokenexp,_ :=strconv.Atoi(beego.AppConfig.String("Tokenexp"))
claims["exp"]=time.Now().Add(time.Hour * time.Duration(tokenexp)).Unix()
claims["iat"] =time.Now().Unix()
claims["phone"]=Phone
token.Claims=claims
tokenString,_ :=token.SignedString([]byte(beego.AppConfig.String("TokenSecrets")))
return tokenString
}
我这边使用手机号去登录的,手机号是唯一的,所以我利用手机号创建的token。
检验token
这一步主要是每次用户访问项目接口,检查一下是否携带了token字段,如果携带了,则解析获取相应的用户名,然后查看是否有相应的权限。
func CheckToken(tokenString string) string {
Phone :=""
token,_ :=jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _,ok :=token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil,fmt.Errorf("Unexpected signing method")
}
return []byte(beego.AppConfig.String("TokenSecrets")),nil
})
claims,_:=token.Claims.(jwt.MapClaims)
Phone =claims["phone"].(string)
return Phone
}
当然了真正的项目中不会这么简单的鉴权,现在的方式。只要有人能拿到token。然后完全可以畅通无阻的用任何脚本去访问。
登录认证模块
func AuthenticateUserForLogin(loginName,password string)(*models.AdminUser,error){
if len(password) ==0 || len(loginName)==0 {
return nil,errors.New("Error:用户或者密码为空")
}
data :=[]byte(password)
has :=md5.Sum(data)
password =fmt.Sprintf("%x",has)
v,err:=models.GetAdminUserByPhone(loginName) //数据库查询语句。自己写的
fmt.Println(v)
if err !=nil {
return nil,errors.New("Error:未找到该用户")
}else if v.Password!=password {
return nil,errors.New("Error:密码错误")
}else{
return v,nil
}
}
我这边只是简单的md5加密了一下密码,然后根据手机号去查询用户,查询得到后判断一下密码是否一致,如果一致,则返回用户和token。
model模块
我是用bee工具创建的model,因为在创建的时候生成的查询语句只是根据id去查询的,所以,我写了一个根据手机号查询的用户的函数。(如果大家不懂bee工具,可以看看我之前的文章,或者百度一下)
func GetAdminUserByPhone(phone string)(v *AdminUser,err error){
o :=orm.NewOrm()
v =&AdminUser{} //数据库模型
v.Phone=phone //将要查询的字段赋值
if err =o.Read(v,"Phone"); err==nil { //o.Read() 方法第一个是模型,第二个开始是要查询的字段,多个字段则用逗号隔开就好了,比如 (“字段1”,“字段二”)
return v,nil
}
return nil,err
}
这个实际做的时候要根据自己的数据库灵活发挥,
controller 模块
这里我做了一下登录的操作
func(c *AdminUserController) Login(){
var v models.AdminUser
//读取解析json数据并复制给AdminUser
err :=json.Unmarshal(c.Ctx.Input.RequestBody,&v)
if err !=nil {
fmt.Println("json Unmarshal error:",err.Error())
}
login_name :=v.UserName
password :=v.Password
//登录检验
user,err :=utils.AuthenticateUserForLogin(login_name,password)
if user ==nil {
c.Data["json"]=map[string]interface{}{"success": -1, "message": err}
c.ServeJSON()
return
}
//创建token
tokenString :=utils.CreateToken(login_name)
c.Ctx.Output.Header("TOKEN",tokenString)
c.Ctx.SetCookie("token",tokenString,"3600","/")
c.Ctx.SetCookie("USERID",user.Phone,"3600","/")
c.Data["json"]=map[string]interface{}{"success":0, "msg": "登录成功", "user": user}
c.ServeJSON()
}
同时我们要在router配置路由,由于我这个是bee api生成的项目,所以就比着葫芦画瓢,做了一下路由配置,这个配置在router里面,如果你不是bee api项目,路由配置可能不一样,这个也可以百度一下,不是很难。网上抄一下就好了。
在当前controller文件中,首先要将这个函数访问URLMapping中。
func (c *AdminUserController) URLMapping() {
c.Mapping("Post", c.Post)
c.Mapping("GetOne", c.GetOne)
c.Mapping("GetAll", c.GetAll)
c.Mapping("Put", c.Put)
c.Mapping("Delete", c.Delete)
c.Mapping("Login", c.Login) //这就是创建的登录接口
}
然后在router下配置,根据map去取值。
beego.GlobalControllerRouter["nuoAdmin/controllers:AdminUserController"] = append(beego.GlobalControllerRouter["nuoAdmin/controllers:AdminUserController"],
beego.ControllerComments{
Method: "Login",
Router: `/Login`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
现在我们可以访问请求试一下。
成功的拿到了token,然后我们试一下访问其他接口
接下来我们做一下中间件,拦截一下所有不携带token的请求。
在router.go文件中,添加:
beego.InsertFilter("*",beego.BeforeRouter,cors.Allow(&cors.Options{
AllowAllOrigins:true,
AllowMethods:[]string{"GET","POST","PUT","DELETE","OPTION"},
AllowHeaders: []string{"Origin", "Authorization", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
ExposeHeaders: []string{"Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
AllowCredentials: true,
}))
beego.InsertFilter("/*",beego.BeforeRouter, func(context *context.Context) {
if context.Request.RequestURI !="/v1/admin_user/Login" {
//此处可以校验一下ip,设备等
cookie,err :=context.Request.Cookie("token")
fmt.Print(strings.Index(context.Request.RequestURI,"/v1/admin_user/Login"))
if strings.Index(context.Request.RequestURI,"/v1/admin_user/Login")>=0 || strings.Index(context.Request.RequestURI,"/static")>=0 {
}else if err !=nil || utils.CheckToken(cookie.Value)=="" {
context.ResponseWriter.Write([]byte("您无权访问"))
}
}
})
//此处上面的是要添加的字段
beego.AddNamespace(ns)
InsertFilter方法第一个是拦截的路由,我是将所有的路由都进行了拦截的,大家到时候也可以根据实际情况只拦截需要的路由,我现在这样写,同样把登录请求拦截了,这时候每次请求登录也会进行校验,但是登录是没有token的所以,我在这里做了一个判断,如果是登录接口,则不进行判断校验。
这时候再访问其他接口,则会如下图所示:
自此,一个简单的登录鉴权做完了。是不是很简单。