用户权限表结构设计_基于 Spring Session & Spring Security 微服务权限控制

微服务架构

bb7f27d5cf7fdde622dc7fd01e8ed5d6.png
  • 网关:路由用户请求到指定服务,转发前端 Cookie 中包含的 Session 信息;
  • 用户服务:用户登录认证(Authentication),用户授权(Authority),用户管理(Redis Session Management)
  • 其他服务:依赖 Redis 中用户信息进行接口请求验证

用户 - 角色 - 权限表结构设计

  • 权限表

权限表最小粒度的控制单个功能,例如用户管理、资源管理,表结构示例:

c82f7aa57b63ce839096238017c4e604.png
  • 角色 - 权限表

自定义角色,组合各种权限,例如超级管理员拥有所有权限,表结构示例:

c2439a999f26e4c1c563ac05138f2312.png
  • 用户 - 角色表

用户绑定一个或多个角色,即分配各种权限,示例表结构:

2e3722fa9749912c37928e05018acfa8.png

用户服务设计

Maven 依赖(所有服务)

 org.springframework.boot spring-boot-starter-security org.springframework.session spring-session-data-redis 

应用配置 application.yml 示例:

# Spring Session 配置spring.session.store-type=redisserver.servlet.session.persistent=trueserver.servlet.session.timeout=7dserver.servlet.session.cookie.max-age=7d# Redis 配置spring.redis.host=spring.redis.port=6379# MySQL 配置spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql://:3306/testspring.datasource.username=spring.datasource.password=

用户登录认证(authentication)与授权(authority)

@Slf4jpublic class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter { private final UserService userService; CustomAuthenticationFilter(String defaultFilterProcessesUrl, UserService userService) { super(new AntPathRequestMatcher(defaultFilterProcessesUrl, HttpMethod.POST.name())); this.userService = userService; } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { JSONObject requestBody = getRequestBody(request); String username = requestBody.getString("username"); String password = requestBody.getString("password"); UserDO user = userService.getByUsername(username); if (user != null && validateUsernameAndPassword(username, password, user)){ // 查询用户的 authority List userAuthorities = userService.getSimpleGrantedAuthority(user.getId()); return new UsernamePasswordAuthenticationToken(user.getId(), null, userAuthorities); } throw new AuthenticationServiceException("登录失败"); } /** * 获取请求体 */ private JSONObject getRequestBody(HttpServletRequest request) throws AuthenticationException{ try { StringBuilder stringBuilder = new StringBuilder(); InputStream inputStream = request.getInputStream(); byte[] bs = new byte[StreamUtils.BUFFER_SIZE]; int len; while ((len = inputStream.read(bs)) != -1) { stringBuilder.append(new String(bs, 0, len)); } return JSON.parseObject(stringBuilder.toString()); } catch (IOException e) { log.error("get request body error."); } throw new AuthenticationServiceException(HttpRequestStatusEnum.INVALID_REQUEST.getMessage()); } /** * 校验用户名和密码 */ private boolean validateUsernameAndPassword(String username, String password, UserDO user) throws AuthenticationException { return username == user.getUsername() && password == user.getPassword(); }}@EnableWebSecurity@AllArgsConstructorpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { private static final String LOGIN_URL = "/user/login"; private static final String LOGOUT_URL = "/user/logout"; private final UserService userService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(LOGIN_URL).permitAll() .anyRequest().authenticated() .and() .logout().logoutUrl(LOGOUT_URL).clearAuthentication(true).permitAll() .and() .csrf().disable(); http.addFilterAt(bipAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .rememberMe().alwaysRemember(true); } /** * 自定义认证过滤器 */ private CustomAuthenticationFilter customAuthenticationFilter() { CustomAuthenticationFilter authenticationFilter = new CustomAuthenticationFilter(LOGIN_URL, userService); return authenticationFilter; }}

其他服务设计

应用配置 application.yml 示例:

# Spring Session 配置spring.session.store-type=redis# Redis 配置spring.redis.host=spring.redis.port=6379

全局安全配置

@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .csrf().disable(); }}

用户认证信息获取

用户通过用户服务登录成功后,用户信息会被缓存到 Redis,缓存的信息与 CustomAuthenticationFilter 中 attemptAuthentication() 方法返回的对象有关,如上所以,返回的对象是 new UsernamePasswordAuthenticationToken(user.getId(), null, userAuthorities) ,即 Redis 缓存了用户的 ID 和用户的权力(authorities)。

UsernamePasswordAuthenticationToken 构造函数的第一个参数是 Object 对象,所以可以自定义缓存对象。

在微服务各个模块获取用户的这些信息的方法如下:

@GetMapping() public WebResponse test(@AuthenticationPrincipal UsernamePasswordAuthenticationToken authenticationToken){ // 略 }

权限控制

  • 启用基于方法的权限注解
@SpringBootApplication@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
  • 简单权限校验

例如,删除角色的接口,仅允许拥有 ROLE_ADMIN_USER 权限的用户访问。

/** * 删除角色 */ @PostMapping("/delete") @PreAuthorize("hasRole('ADMIN_USER')") public WebResponse deleteRole(@RequestBody RoleBean roleBean){ // 略 }

@PreAuthorize("hasRole('')") 可作用于微服务中的各个模块

  • 自定义权限校验

如上所示, hasRole() 方法是 Spring Security 内嵌的,如需自定义,可以使用 Expression-Based Access Control ,示例:

/** * 自定义校验服务 */@Servicepublic class CustomService{ public boolean check(UsernamePasswordAuthenticationToken authenticationToken, String extraParam){ // 略 }}/** * 删除角色 */ @PostMapping() @PreAuthorize("@customService.check(authentication, #userBean.username)") public WebResponse custom(@RequestBody UserBean userBean){ // 略 }

authentication 属于内置对象, # 获取入参的值

  • 任意用户权限动态修改

原理上,用户的权限信息保存在 Redis 中,修改用户权限就需要操作 Redis,示例:

@Service@AllArgsConstructorpublic class HttpSessionService { private final FindByIndexNameSessionRepository sessionRepository; /** * 重置用户权限 */ public void resetAuthorities(Long userId, List authorities){ UsernamePasswordAuthenticationToken newToken = new UsernamePasswordAuthenticationToken(userId, null, authorities); Map redisSessionMap = sessionRepository.findByPrincipalName(String.valueOf(userId)); redisSessionMap.values().forEach(session -> { SecurityContextImpl securityContext = session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); securityContext.setAuthentication(newToken); session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext); sessionRepository.save(session); }); }}

修改用户权限,仅需调用 httpSessionService.resetAuthorities() 方法即可,实时生效。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值