React-登录权限

举报
林太白 发表于 2025/01/31 23:37:32 2025/01/31
【摘要】 React-登录权限

❤ React-登录权限验证以及axios拦截校验

1、页面搭建

接下来我们开发一下我们的登录界面,并且实现对于我们的接口进行白名单的校验,只有通过白名单的接口,才可以进行自由访问,其他的全部都需要用户登录以后进行访问,否则就跳到用户的登录界面,让用户自己去登录!

(1)先来引入我们的登录界面:

Router之中引入:

const Login = lazy(() => import('@/pages/Login'));
<Route path="/login" element={<Login></Login>}></Route>

(2)页面结构搭建

 <div className="backbaige  min-h-screen flex items-center justify-center">
                      <div className="max-w-md w-full p-8 bg-white rounded-lg shadow-lg">
                        <h2 className="text-2xl font-bold mb-4 text-center">登录</h2>

                        <div className="mb-4">
                        <div className="sm:mx-auto sm:w-full sm:max-w-sm">
                                <img
                                    className="mx-auto h-10 w-auto"
                                    src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600"
                                    alt="Your Company"
                                />
                                <h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
                                    Login in to your account
                                </h2>
                            </div>
                        </div>

                        <div className="mb-4">
                        <div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
                                <form className="space-y-6" action="#" method="POST" onSubmit={handleSubmit}>
                                    <div>
                                        <label htmlFor="username" className="block text-sm font-medium leading-6 text-gray-900">
                                            账号
                                        </label>
                                        <div className="mt-2">
                                            <input
                                                id="username"
                                                name="username"
                                                type="text"
                                                autoComplete="false"
                                                required
                                                value={username}
                                                onChange={handleUsernameChange}
                                                className="forminput block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 pl-1.5"
                                            />
                                        </div>
                                    </div>

                                    <div>
                                        <div className="flex items-center justify-between">
                                            <label htmlFor="password" className="block text-sm font-medium leading-6 text-gray-900">
                                                密码
                                            </label>
                                        </div>
                                        <div className="mt-2">
                                            <input
                                                id="password"
                                                name="password"
                                                type="password"
                                                autoComplete="current-password"
                                                required
                                                value={password}
                                                onChange={handlePasswordChange}
                                                className="forminput block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 pl-1.5"
                                            />
                                        </div>
                                    </div>
                                    <div>
                                        <button
                                            type="submit"
                                            className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
                                        >
                                            Login in
                                        </button>
                                    </div>
                                </form>
                            </div>
                        </div>
                      </div> 
 </div>

(3)完善引入和定义

function Login() {}


import React, { useState } from 'react';
import { login } from '@/api/common/login';
import { Button, Form, Input, Radio, Table, Space, Tag, message, Modal, Popconfirm, Flex } from 'antd';
function Login() {
    // 使用 useState 创建状态变量来保存用户输入的用户名、密码和确认密码
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');
    const [confirmPassword, setConfirmPassword] = useState('');
    const [isRegistered, setIsRegistered] = useState(false);
    const [age, setAge] = useState(18);

    return (结构部分);
}

export default Login;

(4)完善登录的方法

// 处理用户名输入变化的函数
    const handleUsernameChange = (event) => {
        setUsername(event.target.value);
    };

    // 处理密码输入变化的函数
    const handlePasswordChange = (event) => {
        setPassword(event.target.value);
    };

    // 处理表单提交的函数
    const handleSubmit = async (event) => {
        event.preventDefault();
        // 在实际应用中,这里可以发送注册请求到服务器进行用户注册
        // 这里简单地假设密码和确认密码相同才能注册成功
        let params={
            username,
            password,
        }
        if (username === '' || password === '') {
            message.error('用户名和密码不能为空');
            return;
        }else{
            console.log(params, 'params');
            try {
                const res:any = await login(params);
                // const data = response.data;
                console.log(res,'res');
                
                if(res.code==200){
                    message.success(res.message);
                }else{
                    message.error(res.message);
                }
            } catch (error) {
                console.log('获取数据详情失败,请重试!',error);
            } finally {
                console.log('完!');
            }
        }
        return;
    };

接下来我们点击登录:

可以发现,已经实现了登录,并且拿到了token:

2、权限完善

那么问题也来了:

之前我们的接口访问都是任何人都可以进行访问的,相当于是一个开放的权限,极不安全,之前接口的请求头是这样子的:

接下来我们针对接口进行优化,为接口赋值权限,在接口没有token的时候,无法访问

也就是日常我们用户必须登录才能访问的场景

当我们没有登录的时候就会提示我们:

{
    "code": 401,
    "message": "登录过期,请重新登录!"
}

那么就需要我们对于接口做一些完善和判断

Redux 身份验证和授权

身份验证的整个过程是这样子的:

身份验证和授权是现代 Web 应用程序中不可或缺的部分。只有授权用户才能访问受保护的资源。在前端中,我们通常使用 JWT(JSON Web Tokens)来进行身份验证和授权。

在实现身份验证和授权时,我们需要完成以下几个步骤:

  1. 用户登录,服务器返回 JWT 令牌
  2. 将 JWT 令牌存储在本地存储中
  3. 在每个请求中,将 JWT 令牌作为请求头发送给服务器
  4. 服务器验证 JWT 令牌,并根据其内容授权或拒绝请求

更改我们的Redux Store,大致的思路我们先理清一下:

我们通过 Redux Store 来管理用户登录的状态。

添加一个名为 auth 的 reducer 来处理用户的登录状态和 Token。

添加一个名为 authMiddleware 的 Redux 中间件来拦截所有非白名单接口的请求,并添加上 Token。

这样子就可以在建立一个权限关卡,只有授权的(也就是携带token的接口)或者白名单之中的才可以直接访问,否则无法访问。

之前我们对于axios的简易封装

// request.js
import axios from 'axios'

const service = axios.create({
  baseURL: 'http://localhost:8888', // 设置基础 URL,根据实际情况修改 '/接口前缀', //import.meta.env.VITE_BASE_URL
  headers: { 'Content-Type': 'application/json;charset=utf-8', },
  timeout: 5000, // 设置请求超时时间
});

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    // 在请求发送之前做一些处理,例如添加 token 等
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  (response) => {
    // 在响应数据返回之前做一些处理
    return response.data;
  },
  (error) => {
    return Promise.reject(error);
  }
);

export default service;

授权登录接口对于本地token的存储

更改我们的仓库:

新建一个文件夹,authReducer.js,之中对于我们的授权进行封装

创建了一个 Redux Slice,包含两个 reducers

loginSuccesslogoutSuccess用于更新用户的登录状态和 Token。

接下来我们完善一下更新用户的登录状态和 Token的部分

src\store\reducers\authSlice.js

import { configureStore, createSlice } from '@reduxjs/toolkit';
import thunk from 'redux-thunk';
const authSlice = createSlice({
    name: 'auth',
    initialState: {
      loggedIn: false,
      token: null,
    },
    reducers: {
      loginSuccess(state, action) {
        state.loggedIn = true;
        state.token = action.payload.token;
      },
      logoutSuccess(state) {
        state.loggedIn = false;
        state.token = null;
      },
    },
  });

export const { loginSuccess, logoutSuccess } = authSlice.actions;
export default authSlice.reducer;

把授权部分放到我们的根目Reduce之中 src\store\reducers\index.js

import authSlice from './authSlice';
const rootReducer = combineReducers({
  menuReducer,
  auth: authSlice,
  // 可以在这里添加其他的 reducer
});

接下来更改我们的登录部分进行调用 src\pages\Login.tsx

// 引入 
import { useDispatch } from 'react-redux';
import { loginSuccess } from '@/store/reducers/authSlice';

const dispatch = useDispatch();

//成功登录以后跳转

const handleSubmit = async (event) => {
        event.preventDefault();
        // 在实际应用中,这里可以发送注册请求到服务器进行用户注册
        // 这里简单地假设密码和确认密码相同才能注册成功
        let params={
            username,
            password,
        }
        if (username === '' || password === '') {
            message.error('用户名和密码不能为空');
            return;
        }else{
            console.log(params, 'params');
            try {
                const res:any = await login(params);
                // const data = response.data;
                console.log(res,'res');
                
                if(res.code==200){
                    message.success(res.message);
                    
                    // 更新登录状态和 Token
                    dispatch(loginSuccess({ token: res.data.token }));

                    // 跳转到主页
                    window.location.href = '/';

                }else{
                    message.error(res.message);
                }
            } catch (error) {
                console.log('获取数据详情失败,请重试!',error);
            } finally {
                console.log('完!');
            }
        }
        return;
    };

在我们的src\store\reducers\authSlice.js 之中查看结果

 loginSuccess(state, action) {
    console.log(state, action,'登录行为1');
    state.loggedIn = true;
    state.token = action.payload.token;
    console.log(state,'登录行为2');
  },

然而我们发现,在登录行为1之中可完整拿到token,但是行为2之中反而什么都没有

持久化存储

保持用户登录状态的同时将 Token 进行持久化保存

这里我们用浏览器的 LocalStorage 来实现。当用户登录成功后,我们将 Token 存储到 LocalStorage 中;用户注销时,从 LocalStorage 中删除。

更改其实存储的部分:

// 初始状态为显示
const initialState = {
  loggedIn: false,
  token: localStorage.getItem('token'), // 从 Local Storage 中读取 Token,
}; 
 loginSuccess(state, action) {
        console.log(state, action,'登录行为1');
        state.loggedIn = true;
        state.token = action.payload.token;
        console.log(action.payload.token,'登录行为2');
        // 将 Token 存储到 Local Storage 中
        localStorage.setItem('token', action.payload.token);
        console.log(state,'登录行为3');
      },
      logoutSuccess(state) {
        state.loggedIn = false;
        state.token = null;
        // 从 Local Storage 中删除 Token
        localStorage.removeItem('token');
        return state;
 },

然而输出以后我们发现,存储到localStorage中的token生效了,但是loggedIn依旧未生效。 难道我们要将loggedIn也存储到localStorage之中吗?

我们换种思路:

是不是我们存储的过程出现了问题?

更改我们的存储方式 :

loginSuccess(state, action) {
    console.log(state, action,'登录行为1');
    localStorage.setItem('token', action.payload.token);
    return { 
      ...state, 
      loggedIn: true,
      token:action.payload.token,
    };
  },
  logoutSuccess(state) {
    localStorage.removeItem('token');
    return { 
      ...state, 
      loggedIn: false,
      token:null,
    };
  },

3、接口封装

接下来略微完善一下我们之前的axios请求src\utils\request.js

请求拦截器完善

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    // 在请求发送之前做一些处理,例如添加 token 等
    const token = localStorage.getItem('token');
    if (token) {
      // 如果有 token,则将 Authorization 头部添加到请求中
      config.headers.Authorization = `Bearer ${token}`;
    }else{
      // 跳转到登录页面
      window.location.href = '/login';
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
    // 跳转到前台页面
    window.location.href = '/';
  }
);

再次请求的时候我们可以发现,已经成功为请求增加了token请求头部

删除token,没有token请求的时候自动跳到登录页面

完善白名单接口

有时候有些接口是白名单接口,这个时候不需要token,我们如何过滤呢?这个时候可以通过白名单接口来进行筛选!

// 白名单接口列表,即不需要进行验证的接口路径
const whiteapilist = [
  '/api/login',
  '/api/register',
];

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    // 在请求发送之前做一些处理,例如添加 token 等
    const token = localStorage.getItem('token');
    const requestPath = config.url;
    // 检查请求路径是否在白名单中
    if (!whitelist.includes(requestPath) && token) {
      // 如果请求不在白名单中且存在 token,则将 Authorization 头部添加到请求中
      config.headers.Authorization = `Bearer ${token}`;
    }else if(whitelist.includes(requestPath)){
      console.log('白名单接口,无需验证');
    }else{
      // 跳转到登录页面
      window.location.href = '/login';
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
    // 跳转到前台页面
    window.location.href = '/';
  }
);
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。