所以我有一个工作的本地Twitter克隆名为Hater,但不能部署前端b/c我不能访问安全cookie (https://github.com/mustafabin/hater)
我使用Django内置的基于会话的认证,我有中间件设置
登录视图@method_decorator(csrf_protect, name="dispatch")
class LoginView(APIView):
permission_classes = (permissions.AllowAny,)
def post(self, request, format=None):
data = self.request.data
username = data['username']
password = data['password']
try:
user = auth.authenticate(username=username, password=password)
if user is not None:
auth.login(request, user)
return Response({'success': 'User authenticated'})
else:
return Response({'error': 'Error Authenticating'})
except:
return Response({'error': 'Something went wrong when logging in'})
报名@method_decorator(csrf_protect, name="dispatch")
class SignupView(APIView):
permission_classes = (permissions.AllowAny,)
def post(self, request, format=None):
data = self.request.data
username = data['username']
password = data['password']
re_password = data['re_password']
tag = data['tag']
try:
if password == re_password:
if User.objects.filter(username=username).exists():
return Response({"error": "Username already exists"})
else:
if len(password) < 6:
return Response({"error": "Password must be at least 6 characters"})
else:
user = User.objects.create_user(
username=username, password=password)
user = User.objects.get(id=user.id)
user_profile = User_profile.objects.create(
user=user, name=username, tag=tag)
return Response({'success': "User created successfully"})
else:
return Response({'error': "Passwords do not match"})
except:
return Response({"error": "Something went wrong signing up"})
我知道有些设置是多余的,但是你的男人已经绝望了
CORS_ORIGIN_ALLOW_ALL = True
CSRF_COOKIE_HTTPONLY = False
SESSION_COOKIE_HTTPONLY = False
CORS_ALLOW_CREDENTIALS = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = 'None'
SESSION_COOKIE_SAMESITE = 'None'
CSRF_TRUSTED_ORIGINS = ['http://localhost:3000', 'http://localhost:8000',
'https://hater.netlify.app', 'https://haterip.netlify.app']
CORS_EXPOSE_HEADERS = ["Set-Cookie"]
django_heroku.settings(locals())
,这是React.js代码,用于处理登录,并在用户未登录时显示登录表单。标记是一个全局状态,如果当前没有用户登录,则该状态为空
let handleLogin = (e) => {
e.preventDefault();
let headerInfo = {
Accept: "application/json",
"Content-Type": "application/json",
};
let loginOptions = {
method: "POST",
headers: headerInfo,
credentials: "include",
body: JSON.stringify(form),
};
let options = {
method: "GET",
headers: headerInfo,
credentials: "include",
};
fetch(`https://haterbackend.herokuapp.com/user/login`, loginOptions)
.then((res) => res.json())
.then((data) => {
if (data["error"]) {
return alert(data["error"]);
} else {
fetch(`https://haterbackend.herokuapp.com/user/grabProfile`, options)
.then((res) => res.json())
.then((data) => {
store.dispatch({ type: "set", payload: data.profile });
})
.then(() => navigate("/home"))
.catch((err) => console.log(err));
}
})
.catch((err) => console.log(err));
};
{!user.tag ? (
<form onSubmit={handleLogin} className="landingForm">
<CSRFToken></CSRFToken>
<input
onChange={handleChange}
className="landingLoginInput"
placeholder="Username"
type="text"
name="username"
/>
<input
onChange={handleChange}
className="landingLoginInput"
placeholder="Password"
type="password"
name="password"
autoComplete="current-password"
/>
<Button id="login" type="submit">
Login
</Button>
</form>
) : (
<div className="landing-signout">
<Link className="landing-home-link" to="/home">
<Button>Home 🏡</Button>
</Link>
<Link className="landing-signout-link" onClick={signOut} to="/">
<Button>Sign out 🚪</Button>
</Link>
</div>
)}
CSRFToken组件只是一个隐藏的输入字段
import React, { useState, useEffect } from "react";
export default function CSRFToken() {
const [csrftoken, setcsrftoken] = useState("");
const getCookie = (name) => {
let cookieValue = null;
if (document.cookie && document.cookie !== "") {
let cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
};
useEffect(() => {
fetch(`https://haterbackend.herokuapp.com/user/csrf_cookie`, {
credentials: "include",
})
.then((res) => {
setcsrftoken(getCookie("csrftoken"));
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<input type="hidden" name="csrfmiddlewaretoken" value={csrftoken || ""} />
);
}
我实现了会话登录与react前端概述如下:https://www.stackhawk.com/blog/django-csrf-protection-guide/https://docs.djangoproject.com/en/3.1/ref/csrf/#ajax官方文档
上面的方法只在本地HTTP上工作,但在HTTPS上不起作用,因为部署的站点不会设置cookie,因为它不安全错误截图
但是客户端脚本不能获取安全cookie,并且Django文档中的getCookie函数只能通过set cookie头进行解析,所以如果cookie未定义或为空,它将不起作用(客户端脚本代码尝试读取cookie,浏览器返回一个空字符串作为结果)https://owasp.org/www-community/HttpOnly
TLDR:项目在本地HTTP工作,但当部署cookie时不能通过HTTPS设置,但客户端脚本无法读取安全cookie,因此无法注册或登录用户,因为这需要csrftoken cookie
超级迟回复,但是当我的后端服务部署在不同的域中时,不可能安全地使用基于会话的验证。
前端on netifly后端在heroku上
我使用的一个更好的替代方案是django knox令牌,它的行为方式与JWT身份验证相同,但有更多的功能和令牌可以无效。
TLDR;因为服务不在同一域名下所以不可能在HTTPS连接下