springsecurity权限控制_别再让你的微服务裸奔了,基于 Spring Session & Spring Security 微服务权限控制...

点击上方☝ SpringForAll社区 ,轻松关注! 及时获取有趣有料的技术文章

fe996fb762a3f1e742e7f3691e30391f.png

微服务架构

076c585f12d3f4ee670aa3516749eb2d.png

  • 网关:路由用户请求到指定服务,转发前端 Cookie 中包含的 Session 信息;

  • 用户服务:用户登录认证(Authentication),用户授权(Authority),用户管理(Redis Session Management)

  • 其他服务:依赖 Redis 中用户信息进行接口请求验证

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

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

idauthoritydescription
1ROLE_ADMIN_USER管理所有用户
2ROLE_ADMIN_RESOURCE管理所有资源
3ROLE_A_1访问 ServiceA 的某接口的权限
4ROLE_A_2访问 ServiceA 的另一个接口的权限
5ROLE_B_1访问 ServiceB 的某接口的权限
6ROLE_B_2访问 ServiceB 的另一个接口的权限
  • 角色 - 权限表
    自定义角色,组合各种权限,例如超级管理员拥有所有权限,表结构示例:

idnameauthority_ids
1超级管理员1,2,3,4,5,6
2管理员A3,4
3管理员B5,6
4普通用户NULL
  • 用户 - 角色表
    用户绑定一个或多个角色,即分配各种权限,示例表结构:

user_idrole_id
11
14
22

用户服务设计

Maven 依赖(所有服务)

        
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>


<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-session-data-redisartifactId>
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=
spring.redis.port=6379# MySQL 配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://:3306/test
spring.datasource.username=
spring.datasource.password=

用户登录认证(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 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=
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('')") 可作用于微服务中的各个模块

  • 自定义权限校验
    如上所示,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 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() 方法即可,实时生效。

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

a72967e91b6bc9899b80e8c9628f1106.gif

● 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 - 优先级队列

32d266a3d5199f469bce5603078f9a44.png

a962b0afefb52ca32aad780bc4704431.gif

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值