React - 处理登录和身份验证的最佳方式是啥?

Posted

技术标签:

【中文标题】React - 处理登录和身份验证的最佳方式是啥?【英文标题】:React - What is the best way to handle login and authentication?React - 处理登录和身份验证的最佳方式是什么? 【发布时间】:2018-09-23 22:37:21 【问题描述】:

对具有身份验证/登录的应用程序做出反应和工作的新手。它目前可以工作,但感觉被黑客攻击在一起。现在我的isAuthenticated 状态位于我的routes.js 中,如下所示:

class Routes extends Component 

    constructor(props) 
        super(props);

        this.state = 
            isAuthenticated: false,
         
     

在我的登录页面上,我需要知道用户何时通过身份验证才能将他们重定向到home 页面。允许访问和操作此isAuthenticated 状态的最佳设计模式是什么?我目前的设置方式是我有一个函数可以在routes.js 中设置状态并将状态作为道具发送,如下所示:

 setAuthenticated = (isAuthenticated) => 
        this.setState(isAuthenticated);
    

在路由器下面...

<Route path="/" exact component=() =>
                            <div>
                                <Login
                                    isAuthenticated=this.state.isAuthenticated
                                    setAuthenticated=this.setAuthenticated
                            </div>
                         />

是的,我知道这是一个糟糕的设计,因为这会改变应该是不可变的 props 值。这也很糟糕,因为当我在login.js 中更改此值时,它会导致多次不必要的重新渲染。我应该将isAuthenticated 声明为某种类型的全局变量吗?顺便说一句,我没有使用任何状态管理。

编辑:我正在设置isAuthenticated,这是基于我的服务器确认正确的登录名/密码组合的响应。

【问题讨论】:

如果您的页面保护依赖于“isAuthenticated”状态变量,您可能应该在生产环境中使用disable the react devtools。否则,可能会检查页面并手动将标志翻转为 true,从而将受保护的页面暴露给未经身份验证的用户。 【参考方案1】:

您可以在登录时在本地存储中设置访问令牌,并在用户注销后将其清除。然后is authenticated方法将用于检查是否有令牌以及在进行API调用时令牌是否有效

【讨论】:

好的,一个更笼统的问题。如果我还需要与isAuthenticated 一起管理其他属性,例如isAdminaccountId,该怎么办? @VincentNguyen 你可以使用 JWT。【参考方案2】:

处理isAuthenticated仅在state 意味着用户每次刷新页面时都将未经身份验证。这不是真正的用户友好! :)

因此,登录页面应该在浏览器的cookieslocalStorage存储access_token(来自您的后端)access_token 证明用户已通过身份验证并验证他的身份。您通常会将此access_token 传递给您服务器的每个下一个请求,以检查该用户是否被允许访问他请求的数据,或允许创建、编辑和删除他试图创建、编辑和删除的内容。

然后,您也可以在所有其他页面上检查此access_token,如果用户不再经过身份验证,则将其重定向到登录页面。


简要说明 access_tokenrefresh_token 之间的区别 - 这将帮助您理解下面的代码,但请随意跳过如果您已经熟悉它

您的后端可能使用OAuth2,这是当今最常见的身份验证协议。使用OAuth2,您的应用程序向服务器发出第一个请求,其中包含用户的用户名和密码以进行身份​​验证。用户通过身份验证后,他会收到 1) access_token,通常在一小时后过期,以及 2) refresh_token,在很长一段时间(几小时,几天)后过期。当access_token 过期时,您的应用不会再次询问用户的用户名和密码,而是将refresh_token 发送到服务器以获取该用户的新access_token


关于 cookieslocalStorage 之间区别的简要说明 - 也可以跳过!

localStorage 是两者之间的最新技术。这是一个简单的键/值持久性系统,似乎非常适合存储access_token 及其值。但我们还需要坚持其到期日期。我们可以存储第二个名为 expires 的键/值对,但在我们这边处理会更符合逻辑。

另一方面,cookies 有一个原生的expires 属性,这正是我们所需要的! cookies 是一项老技术,对开发人员不是很友好,所以我个人使用js-cookie,这是一个小库来操作cookies。它也使它看起来像一个简单的键/值持久性系统:Cookies.set('access_token', value) 然后是Cookies.get('access_token')

cookies 的其他专业人士:他们是交叉subdomains!如果您的登录应用程序是login.mycompany.com,而您的主应用程序是app.mycompany.com,那么您可以在登录应用程序上创建一个cookie 并从主应用程序访问它。 LocalStorage 无法做到这一点。


以下是我用于身份验证的一些方法和特殊的 React 组件:

isAuthenticated()

import Cookies from 'js-cookie'

export const getAccessToken = () => Cookies.get('access_token')
export const getRefreshToken = () => Cookies.get('refresh_token')
export const isAuthenticated = () => !!getAccessToken()

认证()

export const authenticate = async () => 
  if (getRefreshToken()) 
    try 
      const tokens = await refreshTokens() // call an API, returns tokens

      const expires = (tokens.expires_in || 60 * 60) * 1000
      const inOneHour = new Date(new Date().getTime() + expires)

      // you will have the exact same setters in your Login page/app too
      Cookies.set('access_token', tokens.access_token,  expires: inOneHour )
      Cookies.set('refresh_token', tokens.refresh_token)

      return true
     catch (error) 
      redirectToLogin()
      return false
    
  

  redirectToLogin()
  return false

redirectToLogin()

const redirectToLogin = () => 
  window.location.replace(
    `$getConfig().LOGIN_URL?next=$window.location.href`
  )
  // or history.push('/login') if your Login page is inside the same app

AuthenticatedRoute

export const AuthenticatedRoute = (
  component: Component,
  exact,
  path,
) => (
  <Route
    exact=exact
    path=path
    render=props =>
      isAuthenticated() ? (
        <Component ...props />
      ) : (
        <AuthenticateBeforeRender render=() => <Component ...props /> />
      )
    
  />
)

AuthenticateBeforeRender

class AuthenticateBeforeRender extends Component 
  state = 
    isAuthenticated: false,
  

  componentDidMount() 
    authenticate().then(isAuthenticated => 
      this.setState( isAuthenticated )
    )
  

  render() 
    return this.state.isAuthenticated ? this.props.render() : null
  

【讨论】:

感谢您的全面回答。在我的服务器上通过身份验证并发送 ok 响应后,我正在设置 isAuthenticated。因此,在获得良好的身份验证响应后,我应该将其存储在 localStorage 中,就像第一个答案所建议的那样,对吗? 您可能需要来自服务器的令牌。当您的应用将调用您的服务器 API 以检索一些数据时,您希望在每个请求中传递一个标头 Authorization: token 以证明请求作者的身份。 既然你是从代码中访问cookies,这意味着它们不是HttpOnly,这是否意味着存在CSS漏洞? @MickaelMarrache 是的,这种方法容易受到 XSS 攻击。 Auth cookie 应该只是 http,这个答案很糟糕。 这确实是一种存储身份验证令牌的危险且不安全的方式。可以使用HTTPOnly 标志设置 Cookie,该标志专门用于限制 XSS 攻击可能造成的损害。您的回答极端夸张:Web 应用程序中的 XSS 漏洞不会让它窃取 cookie,如果它们得到适当的保护,它们不在您的示例中,并且声称 XSS 可以窃取“密码、电子邮件、电话和信用卡号码”是荒谬的。事实上,这个答案非常不安全。您的前端 JS 不应该处理身份验证令牌。【参考方案3】:

如果您使用的应用程序的身份验证仅持续一个会话,则将其存储在状态中就足够了。 但请注意,这意味着用户将在页面刷新时失去身份验证状态。

这是一个使用 React Context 的示例,我们使用 createContext 创建上下文并使用 Consumer 在整个应用程序中访问它。

const AuthenticationContext = React.createContext();
const  Provider, Consumer  = AuthenticationContext;

function Login(props) 
  return (
    <Consumer>
      
        value=>
        <button onClick=value.login>Login</button>
      
    </Consumer>
  );


function Logout() 
  return (
    <Consumer>
      
        value=>
        <button onClick=value.logout>Logout</button>
      
    </Consumer>
  );


function AnotherComponent() 
  return (
    <Consumer>
      
        value=>
          return value.isAuthenticated?
            <p>Logged in</p>:
            <p>Not Logged in</p>
        
      
    </Consumer>
  );


class App extends React.Component 
  constructor(props) 
    super(props);
    this.login = ()=> 
      this.setState(
        isAuthenticated: true
      );
    
    this.logout = ()=> 
      this.setState(
        isAuthenticated: false
      );
    
    this.state = 
      isAuthenticated: false,
      login: this.login,
      logout: this.logout
    
  
  
  render() 
    return (
      <Provider value=this.state>
        <Login />
        <Logout />
        <AnotherComponent />
      </Provider>
    );
  

ReactDOM.render(<App />, document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div> 

https://reactjs.org/docs/context.html#reactcreatecontext

【讨论】:

以上是关于React - 处理登录和身份验证的最佳方式是啥?的主要内容,如果未能解决你的问题,请参考以下文章

spring Boot 和 Angular JS:处理身份验证的最佳方法是啥?

实施 SSO 的最佳方式是啥?

对像 MERN Stack 这样的 Web 和 api 解决方案进行身份验证和授权的最佳方式是啥?

对 Web 服务进行身份验证的最佳方式是啥

使用 Nuxt 处理登录\注销按钮以避免闪烁内容的最佳方法是啥?

在 ASP.NET MVC 中使用 Universe 数据库处理身份验证的最佳方法是啥?