别再让你的微服务裸奔了,基于 Spring Session & Spring Security 微服务权限控制
微服务架构
- 网关:路由用户请求到指定服务,转发前端 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,缓存的信息与 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('<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()
方法即可,实时生效。
© 著作权归作者所有,转载或内容合作请联系作者
● APM工具寻找了一圈,发现SkyWalking才是我的真爱
● Spring Boot 注入外部配置到应用内部的静态变量
● Java 使用 UnixSocket 调用 Docker API
● Service Mesh - gRPC 本地联调远程服务
● Spring Security 实战干货:如何保护用户密码
● Spring Boot RabbitMQ - 优先级队列
本文由博客一文多发平台 OpenWrite 发布!
别再让你的微服务裸奔了,基于 Spring Session & Spring Security 微服务权限控制的更多相关文章
- .NET Core微服务之路:基于Consul最少集群实现服务的注册与发现(二)
重温Consul最少化集群的搭建
- Spring Boot + Spring Cloud 构建微服务系统(七):API服务网关(Zuul)
技术背景 前面我们通过Ribbon或Feign实现了微服务之间的调用和负载均衡,那我们的各种微服务又要如何提供给外部应用调用呢. 当然,因为是REST API接口,外部客户端直接调用各个微服务是没有问 ...
- 几种常见的微服务架构方案简述——ZeroC IceGrid、Spring Cloud、基于消息队列
微服务架构是当前很热门的一个概念,它不是凭空产生的,是技术发展的必然结果.虽然微服务架构没有公认的技术标准和规范草案,但业界已经有一些很有影响力的开源微服务架构平台,架构师可以根据公司的技术实力并结合 ...
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(二十一):服务网关(Zuul)
在线演示 演示地址:http://139.196.87.48:9002/kitty 用户名:admin 密码:admin 技术背景 前面我们通过Ribbon或Feign实现了微服务之间的调用和负载均衡 ...
- Spring Cloud(六)服务网关 zuul 快速入门
服务网关是微服务架构中一个不可或缺的部分.通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由.均衡负载功能之外,它还具备了权限控制等功能.Spring Cloud Netflix中 ...
- 综合使用spring cloud技术实现微服务应用
在之前的章节,我们已经实现了配置服务器.注册服务器.微服务服务端,实现了服务注册与发现.这一章将实现微服务的客户端,以及联调.实现整个spring cloud框架核心应用. 本文属于<7天学会s ...
- spring Boot+spring Cloud实现微服务详细教程第二篇
上一篇文章已经说明了一下,关于spring boot创建maven项目的简单步骤,相信很多熟悉Maven+Eclipse作为开发常用工具的朋友们都一目了然,这篇文章主要讲解一下,构建spring bo ...
- 微服务架构的基础框架选择:Spring Cloud还是Dubbo?
最近一段时间不论互联网还是传统行业,凡是涉及信息技术范畴的圈子几乎都在讨论微服务架构.近期也看到各大技术社区开始组织一些沙龙和论坛来分享Spring Cloud的相关实施经验,这对于最近正在整理Spr ...
- .NET Core微服务之基于Steeltoe使用Spring Cloud Config统一管理配置
Tip: 此篇已加入.NET Core微服务基础系列文章索引 => Steeltoe目录快速导航: 1. 基于Steeltoe使用Spring Cloud Eureka 2. 基于Steelt ...
随机推荐
- SVN检出后文件没有图标显示
SVN检出后文件没有图标显示 "Win + R"打开运行框,输入"regedit"打开注册表 在注册表编辑界面按"Ctrl + F"快捷 ...
- Appium+python自动化(三十八) - Appium自动化测试框架综合实践 - 框架简介-助你冲击高薪,迎娶白富美(超详解)
简介 好久没有更新博客了,博友们是不是有点等不及了.不好意思啊,中秋节过后太忙了,这篇是好不容易抽点零碎时间写的.从这一篇开始小伙伴或者童鞋们,就跟随宏哥的脚步,一步步的从无到有,从0到1的搭建一个完 ...
- css禁止选中文字
很简单: -moz-user-select:none;/*火狐*/ -webkit-user-select:none;/*webkit浏览器*/ -ms-user-select:none;/*IE10 ...
- WinServer 2012 R2 安装python3.6时出现错误:0x80240017 导致安装失败
解决方法: 依次检查并更新补丁:KB2919442,KB2919355,kb2999226 KB2919442:https://www.microsoft.com/zh-cn/download/det ...
- Python 爬虫监控女神的QQ空间新的说说,实现秒赞,并发送说说内容到你的邮箱
这个文章主要是在前一篇文章上新增了说说秒赞的功能 前一篇文章可以了解一下 那么,这次主要功能就是 监控女神的 QQ空间,一旦女神发布新的说说,马上点赞,你的邮箱马上就会收到说说内容,是不是想了解一下 ...
- java Swing 界面化查询数据库表
两天从0基础写的.没有按钮对话框功能,只是简单的实现. 当然代码上有很多需要优化的,基本需要重写哈哈哈.但是我怕以后有需要所以还是存一下好了.<把RS结果集,放vector里面,用vector构 ...
- FIT文件CRC校验
校验FIT文件CRC代码做个记录,分为两步先校验头部然后再校验整个FIT文件.校验头部不是必需的看个人需要吧.为了偷懒使用Okio库,还有计算CRC的时候用的Garmin的FitSDK. public ...
- JS 取整、取余
一.取整 1. 取整 // 丢弃小数部分,保留整数部分 parseInt(7/2) // 3 2. 向上取整 // 向上取整,有小数就整数部分加1 Math.ceil(7/2) // 4 3. 向下取 ...
- jQuery常用方法(五)-jQuery CSS
JQuery CSS 方法说明 css( name ) 访问第一个匹配元素的样式属性. css( properties ) 把一个"名/值对"对象设置为所有匹配元素的样式属性. $ ...
- 博客的第一天:回顾半年前的基础:SQL--基础查询+年月日格式+拼接
----------------------2019/6月份 <<必知必会>>书本练习-实践练习--------------------------- ---order by没 ...