如何通过KeyCloak实现Java应用认证授权与跨应用访问?
我来一步步帮你理清这些问题,毕竟Keycloak的客户端定位和认证流程确实容易搞混😉
核心问题拆解与解决方案
1. Keycloak客户端testclient的角色定位
你现在的两个应用分别是:
- 受保护的Java Servlet:这是资源服务器,负责验证请求合法性,确保只有认证通过的用户/应用能访问
- 发起请求的Java应用:这是客户端,需要先向Keycloak获取合法令牌,再访问Servlet
所以你的testclient应该代表发起请求的Java应用(也就是那个要调用Servlet的后台服务)。而你的Servlet本身也需要在Keycloak里注册一个独立客户端(比如命名为servlet-resource-client),用来作为资源服务器的身份标识,和Wildfly上的Keycloak适配器做交互。
2. 两端配置步骤
2.1 Servlet端(Wildfly + Keycloak适配器)
- 确保Wildfly已安装对应版本的Keycloak适配器(若未安装,先下载适配包并完成安装)
- 修改Wildfly的
standalone.xml,配置Keycloak子系统:- 绑定你的Keycloak独立服务器地址与领域信息
- 为Servlet注册专属客户端(
servlet-resource-client),设置类型为confidential - 配置路径保护规则,要求
/testservlet路径需testrole角色才能访问
<subsystem xmlns="urn:jboss:domain:keycloak:1.1"> <secure-deployment name="testservlet.war"> <realm>你的领域名称</realm> <auth-server-url>http://你的Keycloak地址:8080/auth</auth-server-url> <ssl-required>external</ssl-required> <resource>servlet-resource-client</resource> <credential name="secret">你的servlet客户端密钥</credential> <principal-attribute>preferred_username</principal-attribute> <policy-enforcer> <policy-enforcer-config> <paths> <path name="/testservlet"> <methods> <method name="GET"> <scopes> <scope>testrole</scope> </scopes> </method> </methods> </path> </paths> </policy-enforcer-config> </policy-enforcer> </secure-deployment> </subsystem>
2.2 发起请求的Java应用端(Keycloak客户端testclient配置)
- 在Keycloak领域内编辑
testclient:- 设置客户端类型为
Confidential(后台服务器应用需存储客户端密钥,不能用公共类型) - 开启Resource Owner Password Credentials Grant(该流程适合后台应用用用户名密码直接获取令牌,无需浏览器重定向)
- 确保
testuser拥有的testrole能被该客户端请求到(或直接给testclient分配对应角色,根据你的权限需求选择) - 记录
testclient的Client ID和Client Secret,后续代码会用到
- 设置客户端类型为
3. 获取JWT令牌的流程与代码示例
后台Java应用不能沿用手动访问的重定向流程(那是给浏览器设计的Authorization Code Flow),必须用Resource Owner Password Credentials Grant直接向Keycloak请求令牌:
3.1 令牌请求代码示例(Java原生HttpURLConnection)
import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.HashMap; import java.util.Map; public class KeycloakTokenFetcher { public static String getAccessToken() throws Exception { // Keycloak令牌端点地址 String tokenUrl = "http://你的Keycloak地址:8080/auth/realms/你的领域名/protocol/openid-connect/token"; // 请求参数 Map<String, String> params = new HashMap<>(); params.put("grant_type", "password"); params.put("client_id", "testclient"); params.put("client_secret", "你的testclient密钥"); params.put("username", "testuser"); params.put("password", "testuser的密码"); params.put("scope", "openid"); // 构建POST请求 URL url = new URL(tokenUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setDoOutput(true); // 写入请求参数 DataOutputStream out = new DataOutputStream(conn.getOutputStream()); StringBuilder postData = new StringBuilder(); for (Map.Entry<String, String> entry : params.entrySet()) { if (postData.length() != 0) postData.append('&'); postData.append(entry.getKey()).append('=').append(entry.getValue()); } out.writeBytes(postData.toString()); out.flush(); out.close(); // 读取响应内容 BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); String inputLine; StringBuilder response = new StringBuilder(); while ((inputLine = in.readLine()) != null) { response.append(inputLine); } in.close(); // 简单解析JSON获取access_token(生产环境建议用Jackson/Gson等工具解析) return response.toString().split("\"access_token\":\"")[1].split("\"")[0]; } }
4. 访问受保护的Servlet
拿到access_token后,在访问localhost:8080/testservlet的请求中添加Authorization头即可:
import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; public class ServletClient { public static void main(String[] args) throws Exception { String accessToken = KeycloakTokenFetcher.getAccessToken(); URL servletUrl = new URL("http://localhost:8080/testservlet"); HttpURLConnection conn = (HttpURLConnection) servletUrl.openConnection(); conn.setRequestMethod("GET"); conn.setRequestProperty("Authorization", "Bearer " + accessToken); // 读取Servlet响应 BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); String inputLine; StringBuilder response = new StringBuilder(); while ((inputLine = in.readLine()) != null) { response.append(inputLine); } in.close(); System.out.println("Servlet响应内容:" + response.toString()); } }
补充说明
- 如果发起请求的Java应用不需要用户身份(仅服务对服务调用),可以改用Client Credentials Grant,无需传入用户名密码,直接用
testclient的身份获取令牌,此时需给testclient分配对应的服务角色 - JWT令牌包含了用户身份、角色等信息,Servlet端的Keycloak适配器会自动验证令牌的合法性、过期时间,以及是否拥有访问所需的角色
内容的提问来源于stack exchange,提问作者Kobbe
相关产品推荐
相关产品推荐

