文章标题 原创 翻译 转载 文章内容 这是我在github上看到的一种实现方式,仅做学习使用,并不是就是最好的解决方案。 首先,服务端要支持这两个请求:/token和/user,/token是根据email和密码获取id_token,/user是根据之前获取到的id_token再去获取用户的基本信息,AuthService.js封装了这两个信息的请求和存储。 ``` // utils/AuthService.js export default class AuthService { constructor(domain) { this.domain = domain || 'http://localhost:5000' this.fetch = this.fetch.bind(this) this.login = this.login.bind(this) this.getProfile = this.getProfile.bind(this) } login(email, password) { // Get a token return this.fetch(`${this.domain}/token`, { method: 'POST', body: JSON.stringify({ email, password }) }).then(res => { this.setToken(res.id_token) return this.fetch(`${this.domain}/user`, { method: 'GET' }) }).then(res => { this.setProfile(res) return Promise.resolve(res) }) } loggedIn(){ // Checks if there is a saved token and it's still valid const token = this.getToken() return !!token && !isTokenExpired(token) // handwaiving here } setProfile(profile){ // Saves profile data to localStorage localStorage.setItem('profile', JSON.stringify(profile)) } getProfile(){ // Retrieves the profile data from localStorage const profile = localStorage.getItem('profile') return profile ? JSON.parse(localStorage.profile) : {} } setToken(idToken){ // Saves user token to localStorage localStorage.setItem('id_token', idToken) } getToken(){ // Retrieves the user token from localStorage return localStorage.getItem('id_token') } logout(){ // Clear user token and profile data from localStorage localStorage.removeItem('id_token'); localStorage.removeItem('profile'); } _checkStatus(response) { // raises an error in case response status is not a success if (response.status >= 200 && response.status < 300) { return response } else { var error = new Error(response.statusText) error.response = response throw error } } fetch(url, options){ // performs api calls sending the required authentication headers const headers = { 'Accept': 'application/json', 'Content-Type': 'application/json' } if (this.loggedIn()){ headers['Authorization'] = 'Bearer ' + this.getToken() } return fetch(url, { headers, ...options }) .then(this._checkStatus) .then(response => response.json()) } } ``` 接下来使用React的高阶组件方式进行一次封装方便组件的使用。向服务端请求是有耗时的,这里加个Loading...缓冲,这样组件渲染的时候看起来更加平滑。 在第一次渲染时,React启动从localStorage读取令牌,这就意味着授权页面不能SEO,目前还可以但绝对不是最佳的。 ``` // utils/withAuth.js - a HOC for protected pages import React, {Component} from 'react' import AuthService from './auth' export default function withAuth(AuthComponent) { const Auth = new AuthService('http://localhost:5000') return class Authenticated extends Component { constructor(props) { super(props) this.state = { isLoading: true }; } componentDidMount () { if (!Auth.loggedIn()) { this.props.url.replaceTo('/') } this.setState({ isLoading: false }) } render() { return ( <div> {this.state.isLoading ? ( <div>LOADING....</div> ) : ( <AuthComponent {...this.props} auth={Auth} /> )} </div> ) } } } ``` ``` // ./pages/dashboard.js // example of a protected page import React from 'react' import withAuth from '../utils/withAuth' class Dashboard extends Component { render() { const user = this.props.auth.getProfile() return ( <div>Current user: {user.email}</div> ) } } export default withAuth(Dashboard) ``` 用高阶组件的方式封装一下用起来就很方便了。但是登录页面是不能使用HOC这种方式的,因为登录是公共的,所以直接使用AuthService的实例,注册页面也类似。 ``` // ./pages/login.js import React, {Component} from 'react' import AuthService from '../utils/AuthService' const auth = new AuthService('http://localhost:5000') class Login extends Component { constructor(props) { super(props) this.handleSubmit = this.handleSubmit.bind(this) } componentDidMount () { if (auth.loggedIn()) { this.props.url.replaceTo('/admin') // redirect if you're already logged in } } handleSubmit (e) { e.preventDefault() // yay uncontrolled forms! auth.login(this.refs.email.value, this.refs.password.value) .then(res => { console.log(res) this.props.url.replaceTo('/admin') }) .catch(e => console.log(e)) // you would show/hide error messages with component state here } render () { return ( <div> Login <form onSubmit={this.handleSubmit} > <input type="text" ref="email"/> <input type="password" ref="password"/> <input type="submit" value="Submit"/> </form> </div> ) } } export default Login ``` 这个实现的灵感来自于react-with-styles,作者建议直接使用next-with-auth这个库更好些。 ``` // ./utils/withAuth.js import nextAuth from 'next/auth' import parseScopes from './parseScopes' const Loading = () => <div>Loading...</div> export default nextAuth({ url: 'http://localhost:5000', tokenEndpoint: '/api/token', profileEndpoint: '/api/me', getTokenFromResponse: (res) => res.id_token, getProfileFromResponse: (res) => res, parseScopes, }) ``` 文章类别 Python Mobile Android Java Shell Life Database Bug Windows IOS Tools Boost Node.js Mac Product Tips C/C++ Golang Javascript React Qt MQ MongoDB Design Web Linux LLM ChatGPT RAG AI 提交