You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Keycloak Adapter关联Java EE模型与登录用户

如何在Java EE应用中关联Keycloak用户与业务模型(Post/Comment等)

你不需要在自己的数据库中维护完整的用户认证体系,只需要基于Keycloak返回的JWT中的**sub(全局唯一用户ID)**,在业务库中维护一个轻量的用户关联实体,用来绑定Post、Comment等业务模型即可。核心思路是:用Keycloak的sub作为用户的唯一标识,在业务库中实现用户信息的"按需同步"(查找或创建)。

1. 创建业务侧的用户关联实体

创建一个仅用于业务关联的实体(比如命名为AppUser),不需要存储密码等认证相关字段,只保留Keycloak用户的唯一标识和业务需要的信息:

@Entity
@Table(name = "app_users")
public class AppUser {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // Keycloak用户的唯一标识sub,设为唯一约束
    @Column(unique = true, nullable = false)
    private String keycloakSub;

    // 从JWT中同步的用户信息,按需添加
    private String username;
    private String email;
    private String givenName;
    private String familyName;
    private String nif;

    // 关联业务模型,比如Post、Comment
    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
    private List<Post> posts;

    // 构造方法、getter、setter
}

2. 实现用户的"查找或创建"逻辑

在每次请求处理前,从JWT中提取sub,先查询数据库中是否存在对应的AppUser,不存在则用JWT中的信息创建新的AppUser。可以封装成一个服务类:

@Stateless
public class AppUserService {

    @PersistenceContext
    private EntityManager em;

    public AppUser getOrCreateCurrentUser(Principal principal) {
        // 从Keycloak Principal中提取用户信息
        KeycloakPrincipal<KeycloakSecurityContext> kp = (KeycloakPrincipal<KeycloakSecurityContext>) principal;
        AccessToken token = kp.getKeycloakSecurityContext().getToken();
        String sub = token.getSubject(); // 核心的用户唯一标识

        // 先查找是否存在该用户
        TypedQuery<AppUser> query = em.createQuery("SELECT u FROM AppUser u WHERE u.keycloakSub = :sub", AppUser.class);
        query.setParameter("sub", sub);
        AppUser appUser;

        try {
            appUser = query.getSingleResult();
            // 可选:同步JWT中的最新信息到业务库(比如用户更新了邮箱)
            appUser.setUsername(token.getPreferredUsername());
            appUser.setEmail(token.getEmail());
            appUser.setGivenName(token.getGivenName());
            appUser.setFamilyName(token.getFamilyName());
            appUser.setNif((String) token.getOtherClaims().get("nif"));
            em.merge(appUser);
        } catch (NoResultException e) {
            // 不存在则创建新用户
            appUser = new AppUser();
            appUser.setKeycloakSub(sub);
            appUser.setUsername(token.getPreferredUsername());
            appUser.setEmail(token.getEmail());
            appUser.setGivenName(token.getGivenName());
            appUser.setFamilyName(token.getFamilyName());
            appUser.setNif((String) token.getOtherClaims().get("nif"));
            em.persist(appUser);
        }
        return appUser;
    }
}

3. 关联业务模型

在Post、Comment等实体中,添加对AppUser的关联:

@Entity
@Table(name = "posts")
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String content;

    // 关联创建该帖子的用户
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id", nullable = false)
    private AppUser author;

    // 构造方法、getter、setter
}

4. 在业务逻辑中使用

在处理业务请求时(比如创建帖子),先获取当前用户,再关联到业务模型:

@Stateless
public class PostService {

    @PersistenceContext
    private EntityManager em;

    @Inject
    private AppUserService appUserService;

    public Post createPost(String title, String content, Principal principal) {
        AppUser currentUser = appUserService.getOrCreateCurrentUser(principal);
        Post post = new Post();
        post.setTitle(title);
        post.setContent(content);
        post.setAuthor(currentUser);
        em.persist(post);
        return post;
    }
}

额外优化建议

  • 拦截器/过滤器自动处理:可以创建一个CDI拦截器或者Servlet过滤器,在请求进入业务方法前自动获取并绑定当前AppUser到上下文,避免重复调用getOrCreateCurrentUser
  • 避免频繁同步:如果Keycloak用户信息不会频繁变更,可以减少同步频率,比如只在创建时同步,后续需要更新时再触发
  • 唯一约束保障:确保AppUser的keycloakSub字段设置了唯一约束,避免重复创建用户

内容的提问来源于stack exchange,提问作者melMakioui

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 11:45:07