在本教程中,我们将讨论授权以及如何用AWS Amplify的DataStore实现它。首先,让我们来了解一下什么是授权和认证。
授权--不同的用户有不同的操作,他们可以执行。认证- 确保某人是他们所说的人,例如通过让他们输入密码。
本教程将绕开React和AWS Amplify的教学--如果你是第一次接触React教程和Amplify管理界面教程,请查看这个教程。你还需要了解React Router。
我创建了一个带有一些启动代码的 repo,以便进入本教程的相关部分。如果你想跟着学的话,就去克隆它吧。在克隆的目录中运行npm i ,以获得所有需要安装的软件包。
我们将建立一个博客平台,有一个前台和后台的认证系统,有管理角色和限制内容创建者的某些操作。我们首先会有博客--类似于Medium出版物或Blogger博客。只有管理员用户能够创建新的博客,尽管任何人都可以查看博客的列表。博客内将有任何人都可以查看的帖子,但只有创建博客的人能够更新或删除博客。
使用管理界面创建一个博客
首先,我们需要为我们的应用程序创建数据模型。你可以去Amplify沙盒,以便开始。我们将创建两个模型,一个博客和一个帖子。博客将是一个出版物,它有一个附加的帖子集合。博客将只有一个名字,然后博客将有一个标题和内容。所有的字段都是字符串,我还把名字和标题作为必填字段。这两个模型之间也将有一个1:n的关系。
现在,按照管理界面提供的指导过程,继续部署你的数据模型。一旦部署完毕,进入管理界面,创建几个博客和几个帖子。
然后,我们将添加认证。在管理界面,点击 "认证 "标签,然后配置认证。我部署的是默认选项。
一旦你的认证部署完毕,添加授权规则。首先,点击博客模型,在右边的面板上,配置授权。在 "任何用API Key认证的人都可以...... "下取消对创建、更新和删除的检查。-- 我们将允许任何人查看博客,但只有管理员可以修改它们。然后,点击添加授权规则下拉菜单。从这里点击 "特定组 "下的 "创建新",并将你的组命名为 "管理员"。允许管理员用户执行所有行动。
现在我们将配置帖子的授权。选择该模型,并再次将 "任何用API密钥认证的人 "的权限改为 "阅读 "帖子。然后将 "启用所有者授权 "切换到开启状态。在 "拒绝其他认证用户对所有者的记录进行这些操作:"选择 "更新 "和 "删除" -- 我们希望任何人都能阅读一个帖子,但只有帖子的所有者能够修改现有的帖子。我们还需要允许某人能够创建帖子!在 "添加授权规则 "下,然后在 "任何登录用户使用的认证 "下,然后选择 "Cognito"。
回到你的代码的目录,用你的应用ID运行Amplify pull -- 你可以在管理界面的 "本地设置说明 "下找到这个命令。如果你没有使用上面的克隆仓库,请安装Amplify的JavaScript和React库。
$ npm i aws-amplify @aws-amplify/ui-react
你还需要在你的index.js 文件中配置Amplify,以便你的前端与你的Amplify配置相连。你还需要在这个步骤中配置多重认证。
import Amplify, { AuthModeStrategyType } from 'aws-amplify'
import awsconfig from './aws-exports'
Amplify.configure({
...awsconfig,
DataStore: {
authModeStrategyType: AuthModeStrategyType.MULTI_AUTH
}
})
实现认证
首先,我们需要为我们的网站实现认证,以便用户可以登录,不同的账户可以执行不同的操作。我创建了一个<SignIn> 组件,并有一个路由到它。然后,添加withAuthenticator 高阶组件来实现用户认证流程
// SignIn.js
import { withAuthenticator } from '@aws-amplify/ui-react'
import React from 'react'
import { Link } from 'react-router-dom'
function SignIn () {
return (
<div>
<h1>Hello!</h1>
<Link to='/'>home</Link>
</div>
)
}
+ export default withAuthenticator(SignIn)
然后,我们将所有的博客加载到应用程序的主页上。我从下面的代码开始,将为我的应用程序实现不同的路由。如果你使用的是克隆的模板,你的代码中已经有了这个。你还需要为BlogPage 、PostPage 和BlogCreate 创建React组件--现在这些可以只是空的组件。
import './App.css'
import { Auth } from 'aws-amplify'
import { DataStore } from '@aws-amplify/datastore'
import { useEffect, useState } from 'react'
import { Switch, Route, Link } from 'react-router-dom'
import BlogPage from './BlogPage'
import PostPage from './PostPage'
import BlogCreate from './BlogCreate'
import SignIn from './SignIn'
import { Blog } from './models'
function App () {
const [blogs, setBlogs] = useState([])
return (
<div className='App'>
<Switch>
<Route path='/sign-in'>
<SignIn />
</Route>
<Route path='/blog/create'>
<BlogCreate isAdmin={isAdmin} />
</Route>
<Route path='/blog/:name'>
<BlogPage user={user} />
</Route>
<Route path='/post/:name'>
<PostPage user={user} />
</Route>
<Route path='/' exact>
<h1>Blogs</h1>
{blogs.map(blog => (
<Link to={`/blog/${blog.name}`} key={blog.id}>
<h2>{blog.name}</h2>
</Link>
))}
</Route>
</Switch>
</div>
)
}
export default App
在<App> 组件中,首先导入Blog 模型。
import { Blog } from './models'
然后,创建一个useEffect ,它将被用来向该组件提取数据。
// create a state variable for the blogs to be stored in
const [blogs, setBlogs] = useState([])
useEffect(() => {
const getData = async () => {
try {
// query for all blog posts, then store them in state
const blogData = await DataStore.query(Blog)
setBlogs(blogData)
} catch (err) {
console.error(err)
}
}
getData()
}, [])
然后,如果有的话,我们要获取当前的用户。我们也要检查并查看该用户是否是管理员。
const [blogs, setBlogs] = useState([])
+ const [isAdmin, setIsAdmin] = useState(false)
+ const [user, setUser] = useState({})
useEffect(() => {w
const getData = async () => {
try {
const blogData = await DataStore.query(Blog)
setBlogs(blogData)
// fetch the current signed in user
+ const user = await Auth.currentAuthenticatedUser()
// check to see if they're a member of the admin user group
+ setIsAdmin(user.signInUserSession.accessToken.payload['cognito:groups'].includes('admin'))
+ setUser(user)
} catch (err) {
console.error(err)
}
}
getData()
}, [])
最后,我们要根据用户是否已经登录来呈现不同的信息。首先,如果用户已经登录,我们要显示一个注销按钮。如果他们已经注销,我们要给他们一个链接到登录表格。我们可以用下面的三元组来做这个。
{user.attributes
? <button onClick={async () => await Auth.signOut()}>Sign Out</button>
: <Link to='/sign-in'>Sign In</Link>}
你也可以添加这个片段,使管理员用户有一个链接来创建一个新的博客。
{isAdmin && <Link to='/blog/create'>Create a Blog</Link>}
我把这两行都加到了我网站的首页路线上。
<Route path='/' exact>
<h1>Blogs</h1>
+ {user.attributes
+ ? <button onClick={async () => await Auth.signOut()}>Sign Out</button>
+ : <Link to='/sign-in'>Sign In</Link>}
+ {isAdmin && <Link to='/blog/create'>Create a Blog</Link>}
{blogs.map(blog => (
<Link to={`/blog/${blog.name}`} key={blog.id}>
<h2>{blog.name}</h2>
</Link>
))}
</Route>
下面是App组件的完整代码。
博客页面
现在,我们将实现显示一个博客的组件。我们将首先查询得到博客的信息,然后得到附在它身上的帖子。在我的应用程序中,我使用React Router为每个博客创建了遵循url模式的博客详细页面/blog/:blogName 。然后我将使用:blogName 来获取该博客的所有信息。
我将从一个渲染每个帖子的页面开始。我还将添加一个按钮来创建一个新的帖子,但只有在有用户的情况下。
import { DataStore } from 'aws-amplify'
import { useEffect, useState } from 'react'
import { useParams, Link } from 'react-router-dom'
import { Post, Blog } from './models'
export default function BlogPage ({ user }) {
const { name } = useParams()
const createPost = async () => {
}
return (
<div>
<h1>{name}</h1>
{user && <button onClick={createPost}>create new post</button>}
{
posts.map(post => (
<h2 key={post.id}>
<Link to={`/post/${post.title}`}>
{post.title}
</Link>
</h2>)
)
}
</div>
)
}
然后,我将添加这个useEffect ,以便加载所有的帖子。
// body of BlogPage component inside BlogPage.js
const [blog, setBlog] = useState({})
const [posts, setPosts] = useState([])
useEffect(() => {
const getData = async () => {
// find the blog whose name equals the one in the url
const data = await DataStore.query(Blog, p => p.name('eq', name))
setBlog(data[0].id)
// find all the posts whose blogID matches the above post's id
const posts = await DataStore.query(Post, p => p.blogID('eq', data[0].id))
setPosts(posts)
}
getData()
}, [])
让我们也为 "创建新帖 "按钮添加功能,使你能够在点击时创建一个新帖!所有者字段将自动填充为当前登录的用户。
const createPost = async () => {
const title = window.prompt('title')
const content = window.prompt('content')
const newPost = await DataStore.save(new Post({
title,
content,
blogID: blog.id
}))
}
BlogPage组件的最终代码。
博客创建
让我们也使人们能够创建一个新的博客。在<BlogCreate> 组件中。首先,创建一个标准的React表单,允许用户创建一个新的博客。
import { DataStore } from 'aws-amplify'
import { useState } from 'react'
import { Blog } from './models'
export default function BlogCreate ({ isAdmin }) {
const [name, setName] = useState('')
const createBlog = async e => {
e.preventDefault()
}
return (
<form onSubmit={createBlog}>
<h2>Create a Blog</h2>
<label htmlFor='name'>Name</label>
<input type='text' id='name' onChange={e => setName(e.target.value)} />
<input type='submit' value='create' />
</form>
)
}
现在,通过添加以下内容实现createBlog 功能。
const createBlog = async e => {
e.preventDefault()
// create a new blog instance and save it to DataStore
const newBlog = await DataStore.save(new Blog({
name
}))
console.log(newBlog)
}
最后,在表单周围添加一个条件--我们只想在用户是管理员的情况下呈现它
if (!isAdmin) {
return <h2>You aren't allowed on this page!</h2>
} else {
return (
<form>
...
</form>
)
}
下面是这个组件的全部内容。
帖子页面
最后一个要实现的组件!这个是帖子的详细页面。我们将实现一个编辑表单,以便内容所有者可以编辑他们的帖子。首先,为帖子创建一个React表单。我们将再次使用React Router来发送帖子的名称到组件中。
import { DataStore } from 'aws-amplify'
import { useEffect, useState } from 'react'
import { useParams, Link } from 'react-router-dom'
import { Post } from './models'
export default function PostPage ({ user }) {
const { name } = useParams()
const [post, setPost] = useState([])
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const handleSubmit = async e => {
e.preventDefault()
}
return (
<div>
<h1>{name}</h1>
<form onSubmit={handleSubmit}>
<label>Title</label>
<input type='text' value={title} onChange={e => setTitle(e.target.value)} />
<label>Content</label>
<input type='text' value={content} onChange={e => setContent(e.target.value)} />
<input type='submit' value='update' />
</form>
</div>
)
}
然后,我们将创建一个useEffect ,从DataStore获取帖子的信息并在表单中呈现。请注意,如果你有两个名字相同的帖子,这将不能很好地工作在一个更大规模的应用程序中,你会希望每个帖子的URL都有一些区别。
useEffect(() => {
const getData = async () => {
const posts = await DataStore.query(Post, p => p.title('eq', name))
setPost(posts[0])
setTitle(posts[0].title)
setContent(posts[0].content)
}
getData()
}, [])
然后,我们需要实现handleSubmit。我们要复制原始帖子,更新所需的属性,并将其保存到DataStore。
const handleSubmit = async e => {
e.preventDefault()
await DataStore.save(Post.copyOf(post, updated => {
updated.title = title
updated.content = content
}))
}
最后,在return ,我们只想在用户拥有该帖子的情况下呈现表单。在表单的外面,添加以下条件,只有当帖子的所有者是该用户时才会渲染它Amplify会自动为我们创建所有者字段。每当你创建一个新的帖子时,它也会为你填入!
{user.attributes && (post.owner === user.attributes.email) && (
<form onSubmit={handleSubmit}>
...
</form>
)}
下面是该组件的最终代码。
总结
在这篇文章中,我们使用Amplify的DataStore multi-auth来实现基于用户的角色和内容所有权的不同权限。你可以继续用更多的表单、样式和数据渲染来扩展这个。我很想听听你对这个应用程序和这个新的Amplify功能的看法!