https://neo4j.com/graphacademy/training-graphql-apis/04-graphql-apis-auth/
设置权限:
1)仅授权用户可以使用数据
2)顾客只能使用自己的数据
3)管理员可以使用所有数据
1. @auth 指令说明
定义GraphQL Type时可以通过@auth指令添加权限规则。
@auth使用JWTs做授权验证。
参考:
http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。
JWT是令牌Token的编码标准,可用于许多事物,其中包括持有人令牌Bearer Token
格式:Authorization: Bearer
实例:
POST / HTTP/1.1
authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJ1c2VyX2FkbWluIiwicG9zdF9hZG1pbiIsImdyb3VwX2FkbWluIl19.IY0LWqgHcjEtOsOw60mqKazhuRFKroSXFQkpCtWpgQI
content-type: application/json
简单说,用户登录后将其权限体现到Token中,用户之后的每次访问都带着这个Token,系统通过Token可以识别该用户的权限,用户可以做权限范围内的操作。当然,Token是有时效的。
为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
2. 设置签名密钥
Neo4j GraphQL Library启用授权,需要做相应的设置。
无需本地环境,使用在线沙盒环境即可:
1)neo4js andbox(neo4j graph database、neo4j browers)
https://sandbox.neo4j.com/
2)code sandbox(graphql、neo4j graphql library、apollo)
https://codesandbox.io/s/github/johnymontana/training-v3/tree/master/modules/graphql-apis/supplemental/code/04-graphql-apis-auth/begin?file=/.env
解码和验证Token需要的签名密钥
示例:dFt8QaYykR6PauvxcyKVXKauxvQuWQTc
在index.js文件中更新,添加了代码片段(设置密钥):config:{……}
const neoSchema = new Neo4jGraphQL({
typeDefs,
config: {
jwt: {
secret: "dFt8QaYykR6PauvxcyKVXKauxvQuWQTc"
}
}
});
如果codesandbox沙盒中已经添加了,就不用再添加了;
只需要在.env环境中配置neo4j数据库连接即可。
3. 定义两个测试用的Token
1)普通顾客的:可以访问自己的数据
用户名:EmilEifrem7474
角色:customer
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJFbWlsRWlmcmVtNzQ3NCIsInJvbGVzIjpbImN1c3RvbWVyIl0sImlhdCI6MTUxNjIzOTAyMn0.YwftAMDTw6GqmYOFLGHC_f6UiUhfrJAGkZGfrGmiQ2U
2)管理员的:可以操作所有数据
用户名:BobLoblaw7687
角色:admin
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJCb2JMb2JsYXc3Njg3Iiwicm9sZXMiOlsiYWRtaW4iXSwiaWF0IjoxNTE2MjM5MDIyfQ.f2GKIu31gz39fMJwj5_byFCMDPDy3ncdWOIhhqcwBxk
https://jwt.io/
可以通过jwt.io解码这两个Token,如下:
4. 使用@auth添加权限规则:isAuthenticated
isAuthenticated ,最简单的授权
表示只要是注册用户都可以访问。当需要增加注册量时,可以用该授权。
schema.graphql 文件中添加权限规则
extend type Subject @auth(rules: [{isAuthenticated: true}])
代码解释:表示所有注册用户都可以访问题材Suject数据
查询Suject数据
{
subjects {
name
}
}
报错:
Neo4jGraphQLAuthenticationError: Unauthenticated
添加该规则前,所有用户可以查询Subject信息
添加该规则后,只有注册用户可以查询Subject信息
在Http Headers中添加普通顾客的JWT Token,模拟注册用户访问,则可以查询subject数据
普通顾客的Token添加到HTTP HEADERS
{
"authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJFbWlsRWlmcmVtNzQ3NCIsInJvbGVzIjpbImN1c3RvbWVyIl0sImlhdCI6MTUxNjIzOTAyMn0.YwftAMDTw6GqmYOFLGHC_f6UiUhfrJAGkZGfrGmiQ2U"
}
管理员的Token添加到HTTP HEADERS
{
"authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJCb2JMb2JsYXc3Njg3Iiwicm9sZXMiOlsiYWRtaW4iXSwiaWF0IjoxNTE2MjM5MDIyfQ.f2GKIu31gz39fMJwj5_byFCMDPDy3ncdWOIhhqcwBxk"
}
5. 使用@auth添加权限规则:operations & rules
使admin角色可以创建/修改/删除书籍信息
在schema.graphql文件中添加权限规则
extend type Book @auth(rules: [{operations: [CREATE, UPDATE, DELETE], roles: ["admin"]}])
代码解释:
@auth(rules:[具体的权限规则]):表示添加权限规则
CREATE, UPDATE, DELETE:创建、修改、删除权限,只有管理员有这三个权限(该设置不影响其他类型用户读取Book信息)
添加Book信息
mutation {
createBooks(
input: {
title: "Graph Databases"
isbn: "1491930896"
subjects: { connect: { where: { name: "Neo4j" } } }
}
) {
books {
title
subjects {
name
}
}
}
}
报错:
Neo4jGraphQLAuthenticationError: Unauthenticated
如果将admin管理员的Token添加到Http Headers中,则可以添加Book信息
6. 使用@auth添加权限规则:Allow
顾客只能访问自己的订单Order信息,设置权限,在schema.graphql文件中添加:
extend type Order @auth(rules: [{allow: {customer: {username: "$jwt.sub"}}}])
代码解释:
$jwt.sub:表示从JWT Token中取 sub属性,该属性对应于顾客的username
规则含义:允许当前用户访问自己的Order信息
参考:
https://neo4j.com/docs/graphql-manual/current/auth/authorization/allow/
当我们以顾客身份访问订单信息
{
orders {
orderID
books {
title
}
}
}
报错:
Neo4jGraphQLForbiddenError: Forbidden
当我们添加条件,只访问自己的订单信息
{
orders(where: {
customer: {
username: "EmilEifrem7474"
}
}) {
orderID
books {
title
}
}
}
成功,无报错
如果我们不仅仅允许顾客访问自己的订单信息,还允许管理员访问所有订单信息,则规则修改如下:
在schema.graphql文件中修改
extend type Order @auth(rules: [{allow: {customer: {username: "$jwt.sub"}}}, {roles: ["admin"]}])
7. 使用@auth添加权限规则:Where
对比这两条规则,都是只允许顾客访问自己的订单信息
extend type Order @auth(rules: [{allow: {customer: {username: "$jwt.sub"}}}])
extend type Customer @auth(rules: [{where: {username: "$jwt.sub"}}])
后一条规则对应的查询
{
customers {
username
orders {
orderID
books {
title
}
}
}
}
代码解释:
该查询只会返回当前顾客对应的订单ID和书籍信息,代码中没有编写逻辑:username = "xxxxx";
而前一条规则对应的查询中添加了硬逻辑:username = "xxxxx";
8. 使用@auth添加权限规则:operations & bind)
参考:
https://neo4j.com/docs/graphql-manual/current/auth/authorization/bind/
在schema.graphql中添加权限规则:
extend type Review @auth(rules: [{operations: [CREATE,UPDATE], bind: {author: {username: "$jwt.sub"} }}])
顾客添加评价Review信息时,该Review的authon只能是自己,而不能是其他用户
添加评价信息的代码
mutation {
createReviews(
input: {
rating: 1
text: "Borrring"
book: { connect: { where: { title: "Ross Poldark" } } }
author: { connect: { where: { username: "BookLover123" } } }
}
) {
reviews {
text
rating
book {
title
}
}
}
}
代码解释:
author 对应的username是"BookLover123',而不是Token中的"EmilEifrem7474",所以执行报错:
Neo4jGraphQLForbiddenError: Forbidden
如果将其中的username改成"EmilEifrem7474",与Token中一致,则执行通过
备注:这里有一点容易误解,写评价Review的是Type Customer,不是Type Author。在schema.graphql文件中Type Review的定义:
type Review {
rating: Int
text: String
createdAt: DateTime @timestamp
book: Book @relationship(type: "REVIEWS", direction: OUT)
author: Customer @relationship(type: "WROTE", direction: IN)
}
9. 使用@auth添加权限规则:将权限添加到Cypher语句中
在schema.graphq文件中添加:
extend type Query {
booksForCurrentUser: [Book] @auth(rules: [{ isAuthenticated: true }]) @cypher(statement: """
MATCH (c:Customer {username: $auth.jwt.sub})-[:PLACED]->(:Order)-[:CONTAINS]->(b:Book)
MATCH (b)-[:ABOUT]->(s:Subject)<-[:ABOUT]-(rec:Book)
WITH rec, COUNT(*) AS score ORDER BY score DESC
RETURN rec
""")
}
代码解释:
@auth(rules:[……]),定义了注册用户可以使用Book信息
@cypher(statement """……"""),查询并返回与当前用户订单书籍指向同一题材的书籍信息rec
首先,查询所有书籍的title和subject
{
books {
title
subjects {
name
}
}
}
结果如下:
{
"data": {
"books": [
{
"title": "Graph Algorithms",
"subjects": [
{
"name": "Non-fiction"
},
{
"name": "Neo4j"
},
{
"name": "Graph theory"
}
]
},
{
"title": "Inspired",
"subjects": [
{
"name": "Non-fiction"
},
{
"name": "Design"
},
{
"name": "Product management"
}
]
},
{
"title": "Ross Poldark",
"subjects": [
{
"name": "Cornwall"
},
{
"name": "Historical fiction"
}
]
},
{
"title": "Graph Databases",
"subjects": [
{
"name": "Neo4j"
}
]
}
]
}
}
然后,再查询出当前用户订单书籍信息
{
customers {
orders{
orderID
books {
title
subjects {
name
}
}
}
}
}
查询结果
{
"data": {
"customers": [
{
"orders": [
{
"orderID": "9f08e841-0325-413e-b3da-43f156bd8724",
"books": [
{
"title": "Graph Algorithms",
"subjects": [
{
"name": "Non-fiction"
},
{
"name": "Neo4j"
},
{
"name": "Graph theory"
}
]
}
]
}
]
}
]
}
}
最后,使用该查询函数booksForCurrentUser获取规则定义的信息
{
booksForCurrentUser {
title
}
}
{
"data": {
"booksForCurrentUser": [
{
"title": "Inspired"
},
{
"title": "Graph Databases"
}
]
}
}
该功能有点类似于猜你喜欢,比如顾客购买的书籍属于Non-fiction题材,则系统会推荐该题材下的其他书籍给顾客。
10. 练习
基于该codesandbox(已经定义好了权限,只需配置neo4j数据库连接)
https://codesandbox.io/s/github/johnymontana/training-v3/tree/master/modules/graphql-apis/supplemental/code/04-graphql-apis-auth/end?file=/.env
略