当前位置: 首页 > news >正文

Smart-SSO单点登录接入后,如何优雅地获取用户信息和权限?实战代码示例

Smart-SSO单点登录接入后如何优雅地获取用户信息和权限?实战代码示例

当你已经完成了Smart-SSO的基础接入,接下来面临的实际问题可能是:如何在业务代码中高效地获取用户信息?如何基于权限数据实现动态菜单和接口控制?本文将深入探讨这些实战场景,提供可直接复用的代码方案。

1. 理解Smart-SSO的核心上下文

Smart-SSO在用户登录后,会在请求上下文中注入两个关键对象:

  • TokenUser:包含用户基础信息(如username、id等)
  • TokenPermission:包含权限数据(如菜单列表、权限集合等)

这两个对象通过ClientContextHolder工具类获取,这是整个权限体系的核心入口。理解它们的结构是后续开发的基础:

// 典型TokenUser结构示例 public class TokenUser { private String username; private Long id; private String email; // 其他用户属性... } // 典型TokenPermission结构示例 public class TokenPermission { private List<Menu> menuList; // 菜单树 private Set<String> permissionSet; // 权限标识集合 // 其他权限属性... }

2. 后端实战:从Controller到权限控制

2.1 基础用户信息获取

在Spring MVC的Controller中,最直接的方式是通过HttpServletRequest获取用户上下文:

@RestController @RequestMapping("/api/user") public class UserController { @GetMapping("/profile") public ResponseEntity<UserProfile> getProfile(HttpServletRequest request) { TokenUser user = ClientContextHolder.getUser(request); // 构建响应对象 UserProfile profile = new UserProfile(); profile.setUsername(user.getUsername()); profile.setUserId(user.getId()); return ResponseEntity.ok(profile); } }

2.2 权限校验的三种实现方式

根据不同的业务场景,权限校验可以有多种实现路径:

方式一:编程式校验
@GetMapping("/admin/report") public ResponseEntity<ReportData> getAdminReport(HttpServletRequest request) { TokenPermission permission = ClientContextHolder.getPermission(request); if (!permission.getPermissionSet().contains("report:admin")) { throw new AccessDeniedException("无权访问管理员报表"); } // 业务逻辑... }
方式二:注解式校验(结合Spring Security)
@PreAuthorize("hasPermission(#request, 'report:admin')") @GetMapping("/admin/report") public ResponseEntity<ReportData> getAdminReport(HttpServletRequest request) { // 业务逻辑... }

需要先配置自定义的权限表达式处理器:

@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Override protected MethodSecurityExpressionHandler createExpressionHandler() { DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler(); handler.setPermissionEvaluator(new SmartSsoPermissionEvaluator()); return handler; } }
方式三:动态数据过滤

对于数据敏感的查询接口,可以在Service层实现数据过滤:

public List<Order> getUserOrders(HttpServletRequest request) { TokenUser user = ClientContextHolder.getUser(request); return orderRepository.findByUserId(user.getId()); }

3. 前端集成方案

3.1 用户信息在前端的存储与使用

建议在登录成功后,将必要的用户信息存储在前端状态管理中:

// Vue示例 - 登录成功后 login().then(response => { const userInfo = response.data store.commit('SET_USER', { id: userInfo.id, name: userInfo.username, // 其他需要前端使用的字段 }) })

3.2 动态菜单的实现

从后端获取菜单数据后,可以动态生成路由配置:

// React示例 - 动态路由 const App = () => { const [menu, setMenu] = useState([]) useEffect(() => { fetchMenu().then(data => { const routes = transformMenuToRoutes(data) setMenu(routes) }) }, []) return ( <Router> <Layout> <Sidebar menus={menu} /> <Switch> {menu.map(route => ( <Route key={route.path} {...route} /> ))} </Switch> </Layout> </Router> ) }

菜单数据结构转换示例:

function transformMenuToRoutes(menus) { return menus.map(menu => ({ path: menu.path, component: lazy(() => import(`@/views/${menu.component}`)), meta: { title: menu.title, icon: menu.icon } })) }

4. 高级应用场景

4.1 微服务间的用户上下文传递

在微服务架构中,需要将用户信息通过请求头传递给下游服务:

// 使用Feign Client的拦截器 public class UserInfoFeignInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { HttpServletRequest request = getCurrentRequest(); if (request != null) { TokenUser user = ClientContextHolder.getUser(request); if (user != null) { template.header("X-User-Id", user.getId().toString()); template.header("X-Username", user.getUsername()); } } } }

下游服务可以通过过滤器还原用户上下文:

public class UserInfoFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { String userId = request.getHeader("X-User-Id"); String username = request.getHeader("X-Username"); if (userId != null) { TokenUser user = new TokenUser(); user.setId(Long.parseLong(userId)); user.setUsername(username); ClientContextHolder.setUser(user); } chain.doFilter(request, response); } }

4.2 权限数据的缓存优化

频繁从SSO服务端获取权限数据可能影响性能,可以考虑本地缓存:

@Aspect @Component public class PermissionCacheAspect { @Autowired private CacheManager cacheManager; @Around("execution(* ClientContextHolder.getPermission(..))") public Object cachePermission(ProceedingJoinPoint joinPoint) throws Throwable { HttpServletRequest request = (HttpServletRequest) joinPoint.getArgs()[0]; String cacheKey = buildCacheKey(request); Cache cache = cacheManager.getCache("permissionCache"); TokenPermission permission = cache.get(cacheKey, TokenPermission.class); if (permission == null) { permission = (TokenPermission) joinPoint.proceed(); cache.put(cacheKey, permission); } return permission; } private String buildCacheKey(HttpServletRequest request) { TokenUser user = ClientContextHolder.getUser(request); return "perm:" + user.getId(); } }

5. 安全最佳实践

在获取和使用用户信息时,需要注意以下安全要点:

  1. 敏感信息过滤:返回给前端的用户信息应该经过过滤

    // 不返回完整用户对象 public UserProfile safeUserProfile(TokenUser user) { UserProfile profile = new UserProfile(); profile.setUsername(user.getUsername()); // 不包含password、email等敏感字段 return profile; }
  2. 权限变更的实时性:考虑权限变更时的缓存失效策略

    @EventListener public void handlePermissionChange(PermissionChangeEvent event) { cacheManager.getCache("permissionCache").evict("perm:" + event.getUserId()); }
  3. 接口防越权:即使前端隐藏了无权限的菜单,后端也必须二次校验

    @PreAuthorize("hasPermission(#request, 'order:query')") @GetMapping("/orders") public List<Order> queryOrders(OrderQuery query, HttpServletRequest request) { // 必须添加方法级权限校验 }
  4. 日志审计:关键操作应记录操作人信息

    @Aspect @Component public class OperationLogAspect { @AfterReturning("execution(* com..service.*.*(..)) && @annotation(loggable)") public void logOperation(JoinPoint joinPoint, Loggable loggable) { HttpServletRequest request = getCurrentRequest(); TokenUser user = ClientContextHolder.getUser(request); // 记录操作日志,包含用户信息 } }
http://www.cnnetsun.cn/news/1569578.html

相关文章:

  • 摆脱论文困扰!2026年实打实好用的专业降AI率平台
  • 从华驼到本草:揭秘医学大模型的命名故事与技术演进之路
  • 如何通过本草模型实现医学AI智能诊断:中文医疗大语言模型的完整指南
  • 终极指南:如何为智能硬件构建高效语音交互系统
  • 终极指南:如何用Muzic的MusicBERT实现符号音乐深度理解(从入门到实践)
  • Loop:重新定义macOS窗口管理的优雅革命,让效率触手可及
  • 终极Rocket性能优化指南:10个提升Web应用速度的实用技巧
  • 使用usearch进行聚类分析:从向量数据中发现隐藏模式
  • EVA-01部署案例:科研团队将EVA-01用于卫星遥感图农田病虫害早期识别验证
  • SSD硬性负样本挖掘:解决正负样本不平衡的关键技术
  • 算法/力扣--字符串经典题目
  • KubeSphere Core 离线部署实战:从镜像搬运到私有仓库配置
  • 10个必学的.NET Interactive魔法命令:提升你的多语言编程效率
  • OpenTelemetry日志导出全攻略:Kafka、Elasticsearch和Loki的对比与选择
  • 极简部署方案:星图GPU平台OpenClaw+GLM-4.7-Flash体验
  • 认知几何学:思维对意义空间的弯曲效应V0.2【世毫九实验室原创理论】
  • TSL2561光传感器Arduino库原理与低功耗工程实践
  • WebRTC信令交换实战:从Socket.io到RTCPeerConnection的完整流程解析
  • Realistic Vision V5.1在产品设计中的应用:目标用户画像写实化呈现
  • 软考架构师备考:别死记硬背了,用这3个真实项目场景串联核心知识点
  • AMC1100隔离放大器实战:如何用DUB封装搞定三相电流电压测量?
  • **标题:自进化系统新范式:用Python实现动态代码优化与自我修复能力**
  • Windows屏保设置失效?解锁注册表权限的终极指南
  • 4YA-3玉米联合收割机全套(共有800多张CAXA图纸)(三行中原)
  • mips uboot 阶段nand flash代码注册流程
  • 前端部署:别让你的应用在上线后掉链子
  • 实测避坑:RetinaFace/LFFD等轻量算法在密集人脸场景的漏检率对比(含106关键点方案)
  • ESP32物联网开发环境配置挑战:基于Arduino框架的跨平台解决方案
  • LeetCodehot100-25 K 个一组翻转链表
  • 静态图≠安全!PyTorch 3.0中Graph IR层的3处内存越界隐患,及LLVM后端级修补补丁(已提交CVE-2024-XXXXX)