概念:
大家想一下,如果graphql可以通过节点来访问数据的话,那我如果加了peter为friend,那我岂不是可以通过以下查询来获得peter的todo:
query {
queryTodo() {
content # 这里的cotent依旧是我本人的
done
owner {
name # 这里的owner的name依旧是我本人
friend(filter:{name:{eq:"peter"}}) { # 注意,这里开始就不对劲了,想要查询peter的信息了
name
todo {
content
}
}
}
}
}
如果不做鉴权,不做jwt认证,不做任何限制,那么,上面的查询语句是可以返回预期结果的,任何人都可以通过自己写的query把它post过来获取所有信息,这是我们所不容许的,所以有了验证身份和鉴权这个需求;
authentication是身份认证的意思,就是认证用户的身份;
authorization是授权的意思,就是认证用户,用户登陆进来之后,他能获得那些数据,哪些数据是他不能拿到的;
从dgraph的v20.11.x
版本开始,type User
的@auth
和@secret
这两个directive才可以并存,之前的版本会出错;
实现:
这里使用的后台是go语言写的jwt鉴权,机制明白了,可以用其他任何编程语言做jwt鉴权!
dgraph有鉴权机制,它的原理是,用户登录,先通过提交用户名和密码到我们自己做的后台(比如用go语言做一个后台),审核了账号密码的正确性后,返回的一个jwt的token,这个token是经过加密的,在go中,是通过"github.com/dgrijalva/jwt-go"库进行jwt的认证开发的,它需要一个secret_key作为密钥,生成一个HS256算法的token(token是一个很长的加密字符串,没有secret_key是无法逆向解出这个token含有的信息的),然后将这个token返回给前端保存起来,前端就可以拿着这个token去dgraph用graphql访问需要权限的数据了,具体步骤如下:
生成token:
package services
import (
"errors"
"time"
"github.com/dgrijalva/jwt-go"
)
const JWT_SECRETKEY = "my_secret_abcd"
type UserData struct {
UserName string `json:"user_name"`
UserRole []string `json:"user_role"`
}
type CustomClaims struct {
UserData `json:"jwt_data"`
jwt.StandardClaims
}
type JWT struct {
SigningKey []byte
}
var (
TokenExpired = errors.New("Token is expired")
TokenNotValidYet = errors.New("Token not active yet")
TokenMalformed = errors.New("That's not even a token")
TokenInvalid = errors.New("Couldn't handle this token:")
)
// 创建一个token
func (j *JWT) CreateToken(claims CustomClaims) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(j.SigningKey)
}
// 更新token
func (j *JWT) RefreshToken(tokenString string) (string, error) {
jwt.TimeFunc = func() time.Time {
return time.Unix(0, 0)
}
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return j.SigningKey, nil
})
if err != nil {
return "", err
}
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
jwt.TimeFunc = time.Now
claims.StandardClaims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix()
return j.CreateToken(*claims)
}
return "", TokenInvalid
}
var myJwt JWT = JWT{
SigningKey: []byte(JWT_SECRETKEY),
}
这里定义了CustomClaims
和JWT
这两个结构体,前者是储存token的解码后的信息的结构体,后者的SigningKey
就是我们上面说的secret_key
。我们生成了token后,只要告诉dgraph我们的secret_key
和我们的加密算法(这里是HS256),那么dgraph就能解出CustomClaims
的user_name和user_role这些信息,go语言定义好这些结构体后,还要调用,这里调用就不细说了,反正最后就是生成一个token返回前端保存。
这里给出一段go的生成token的代码:
theUser := UserData{
UserName: "zhilong",
UserRole: []string{"admin"},
}
// 创建token
claims := CustomClaims{
UserData: theUser,
StandardClaims: jwt.StandardClaims{
NotBefore: time.Now().Unix() - 1000, // 签名生效时间
ExpiresAt: time.Now().Unix() + 60*60*24*7, // 过期时间 一周
Issuer: "sherlock", // 签名的发行者
},
}
token, err := myJwt.CreateToken(claims)
这样生成的token,解析出来大概是这样:
{
"jwt_data": {
"user_name": "zhilong",
"user_role": [
"admin"
]
},
"exp": 1606918541,
"iss": "sherlock",
"nbf": 1606312741
}
定义dgraph的schema时,要在最后面加上一行定义,比如:
type A @auth(...) {
...
}
type B @auth(...) {
...
}
# Dgraph.Authorization {"VerificationKey":"","Header":"","Namespace":"","Algo":""}
这最后一行的注释的各字段的意义是:
VerificationKey:就是告诉dgraph我们上一步说的那个secret_key
是什么,dgraph要用这个来解出token的信息;
Header:告诉dgraph我前端的请求的header中,哪个header字段是带有token的,它好取出来;
Namespace:就是包含data的字段,如上面的"jwt_data";
Algo:就是算法类型,如上面的“HS256”;
一个完整的示例:
type A @auth(...){
...
}
# Dgraph.Authorization {"VerificationKey":"my_secret_abcd","Header":"X-Todo-App-Auth","Namespace":"jwt_data","Algo":"HS256"}
这样,只要我们的每个request都带上X-Todo-App-Auth:token值
这个头部信息,就能验证用户的身份和权限了;
schema的设计: 要限制用户能查看或不能查看哪些内容,或者谁能增添数据,谁能删除数据,都要在@auth的rule中,以下给出几个示例:
基于用户角色的访问权限控制:
type User @auth(
delete: { rule: "{$ROLE: { eq: \"ADMIN\" } }"}
) {
username: String! @id
todos: [Todo]
}
以上的User的定义,限制了能删除user节点的只有role为"ADMIN"的用户,而$ROLE是token中解析出来的变量名,也就是说,带着这个token的query要删除某user,dgraph在解析了token后,得到该用户的$ROLE是“ADMIN”的话,就可以删,否则不行;
and , or & not:
type Todo @auth(
delete: { or: [
{ rule: "query ($USER: String!) { ... }" }, # you are the author graph query
{ rule: "{$ROLE: { eq: \"ADMIN\" } }" }
]}
)
还可以有多个不同条件的rule用and,or连在一起,或者用{not: {rule: "..."}}来表示非逻辑;
add:
type Todo @auth(
add: { rule: """
query ($USER: String!) {
queryTodo {
owner(filter: { username: { eq: $USER } } ) {
username
}
}
}"""
}
){
id: ID!
text: String!
owner: User
}
type User {
username: String! @id
todos: [Todo]
}
上例是add的规则设计,你只能添加自己的todo,而不能添加别人的;
update: 这个update的rule在目前dgraph团队还在开发中,仍然没有文档出来;
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。