问题描述
我的 Phoenix API 中的受保护路由正在向请求发送 403 响应。调试日志显示:
(Plug.CSRFProtection.InvalidCSrftokenError) invalid CSRF (Cross Site Request Forgery) token,please make sure that:
* The session cookie is being sent and session is loaded
* The request include a valid '_csrf_token' param or 'x-csrf-token' header
我的路由器如下所示:
defmodule MyWeb.Router do
use MyWeb,:router
alias MyWeb.Auth.GuardianPipeline
pipeline :api do
plug :accepts,["json"]
plug :fetch_session
plug GuardianPipeline
end
pipeline :protected do
plug Guardian.Plug.EnsureAuthenticated
plug :protect_from_forgery
end
scope "/api/v1",MyWeb.API.V1,as: :api_v1 do
pipe_through :api
post "/login",SessionController,:login
end
scope "/api/v1",as: :api_v1 do
pipe_through [:api,:protected]
delete "/logout",:logout
end
end
我的会话控制器登录操作如下所示:
defp login_reply({:ok,user},conn) do
conn
|> Guardian.Plug.sign_in(user)
|> put_session(:_csrf_token,csrf_token)
|> json(%{csrf_token: get_csrf_token()})
end
在前端,我对受保护的“注销”路径的请求如下所示:
const logout = async () => {
const method = 'DELETE'
const headers = {
'Content-Type': 'application/json','x-csrf-token': csrftoken.value
}
await fetch('http://localhost:4000/api/v1/logout',{ method,headers,credentials: 'include' })
-
CSRF 错误说我应该确保会话已发送并加载 我看到浏览器中加载了会话 cookie,所以我知道它已发送。会话由
:fetch_session
插件加载。所以应该满足第一个条件。 -
正在提取中设置
x-csrf_token
标头,并且我已验证csrftoken.value
与get_csrf_token()
创建的标头相匹配。所以第二个条件也应该满足。
我做错了什么?为什么未验证 csrf 令牌?
--
这里是整个JS组件
<script lang="ts">
import { defineComponent,ref,onMounted } from '@nuxtjs/composition-api'
export default defineComponent({
setup (_props) {
const csrftoken = ref('')
const email = 'free@city.17'
const error = ref('')
const submitting = false
const isAuthenticated = () => !!csrftoken.value
const setAuthenticated = () => {
return isAuthenticated()
}
const logout = async () => {
const method = 'DELETE'
const headers = {
'Content-Type': 'application/json','x-csrf-token': csrftoken.value
}
await fetch('http://localhost:4000/api/v1/logout',credentials: 'include' })
.then((response) => {
if (response.status === 401) {
error.value = 'No account found with that email address'
} else if (!response.ok) {
error.value = 'Something went wrong'
}
return response
})
.then(response => response.json())
.catch((_err) => {
error.value = 'network error'
})
if (error.value) {
throw new Error(error.value)
}
csrftoken.value = ''
return true
}
const onlogout = () => {
error.value = ''
return logout().catch(_e => '')
}
const login = async () => {
const method = 'POST'
const body = JSON.stringify({ user: { email } })
const headers = {
'Content-Type': 'application/json'
}
const resp = await fetch('http://localhost:4000/api/v1/login',body,credentials: 'include' })
.then((response) => {
if (response.status === 401) {
error.value = 'No account found with that email address'
} else if (!response.ok) {
error.value = 'Something went wrong'
}
return response
})
.then(response => response.json())
.catch((_err) => {
error.value = 'network error'
})
if (error.value) {
throw new Error(error.value)
}
console.log(resp)
csrftoken.value = resp.csrf_token
return true
}
const onSubmit = () => {
error.value = ''
return login().catch(_e => '')
}
onMounted(setAuthenticated)
return {
csrftoken,email,error,submitting,isAuthenticated,setAuthenticated,onlogout,onSubmit
}
}
})
</script>
整个 Phoenix 会话控制器
defmodule MyWeb.API.V1.SessionController do
use MyWeb,:controller
alias MyWeb.Auth.Guardian
alias MyWeb.API.V1.Auth
def login(conn,%{"user" => %{"email" => email}}) do
Auth.authenticate_user(%{email: email})
|> login_reply(conn)
end
def logout(conn,_params) do
conn
|> Guardian.Plug.sign_out(clear_remember_me: true)
|> configure_session(drop: true)
|> send_resp(:ok,"")
end
defp login_reply({:ok,conn) do
conn
|> Guardian.Plug.sign_in(user)
# |> Guardian.Plug.remember_me(user)
|> put_session(:_csrf_token,csrf_token)
|> json(%{csrf_token: csrf_token})
end
defp login_reply({:error,reason},conn) do
conn
|> put_status(:unauthorized)
|> json(%{reason: reason})
end
end
我为路由器的 protected
管道创建了这个自定义插件:
defp inspect_plug(conn,_opts) do
IO.inspect(Plug.Conn.get_session(conn),label: :session)
session_token = Plug.Conn.get_session(conn,"_csrf_token")
IO.inspect(session_token,label: :session_token)
IO.inspect(byte_size(session_token),label: :session_token_size)
csrf_token = Plug.CSRFProtection.dump_state_from_session(session_token)
IO.inspect(csrf_token,label: :csrf_token)
conn
end
以下是它记录“注销”请求的示例:
session: %{
"_csrf_token" => "HSwrOF5AOQ5nKyIWHH9RFFg0HVMWDRA_dFIVlulMQipLw39PmPzkPXIf","guardian_default_token" => "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRvbWVldHVwIiwiZXhwIjoxNjI2NDU1MDQ4LCJpYXQiOjE2MjQwMzU4NDgsImlzcyI6ImF1dG9tZWV0dXAiLCJqdGkiOiJkMDczYTMwZi00ZTMzLTRkZjItODBhMi04YzRkZDNiYzAzMzAiLCJuYmYiOjE2MjQwMzU4NDcsInN1YiI6IjEiLCJ0eXAiOiJhY2Nlc3MifQ.KF-rv-65G_0NUMsFa9tYedWKE0gRTd9hvx_pOyBHFV5DTGFPtsfJnaz3WOk63ARKu8-bEbjAZGXRRPAZ0E3LMw"
}
session_token: "HSwrOF5AOQ5nKyIWHH9RFFg0HVMWDRA_dFIVlulMQipLw39PmPzkPXIf"
session_token_size: 56
csrf_token: nil
我想知道为什么 Plug.CSRFProtection.dump_state_from_session
从我的 _csrf_token
会话 cookie 中返回 nil。
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)