微服务架构

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

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

  • 权限表

    权限表最小粒度的控制单个功能,例如用户管理、资源管理,表结构示例:
id authority description
1 ROLE_ADMIN_USER 管理所有用户
2 ROLE_ADMIN_RESOURCE 管理所有资源
3 ROLE_A_1 访问 ServiceA 的某接口的权限
4 ROLE_A_2 访问 ServiceA 的另一个接口的权限
5 ROLE_B_1 访问 ServiceB 的某接口的权限
6 ROLE_B_2 访问 ServiceB 的另一个接口的权限
  • 角色 - 权限表

    自定义角色,组合各种权限,例如超级管理员拥有所有权限,表结构示例:
id name authority_ids
1 超级管理员 1,2,3,4,5,6
2 管理员A 3,4
3 管理员B 5,6
4 普通用户 NULL
  • 用户 - 角色表

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

用户服务设计

Maven 依赖(所有服务)

 <!-- Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency> <!-- Spring Session Redis -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>

应用配置 application.yml 示例:

# Spring Session 配置
spring.session.store-type=redis
server.servlet.session.persistent=true
server.servlet.session.timeout=7d
server.servlet.session.cookie.max-age=7d # Redis 配置
spring.redis.host=<redis-host>
spring.redis.port=6379 # MySQL 配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://<mysql-host>:3306/test
spring.datasource.username=<username>
spring.datasource.password=<passowrd>

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

Slf4j
public 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<SimpleGrantedAuthority> 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
@AllArgsConstructor
public 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=<redis-host>
spring.redis.port=6379

全局安全配置

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

用户认证信息获取

用户通过用户服务登录成功后,用户信息会被缓存到 Redis,缓存的信息与 CustomAuthenticationFilterattemptAuthentication() 方法返回的对象有关,如上所以,返回的对象是 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('<authority>')") 可作用于微服务中的各个模块

  • 自定义权限校验

    如上所示,hasRole() 方法是 Spring Security 内嵌的,如需自定义,可以使用 Expression-Based Access Control,示例:
/**
* 自定义校验服务
*/
@Service
public 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
@AllArgsConstructor
public class HttpSessionService<S extends Session> { private final FindByIndexNameSessionRepository<S> sessionRepository; /**
* 重置用户权限
*/
public void resetAuthorities(Long userId, List<GrantedAuthority> authorities){
UsernamePasswordAuthenticationToken newToken = new UsernamePasswordAuthenticationToken(userId, null, authorities);
Map<String, S> 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() 方法即可,实时生效。

© 著作权归作者所有,转载或内容合作请联系作者

Spring Cloud Gateway - 快速开始

APM工具寻找了一圈,发现SkyWalking才是我的真爱

Spring Boot 注入外部配置到应用内部的静态变量

将 HTML 转化为 PDF新姿势

Java 使用 UnixSocket 调用 Docker API

Fastjson致命缺陷

Service Mesh - gRPC 本地联调远程服务

使用 Thymeleaf 动态渲染 HTML

Fastjson致命缺陷

Spring Boot 2 集成log4j2日志框架

Java面试通关要点汇总集之核心篇参考答案

Java面试通关要点汇总集之框架篇参考答案

Spring Security 实战干货:如何保护用户密码

Spring Boot RabbitMQ - 优先级队列

原文链接:https://mp.weixin.qq.com/s?__biz=MzU0MDEwMjgwNA==&mid=2247486167&idx=2&sn=76dba01d16b7147c9b1dfb7cbf2d8d28&chksm=fb3f132ccc489a3ad2ea05314823d660c40e8af90dcd35800422899958f98b4a258d23badba8&token=280305379&lang=zh_CN#rd

本文由博客一文多发平台 OpenWrite 发布!

别再让你的微服务裸奔了,基于 Spring Session & Spring Security 微服务权限控制的更多相关文章

  1. .NET Core微服务之路:基于Consul最少集群实现服务的注册与发现(二)

    重温Consul最少化集群的搭建  

  2. Spring Boot + Spring Cloud 构建微服务系统(七):API服务网关(Zuul)

    技术背景 前面我们通过Ribbon或Feign实现了微服务之间的调用和负载均衡,那我们的各种微服务又要如何提供给外部应用调用呢. 当然,因为是REST API接口,外部客户端直接调用各个微服务是没有问 ...

  3. 几种常见的微服务架构方案简述——ZeroC IceGrid、Spring Cloud、基于消息队列

    微服务架构是当前很热门的一个概念,它不是凭空产生的,是技术发展的必然结果.虽然微服务架构没有公认的技术标准和规范草案,但业界已经有一些很有影响力的开源微服务架构平台,架构师可以根据公司的技术实力并结合 ...

  4. Spring Boot + Spring Cloud 实现权限管理系统 后端篇(二十一):服务网关(Zuul)

    在线演示 演示地址:http://139.196.87.48:9002/kitty 用户名:admin 密码:admin 技术背景 前面我们通过Ribbon或Feign实现了微服务之间的调用和负载均衡 ...

  5. Spring Cloud(六)服务网关 zuul 快速入门

    服务网关是微服务架构中一个不可或缺的部分.通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由.均衡负载功能之外,它还具备了权限控制等功能.Spring Cloud Netflix中 ...

  6. 综合使用spring cloud技术实现微服务应用

    在之前的章节,我们已经实现了配置服务器.注册服务器.微服务服务端,实现了服务注册与发现.这一章将实现微服务的客户端,以及联调.实现整个spring cloud框架核心应用. 本文属于<7天学会s ...

  7. spring Boot+spring Cloud实现微服务详细教程第二篇

    上一篇文章已经说明了一下,关于spring boot创建maven项目的简单步骤,相信很多熟悉Maven+Eclipse作为开发常用工具的朋友们都一目了然,这篇文章主要讲解一下,构建spring bo ...

  8. 微服务架构的基础框架选择:Spring Cloud还是Dubbo?

    最近一段时间不论互联网还是传统行业,凡是涉及信息技术范畴的圈子几乎都在讨论微服务架构.近期也看到各大技术社区开始组织一些沙龙和论坛来分享Spring Cloud的相关实施经验,这对于最近正在整理Spr ...

  9. .NET Core微服务之基于Steeltoe使用Spring Cloud Config统一管理配置

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 =>  Steeltoe目录快速导航: 1. 基于Steeltoe使用Spring Cloud Eureka 2. 基于Steelt ...

随机推荐

  1. [LeetCode]sum合集

    LeetCode很喜欢sum,里面sum题一堆. 1.Two Sum Given an array of integers, return indices of the two numbers suc ...

  2. 从零开始入门 K8s | 应用编排与管理(酒祝)

    一.需求来源 背景问题 首先来看一下背景问题.如下图所示:如果我们直接管理集群中所有的 Pod,应用 A.B.C 的 Pod,其实是散乱地分布在集群中. 现在有以下的问题: 首先,如何保证集群内可用 ...

  3. 新手学习FFmpeg - 调用API完成视频的读取和输出

    在写了几个avfilter之后,原本以为对ffmpeg应该算是入门了. 结果今天想对一个视频文件进行转码操作,才发现基本的视频读取,输出都搞不定. 痛定思痛,仔细研究了一下ffmpeg提供的examp ...

  4. springboot 集成Redis单机

    1.redis服务搭建 centos7 搭建redis服务 2.接入相关 pom文件依赖引入 <dependencies> <dependency> <groupId&g ...

  5. linux 配置多个tomcat

    一.安装tomcat 1.下载链接:https://tomcat.apache.org/download-70.cgi,选择需要的版本下载(.tar.gz文件后缀) 2.通过Xshell.Xftp上传 ...

  6. org.hibernate.AnnotationException: No identifier specified for entity 错误解决

    主键对应的属性上加上@Id注解,对应javax.persistence.Id @Id private Long id;

  7. CSS技巧 (2) · 多列等高布局

    前言  最近,面试的时候都碰到一些关于利用CSS实现多列等高布局或者一侧宽度固定,另一侧宽度自适应的问题,下面稍微总结一下: 先看一道题目 巧妙的多列等高布局 规定下面的布局,实现多列等高布局,要求两 ...

  8. Java 学习笔记之 Thread运行过程分析

    Thread运行过程分析: 以下是一个最普通的Thread实现过程,我们今天就来看仔细分析下他是如何运行的. public class ThreadRunMain { public static vo ...

  9. JavaSE----02.Java语言基础

    02.Java语言基础 1.关键字     Java关键字是电脑语言里事先定义的,有特别意义的标识符,有时又叫保留字,还有特别意义的变量.Java的关键字对Java的编译器有特殊的意义,他们用来表示一 ...

  10. 【WPF】EntityframeworkCore NLog出力设置

    最近在用EFcore,由于不熟悉,经常出现一些异常都不知道如何排查,只能把EFcore的执行记录打印出来调查.确实简化了很多问题的调查. 官网提供了Asp.net Core与.net core 应用的 ...