基于OAuth2.0和JWT规范实现安全易用的用户认证


遵循OAuth2.0JWT规范实现用户认证,不但具有很好的实用性,还能提供很不错的安全保障。
本文结合实用的代码讲述了基于OAuth2.0JWT,在前后端分离的系统中,实现用户使用方便而又安全可靠的用户认证的基本思路。

预备知识

OAuth2.0

OAuth2.0 是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用。比如:微信登录、Facebook,Google,Twitter,GitHub等。
OAuth2.0规范要求使用密码流时,客户端或用户必须以表单数据形式发送 usernamepassword 字段。这两个字段必须命名为 usernamepassword ,不能使用 user-nameemail 等其它名称。
该规范要求必须以表单数据形式发送 username 和 password,因此,不能使用 JSON 对象。
当然,前端仍可以显示终端用户所需的名称,数据库模型也可以使用自己定义的名称。

关于OAuth的更多知识,您可以参考:理解OAuth2.0

JWT

JWT 即JSON 网络令牌(JSON Web Tokens),是目前最流行的跨域认证解决方案。
它在服务端将用户信息进行签名(如果有保密信息也可以加密后再签名),这样可以防止它被篡改。
每次客户端提交请求时,都附带 JWT 内容,这样服务端可以直接读取用户信息,而不用必须从服务器端的会话中获取。

关于JWT的更多知识,可以参考:JSON Web Token 入门教程

基本思路

以下是基本的实现思路:

  1. 客户端以表单方式,携带usernamepassword向后台接口发起登录认证请求
  2. 服务端接口验证账号和密码后,生成JWT格式的token,其中放置加密信息,签名后发放给客户端
  3. 客户端再访问服务端接口时,在HTTP Header中携带token即可

详细步骤

这些步骤中包含的代码片段的目的是能把过程讲得更清楚,如果想查看详细的代码并且自己动手实践,可参见本文最后的代码下载地址。

以下代码片段前端使用javascript和vue3,后端使用的是python

1. 客户端提交认证请求

前端javascript发送form请求:

import { ref } from "vue";
import axios from "axios";
import { jwtDecode } from "jwt-decode";

import { setToken } from "@/assets/js/auth";

const login_url = "http://127.0.0.1:8000/token";

//表单数据
const form_data = ref({
  username: "",
  password: "",
  remember: false,
});

const isLoading = ref(false);
const error = ref(false);
const error_msg = ref("");

// 获取路由实例
import { useRoute ,useRouter } from "vue-router";
const route = useRoute();
const router = useRouter();

const emits = defineEmits(["login"]);

//提交
async function submit() {
  if (form_data.value.username === "" || form_data.value.password === "") {
    return;
  }

  isLoading.value = true;
  error.value = false;
  error_msg.value = "";

  const formData = new FormData();
  formData.append("username", form_data.value.username);
  formData.append("password", form_data.value.password);
  formData.append("remember", form_data.value.remember);

  try {
    const response = await axios.post(login_url, formData, {
      headers: {
        "Content-Type": "multipart/form-data", // 指定使用 form-data 格式
      },
    });

    const token = response.data.access_token;
    //console.log(token);

    // 解析 JWT Token 内容
    const decodedToken = jwtDecode(token);
    console.log("解析后的 Token 内容:", decodedToken);

    // 存储token
    setToken(token);

    let userid =  decodedToken["sub"];
    emits("login",userid);    

    if (route.query && route.query['redirect']) {
      router.push({ path: route.query['redirect']});
    } else {
      router.push({ path: "/" });
    }
  } catch (error) {
    if (error.code == "ERR_NETWORK") {
      error_msg.value = "网络错误,无法连接到服务器。";
    } else if (error.code == "ERR_BAD_REQUEST") {
      if (error.response.status == 401) {
        error_msg.value = "用户名或密码错误。";
      }
    } else {
      error_msg.value = error.message;
    }
  }

  isLoading.value = false;
  if (error_msg.value != "") {
    error.value = true;
  }
}

JWT字符串可以解密
后台颁发的JWT token是经过签名的,但是签名的目的是防篡改,客户端收到这个token后,是可以解析出签名前的JWT token原文的,但是由于不知道签名JWT使用的密钥,修改后无法再签名并提交给服务端。

2. 服务端验证用户登录

以下代码基于FastAPI

# 登录方法
@app.post("/token")
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(),remember: bool|None=Body(None),log_details: None = Depends(log_request_details))-> Token:
    '''
    OAuth2PasswordRequestForm 是用以下几项内容声明表单请求体的类依赖项:

    username
    password
    scope、grant_type、client_id等可选字段。
    '''
    
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="用户名或者密码错误",
            headers={
   "WWW-Authenticate": "Bearer"},
        )
    m = 0
    if
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值