编程技术文章分享与教程

网站首页 > 技术文章 正文

探索未知:使用 Vanilla JS 连接到 Keycloak

hmc789 2024-11-22 15:30:14 技术文章 3 ℃

在 Web 开发中,安全和身份管理至关重要。 在这种情况下,Keycloak 经常占据中心舞台。 尽管存在大量文档,但没有什么比实践经验更好的了。 本文介绍了我通过 Vanilla JavaScript 对 Keycloak 的探索。

注意:这是一项实验研究,而不是可用于生产的指南。

使用简单的 HTML5 页面和 id 为“root”的 div 设置 HTMLStart,以显示经过身份验证的用户信息。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>VANILLA JS AUTH</title>
</head>
<body>
    <div id="root" />
    <script src="./main.js"></script>
</body>
</html>

初始化

创建main.js并初始化baseUrl、realm和client_id等变量。

const baseUrl = "KEYCLOAK_URL";
const realm = "KEYCLOAK_REALM";
const client_id = "KEYCLOAK_CLIENT_ID";
const redirect_uri = `${location.protocol}//${location.host}/`;

随机状态和随机数生成

生成随机状态和随机数对于 OAuth 2.0 和 OpenID Connect 安全至关重要。 我们使用generateRandomState()来创建这些值。

const generateRandomState=()=> {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = Math.random() * 16 | 0,
          v = c == 'x' ? r : (r & 0x3 | 0x8);
      return v.toString(16);
  });
}

重定向到Keycloak

未经身份验证的用户会使用redirectToKeycloak() 重定向到Keycloak。

const redirectToKeycloak = () => {
    const state = generateRandomState();
    const nonce = generateRandomState();
    const urlParams = {
        client_id,
        redirect_uri,
        response_mode: "fragment",
        response_type: "code",
        scope: "openid",
        nonce,
        state,
    };
    const conectionURI = new URL(`${baseUrl}/realms/${realm}/protocol/openid-connect/auth`);
    for(const key of Object.keys(urlParams)){
        conectionURI.searchParams.append(key, urlParams[key]);
    }
    window.location.href = conectionURI;
}

在此函数中,我们使用 baseurl 和领域构造一个新的 URL。 然后我们附加各种参数,如 client_id、redirect_uri、nonce 和 state。 response_type 参数设置为“code”,向 Keycloak 发出信号,表明我们正在请求授权代码。 稍后将使用此代码来获取令牌。 最后,我们将用户重定向到此 Keycloak URL。

token获取

身份验证成功后,Keycloak 会使用代码将您重定向回来。 使用 getToken(code) 获取令牌。

const getToken = async (code) => {
    const tokenUri = new URL(`${baseUrl}/realms/${realm}/protocol/openid-connect/token`);
    var body = new URLSearchParams();
    body.append("client_id", client_id);
    body.append("redirect_uri", redirect_uri);
    body.append("grant_type", "authorization_code");
    body.append("code", code);
    const data = await fetch(tokenUri, {
        method: 'post',
        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
        body,
    });
    const jsonData = await data.json();
    sessionStorage.setItem("token", jsonData.access_token);
    sessionStorage.setItem("id_token", jsonData.id_token);
    sessionStorage.setItem("refresh_token", jsonData.refresh_token);
    sessionStorage.setItem("session_state", jsonData.session_state);
    history.replaceState({}, '', '/');
    location.reload();
}

显示用户数据

从 sessionStorage 中获取令牌并显示用户详细信息。

const decodeJWT = (token) => {
    const parts = token.split('.');
    if (parts.length !== 3) throw new Error('Invalid JWT');
    const decoded = atob(parts[1]);
    const payload = JSON.parse(decoded);
    return payload;
}
const generateUserProfile = () => {
    const JWT = sessionStorage.getItem("token");
    const payload = decodeJWT(JWT);
    console.log(payload);
    const div = document.createElement("div");
    const userName =  document.createElement("label");
    userName.innerText = `--------${payload.given_name} ${payload.family_name} (${payload.email})--------`;
    const logoutButton = document.createElement("button");
    logoutButton.innerText = "Logout";
    logoutButton.addEventListener("click", ()=>{
        logout();
    });
    div.appendChild(userName);
    div.appendChild(logoutButton);
    return div;
}

注销

通过发送存储的 id_token 来启用用户注销。

const logout = () => {
    const logoutURI = new URL(`${baseUrl}/realms/${realm}/protocol/openid-connect/logout`);
    const id_token_hint = sessionStorage.getItem("id_token");
    const urlParams = {
        client_id,
        post_logout_redirect_uri: redirect_uri,
        id_token_hint,
    };
    for(const key of Object.keys(urlParams)){
        logoutURI.searchParams.append(key, urlParams[key]);
    }
    sessionStorage.clear();
    window.location.href = logoutURI;
}

事件处理

最后,我们处理窗口加载和不同的场景,例如拥有令牌或需要生成令牌。

window.addEventListener("load", ()=>{
  const token = sessionStorage.getItem("token");
  if(token!==null){
      const __rootElement = document.querySelector("#root");
      const __content = generateUserProfile();
      __rootElement.appendChild(__content);
      return;
  }
  const params = new URLSearchParams(window.location.hash.split("#")[1]);
  const code = params.get("code");
  if(code){
      getToken(code);
      return;
  }
  redirectToKeycloak();
});

最后的想法

这个 Vanilla JS-Keycloak 实验提供了对身份验证流程的基本了解,为更高级的应用程序奠定了基础。 请注意,在现实场景中,验证“状态”和“随机数”等额外的安全措施至关重要。 本文可为那些热衷于应用程序安全和身份管理的人提供实用指南。

Tags:

标签列表
最新留言