大家。我最近开始在一家公司实习,并被指派检修他们的一个使用 Keycloak 的 Web 应用程序。我之前没有任何使用 Keycloak 的经验。目前,当用户登录应用程序时,其会话仅持续 5 分钟,然后访问令牌就会过期。在 JavaScript 代码中,每个函数都会检查响应中是否有更新的访问令牌,如果有则存储它。这意味着如果用户只是浏览应用程序而不发出请求,那么他们将被注销。当然,这不是一个好的实现,我想修复它,以便如果存在任何类型的用户活动(即使只是移动鼠标或浏览页面),那么访问令牌都会更新。
所有其他请求都工作正常,并且登录请求在其响应中返回刷新令牌。但是,当我发出 POST 请求以使用刷新令牌获取新的访问令牌时,我收到 401 错误。我尝试在应用程序内使用 Postman 和 Insomnia 发出此请求,但总是收到 401 错误。
这是我提出的要求:
我读到,当客户端保密时需要 client_secret。但是,“admin-cli”不是机密的,也没有设置客户端机密。另外,我已经在 Keycloak 管理 UI 中检查了刷新令牌已启用。
我已经向 ChatGPT 询问过这个问题,但它的答案没有帮助。感谢您的帮助,非常感谢!
此 URL 和正文数据将获得新的令牌
网址
POST ${keycloakUrl}/realms/${realmName}/protocol/openid-connect/token
以
x-www-form-urlencoded
格式输入正文
client_id: {id}
client_secret: {secret}
grant_type: 'refresh_token'
scope: {scope}
refresh_token: {previous refresh_token}
我将演示从用户登录到通过本地 PC 中的 API 获取
refresh token
的整个过程。
另存为
docker-compose.yml
version: '3.7'
services:
postgres:
image: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: password
keycloak:
image: quay.io/keycloak/keycloak:latest # Update to the latest Keycloak image
command: start-dev
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: password
KC_HTTP_ENABLED: true # Enable HTTP if you're not using HTTPS
KC_HEALTH_ENABLED: true
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
ports:
- 8080:8080
restart: always
depends_on:
- postgres
volumes:
postgres_data:
driver: local
docker compose up
即将推出Keycloak 23.0.3版本
Step 1
创建“my_realm”
Step 2
创建“my_client”
Step 3
添加重定向 URI 'http://localhost:3000/auth/callback'
Step 4
设置my_client
配置
Step 5
复制客户端密钥用于演示(server.js)
Step 6
创建用户1并设置密码“1234”
另存为“server.js”
const express = require('express');
const axios = require('axios');
const cors = require('cors');
const crypto = require('crypto');
const session = require('express-session');
// TODO: Replace these with your actual configuration values
const clientId = 'my_client';
const clientSecret = 'MplDSOhQoiNwjjmA4w1YkBh5YteV8CJx';
const redirectUri = 'http://localhost:3000/auth/callback';
const realmName = 'my_realm';
const keycloakUrl = 'http://localhost:8080';
const responseType = 'code';
// Express setup
const app = express();
const port = 3000;
// Set up the session middleware
app.use(session({
secret: 'top-secret-key',
resave: false,
saveUninitialized: true,
}));
app.use(cors()); // Add CORS middleware
// Function to generate a code verifier for PKCE
function generateCodeVerifier() {
return crypto.randomBytes(32).toString('base64')
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
// Exchange Authorization Code For Tokens
async function exchangeAuthorizationCodeForTokens(authorizationCode, clientId, redirectUri, realmName, keycloakUrl) {
const tokenEndpoint = `${keycloakUrl}/realms/${realmName}/protocol/openid-connect/token`;
const codeVerifier = generateCodeVerifier();
const data = {
grant_type: 'authorization_code',
client_id: clientId,
client_secret: 'MplDSOhQoiNwjjmA4w1YkBh5YteV8CJx',
redirect_uri: redirectUri,
code: authorizationCode,
code_verifier: codeVerifier
};
try {
const response = await axios.post(tokenEndpoint, new URLSearchParams(data), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }), // Ignore self-signed certificate
});
console.log('Token exchange successful');
console.log(response.data);
return {
access_token: response.data.access_token,
refresh_token: response.data.refresh_token,
};
} catch (error) {
console.error('Token exchange failed:', error.response ? error.response.data : error.message);
return false;
}
}
// Get New Tokens by old Refresh Token
async function getRefreshToken(clientId, clientSecret, refresh_token, realmName, keycloakUrl) {
const tokenEndpoint = `${keycloakUrl}/realms/${realmName}/protocol/openid-connect/token`;
const codeVerifier = generateCodeVerifier();
const data = {
grant_type: 'refresh_token',
client_id: clientId,
client_secret: clientSecret,
scope: 'openid email',
refresh_token: refresh_token
};
try {
const response = await axios.post(tokenEndpoint, new URLSearchParams(data), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }), // Ignore self-signed certificate
});
console.log('Token refresh successful');
console.log(response.data);
return {
access_token: response.data.access_token,
refresh_token: response.data.refresh_token,
};
} catch (error) {
console.error('Token exchange failed:', error.response ? error.response.data : error.message);
return false;
}
}
app.get('/login', (req, res) => {
// Construct the Keycloak login URL
const keycloakLoginUrl = `${keycloakUrl}/realms/${realmName}/protocol/openid-connect/auth?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=${encodeURIComponent(responseType)}&scope=openid`;
// Redirect the user to the Keycloak login page
res.redirect(keycloakLoginUrl);
});
app.get('/auth/callback', async (req, res) => {
const authorizationCode = req.query.code;
if (!authorizationCode) {
return res.status(400).send('Authorization code is required');
}
// Exchange authorization code for tokens
const tokens = await exchangeAuthorizationCodeForTokens(authorizationCode, clientId, redirectUri, realmName, keycloakUrl);
if (tokens) {
req.session.accessToken = tokens.access_token;
req.session.refresh_token = tokens.refresh_token;
res.send(JSON.stringify(`{'Access Token': ${tokens.access_token}, 'Refresh Token': ${tokens.refresh_token}}`, null, 4));
} else {
res.status(500).send('Failed to exchange authorization code for tokens');
}
});
app.get('/refresh_token', async (req, res) => {
const tokens = await getRefreshToken(clientId, clientSecret, req.session.refresh_token, realmName, keycloakUrl);
if (tokens) {
req.session.accessToken = tokens.access_token;
req.session.refresh_token = tokens.refresh_token;
res.send(JSON.stringify(`{'new Access Token': ${req.session.accessToken}, 'new Refresh Token': ${req.session.refresh_token}}`, null, 4));
} else {
res.status(500).send('Failed to exchange authorization code for tokens');
}
});
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
node server.js
打开浏览器
http://localhost:3000/login
登录后,浏览器中会显示token
http://localhost:3000/refresh_token
将refresh_token从浏览器复制到Postman 然后点击
Send
按钮,其他设置按照上面的说明。