keycloak-用户session数量限制

  1. session并发限制
    1. 代码
    2. 使用

session并发限制

有时候需要限制用户同时在线的数量,就像微信一样同一时间只能有一个手机能够登陆上。

代码

废话不多说,直接放码,要的拿去。

UserSessionLimitsAuthenticatorFactory.java


@AutoService(AuthenticatorFactory.class)
public class UserSessionLimitsAuthenticatorFactory implements AuthenticatorFactory {

  public static final String USER_REALM_LIMIT = "userRealmLimit";
  public static final String USER_CLIENT_LIMIT = "userClientLimit";
  public static final String BEHAVIOR = "behavior";
  public static final String DENY_NEW_SESSION = "Deny new session";
  public static final String TERMINATE_OLDEST_SESSION = "Terminate oldest session";
  public static final String USER_SESSION_LIMITS = "user-session-limits";


  private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
    AuthenticationExecutionModel.Requirement.REQUIRED,
    AuthenticationExecutionModel.Requirement.DISABLED
  };

  @Override
  public String getDisplayType() {
    return "User session count limiter";
  }

  @Override
  public String getReferenceCategory() {
    return null;
  }

  @Override
  public boolean isConfigurable() {
    return true;
  }

  @Override
  public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
    return REQUIREMENT_CHOICES.clone();
  }

  @Override
  public boolean isUserSetupAllowed() {
    return false;
  }

  @Override
  public String getHelpText() {
    return "Configures how many concurrent sessions a single user is allowed to create for this realm and/or client";
  }

  @Override
  public List<ProviderConfigProperty> getConfigProperties() {
    ProviderConfigProperty userRealmLimit = new ProviderConfigProperty();
    userRealmLimit.setName(USER_REALM_LIMIT);
    userRealmLimit.setLabel("Maximum concurrent sessions for each user");
    userRealmLimit.setType(ProviderConfigProperty.STRING_TYPE);

    ProviderConfigProperty userClientLimit = new ProviderConfigProperty();
    userClientLimit.setName(USER_CLIENT_LIMIT);
    userClientLimit.setLabel("Maximum concurrent sessions for each user per keycloak client");
    userClientLimit.setType(ProviderConfigProperty.STRING_TYPE);

    ProviderConfigProperty behaviourProperty = new ProviderConfigProperty();
    behaviourProperty.setName(BEHAVIOR);
    behaviourProperty.setLabel("Behavior when user session limit is exceeded");
    behaviourProperty.setType(ProviderConfigProperty.LIST_TYPE);
    behaviourProperty.setDefaultValue(DENY_NEW_SESSION);
    behaviourProperty.setOptions(Arrays.asList(DENY_NEW_SESSION, TERMINATE_OLDEST_SESSION));

    return Arrays.asList(userRealmLimit, userClientLimit, behaviourProperty);
  }

  @Override
  public Authenticator create(KeycloakSession keycloakSession) {
    return new UserSessionLimitsAuthenticator(keycloakSession);
  }

  @Override
  public void init(Config.Scope scope) {
    // Do nothing
  }

  @Override
  public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
    // Do nothing
  }

  @Override
  public void close() {
    // Do nothing
  }

  @Override
  public String getId() {
    return USER_SESSION_LIMITS;
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95

AbstractSessionLimitsAuthenticator.java

public abstract class AbstractSessionLimitsAuthenticator implements Authenticator {

  protected KeycloakSession session;

  protected boolean exceedsLimit(long count, long limit) {
    if (limit < 0) { // if limit is negative, no valid limit configuration is found
      return false;
    }
    return count > limit - 1;
  }

  protected int getIntConfigProperty(String key, Map<String, String> config) {
    String value = config.get(key);
    if (StringUtils.isBlank(value)) {
      return -1;
    }
    return Integer.parseInt(value);
  }

  @Override
  public void action(AuthenticationFlowContext context) {

  }

  @Override
  public boolean requiresUser() {
    return false;
  }

  @Override
  public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
    return true;
  }

  @Override
  public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
  }

  @Override
  public void close() {

  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

UserSessionLimitsAuthenticator.java

@JBossLog
public class UserSessionLimitsAuthenticator extends AbstractSessionLimitsAuthenticator {

  String behavior;

  public UserSessionLimitsAuthenticator(KeycloakSession session) {
    this.session = session;
  }

  @Override
  public void authenticate(AuthenticationFlowContext context) {
    AuthenticatorConfigModel authenticatorConfig = context.getAuthenticatorConfig();
    Map<String, String> config = authenticatorConfig.getConfig();

    // Get the configuration for this authenticator
    behavior = config.get(UserSessionLimitsAuthenticatorFactory.BEHAVIOR);
    int userRealmLimit = getIntConfigProperty(
      UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, config);
    int userClientLimit = getIntConfigProperty(
      UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, config);

    if (context.getRealm() != null && context.getUser() != null) {

      // Get the session count in this realm for this specific user
      List<UserSessionModel> userSessionsForRealm = session.sessions()
        .getUserSessions(context.getRealm(), context.getUser());
      int userSessionCountForRealm = userSessionsForRealm.size();

      // Get the session count related to the current client for this user
      ClientModel currentClient = context.getAuthenticationSession().getClient();
      log.infof("session-limiter's current keycloak clientId: %s", currentClient.getClientId());

      List<UserSessionModel> userSessionsForClient = userSessionsForRealm.stream().filter(
        session -> session.getAuthenticatedClientSessionByClient(currentClient.getId()) != null)
        .collect(Collectors.toList());
      int userSessionCountForClient = userSessionsForClient.size();
      log.infof("session-limiter's configured realm session limit: %s", userRealmLimit);
      log.infof("session-limiter's configured client session limit: %s", userClientLimit);
      log.infof(
        "session-limiter's count of total user sessions for the entire realm (could be apps other than web apps): %s",
        userSessionCountForRealm);
      log.infof("session-limiter's count of total user sessions for this keycloak client: %s",
        userSessionCountForClient);

      // First check if the user has too many sessions in this realm
      if (exceedsLimit(userSessionCountForRealm, userRealmLimit)) {
        log.info("Too many session in this realm for the current user.");
        handleLimitExceeded(context, userSessionsForRealm);
      } // otherwise if the user is still allowed to create a new session in the realm, check if this applies for this specific client as well.
      else if (exceedsLimit(userSessionCountForClient, userClientLimit)) {
        log.info("Too many sessions related to the current client for this user.");
        handleLimitExceeded(context, userSessionsForClient);
      } else {
        context.success();
      }
    } else {
      context.success();
    }
  }

  private void handleLimitExceeded(AuthenticationFlowContext context,
    List<UserSessionModel> userSessions) {
    switch (behavior) {
      case UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION:
        log.info("Denying new session");
        context.failure(AuthenticationFlowError.INVALID_CLIENT_SESSION);
        break;
      case UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION:
        log.info("Terminating oldest session");
        logoutOldestSession(userSessions);
        context.success();
        break;
      default:
        break;
    }
  }

  private void logoutOldestSession(List<UserSessionModel> userSessions) {
    log.info("Logging out oldest session");
    Optional<UserSessionModel> oldest = userSessions.stream()
      .sorted(Comparator.comparingInt(UserSessionModel::getStarted)).findFirst();
    oldest.ifPresent(
      userSession -> AuthenticationManager.backchannelLogout(session, userSession, true));
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

使用

authentication flow


config 进行配置。

可设置 每个用户可以最多有几个session。对每个client最多可以有几个session。 如果session数量超过限制如何处理:有两种选择,一、 拒绝创建新session。二、结束老session。

已测可用。 有问题请留言。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 wzslw@163.com

文章标题:keycloak-用户session数量限制

文章字数:891

本文作者:武继明

发布时间:2020-06-08, 13:40:52

最后更新:2020-08-21, 06:30:57

原始链接:https://www.omingo.com/2020/06/08/keycloak-用户session数量限制/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

0 条评论
未登录用户
Error: Not Found.
支持 Markdown 语法

来做第一个留言的人吧!

目录
  1. session并发限制
    1. 代码
    2. 使用
×

喜欢就点赞,疼爱就打赏