如何使用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
相关产品推荐
相关产品推荐

