React 实现登录访问控制

项目采用前后端分离进行设计,实现token登录验证
该示例只展示React Login Model 设计思路
前端:React 18 , Router V6; 后端(服务端) Springboot 2.4.5

React版本(可从package.json中查看)

"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.12.1",

目的:
对项目进行登录访问控制,即未进行登录的用户不可访问登录可见页面。
未登录时,页面会自动跳转到登录页面,进行登录。

我们需要:

  • 通过【路由】方式访问,需要从路由角度进行访问控制
  • 创建 Login 组件,结合路由控制,来判断是否登录 : 1 未登录实现自动跳转到登录页面 2 登录后可跳转到访问页面

从实现一个简单的表单请求开始

简单页面展示效果

(注:这里无用户注册步骤,只有输入用户名密码后进行登录的功能)

登录验证通常会使用表单发送请求的方式

参考展示示例,如下为简单的html表单请求代码,通过点击button组件,上传输入的数据

<form method="post" action="/post">
    <label for="name">User name</label>
    <input id="name" name="name" />
    <label for="password">Password</label>
    <input id="password" name="password" />
    <button>Login</button>
</form>

鉴于以上方式会刷新浏览器,并经历时代发展已被新方法Ajax替代,改用JavaScript编写代码提交请求

const form = document.querySelector('form');
form.addEventListener('submit', handleSubmit);

function handleSubmit(event) {
  const form = event.currentTarget;
  fetch(form.action, {
    method: form.method,
    body: new FormData(form)
  });
  event.preventDefault();
}
  • 通过获取表单元素,监听submit事件,点击提交按钮使用fetch() 发送接口请求,实现服务端的数据发送。
  • event.preventDefault()可以阻止浏览器的刷新,这就是典型的异步请求,可以使浏览器在不刷新的情况下,完成交互。

更进一步:Fetch API的使用

倘若我们快速点击Login按钮,代码中未设置阻止请求方法,从而会产生大量接口请求,导致服务器过载。
我们需要新的解决方法----在发送请求时,中止上一次请求。而Fetch恰好可以做到。

Fetch是下一代Ajax技术,通过Promise方式来处理数据,具有更简洁明了的API.
从MDN Web Docs 社区 使用Fetch的wiki 可以了解到:

  • Fetch API 提供了一个JavaScript接口,用于访问和操纵 HTTP 管道的一些具体部分,例如请求和响应。
  • 它还提供了一个全局 fetch() 方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。

一个基本的 fetch 请求设置如下:

fetch(URL)
  .then(response => response.json())
  .then(data => console.log(data));
  • Fetch函数通过网络(URL)获取所需数据(返回值可以是Json,HashMap等,方法中展示的是获取JSON文件), 并将其(data)打印到控制台。
  • 代码中'response'只是一个 HTTP 响应,而不是真的JSON。为了获取JSON的内容,我们需要使用response.json() 方法(该方法返回一个将响应 body 解析成JSON格式的Promise对象),再通过.then()函数,获取Pormise对象中的JSON对象

接下来我们可以更近一步,参考fetch(),上传Json、上传多文件、发送带凭证等请求。以下为示例

//Example POST method implementation:
async function postData(url = '', data = {}) {
  // Default options are marked with *
  const response = await fetch(url, {
    method: 'POST', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, *cors, same-origin
    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin', // include, *same-origin, omit
    headers: {
      'Content-Type': 'application/json'
      // 'Content-Type': 'application/x-www-form-urlencoded',
    },
    redirect: 'follow', // manual, *follow, error
    referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
    body: JSON.stringify(data) // body data type must match "Content-Type" header
  });
  return response.json(); // parses JSON response into native JavaScript objects
}

postData('https://example.com/answer', { answer: 42 })
  .then(data => {
    console.log(data); // JSON data parsed by `data.json()` call
  });

  • 该示例涉及 async/await 语法,postData()是一个异步函数,因为被标记为async关键字
  • await fetch('url')开始一个HTTP请求到urlURL,因为await关键字的存在,异步函数被暂停,直到请求完成
  • 当请求完成过后,获取response,response.json().then(data)获取response中的Json文件数据

登录受控组件设计

现在我们可以开始着手创建我们自己的Login组件

一个简单的Login组件示例

function Login(){
    return(
        <div>This is Login page</div>
    );
}
export default Login;

我们使用函数定义了名为Login的组件,在页面上显示返回值“This is Login page”
结合上述我们了解到的方法,我们可以开始构造复合组件

还是引用之前的页面展示:

登录操作:输入框中输入用户的Username, Password,点击Login按钮进行登录

我们设置<Input>元素默认值为"",文本输入的操作会更改Input的值
可借助变更<Input>元素的值,触发HTMLchange事件,从而达到值的存储及后续函数调用

由于我们需要处理多个input的元素的,可参考以下方法写多个input的触发change事件,进而保存表单数据
React handleChange() function explained - handle single/ multiple inputs

  const [users, setUser] = useState({
    username: '',
    password: '',
  });
  const handleChange = (event) => {
    const { name, value } = event.target
    setUser({ ...users, [name]: value })
  }

  return (<div>
          <Form>
            <FormGroup>
              <Label for="username">User Name</Label>
              <Input type="text" name="username" id="username" value={users.username || ''}
                    onChange={handleChange} />
            </FormGroup>
            <FormGroup>
              <Label for="password">Password</Label>
              <Input type="password" name="password" id="password" value={users.password || ''}
                    onChange={handleChange} />
            </FormGroup>
          </Form>
    </div>
  )

接下来我们需要写阻止表单提交的onSubmit方法,即 <Form onSubmit={}>中的{}函数
这里就要结合fetch()async&await一起使用
以下示例为,上传Json文件,解析response中返回的值,并把后端的token值保存到LocalStorage

const handleSubmit = async (event) => {
    event.preventDefault();
    //该方法阻止表单的提交
    await fetch(url, {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(users)
    }) 
      .then(res => {
          res.json().then(data =>{
            localStorage.setItem('token',data.token)
            //把获取到的Json文件里的token值保存到localStorage,便于之后登录验证
          })
      
      })  ;
  }

这样,我们的Login模块就新鲜出炉了
我们在这基础上利用antd增加一些新的功能,同时也可以在Login组件增加跳转功能,及捕捉异常

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button, Container, Form, FormGroup, Input, Label} from 'reactstrap';
import { Card, message } from 'antd';


const Login= () => {

  const navigate = useNavigate();

  const initialFormState = {
    username: '',
    password: '',
  };

  const [users, setUser] = useState(initialFormState);


  const handleChange = (event) => {
    const { name, value } = event.target
    setUser({ ...users, [name]: value })
  }

  const handleSubmit = async (event) => {
    event.preventDefault();
    await fetch('url, {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(users)
    }) 
      .then(res => {
        if (res.status === 200) {
          message.success('Login success.')
          res.json().then(data =>{
            localStorage.setItem('token',data.token)
          })
      } 
      })
      .catch(err =>{
        console.log(err)
        message.error('Login failed')
      })
    ;

    setUser(initialFormState);
    navigate(url);
  }

  return (<div>
      <Container>
        <Card title="Test System">
          <h5>Welcome Back. Please Login your account</h5>
          <hr/>
          <Form onSubmit={handleSubmit}>
            <FormGroup>
              <Label for="username">User Name</Label>
              <Input type="text" name="username" id="username" value={users.username || ''}
                    onChange={handleChange} />
            </FormGroup>
            <FormGroup>
              <Label for="password">Password</Label>
              <Input type="password" name="password" id="password" value={users.password || ''}
                    onChange={handleChange} />
            </FormGroup>
            <FormGroup>
              <Button color="primary" type="submit">Login</Button>{' '}
            </FormGroup>
          </Form>
        </Card>
      </Container>
    </div>
  ) 
};

export default Login;
posted @ 2023-07-05 18:12  希望能摸鱼的凛耶酱  阅读(738)  评论(0)    收藏  举报