revoke on logoutAll
This commit is contained in:
parent
fcc95ef99b
commit
52018b1f81
14 changed files with 80 additions and 14 deletions
|
@ -37,7 +37,7 @@
|
|||
<tr>
|
||||
<th class="kc-table-actions" colspan="3">
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-primary" ng-click="logoutAll()">Logout All</a>
|
||||
<a class="btn btn-primary" ng-click="logoutAll()">Invalidate All Sessions</a>
|
||||
<a class="btn btn-primary" ng-click="loadUsers()">Show Users</a>
|
||||
</div>
|
||||
</th>
|
||||
|
@ -52,7 +52,7 @@
|
|||
<tr data-ng-repeat="(user, data) in users">
|
||||
<td><a href="#/realms/{{realm.realm}}/users/{{user}}">{{user}}</a></td>
|
||||
<td>{{data.whenLoggedIn | date:'medium'}}</td>
|
||||
<td><a ng-click="logoutUser(user)">logout</a> </td>
|
||||
<td><a ng-click="logoutUser(user)">invalidate session</a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<tr data-ng-repeat="(application, data) in stats">
|
||||
<td><a href="#/realms/{{realm.realm}}/applications/{{application}}/sessions">{{application}}</a></td>
|
||||
<td>{{data.whenLoggedIn | date:'medium'}}</td>
|
||||
<td><a ng-click="logoutApplication(application)">logout</a> </td>
|
||||
<td><a ng-click="logoutApplication(application)">invalidate session</a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -7,11 +7,12 @@ package org.keycloak.representations.adapters.action;
|
|||
public class LogoutAction extends AdminAction {
|
||||
public static final String LOGOUT = "LOGOUT";
|
||||
protected String user;
|
||||
protected int notBefore;
|
||||
|
||||
public LogoutAction() {
|
||||
}
|
||||
|
||||
public LogoutAction(String id, int expiration, String resource, String user) {
|
||||
public LogoutAction(String id, int expiration, String resource, String user, int notBefore) {
|
||||
super(id, expiration, resource, LOGOUT);
|
||||
this.user = user;
|
||||
}
|
||||
|
@ -24,6 +25,14 @@ public class LogoutAction extends AdminAction {
|
|||
this.user = user;
|
||||
}
|
||||
|
||||
public int getNotBefore() {
|
||||
return notBefore;
|
||||
}
|
||||
|
||||
public void setNotBefore(int notBefore) {
|
||||
this.notBefore = notBefore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate() {
|
||||
return LOGOUT.equals(action);
|
||||
|
|
|
@ -105,6 +105,9 @@ public class PreAuthActionsHandler {
|
|||
userSessionManagement.logout(user);
|
||||
} else {
|
||||
log.info("logout of all sessions");
|
||||
if (action.getNotBefore() > deployment.getNotBefore()) {
|
||||
deployment.setNotBefore(action.getNotBefore());
|
||||
}
|
||||
userSessionManagement.logoutAll();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -55,6 +55,9 @@ public interface UserModel {
|
|||
|
||||
void setTotp(boolean totp);
|
||||
|
||||
int getNotBefore();
|
||||
void setNotBefore(int notBefore);
|
||||
|
||||
public static enum RequiredAction {
|
||||
VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD
|
||||
}
|
||||
|
|
|
@ -144,4 +144,14 @@ public class UserAdapter implements UserModel {
|
|||
public void setTotp(boolean totp) {
|
||||
user.setTotp(totp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNotBefore() {
|
||||
return user.getNotBefore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotBefore(int notBefore) {
|
||||
user.setNotBefore(notBefore);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ public class UserEntity {
|
|||
protected boolean enabled;
|
||||
protected boolean totp;
|
||||
protected boolean emailVerified;
|
||||
protected int notBefore;
|
||||
|
||||
@ManyToOne
|
||||
protected RealmEntity realm;
|
||||
|
@ -158,4 +159,12 @@ public class UserEntity {
|
|||
public void setCredentials(Collection<CredentialEntity> credentials) {
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
public int getNotBefore() {
|
||||
return notBefore;
|
||||
}
|
||||
|
||||
public void setNotBefore(int notBefore) {
|
||||
this.notBefore = notBefore;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,16 @@ public class UserAdapter extends AbstractMongoAdapter<UserEntity> implements Use
|
|||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNotBefore() {
|
||||
return user.getNotBefore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotBefore(int notBefore) {
|
||||
user.setNotBefore(notBefore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstName() {
|
||||
return user.getFirstName();
|
||||
|
|
|
@ -23,6 +23,7 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
|
|||
private boolean emailVerified;
|
||||
private boolean totp;
|
||||
private boolean enabled;
|
||||
private int notBefore;
|
||||
|
||||
private String realmId;
|
||||
|
||||
|
@ -96,6 +97,15 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
|
|||
this.totp = totp;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public int getNotBefore() {
|
||||
return notBefore;
|
||||
}
|
||||
|
||||
public void setNotBefore(int notBefore) {
|
||||
this.notBefore = notBefore;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
|
|
|
@ -156,12 +156,19 @@ public class AuthenticationManager {
|
|||
}
|
||||
|
||||
UserModel user = realm.getUserById(token.getSubject());
|
||||
if (user == null || !user.isEnabled()) {
|
||||
if (user == null || !user.isEnabled() ) {
|
||||
logger.info("Unknown user in identity cookie");
|
||||
expireIdentityCookie(realm, uriInfo);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (token.getIssuedAt() < user.getNotBefore()) {
|
||||
logger.info("Stale cookie");
|
||||
expireIdentityCookie(realm, uriInfo);
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
return user;
|
||||
} catch (VerificationException e) {
|
||||
logger.info("Failed to verify identity cookie", e);
|
||||
|
|
|
@ -108,16 +108,17 @@ public class ResourceAdminManager {
|
|||
|
||||
}
|
||||
|
||||
public void logoutUser(RealmModel realm, String user) {
|
||||
public void logoutUser(RealmModel realm, UserModel user) {
|
||||
ResteasyClient client = new ResteasyClientBuilder()
|
||||
.disableTrustManager() // todo fix this, should have a trust manager or a good default
|
||||
.build();
|
||||
|
||||
try {
|
||||
// don't set user notBefore as we don't want a database hit on a user driven logout
|
||||
List<ApplicationModel> resources = realm.getApplications();
|
||||
logger.debug("logging out {0} resources ", resources.size());
|
||||
for (ApplicationModel resource : resources) {
|
||||
logoutApplication(realm, resource, user, client);
|
||||
logoutApplication(realm, resource, user.getId(), client, 0);
|
||||
}
|
||||
} finally {
|
||||
client.close();
|
||||
|
@ -129,10 +130,11 @@ public class ResourceAdminManager {
|
|||
.build();
|
||||
|
||||
try {
|
||||
realm.setNotBefore((int)(System.currentTimeMillis()/1000));
|
||||
List<ApplicationModel> resources = realm.getApplications();
|
||||
logger.debug("logging out {0} resources ", resources.size());
|
||||
for (ApplicationModel resource : resources) {
|
||||
logoutApplication(realm, resource, null, client);
|
||||
logoutApplication(realm, resource, null, client, realm.getNotBefore());
|
||||
}
|
||||
} finally {
|
||||
client.close();
|
||||
|
@ -145,7 +147,8 @@ public class ResourceAdminManager {
|
|||
.build();
|
||||
|
||||
try {
|
||||
logoutApplication(realm, resource, user, client);
|
||||
resource.setNotBefore((int)(System.currentTimeMillis()/1000));
|
||||
logoutApplication(realm, resource, user, client, resource.getNotBefore());
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
|
@ -153,10 +156,10 @@ public class ResourceAdminManager {
|
|||
}
|
||||
|
||||
|
||||
protected boolean logoutApplication(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client) {
|
||||
protected boolean logoutApplication(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client, int notBefore) {
|
||||
String managementUrl = resource.getManagementUrl();
|
||||
if (managementUrl != null) {
|
||||
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, resource.getName(), user);
|
||||
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, resource.getName(), user, notBefore);
|
||||
String token = new TokenManager().encodeToken(realm, adminAction);
|
||||
logger.info("logout user: {0} resource: {1} url: {2}", user, resource.getName(), managementUrl);
|
||||
Response response = client.target(managementUrl).path(AdapterConstants.K_LOGOUT).request().post(Entity.text(token));
|
||||
|
|
|
@ -131,7 +131,7 @@ public class TokenManager {
|
|||
|
||||
}
|
||||
|
||||
if (refreshToken.getIssuedAt() < client.getNotBefore()) {
|
||||
if (refreshToken.getIssuedAt() < client.getNotBefore() || refreshToken.getIssuedAt() < user.getNotBefore()) {
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
|
||||
}
|
||||
|
||||
|
|
|
@ -580,7 +580,7 @@ public class TokenService {
|
|||
logger.info("Logging out: {0}", user.getLoginName());
|
||||
authManager.expireIdentityCookie(realm, uriInfo);
|
||||
authManager.expireRememberMeCookie(realm, uriInfo);
|
||||
resourceAdminManager.logoutUser(realm, user.getId());
|
||||
resourceAdminManager.logoutUser(realm, user);
|
||||
} else {
|
||||
logger.info("No user logged in for logout");
|
||||
}
|
||||
|
|
|
@ -176,7 +176,9 @@ public class UsersResource {
|
|||
if (user == null) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
new ResourceAdminManager().logoutUser(realm, user.getId());
|
||||
// set notBefore so that user will be forced to log in.
|
||||
user.setNotBefore((int)(System.currentTimeMillis()/1000));
|
||||
new ResourceAdminManager().logoutUser(realm, user);
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue