接了 LDAP 之后,Nacos 的用户管理从“谁都能登录“变成了“只有 AD 里有的人才行“:自定义鉴权插件实战

接了 LDAP 之后,Nacos 的用户管理从“谁都能登录“变成了“只有 AD 里有的人才行“:自定义鉴权插件实战 接了 LDAP 之后Nacos 的用户管理从谁都能登录变成了只有 AD 里有的人才行自定义鉴权插件实战文章目录接了 LDAP 之后Nacos 的用户管理从谁都能登录变成了只有 AD 里有的人才行自定义鉴权插件实战你的公司不允许在 Nacos 里单独维护一套用户AuthPluginService 接口定义实现一LDAP 鉴权插件项目结构核心实现启用插件验证登录实现二OAuth2.0 鉴权插件核心区别login 方法改造外部系统用户同步方案一登录时自动同步方案二定时同步一张图带走鉴权插件的请求处理流程你的公司不允许在 Nacos 里单独维护一套用户Nacos 默认的认证是本地用户管理用户名密码存在 MySQL 的users表里。你给每个运维、每个开发、每个测试各建一个账号。公司安全团队过来说这不行。用户信息必须在 ADActive Directory里统一管理。员工离职后 AD 账号禁用Nacos 登录必须同步失效。不允许在 Nacos 里单独维护一套用户。你需要一个鉴权插件把 Nacos 的登录认证接到公司的 LDAP 服务器上。这篇文章用两个完整例子走一遍 AuthPluginService 插件的开发——一个接 LDAP一个接 OAuth2.0。代码可以直接拿去改。⚠️ 本文基于 Nacos Server 2.3.x。AuthPluginService 接口在 2.1.0 后稳定。如果你用的是 2.0.x接口有NacosAuthManager而非AuthPluginService需要升级或适配。AuthPluginService 接口定义// com.alibaba.nacos.plugin.auth.spi.server.AuthPluginServicepublicinterfaceAuthPluginService{/** * 插件类型标识和 application.properties 中的 auth.system.type 对应 */StringgetAuthServiceName();/** * 从请求中提取身份信息如 Token、用户名 */IdentityContextvalidateIdentity(IdentityContextidentityContext);/** * 验证身份是否合法检查 Token 是否有效、用户是否存在 */AuthResultvalidateAuthInfo(IdentityContextidentityContext);/** * 生成访问凭证登录成功后返回 Token */AccessTokenlogin(IdentityContextidentityContext);}方法调用时机你的实现getAuthServiceName()插件加载时返回ldapvalidateIdentity()每个请求进来时从 Header 提取 TokenvalidateAuthInfo()Token 验证阶段校验 Token 有效性login()用户登录时验证 LDAP 密码签发 Token实现一LDAP 鉴权插件项目结构nacos-auth-ldap/ ├── pom.xml └── src/main/ ├── java/com/example/nacos/auth/ │ └── LdapAuthPluginService.java └── resources/META-INF/services/ └── com.alibaba.nacos.plugin.auth.spi.server.AuthPluginService核心实现packagecom.example.nacos.auth;importcom.alibaba.nacos.plugin.auth.api.IdentityContext;importcom.alibaba.nacos.plugin.auth.api.AuthResult;importcom.alibaba.nacos.plugin.auth.api.AccessToken;importcom.alibaba.nacos.plugin.auth.spi.server.AuthPluginService;importjavax.naming.directory.DirContext;importjavax.naming.directory.InitialDirContext;importjava.util.Hashtable;publicclassLdapAuthPluginServiceimplementsAuthPluginService{// 从 Nacos 配置中读取 LDAP 服务器地址privateStringldapUrlSystem.getProperty(nacos.plugin.ldap.url,ldap://ldap.company.com:389);privateStringldapBaseDnSystem.getProperty(nacos.plugin.ldap.baseDn,dccompany,dccom);OverridepublicStringgetAuthServiceName(){returnldap;}OverridepublicIdentityContextvalidateIdentity(IdentityContextidentityContext){// 从请求 Header 中提取 Token 或用户名StringtokenidentityContext.getParameter(accessToken);if(token!null){// 验证 JWT Token 并提取用户名StringusernamedecodeJwt(token);identityContext.setParameter(username,username);}returnidentityContext;}OverridepublicAuthResultvalidateAuthInfo(IdentityContextidentityContext){StringusernameidentityContext.getParameter(username);StringtokenidentityContext.getParameter(accessToken);// 验证 Token 是否过期if(token!null!isTokenExpired(token)){AuthResultresultnewAuthResult();result.setSuccess(true);result.setToken(token);returnresult;}AuthResultresultnewAuthResult();result.setSuccess(false);result.setMessage(Token expired or invalid);returnresult;}OverridepublicAccessTokenlogin(IdentityContextidentityContext){StringusernameidentityContext.getParameter(username);StringpasswordidentityContext.getParameter(password);// 用 LDAP bind 验证用户名密码if(authenticateWithLdap(username,password)){// LDAP 验证通过签发 JWT TokenStringtokengenerateJwt(username);AccessTokenaccessTokennewAccessToken();accessToken.setAccessToken(token);accessToken.setTokenTtl(18000);// 5 小时returnaccessToken;}// LDAP 验证失败returnnull;}/** * 用 LDAP bind 验证用户凭据 */privatebooleanauthenticateWithLdap(Stringusername,Stringpassword){HashtableString,StringenvnewHashtable();env.put(java.naming.factory.initial,com.sun.jndi.ldap.LdapCtxFactory);env.put(java.naming.provider.url,ldapUrl);env.put(java.naming.security.authentication,simple);env.put(java.naming.security.principal,uidusername,ldapBaseDn);env.put(java.naming.security.credentials,password);try{DirContextctxnewInitialDirContext(env);ctx.close();returntrue;// bind 成功 密码正确}catch(Exceptione){returnfalse;// bind 失败 密码错误或用户不存在}}}启用插件# nacos/conf/application.properties # 切换认证插件类型为 ldap nacos.core.auth.system.typeldap # LDAP 配置 nacos.plugin.ldap.urlldap://ldap.company.com:389 nacos.plugin.ldap.baseDndccompany,dccom验证登录# 用 AD 账号登录 Nacoscurl-XPOSThttp://nacos:8848/nacos/v1/auth/login\-dusernamezhangsanpasswordADPassword123# 期望返回 accessToken如果 AD 密码正确# 如果 AD 密码错误返回 403实现二OAuth2.0 鉴权插件如果你的公司用的是 OAuth2.0比如企业微信、钉钉、GitHub SSO需要另一种方式。核心区别特性LDAP 插件OAuth2.0 插件密码验证直接 LDAP bind不直接验证密码登录流程用户名密码→Token重定向到 OAuth Provider→授权码→TokenToken 来源Nacos 自己签发 JWTOAuth Provider 签发适用场景企业内网云端、多租户login 方法改造OverridepublicAccessTokenlogin(IdentityContextidentityContext){StringauthorizationCodeidentityContext.getParameter(code);// 用授权码换取 access_tokenStringoauthTokenexchangeCodeForToken(authorizationCode);// 用 access_token 获取用户信息UserInfouserInfogetUserInfo(oauthToken);// 验证用户是否在白名单中可选if(!isUserAllowed(userInfo.getEmail())){returnnull;}// 签发 Nacos 内部的 JWTStringnacosTokengenerateJwt(userInfo.getUsername());AccessTokenaccessTokennewAccessToken();accessToken.setAccessToken(nacosToken);accessToken.setTokenTtl(18000);returnaccessToken;}privateStringexchangeCodeForToken(Stringcode){// POST 请求 OAuth Provider 的 token endpoint// grant_typeauthorization_codecode{code}redirect_uri{uri}client_id{id}client_secret{secret}// ...}外部系统用户同步接了 LDAP/OAuth 后Nacos 本地的users表里可能没有这些用户。角色和权限怎么管理方案一登录时自动同步OverridepublicAccessTokenlogin(IdentityContextidentityContext){// ... 验证通过后 ...// 检查 Nacos 本地是否有这个用户if(!userExistsInNacos(username)){// 自动创建用户并分配默认角色createUserInNacos(username,ROLE_USER);}// 签发 Token// ...}方案二定时同步# 每天凌晨从 LDAP 同步用户到 Nacos# 脚本sync_ldap_to_nacos.sh# 1. ldapsearch 拉取所有活跃用户# 2. 对比 Nacos users 表# 3. 新增的自动 INSERT# 4. LDAP 已禁用的自动 DELETE 或禁用一张图带走鉴权插件的请求处理流程登录请求验证通过验证失败业务请求注册/发现/配置有效无效/过期客户端请求携带 accessToken 或 usernamepassword请求类型?login()验证 LDAP/OAuth 密码签发 JWT Token返回给客户端返回 403validateIdentity()从 Header 提取 TokenvalidateAuthInfo()校验 Token 有效性放行请求返回 403登录请求走 login() → 签发 Token。后续业务请求走 validateIdentity() validateAuthInfo() → 校验 Token。两个阶段独立插件可以在每个阶段做自定义逻辑。你们公司的 Nacos 认证是怎么做的评论区留数字1Nacos 默认本地用户 2接了 LDAP 3接了 OAuth/SSO 4有网关层统一认证Nacos 不开认证