使用Bearer Token通过Keycloak认证失败,无法访问后端接口
问题:携带Keycloak Access Token请求后端接口被重定向到登录页
背景
项目采用Python/Django后端,通过Keycloak 17.0.0实现认证功能,用户正常登录流程可正常运行。目前有客户端需要程序化访问后端部分接口,计划提供类API密钥的认证方式,经调研选择Offline Token方案。已成功获取access_token并将其作为Bearer Token请求后端接口,但响应返回Keycloak登录页面,无法通过认证访问接口。
实现代码
获取Token的Python代码
# 获取Token data = { "client_id": <id>, "client_secret": <secret>, "username": "admin", "password": "foo", "grant_type": "password", "scope": "openid offline_access" } response = requests.post( 'http://keycloak:8080/realms/<realm>/protocol/openid-connect/token', headers={'Content-Type': 'application/x-www-form-urlencoded'}, data=data )
Token响应内容
{ "access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkWVQ3eWJOdWM3ODN5OU53d3VabmdTaHNSMV83T0xtQ2E5NW5HZHh5bXZvIn0.eyJleHAiOjE2NjI3NDU5NjgsImlhdCI6MTY2Mjc0NTY2OCwianRpIjoiZjcyZDBhYWUtOGFlMy00NjA1LTgzMWYtYzdiN2YzN2E2MzY1IiwiaXNzIjoiaHR0cDovL2tleWNsb2FrOjgwODAvcmVhbG1zL2FyY2FuZS1maXJlIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjJmNzM5ZGQ1LTQxZmUtNDVmNy1hYzBjLTM2NTU4N2MyNTE3MyIsInR5cCI6IkJlYXJlciIsImF6cCI6Im5naW54Iiwic2Vzc2lvbl9zdGF0ZSI6IjVjMGIzNGM2LTc2YWItNDJlNy04ZjBjLTljODEzNzA4ZjcwZCIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1hcmNhbmUtZmlyZSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIG9mZmxpbmVfYWNjZXNzIGVtYWlsIiwic2lkIjoiNWMwYjM0YzYtNzZhYi00MmU3LThmMGMtOWM4MTM3MDhmNzBkIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiQ2hyaXMiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImdpdmVuX25hbWUiOiJDaHJpcyJ9.J3oPEDWw_c-L6jMySSBF1uNV9mjl7d8AFwky8kN71qhTEJLDJFWKznIQ0sAMK8pGenbYOQFGDnAiL5E5BGh5g97jkv-PK1xSsvjKjIEAJuH15FGIeHB8RSth7ZYPcca-2kzRsqfs9ueeKbe1IAMlMcFdKgX3qJa3MPsLLYWVBI5QbxTf068sWxAoWNWCQzYfyTnsZKnnbGEHzMyHyTjKaFfCHj8Y2lTw1RXeEVdts2ck8OVg5B66NHxu4KHQqnS2t3EhhX-vsovctrZ-yyX_KXkv9uaZ8OUjbsPcAFr0Ta8vcK5ay-FyXfmcApwp6JptNBcL4M54OHcYAV3wmJS5Tg", "expires_in":300, "refresh_expires_in":0, "refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlMWI1ZjRmNC0xY2RiLTQwZmUtYWI0OS0yODFmNWExNzE1Y2YifQ.eyJpYXQiOjE2NjI3NDU2NjgsImp0aSI6IjRkMDUwMWQyLTEwYzktNDcwYS1iNmExLWNmZTE3MGUwMzc5YyIsImlzcyI6Imh0dHA6Ly9rZXljbG9hazo4MDgwL3JlYWxtcy9hcmNhbmUtZmlyZSIsImF1ZCI6Imh0dHA6Ly9rZXljbG9hazo4MDgwL3JlYWxtcy9hcmNhbmUtZmlyZSIsInN1YiI6IjJmNzM5ZGQ1LTQxZmUtNDVmNy1hYzBjLTM2NTU4N2MyNTE3MyIsInR5cCI6Ik9mZmxpbmUiLCJhenAiOiJuZ2lueCIsInNlc3Npb25fc3RhdGUiOiI1YzBiMzRjNi03NmFiLTQyZTctOGYwYy05YzgxMzcwOGY3MGQiLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIG9mZmxpbmVfYWNjZXNzIGVtYWlsIiwic2lkIjoiNWMwYjM0YzYtNzZhYi00MmU3LThmMGMtOWM4MTM3MDhmNzBkIn0.DOu_zKq5WFI4PcTn5Qpe-VFwOj-aLtvc3q9SACs51Ew", "token_type":"Bearer", "id_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkWVQ3eWJOdWM3ODN5OU53d3VabmdTaHNSMV83T0xtQ2E5NW5HZHh5bXZvIn0.eyJleHAiOjE2NjI3NDU5NjgsImlhdCI6MTY2Mjc0NTY2OCwiYXV0aF90aW1lIjowLCJqdGkiOiI0MDI5ZmI1ZC05ZDQwLTQ5NTQtYmNiMC02MmYzOTU2YjdmOTIiLCJpc3MiOiJodHRwOi8va2V5Y2xvYWs6ODA4MC9yZWFsbXMvYXJjYW5lLWZpcmUiLCJhdWQiOiJuZ2lueCIsInN1YiI6IjJmNzM5ZGQ1LTQxZmUtNDVmNy1hYzBjLTM2NTU4N2MyNTE3MyIsInR5cCI6IklEIiwiYXpwIjoibmdpbngiLCJzZXNzaW9uX3N0YXRlIjoiNWMwYjM0YzYtNzZhYi00MmU3LThmMGMtOWM4MTM3MDhmNzBkIiwiYXRfaGFzaCI6InNuVW11a3I1VUZWbTRyYVlxM1FxVlEiLCJhY3IiOiIxIiwic2lkIjoiNWMwYjM0YzYtNzZhYi00MmU3LThmMGMtOWM4MTM3MDhmNzBkIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiQ2hyaXMiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImdpdmVuX25hbWUiOiJDaHJpcyJ9.AUGLQUiA8gyz_yEz5mShGdgVS9vQx_LbmJ6Xh9sEqTtIo9rDgflhKBlGK_b8V8KHir1NExtcuLAlSdMAwJWZ02IzMPNazkYEqxM_PMJh3nLXV6Q7Ph9a4BoPsN2xs6c9BxhlrXKgN1NChm38cKZHnBzw0ZlXcGTJfJdQvGjO8GGiXlZuzo9JioCByn-ZQvWtfepEHKZREx6rVcahSBM5PNG1i8GATRbAIxWpl88CRwv6r9OHXcvdjEEZo8Jl4yePumchyEo9NGMnf2Vk0Alp-cZv90AIO91uUAwo6a3P-iT-rJvk-tfJVed7XzxDqOaUA4ZMD9kCmZCyERolglV2QQ", "not-before-policy":0, "session_state":"5c0b34c6-76ab-42e7-8f0c-9c813708f70d", "scope":"openid profile offline_access email" }
请求后端接口的Python代码
# 请求后端接口 response = requests.get( 'http://localhost/api/foo', headers={'Authorization': 'Bearer ' + response.json().get('access_token')} )
异常响应
响应返回Keycloak登录页面代码片段:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" class="login-pf"> <head> <meta charset="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta name="robots" content="noindex, nofollow"> <meta name="viewport" content="width=device-width,initial-scale=1"/> <title>Sign in to ***</title> ...
相关配置文件
Nginx配置(dev.conf)
server { listen 80; server_name localhost; charset utf-8; client_max_body_size 10000m; resolver 127.0.0.11 valid=30s ipv6=off; resolver_timeout 10s; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; # login with keycloak access_by_lua_file /etc/nginx/lua/auth.lua; ... }
认证Lua脚本(auth.lua)
local opts = { redirect_uri_path = "/redirect_uri", accept_none_alg = true, discovery = "http://keycloak:8080/realms/<realm>/.well-known/openid-configuration", client_id = <id>, client_secret = <secret>, redirect_uri_scheme = "http", logout_path = "/logout", redirect_after_logout_uri = "http://keycloak:8080/realms/<realm>/protocol/openid-connect/logout", post_logout_redirect_uri = "http://localhost", redirect_after_logout_with_id_token_hint = false, revoke_tokens_on_logout = true, session_contents = {id_token=true,access_token=true,user=true} } -- 调用introspect验证OAuth 2.0 Bearer Access Token local res, err = require("resty.openidc").authenticate(opts) if err then ngx.status = 403 ngx.say(err) ngx.exit(ngx.HTTP_FORBIDDEN) end -- 解码并传递会话用户角色 local jwt = require "resty.jwt" local jwt_obj = jwt:load_jwt(res.access_token) local cjson = require "cjson" ngx.log(ngx.DEBUG, "res.access_token.sub=", cjson.encode(jwt_obj)) ngx.req.set_header("ACCESS-TOKEN", cjson.encode(jwt_obj))
疑问与需求
- 无法确定为何携带access_token仍被重定向到登录页,怀疑问题出在Lua脚本逻辑或配置中
- 希望了解针对程序化客户端访问后端接口的更优方案
内容的提问来源于stack exchange,提问作者anand4
相关产品推荐
相关产品推荐

