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
一起管理其他属性,例如isAdmin
或accountId
,该怎么办?
@VincentNguyen 你可以使用 JWT。【参考方案2】:
处理isAuthenticated
仅在state
意味着用户每次刷新页面时都将未经身份验证。这不是真正的用户友好! :)
因此,登录页面应该在浏览器的 然后,您也可以在所有其他页面上检查此 简要说明 您的后端可能使用 关于 另一方面, 以下是我用于身份验证的一些方法和特殊的 React 组件:cookies
或localStorage
中存储access_token
(来自您的后端)。 access_token
证明用户已通过身份验证并验证他的身份。您通常会将此access_token
传递给您服务器的每个下一个请求,以检查该用户是否被允许访问他请求的数据,或允许创建、编辑和删除他试图创建、编辑和删除的内容。
access_token
,如果用户不再经过身份验证,则将其重定向到登录页面。
access_token
和 refresh_token
之间的区别 - 这将帮助您理解下面的代码,但请随意跳过如果您已经熟悉它。OAuth2
,这是当今最常见的身份验证协议。使用OAuth2
,您的应用程序向服务器发出第一个请求,其中包含用户的用户名和密码以进行身份验证。用户通过身份验证后,他会收到 1) access_token
,通常在一小时后过期,以及 2) 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)
然后是Cookies.get('access_token')
。cookies
的其他专业人士:他们是交叉subdomains!如果您的登录应用程序是login.mycompany.com
,而您的主应用程序是app.mycompany.com
,那么您可以在登录应用程序上创建一个cookie
并从主应用程序访问它。 LocalStorage
无法做到这一点。
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:处理身份验证的最佳方法是啥?
对像 MERN Stack 这样的 Web 和 api 解决方案进行身份验证和授权的最佳方式是啥?