React教程(十六) : 如何实现身份验证 (expressjs)
要解决的问题:
1:JWT存在local storage和cookie里面有什么不同,如何选择?
2:使用ExpressJS作为前端React应用的Web Server。 登录、登出等请求都会由ExpressJS代码处理。
先来看第一个问题,local storage 和 cookie的方案各自优缺点。local storage的缺点是会被脚本注入攻击。 因为JS可以访问local storage中的内容,所以如果攻击者在网站的可编辑部分加入一段JS脚本,就有可能使得运行该页面的用户的身份信息(比如保存在local storage中的JWT)泄漏。
cookie虽然可以通过设置http-only来做到不被JS脚本访问,但cookie同样受到被csrf(跨站请求伪造,cross site request forgery)攻击。
如果后台设置了:Access-Controll-Allow-Origin 不允许其他域的访问,可以解决该问题。 当然,还有其他各种方案,如每次编辑表单时,都从后台请求一个临时token,提交时带上该token。
考虑到React框架会自动对输入做escape,所以用local storage作为选择方案。
再考虑第二个问题:,用ExpressJS发布React应用,参考:
React教程(四) : ExpressJS
https://www.cnblogs.com/Andy1982/p/13922735.html
代码示例:https://github.com/992990831/modernization/tree/main/full-demo
在上述代码的基础上,先执行:npm install node-fetch --save
在server文件夹下新增controller.js, 代码如下:
const fetch = require('node-fetch');
async function login(req, res, next) {
let mockAuthAPI = new Promise((resolve, reject) => {
setTimeout(() => {
let userInfo = {
name: 'Andy',
age: '38',
title: '开发'
};
resolve(userInfo);
}, 2000);
});
let accountInfoJson = await mockAuthAPI;
console.log(accountInfoJson);
return res.status(200).json(accountInfoJson);
}
async function logout(req, res, next) {
res.status(200).send();
}
module.exports.login = login;
module.exports.logout = logout;
修改router.js, 加入以下代码
const authController = require('./controller');
appRouter.get('/login', authController.login);
appRouter.get('/logout', authController.logout);
运行node ./server/index.js
访问http://localhost:3006/login
两秒的延迟后,就会返回hardcode的user info。
模拟接口已经ready,接下来在react中调用该接口。
打开src->util->Request.tsx 修改如下:
export const Get_Order_Url = '/order';
export const Sign_In_Url = '/login';
export const Request = axios.create({
baseURL: 'http://localhost:3006/', //改为express的端口地址。
responseType: 'json'
});
新建 src->services->AuthService.tsx
添加代码如下:
import { Request, Sign_In_Url } from '../util/Request';
export interface UserInfo{
id: number;
note: string;
}
class AuthService {
async authenticate(userName:string='andy', password:string='123456'): Promise<void> {
let success = false;
try {
const resp = await Request.get(Sign_In_Url);
localStorage.setItem('auth_token', JSON.stringify(resp.data));
success = true;
} catch {
console.error('auth failed');
}
return success ? Promise.resolve() : Promise.reject();
}
}
export const authervice = new AuthService();
src -> components -> account -> Account.tsx 代码:
import { debug } from 'console';
import React, { FC, useState } from 'react';
import styled from "styled-components";
import { authervice, UserInfo } from '../../services/AuthService'
const Layout = styled.div`
position: absolute;
left: 0;
top: 0;
width: 90vw;
height: 75vh;
background-color: #ffeedd;
margin: 5vw;
max-width: 100%;
font-size: 2rem;
@media (min-width: 1024px) {
flex-wrap: nowrap;
}
`;
const SignInButton = styled.a`
font-size: 2rem;
color: blue;
height: 50px;
`;
const SignOutButton = styled.a`
font-size: 2rem;
color: red;
height: 50px;
`;
const ResultArea = styled.div`
font-size: 2rem;
color: grey;
margin-top: 5vh;
text-align: center;
`
const LoadingArea = styled.div`
font-size: 1rem;
color: grey;
margin-top: 5vh;
text-align: center;
`
export const Account = () => {
const [userInfo, setUserInfo] = useState<string | null>();
const [loading, setLoading] = useState<boolean>(false);
function login() {
setLoading(true);
authervice.authenticate('andy', '123456').then(() => {
setTimeout(() => {
let userInfo = localStorage.getItem('auth_token')
setUserInfo(userInfo);
setLoading(false);
}, 500)
});
}
function logout() {
localStorage.removeItem('auth_token');
setUserInfo(null);
}
return (
<Layout>
<div style={{ textAlign: 'center' }}>
{
userInfo ?
<SignOutButton onClick={logout} href='javascript:void(0)'>登出</SignOutButton> :
<SignInButton onClick={login} href='javascript:void(0)'>登录</SignInButton>
}
</div>
{
loading && <LoadingArea>登录中......</LoadingArea>
}
<ResultArea>
{userInfo}
</ResultArea>
</Layout>
)
}
由于使用nodejs作为web server,所以运行上述代码的过程是:
npm run build
node ./server/index.js
访问 http://localhost:3006/account
单击登录按钮