React 登录授权案例(二十四)

1,139 阅读5分钟

案例代码:React/Login · 云牧/exampleCode - 码云 - 开源中国 (gitee.com)

案例图示:

image-20220227232751611

image-20220227232920270

image-20220227232843733

1.前置准备

1.antd的基本使用:

	(1).yarn add antd
	(2).引入样式:import 'antd/dist/antd.css';
	(3).根据文档引入组件

2.antd按需引入样式

	(1).yarn add react-app-rewired customize-cra babel-plugin-import
	(2).修改package.json,内容如下:
		.....
		  "scripts": {
				"start": "react-app-rewired start",
				"build": "react-app-rewired build",
				"test": "react-app-rewired test",
				"eject": "react-scripts eject"
			},
	  .....
	(3).在根目录下创建:config-overrides.js,内容如下:
		const { override, fixBabelImports } = require('customize-cra');
		module.exports = override(
				fixBabelImports('import', {
					libraryName: 'antd', //对哪个库进行按需引入
					libraryDirectory: 'es', //样式模块作为ES6模块处理
					style: 'css',//处理的css样式
				}),
			);

3.antd自定义主题

	(1).yarn add less less-loader
	(2).修改config-overrides.js,内容如下:
			const { override, fixBabelImports,addLessLoader } = require('customize-cra');
			module.exports = override(
					fixBabelImports('import', {
						libraryName: 'antd', //对哪个库进行按需引入
						libraryDirectory: 'es', //样式模块作为ES6模块处理
						style: true,//处理原文件样式
					}),
					addLessLoader({
						lessOptions:{
							javascriptEnabled: true, //允许js更改修改antd的less文件中的变量
							modifyVars: { '@primary-color': 'green' },
						}
					}),
				);

4.antd-mobile配置:

	修改config-overridesconst { override, fixBabelImports,addLessLoader,addPostcssPlugins } = require('customize-cra');
		module.exports = override(
				fixBabelImports('import', {
					libraryName: 'antd-mobile', //对哪个库进行按需引入
					libraryDirectory: 'es', //样式模块作为ES6模块处理
					style: true,//处理原文件样式
				}),
				addLessLoader({
					lessOptions:{
						javascriptEnabled: true, //允许js更改修改antd的less文件中的变量
						// modifyVars: { '@primary-color': 'green' }, //antd要修改的是@primary-color
						modifyVars: { 
							"@brand-primary": "green",
							"@brand-primary-tap":"rgb(1, 99, 1);"
							},
					}
				}),
				addPostcssPlugins([
					require("postcss-px2rem")({ 
						remUnit: 37.5 //按照设计师的设计稿计算出来的根字体大小
					})
				])
			);

5.react脚手架中的rem适配

1.初始化react脚手架

create-react-app he11lo_react

2.安装依赖

yarn add postcss-px2rem customize-cra react-app-rewired

3.根目录下建立 config.overrides.js 内容如下:

module.exports = override( addPostcssPlugins([
  addPostcssPlugins([
    require("postcss-px2rem")({
      remUnit: 37.5, //按照设计师的设计稿计算出来的根字体大小
    }),
  ])
);

4.更改 package.json 的启动命令:

"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
},

5.src 目录下建立 utils/rem.js,内容如下(给不同设备设置根字体大小):

/* 
	该文件是移动端适配器
*/
function adapter() {
  //获取布局视口宽度,因为开启了理想视口,布局视口=设备横向独立像素值
  const dpWidth = document.documentElement.clientWidth;
  //计算根字体大小
  const rootFonstSize = dpWidth / 10;
  //设置根字体大小
  document.documentElement.style.fontSize = rootFonstSize + "px";
}
adapter();
window.onresize = adapter;

2.项目编码

app.jsx

import React, { Component } from 'react'
import routes from './config/routes'
import {Route,Switch} from 'react-router-dom'
import './utils/rem'

export default class App extends Component {
	render() {
		return (
			<div>
				<Switch>
					{routes.map( routeObj => <Route key={routeObj.path} {...routeObj}/> )}
				</Switch>
			</div>
		)
	}
}

config/routes

// 路由配置文件,项目中所有路由都在此配置
import Login from "../pages/Login";
import UserCenter from "../pages/UserCenter";

const routes = [
  //登录路由
  {
    path: "/login",
    component: Login,
  },
  //个人中心路由
  {
    path: "/usercenter",
    component: UserCenter,
  },
];

export default routes;

pages/Login/index.jsx

// 登录组件
import React, { Component } from "react";
import { NavBar, InputItem, Button, Toast } from "antd-mobile";
import { reqVerifyCode, reqLogin } from "../../api";
import github from "./imgs/github.png";
import qq from "./imgs/qq.png";
import wechat from "./imgs/wechat.png";
import { phoneReg, verifyCodeReg } from "../../utils/reg";
import "./index.less";

export default class Login extends Component {
  state = {
    phone: "",
    verifyCode: "",
    time: 60,
    canClick: true,
  };

  //保存数据
  saveData = (type) => {
    return (value) => {
      //如果用户输入的数据,符合要求,那么维护进状态
      if (type === "phone" && phoneReg.test(value)) return this.setState({ [type]: value });
      if (type === "verifyCode" && verifyCodeReg.test(value))
        return this.setState({ [type]: value });
      this.setState({ [type]: "" });
    };
  };

  //获取验证码的回调
  getVerifyCode = async () => {
    //获取手机号、按钮状态
    const { phone, canClick } = this.state;
    //如果按钮不可点击,终止逻辑
    if (!canClick) return;
    //校验手机号
    if (!phone) return Toast.fail("手机号格式不合法", 1);
    //更新状态让获取验证码按钮不可点击
    this.setState({ canClick: false });
    //开启定时器更新倒计时
    this.timer = setInterval(() => {
      let { time } = this.state;
      time--;
      //若倒计时结束
      if (time <= 0) {
        clearInterval(this.timer);
        return this.setState({ canClick: true, time: 60 });
      }
      this.setState({ time });
    }, 1000);
    //发送请求
    await reqVerifyCode(phone);
    Toast.success("验证码发送成功");
  };

  //登录的回调
  login = async () => {
    //获取手机号,验证码
    const { phone, verifyCode } = this.state;
    if (!(phone && verifyCode)) return Toast.fail("请检查手机号或验证码格式", 2);
    const result = await reqLogin(phone, verifyCode);
    const { code, message } = result;
    if (code === 20000) {
      Toast.success("登录成功!");
      this.props.history.push("/usercenter");
    } else Toast.fail(message);
  };

  componentWillUnmount() {
    clearInterval(this.timer);
  }

  githubLogin = () => {
    window.location.href =
      "https://github.com/login/oauth/authorize?client_id=908ba7f76c6df2f6ac33";
  };

  render() {
    const { time, canClick, phone, verifyCode } = this.state;
    return (
      <div className="login">
        {/* 顶部导航区 */}
        <NavBar mode="light">手机验证码登录</NavBar>

        {/* 手机验证码输入框 */}
        <InputItem onChange={this.saveData("phone")} clear placeholder="请输入手机号" />

        {/* 验证码输入框 */}
        <div className="verify-input">
          <InputItem onChange={this.saveData("verifyCode")} clear placeholder="请输入验证码" />
          <button
            className="verify-btn"
            style={{ color: canClick ? "#F40700" : "gray" }}
            onTouchEnd={this.getVerifyCode}
          >
            获取验证码{canClick ? "" : `(${time})`}
          </button>
        </div>

        {/* 登录按钮 */}
        <Button
          onTouchEnd={this.login}
          type="primary"
          disabled={phone && verifyCode ? false : true}
        >
          登录
        </Button>

        {/* 底部其他登录方式区 */}
        <footer className="footer">
          <span className="other">其他登录方式</span>
          <div className="login-type">
            <img onTouchEnd={this.githubLogin} src={github} alt="" />
            <img src={qq} alt="" />
            <img src={wechat} alt="" />
          </div>
          <span className="footer-text">
            未注册的手机号,验证后会自动创建硅谷账号,登录即代表您同意:
            <a href="http://www.atguigu.com">《硅谷隐私政策》</a>
          </span>
        </footer>
      </div>
    );
  }
}

api/ajax.js

//该文件是对axios的二次封装,目的是:统一处理请求的错误,返回服务器的纯数据
import axios from "axios";
import { Toast } from "antd-mobile";
import NProgress from "nprogress";
import "nprogress/nprogress.css";

//使用axios的请求拦截器
axios.interceptors.request.use((config) => {
  NProgress.start(); //进度条开始
  return config;
});

//使用axios的响应拦截器
axios.interceptors.response.use(
  (response) => {
    NProgress.done(); //进度条结束
    return response.data;
  },
  (error) => {
    //如果状态码不是2开头,就会进入该回调
    NProgress.done(); //进度条结束
    Toast.fail(error.message);
    return new Promise(() => {});
  }
);

export default axios;

api/index.js

//统一管理项目中所有的ajax请求
import ajax from "./ajax";

//请求验证码
export const reqVerifyCode = (phone) => ajax.post("/login/digits", { phone });

//请求登录
export const reqLogin = (phone, code) => ajax.post("/login/phone", { phone, code });

//请求校验用户身份
export const reqVerifyToken = () => ajax.post("/login/verify");

//请求校验用户身份
export const reqLogout = (_id) => ajax.post("/logout", { _id });

pages/UseCenter/index.jsx

//个人中心组件
import React, { Component } from "react";
import { Toast, NavBar, Button } from "antd-mobile";
import { reqVerifyToken, reqLogout } from "../../api";
import "./index.less";

export default class UserCenter extends Component {
  state = {
    nickName: "",
    phone: "",
    avatar: "",
    _id: "",
  };

  logout = async () => {
    await reqLogout(this.state._id);
    this.props.history.replace("/login");
  };

  async componentDidMount() {
    const result = await reqVerifyToken();
    const { code, message, data, _id } = result;
    if (code !== 20000) {
      Toast.fail(message);
      this.props.history.replace("/login");
    } else {
      const { nickName, phone, avatar } = data;
      this.setState({ nickName, phone, avatar });
    }
  }

  render() {
    const { nickName, phone, avatar } = this.state;
    return (
      <div className="user-info">
        <NavBar mode="light">个人中心</NavBar>
        <img className="avatar" src={avatar} alt="" />
        <div className="nick-name">昵称:{nickName}</div>
        <Button onTouchEnd={this.logout} type="primary">
          退出登录
        </Button>
      </div>
    );
  }
}

3.token原理图

token原理图

4.OAuth 2.0

1.OAuth 2.0

  • OAuth 2.0 是目前最流行的授权机制,用来授权第三方应用,获取用户数据。
  • 简单说,OAuth 就是一种授权机制。数据的所有者同意其他应用使用自己存储的用户信息。

2. 授权流程(以GitHub为例)

  • 开发流程介绍

    • 从A 网站跳转到 GitHub授权页面。
    • GitHub 要求校验用户信息,引导用户登录。
    • GitHub 询问"A 网站要求获得你的xx数据,你是否同意?"
    • 用户同意,GitHub 就会重定向到A网站对应的服务器,同时发回一个授权码。
    • A网站服务器使用授权码,向 GitHub 请求令牌。
    • GitHub 返回令牌token. A网站服务器使用令牌,向 GitHub 请求用户数据。
  • 应用登记

    • 一个应用要 OAuth 授权,必须先到对方网站登记,让对方知道是谁在请求。
  • Github OAuth 2.0 文档

3.使用GitHub授权

1.GitHub登记应用

​ 登记地址:github.com/settings/ap…

2.获得client_id

查看地址:github.com/settings/de…

3.配置

3.1 前台项目准备好个人中心组件,供授权成功后查看

3.2 将得到的 client_id 、clinet_secret配置到服务器config\index.js中,随后重启服务器。

// github oauth
const CLIENT_ID = "xxxxxxxxxxxxxxx";
const CLIENT_SECRET = "xxxxxxxxxxxxxxx";

3.3 将得到的 client_id 存到前端项目 config\oauth.js 中

// github申请成功后得到的client_id
const client_id = "xxxx";
// oauth验证网址
const auth_url = "https://github.com/login/oauth/authorize";

export { client_id, auth_url };

3.4 项目中携带网站标识跳转到授权页

loginGithub = ()=>{
    window.location.href = AUTH_BASE_URL+'?client_id='+CLIENT_ID
}

image-20220227230535496