keycloak提供了jwks服務,其地址可以在/auth/realms/fabao/.well-known/openid-configuration
的返回結果中找到,jwks_uri
它表示了公鑰的頒發者,可以使用頒發出來的公鑰來驗證token的簽名,基地址也是固定的/auth/realms/fabao/protocol/openid-connect/certs
。
springboot構建keycloak的token校驗服務
依賴包
jwt的解析以來於java-jwt
包,由jwks服務解析依賴於jwks-rsa
包,jwks是什麼,可以看這裡
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>14.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- keycloak -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
<!-- jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.12.0</version>
</dependency>
</dependencies>
相關配置
keycloak:
realm: fabao
resource: pkulaw
client-key-password: c0b7ab8e-485b-4a10-bff8-7c7d3f472096
auth-server-url: http://192.168.xx.xx:8080/auth/realms/fabao/protocol/openid-connect/auth
kc:
jwk-set-uri: http://192.168.xx.xx:8080/auth/realms/fabao/protocol/openid-connect/certs
certs-id: E_6ih35yTLJMieI0vqg9MmTQrJ6RcUSxiXeNdcMaoYk
jwks服務
@Service
public class JwtService {
@Value("${kc.jwk-set-uri}")
private String jwksUrl;
@Value("${kc.certs-id}")
private String certsId;
@Cacheable(value = "jwkCache")
public Jwk getJwk() throws Exception {
return new UrlJwkProvider(new URL(jwksUrl)).get(certsId);
}
}
校驗token
這只是個簡單的demo,真實專案中,這種校驗的程式碼應該寫在攔截器中,統一進行處理
@GetMapping("/teacher")
public HashMap teacher(@RequestHeader("Authorization") String authHeader) {
try {
DecodedJWT jwt = JWT.decode(authHeader.replace("Bearer", "").trim());
// check JWT is valid
Jwk jwk = jwtService.getJwk();
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
algorithm.verify(jwt);
// check JWT role is correct
List<String> roles = ((List)jwt.getClaim("realm_access").asMap().get("roles"));
if(!roles.contains("teacher"))
throw new Exception("not a teacher role");
// check JWT is still active
Date expiryDate = jwt.getExpiresAt();
if(expiryDate.before(new Date()))
throw new Exception("token is expired");
// all validation passed
return new HashMap() {{
put("role", "teacher");
}};
} catch (Exception e) {
logger.error("exception : {} ", e.getMessage());
return new HashMap() {{
put("status", "forbidden");
}};
}
}
JWKS(JSON Web Key Set)是一個包含一組公鑰的 JSON 格式檔案,用於在使用 JSON Web Token(JWT)進行身份驗證和授權時,驗證 JWT 的簽名。JWKS 通常用於在 OAuth 2.0 和 OpenID Connect 等認證協議中進行金鑰管理。
在 JWKS 中,每個公鑰都包含了演算法、公鑰型別和實際的公鑰值。透過 JWKS,驗證方可以獲取到簽發方使用的公鑰,從而驗證 JWT 的簽名是否有效。
JWKS 包含以下主要欄位:
keys
:一個陣列,包含多個公鑰資訊的物件,每個物件包括了公鑰的相關資訊,如演算法、公鑰型別和公鑰值等。
示例 JWKS 檔案如下所示:
{
"keys": [
{
"kty": "RSA",
"kid": "123",
"use": "sig",
"alg": "RS256",
"n": "public_key_value",
"e": "AQAB"
}
]
}
在使用 JWT 進行身份驗證時,驗證方可以透過獲取並解析 JWKS 檔案中的公鑰資訊,然後使用這些公鑰來驗證 JWT 的簽名是否有效。這樣可以提高安全性,並確保只有合法的簽發方才能生成有效的 JWT。
參考:https://developers.redhat.com/blog/authentication-and-authorization-using-the-keycloak-rest-api#keycloak_connection_using_a_java_application