0
# JAAS Integration
1
2
JAAS (Java Authentication and Authorization Service) integration for enterprise Java applications with login modules and principal management. This module provides standard JAAS interfaces for integrating Keycloak authentication into existing Java security frameworks.
3
4
## Capabilities
5
6
### AbstractKeycloakLoginModule
7
8
Base abstract login module providing common JAAS integration functionality for Keycloak authentication.
9
10
```java { .api }
11
/**
12
* Base abstract login module providing common JAAS integration functionality for Keycloak authentication
13
*/
14
public abstract class AbstractKeycloakLoginModule implements LoginModule {
15
/**
16
* Configuration option key for Keycloak configuration file path
17
*/
18
public static final String KEYCLOAK_CONFIG_FILE_OPTION = "keycloak-config-file";
19
20
/**
21
* Configuration option key for role principal class name
22
*/
23
public static final String ROLE_PRINCIPAL_CLASS_OPTION = "role-principal-class";
24
25
/**
26
* Resource path for profile configuration
27
*/
28
public static final String PROFILE_RESOURCE = "profile-resource";
29
30
/**
31
* Authentication result container
32
*/
33
public static class Auth {
34
// Contains authentication details and tokens
35
}
36
37
/**
38
* Initialize the login module with configuration
39
* @param subject Subject to authenticate
40
* @param callbackHandler Handler for obtaining credentials
41
* @param sharedState Shared state between login modules
42
* @param options Configuration options
43
*/
44
public void initialize(
45
Subject subject,
46
CallbackHandler callbackHandler,
47
Map<String, ?> sharedState,
48
Map<String, ?> options
49
);
50
51
/**
52
* Perform authentication
53
* @return true if authentication succeeded
54
* @throws LoginException If authentication fails
55
*/
56
public boolean login() throws LoginException;
57
58
/**
59
* Commit the authentication (add principals to subject)
60
* @return true if commit succeeded
61
* @throws LoginException If commit fails
62
*/
63
public boolean commit() throws LoginException;
64
65
/**
66
* Abort the authentication (cleanup on failure)
67
* @return true if abort succeeded
68
* @throws LoginException If abort fails
69
*/
70
public boolean abort() throws LoginException;
71
72
/**
73
* Logout (remove principals from subject)
74
* @return true if logout succeeded
75
* @throws LoginException If logout fails
76
*/
77
public boolean logout() throws LoginException;
78
79
/**
80
* Resolve Keycloak deployment from configuration file
81
* @param keycloakConfigFile Path to Keycloak configuration file
82
* @return Resolved KeycloakDeployment
83
* @throws RuntimeException If configuration cannot be loaded
84
*/
85
protected KeycloakDeployment resolveDeployment(String keycloakConfigFile);
86
87
/**
88
* Create role principal for the given role name
89
* @param roleName Name of the role
90
* @return Principal representing the role
91
*/
92
protected Principal createRolePrincipal(String roleName);
93
94
/**
95
* Authenticate using bearer token
96
* @param tokenString Bearer token string
97
* @return Authentication result
98
* @throws VerificationException If token verification fails
99
*/
100
protected Auth bearerAuth(String tokenString) throws VerificationException;
101
102
/**
103
* Post-process authentication result after token verification
104
* @param tokenString Original token string
105
* @param token Verified access token
106
* @return Updated authentication result
107
*/
108
protected Auth postTokenVerification(String tokenString, AccessToken token);
109
110
/**
111
* Perform authentication with username and password (abstract method)
112
* @param username Username credential
113
* @param password Password credential
114
* @return Authentication result
115
* @throws Exception If authentication fails
116
*/
117
protected abstract Auth doAuth(String username, String password) throws Exception;
118
119
/**
120
* Get logger for this login module (abstract method)
121
* @return Logger instance
122
*/
123
protected abstract Logger getLogger();
124
}
125
```
126
127
**Usage Examples:**
128
129
```java
130
// Custom login module implementation
131
public class CustomKeycloakLoginModule extends AbstractKeycloakLoginModule {
132
private static final Logger logger = LoggerFactory.getLogger(CustomKeycloakLoginModule.class);
133
134
@Override
135
protected Auth doAuth(String username, String password) throws Exception {
136
// Implement username/password authentication
137
KeycloakDeployment deployment = resolveDeployment(getConfigFile());
138
139
// Use Keycloak's direct access grants (resource owner password credentials)
140
try {
141
AccessTokenResponse tokenResponse = authenticateWithPassword(deployment, username, password);
142
String tokenString = tokenResponse.getToken();
143
144
// Verify the token
145
AccessToken token = AdapterTokenVerifier.verifyToken(tokenString, deployment);
146
147
// Return authentication result
148
return postTokenVerification(tokenString, token);
149
150
} catch (Exception e) {
151
getLogger().warn("Authentication failed for user: {}", username, e);
152
throw new LoginException("Authentication failed: " + e.getMessage());
153
}
154
}
155
156
@Override
157
protected Logger getLogger() {
158
return logger;
159
}
160
161
private AccessTokenResponse authenticateWithPassword(KeycloakDeployment deployment,
162
String username, String password) throws Exception {
163
// Implementation for direct access grant
164
HttpClient client = deployment.getClient();
165
HttpPost post = new HttpPost(deployment.getTokenUrl());
166
167
List<NameValuePair> formParams = new ArrayList<>();
168
formParams.add(new BasicNameValuePair("grant_type", "password"));
169
formParams.add(new BasicNameValuePair("username", username));
170
formParams.add(new BasicNameValuePair("password", password));
171
172
AdapterUtils.setClientCredentials(deployment, post, formParams);
173
174
post.setEntity(new UrlEncodedFormEntity(formParams));
175
176
HttpResponse response = client.execute(post);
177
int statusCode = response.getStatusLine().getStatusCode();
178
179
if (statusCode == 200) {
180
return JsonSerialization.readValue(response.getEntity().getContent(), AccessTokenResponse.class);
181
} else {
182
throw new Exception("Authentication failed with status: " + statusCode);
183
}
184
}
185
}
186
187
// JAAS configuration file (jaas.conf)
188
/*
189
MyApplication {
190
com.example.CustomKeycloakLoginModule required
191
keycloak-config-file="/path/to/keycloak.json"
192
role-principal-class="org.keycloak.adapters.jaas.RolePrincipal";
193
};
194
*/
195
196
// Usage in application
197
System.setProperty("java.security.auth.login.config", "/path/to/jaas.conf");
198
199
LoginContext loginContext = new LoginContext("MyApplication", new CallbackHandler() {
200
@Override
201
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
202
for (Callback callback : callbacks) {
203
if (callback instanceof NameCallback) {
204
((NameCallback) callback).setName("user@example.com");
205
} else if (callback instanceof PasswordCallback) {
206
((PasswordCallback) callback).setPassword("password".toCharArray());
207
}
208
}
209
}
210
});
211
212
try {
213
loginContext.login();
214
Subject subject = loginContext.getSubject();
215
216
// Access authenticated user information
217
Set<Principal> principals = subject.getPrincipals();
218
for (Principal principal : principals) {
219
System.out.println("Principal: " + principal.getName());
220
}
221
222
// Perform authorized actions
223
Subject.doAs(subject, new PrivilegedAction<Void>() {
224
@Override
225
public Void run() {
226
// Code running with authenticated subject
227
return null;
228
}
229
});
230
231
} finally {
232
loginContext.logout();
233
}
234
```
235
236
### RolePrincipal
237
238
Principal implementation representing a user role in JAAS context.
239
240
```java { .api }
241
/**
242
* Principal implementation representing a user role in JAAS context
243
*/
244
public class RolePrincipal implements Principal {
245
/**
246
* Constructor with role name
247
* @param roleName Name of the role
248
*/
249
public RolePrincipal(String roleName);
250
251
/**
252
* Check equality with another principal
253
* @param p Principal to compare with
254
* @return true if principals are equal
255
*/
256
public boolean equals(Object p);
257
258
/**
259
* Get hash code for this principal
260
* @return Hash code value
261
*/
262
public int hashCode();
263
264
/**
265
* Get the role name
266
* @return Role name string
267
*/
268
public String getName();
269
270
/**
271
* String representation of the role principal
272
* @return Formatted string representation
273
*/
274
public String toString();
275
}
276
```
277
278
**Usage Examples:**
279
280
```java
281
// Create role principals
282
RolePrincipal adminRole = new RolePrincipal("admin");
283
RolePrincipal userRole = new RolePrincipal("user");
284
285
// Use in custom login module
286
@Override
287
public boolean commit() throws LoginException {
288
if (authenticationSucceeded) {
289
// Add user principal
290
subject.getPrincipals().add(new KeycloakPrincipal<>(username, securityContext));
291
292
// Add role principals
293
Set<String> roles = extractRolesFromToken(accessToken);
294
for (String role : roles) {
295
subject.getPrincipals().add(new RolePrincipal(role));
296
}
297
298
return true;
299
}
300
return false;
301
}
302
303
// Check roles in authorized code
304
public boolean hasRole(Subject subject, String roleName) {
305
Set<RolePrincipal> rolePrincipals = subject.getPrincipals(RolePrincipal.class);
306
return rolePrincipals.stream()
307
.anyMatch(role -> roleName.equals(role.getName()));
308
}
309
310
// Authorization check example
311
Subject.doAs(subject, new PrivilegedAction<Void>() {
312
@Override
313
public Void run() {
314
Subject currentSubject = Subject.getSubject(AccessController.getContext());
315
316
if (hasRole(currentSubject, "admin")) {
317
// Perform admin operations
318
performAdminOperation();
319
} else if (hasRole(currentSubject, "user")) {
320
// Perform user operations
321
performUserOperation();
322
} else {
323
throw new SecurityException("Insufficient privileges");
324
}
325
326
return null;
327
}
328
});
329
```
330
331
## JAAS Integration Patterns
332
333
### Bearer Token Login Module
334
335
```java
336
// Login module for bearer token authentication
337
public class BearerTokenLoginModule extends AbstractKeycloakLoginModule {
338
private static final Logger logger = LoggerFactory.getLogger(BearerTokenLoginModule.class);
339
private String bearerToken;
340
341
@Override
342
public void initialize(Subject subject, CallbackHandler callbackHandler,
343
Map<String, ?> sharedState, Map<String, ?> options) {
344
super.initialize(subject, callbackHandler, sharedState, options);
345
346
// Extract bearer token from shared state or callback
347
this.bearerToken = (String) sharedState.get("bearer.token");
348
}
349
350
@Override
351
protected Auth doAuth(String username, String password) throws Exception {
352
// This module doesn't use username/password
353
throw new UnsupportedOperationException("Use bearer token authentication");
354
}
355
356
@Override
357
public boolean login() throws LoginException {
358
if (bearerToken == null) {
359
// Try to get token via callback
360
try {
361
BearerTokenCallback tokenCallback = new BearerTokenCallback();
362
callbackHandler.handle(new Callback[]{tokenCallback});
363
bearerToken = tokenCallback.getToken();
364
} catch (Exception e) {
365
throw new LoginException("Failed to obtain bearer token: " + e.getMessage());
366
}
367
}
368
369
if (bearerToken == null) {
370
return false;
371
}
372
373
try {
374
auth = bearerAuth(bearerToken);
375
return auth != null;
376
} catch (VerificationException e) {
377
getLogger().warn("Bearer token verification failed", e);
378
throw new LoginException("Invalid bearer token: " + e.getMessage());
379
}
380
}
381
382
@Override
383
protected Logger getLogger() {
384
return logger;
385
}
386
}
387
388
// Custom callback for bearer token
389
public class BearerTokenCallback implements Callback {
390
private String token;
391
392
public String getToken() {
393
return token;
394
}
395
396
public void setToken(String token) {
397
this.token = token;
398
}
399
}
400
```
401
402
### Web Application Integration
403
404
```java
405
// Servlet filter integrating JAAS with web requests
406
public class JAASKeycloakFilter implements Filter {
407
private String jaasConfigName;
408
409
@Override
410
public void init(FilterConfig filterConfig) throws ServletException {
411
jaasConfigName = filterConfig.getInitParameter("jaas-config-name");
412
if (jaasConfigName == null) {
413
jaasConfigName = "KeycloakWeb";
414
}
415
}
416
417
@Override
418
public void doFilter(ServletRequest request, ServletResponse response,
419
FilterChain chain) throws IOException, ServletException {
420
HttpServletRequest httpRequest = (HttpServletRequest) request;
421
HttpServletResponse httpResponse = (HttpServletResponse) response;
422
423
String authHeader = httpRequest.getHeader("Authorization");
424
if (authHeader != null && authHeader.startsWith("Bearer ")) {
425
String token = authHeader.substring(7);
426
427
try {
428
// Authenticate using JAAS
429
LoginContext loginContext = new LoginContext(jaasConfigName, new BearerTokenCallbackHandler(token));
430
loginContext.login();
431
432
Subject subject = loginContext.getSubject();
433
434
// Continue with authenticated subject
435
Subject.doAs(subject, new PrivilegedExceptionAction<Void>() {
436
@Override
437
public Void run() throws Exception {
438
chain.doFilter(request, response);
439
return null;
440
}
441
});
442
443
loginContext.logout();
444
445
} catch (LoginException e) {
446
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
447
httpResponse.getWriter().write("Authentication failed: " + e.getMessage());
448
return;
449
} catch (PrivilegedActionException e) {
450
throw new ServletException(e.getException());
451
}
452
} else {
453
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
454
httpResponse.getWriter().write("Bearer token required");
455
}
456
}
457
458
private static class BearerTokenCallbackHandler implements CallbackHandler {
459
private final String token;
460
461
public BearerTokenCallbackHandler(String token) {
462
this.token = token;
463
}
464
465
@Override
466
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
467
for (Callback callback : callbacks) {
468
if (callback instanceof BearerTokenCallback) {
469
((BearerTokenCallback) callback).setToken(token);
470
} else {
471
throw new UnsupportedCallbackException(callback);
472
}
473
}
474
}
475
}
476
}
477
```
478
479
### Enterprise Application Server Integration
480
481
```java
482
// JBoss/WildFly integration example
483
public class KeycloakSecurityDomain {
484
485
// Configure in standalone.xml or domain.xml
486
/*
487
<security-domain name="keycloak">
488
<authentication>
489
<login-module code="com.example.KeycloakLoginModule" flag="required">
490
<module-option name="keycloak-config-file" value="/opt/keycloak/keycloak.json"/>
491
<module-option name="role-principal-class" value="org.keycloak.adapters.jaas.RolePrincipal"/>
492
</login-module>
493
</authentication>
494
</security-domain>
495
*/
496
497
// EJB with security annotations
498
@Stateless
499
@SecurityDomain("keycloak")
500
public class SecureEJB {
501
502
@RolesAllowed({"admin", "manager"})
503
public void adminOperation() {
504
Subject subject = Subject.getSubject(AccessController.getContext());
505
// Access authenticated subject
506
}
507
508
@RolesAllowed("user")
509
public void userOperation() {
510
// User-level operation
511
}
512
}
513
}
514
```
515
516
### Configuration Examples
517
518
```java
519
// Complete JAAS configuration
520
/*
521
# jaas.conf
522
KeycloakApp {
523
com.example.CustomKeycloakLoginModule required
524
keycloak-config-file="/etc/keycloak/keycloak.json"
525
role-principal-class="org.keycloak.adapters.jaas.RolePrincipal";
526
};
527
528
KeycloakWeb {
529
com.example.BearerTokenLoginModule required
530
keycloak-config-file="/etc/keycloak/keycloak.json";
531
};
532
*/
533
534
// System properties setup
535
System.setProperty("java.security.auth.login.config", "/etc/jaas.conf");
536
537
// Programmatic configuration
538
Configuration.setConfiguration(new Configuration() {
539
@Override
540
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
541
if ("KeycloakApp".equals(name)) {
542
Map<String, String> options = new HashMap<>();
543
options.put("keycloak-config-file", "/etc/keycloak/keycloak.json");
544
options.put("role-principal-class", "org.keycloak.adapters.jaas.RolePrincipal");
545
546
return new AppConfigurationEntry[] {
547
new AppConfigurationEntry(
548
"com.example.CustomKeycloakLoginModule",
549
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
550
options
551
)
552
};
553
}
554
return null;
555
}
556
});
557
```
558
559
### BearerTokenLoginModule
560
561
Concrete JAAS login module for authenticating bearer tokens in JAAS environments.
562
563
```java { .api }
564
/**
565
* JAAS login module for bearer token authentication
566
* Expects username (ignored) and password (bearer token)
567
*/
568
public class BearerTokenLoginModule extends AbstractKeycloakLoginModule {
569
/**
570
* Authenticate using bearer token passed as password parameter
571
* @param username Username (ignored for bearer token auth)
572
* @param password Bearer token string
573
* @return Authentication result
574
* @throws VerificationException If token verification fails
575
*/
576
protected Auth doAuth(String username, String password) throws VerificationException;
577
578
/**
579
* Get logger for this login module
580
* @return Logger instance
581
*/
582
protected Logger getLogger();
583
}
584
```
585
586
### DirectAccessGrantsLoginModule
587
588
JAAS login module implementing OAuth2 Resource Owner Password Credentials Grant for direct username/password authentication with Keycloak.
589
590
```java { .api }
591
/**
592
* JAAS login module for direct access grants (username/password authentication)
593
* Implements OAuth2 Resource Owner Password Credentials Grant
594
*/
595
public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
596
/**
597
* Configuration option key for OAuth2 scope parameter
598
*/
599
public static final String SCOPE_OPTION = "scope";
600
601
/**
602
* Authenticate using direct access grants (username/password)
603
* @param username User's username
604
* @param password User's password
605
* @return Authentication result with tokens
606
* @throws IOException If network communication fails
607
* @throws VerificationException If token verification fails
608
*/
609
protected Auth doAuth(String username, String password) throws IOException, VerificationException;
610
611
/**
612
* Perform direct grant authentication against Keycloak
613
* @param username User's username
614
* @param password User's password
615
* @return Authentication result with access and refresh tokens
616
* @throws IOException If HTTP request fails
617
* @throws VerificationException If token verification fails
618
*/
619
protected Auth directGrantAuth(String username, String password) throws IOException, VerificationException;
620
621
/**
622
* Commit authentication (save refresh token to subject's private credentials)
623
* @return true if commit succeeded
624
* @throws LoginException If commit fails
625
*/
626
public boolean commit() throws LoginException;
627
628
/**
629
* Logout and revoke refresh token with Keycloak
630
* @return true if logout succeeded
631
* @throws LoginException If logout fails
632
*/
633
public boolean logout() throws LoginException;
634
}
635
```