企业微信自建应用权限模型与 RBAC 在 Spring Security 中的映射
企业微信权限体系结构
企业微信自建应用的权限控制分为两层:
- 应用可见范围(可见部门/成员):由管理员在管理后台配置,决定哪些用户可使用该应用;
- API 调用权限:如读取通讯录、发送消息等,由应用的"权限-功能"粒度控制。
在内部业务系统中,需将企业微信的 UserId 或 OpenId 映射为本地用户,并基于 RBAC(基于角色的访问控制) 实现细粒度接口权限。
Spring Security 集成企业微信身份认证
通过 OAuth2 获取用户身份后,构建 Authentication 对象:
java
package wlkankan.cn.security.wx;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
public class WeComUserDetails implements UserDetails {
private final String userId; // 企业微信 UserId
private final String name;
private final List<String> roles; // 本地角色,如 "ADMIN", "AUDITOR"
public WeComUserDetails(String userId, String name, List<String> roles) {
this.userId = userId;
this.name = name;
this.roles = roles;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.toList();
}
@Override
public String getPassword() { return null; }
@Override
public String getUsername() { return userId; }
@Override
public boolean isAccountNonExpired() { return true; }
@Override
public boolean isAccountNonLocked() { return true; }
@Override
public boolean isCredentialsNonExpired() { return true; }
@Override
public boolean isEnabled() { return true; }
}

OAuth2 登录成功处理器
在获取企业微信 code 后换取用户信息,并加载本地角色:
java
package wlkankan.cn.security.handler;
import wlkankan.cn.security.wx.WeComUserDetails;
import wlkankan.cn.service.UserService;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class WeComAuthSuccessHandler implements AuthenticationSuccessHandler {
private final UserService userService;
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException {
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
String userId = oauthToken.getPrincipal().getName(); // 企业微信 UserId
// 从本地 DB 加载角色
List<String> roles = userService.findRolesByWxUserId(userId);
WeComUserDetails userDetails = new WeComUserDetails(userId, "", roles);
// 手动构建认证对象
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
response.sendRedirect("/dashboard");
}
}
RBAC 权限注解与方法级控制
使用 @PreAuthorize 结合 SpEL 表达式实现接口权限:
java
package wlkankan.cn.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/customer")
public class CustomerController {
@PreAuthorize("hasRole('ADMIN') or hasRole('SALES')")
@PostMapping
public ResponseEntity<?> createCustomer(@RequestBody CustomerDto dto) {
// 创建客户
}
@PreAuthorize("hasAuthority('PERM_CUSTOMER_VIEW_SENSITIVE')")
@GetMapping("/{id}/detail")
public CustomerDetail getSensitiveDetail(@PathVariable String id) {
// 返回含手机号等敏感字段
}
}
动态权限:从数据库加载权限表达式
将权限规则存储于数据库,避免硬编码:
sql
-- permission 表
INSERT INTO permission (code, expression) VALUES
('PERM_CUSTOMER_VIEW_SENSITIVE', 'hasRole(''FINANCE'') or hasRole(''AUDITOR'')');
自定义 PermissionEvaluator:
java
package wlkankan.cn.security.eval;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import wlkankan.cn.service.PermissionService;
import java.io.Serializable;
public class DbPermissionEvaluator implements PermissionEvaluator {
private final PermissionService permissionService;
@Override
public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) {
if (!(permission instanceof String permCode)) return false;
String expression = permissionService.getExpressionByCode(permCode);
if (expression == null) return false;
return ExpressionEvaluator.evaluate(expression, auth);
}
@Override
public boolean hasPermission(Authentication auth, Serializable targetId,
String targetType, Object permission) {
return hasPermission(auth, null, permission);
}
}
启用表达式求值器:
java
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Autowired
private PermissionService permissionService;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler handler =
new DefaultMethodSecurityExpressionHandler();
handler.setPermissionEvaluator(new DbPermissionEvaluator(permissionService));
return handler;
}
}
企业微信可见范围同步
定期同步企业微信应用可见成员至本地用户表,确保 RBAC 基础数据一致:
java
package wlkankan.cn.sync;
@Service
public class WeComVisibleScopeSync {
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点
public void syncVisibleUsers() {
List<String> wxUserIds = weComApiClient.getVisibleUserIds(appId, appSecret);
userService.syncVisibleUsers(wxUserIds); // 激活/停用本地账号
}
}
通过将企业微信身份体系与 Spring Security 的 UserDetails、GrantedAuthority、PermissionEvaluator 深度集成,可实现外部身份可信接入 + 内部 RBAC 精细化授权的双重安全模型,满足企业微信自建应用在复杂组织架构下的合规访问控制需求。