React - 处理身份验证/登录状态的最佳方法是什么?

Vin*_*yen 17 javascript authentication reactjs react-router

新的用于对具有身份验证/登录的应用程序做出反应和处理.它目前有效,但感觉被黑客攻击.现在我的isAuthenticated状态routes.js就像我一样:

class Routes extends Component {

    constructor(props) {
        super(props);

        this.state = {
            isAuthenticated: false,
         }
     }
Run Code Online (Sandbox Code Playgroud)

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

 setAuthenticated = (isAuthenticated) => {
        this.setState({isAuthenticated});
    }
Run Code Online (Sandbox Code Playgroud)

在路由器下面......

<Route path="/" exact component={() =>
                            <div>
                                <Login
                                    isAuthenticated={this.state.isAuthenticated}
                                    setAuthenticated={this.setAuthenticated}
                            </div>
                        } />
Run Code Online (Sandbox Code Playgroud)

是的,我理解这是糟糕的设计,因为这是改变道具值,这些道具值应该是不可变的.这也很糟糕,因为当我更改此值时login.js会导致多次不必要的重新渲染.我应该宣布isAuthenticated某种类型的全局变量吗?顺便说一下,我没有使用任何国家管理.

编辑:我isAuthenticated根据服务器的响应进行设置,确认正确的登录/密码组合.

GG.*_*GG. 39

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

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

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


除了access_tokenrefresh_token– 之间的区别之外,还简要介绍一下这将有助于您理解下面的代码,但是如果您已经熟悉它,可以随时跳过

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


除了cookies和之间的区别之外,还简要介绍一下localStorage-也可以跳过它!

localStorage是两者之间的最新技术。这是一个简单的键/值持久性系统,似乎非常适合存储access_token和其值。但我们还需要保留其到期日期。我们可以存储第二个名为的键/值对,expires但这将是逻辑上更多的事情。

另一方面,cookies拥有本机expires属性,这正是我们所需要的!cookies是一项过时的技术,并且对开发人员不太友好,因此我个人使用js-cookie这是一个小型库来进行操作cookies。它也使它看起来像一个简单的键/值持久性系统:Cookies.set('access_token', value)then Cookies.get('access_token')

其他支持cookies:它们是跨子域!如果您的登录应用程序是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()
Run Code Online (Sandbox Code Playgroud)

认证()

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
}
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

认证路线

export const AuthenticatedRoute = ({
  component: Component,
  exact,
  path,
}) => (
  <Route
    exact={exact}
    path={path}
    render={props =>
      isAuthenticated() ? (
        <Component {...props} />
      ) : (
        <AuthenticateBeforeRender render={() => <Component {...props} />} />
      )
    }
  />
)
Run Code Online (Sandbox Code Playgroud)

AuthenticateBeforeRender

class AuthenticateBeforeRender extends Component {
  state = {
    isAuthenticated: false,
  }

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

  render() {
    return this.state.isAuthenticated ? this.props.render() : null
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 这确实是一种危险且不安全的存储身份验证令牌的方式。Cookie 可以设置“HTTPOnly”标志,该标志专门用于限制 XSS 攻击可能造成的损害。您的回答极端夸张:网络应用程序中的 XSS 漏洞*不会*让它窃取 cookie,*如果它们得到适当保护*,它们不在您的示例中,并声称 XSS 可以窃取“密码、电子邮件、电话”和信用卡号码”是荒谬的。事实上,这个答案是非常不安全的。你的前端 JS 不应该处理身份验证令牌。 (7认同)
  • 由于您是从代码访问 cookie,这意味着它们不是 HttpOnly,这是否意味着存在 CSS 漏洞? (5认同)
  • @MickaelMarrache 是的,这种方法容易受到 XSS 攻击。Auth cookie 应该只是 http,这个答案很糟糕。 (3认同)

Agn*_*ney 6

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

这是一个使用 React Context 的示例,我们在其中创建上下文 usingcreateContext并用于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"));
Run Code Online (Sandbox Code Playgroud)
<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> 
Run Code Online (Sandbox Code Playgroud)

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


小智 5

如果页面保护依赖于“ isAuthenticated”状态变量,则可能应该在生产环境中禁用react devtools。否则,可以检查页面并手动将标志翻转为true,从而将受保护的页面暴露给未经身份验证的用户。

  • 我认为,无论是否禁用devtools,无论该页面是否显示在客户端浏览器上,该页面都应视为已显示(即使该页面隐藏在某些JavaScript后面)。如果不需要未经身份验证的人来查看它,则应以服务器呈现。 (2认同)

小智 4

您可以在登录时在本地存储中设置访问令牌,并在用户注销后将其清除。然后,在进行 API 调用时,将使用经过身份验证的方法来检查是否存在令牌以及令牌是否有效