资讯专栏INFORMATION COLUMN

基于 Spring Session & Spring Security 微服务权限控制

clasnake / 1742人阅读

摘要:构造函数的第一个参数是对象,所以可以自定义缓存对象。在微服务各个模块获取用户的这些信息的方法如下略权限控制启用基于方法的权限注解简单权限校验例如,删除角色的接口,仅允许拥有权限的用户访问。

微服务架构

网关:路由用户请求到指定服务,转发前端 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 依赖(所有服务)

        
        
            org.springframework.boot
            spring-boot-starter-security
        

        
        
            org.springframework.session
            spring-session-data-redis
        

应用配置 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,缓存的信息与 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("")") 可作用于微服务中的各个模块

自定义权限校验

如上所示,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  {

    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() 方法即可,实时生效。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75029.html

相关文章

  • Spring Security

    摘要:框架具有轻便,开源的优点,所以本译见构建用户管理微服务五使用令牌和来实现身份验证往期译见系列文章在账号分享中持续连载,敬请查看在往期译见系列的文章中,我们已经建立了业务逻辑数据访问层和前端控制器但是忽略了对身份进行验证。 重拾后端之Spring Boot(四):使用JWT和Spring Security保护REST API 重拾后端之Spring Boot(一):REST API的搭建...

    keelii 评论0 收藏0
  • 不用 Spring Security 可否?试试这个小而美的安全框架

    摘要:写在前面在一款应用的整个生命周期,我们都会谈及该应用的数据安全问题。用户的合法性与数据的可见性是数据安全中非常重要的一部分。 写在前面 在一款应用的整个生命周期,我们都会谈及该应用的数据安全问题。用户的合法性与数据的可见性是数据安全中非常重要的一部分。但是,一方面,不同的应用对于数据的合法性和可见性要求的维度与粒度都有所区别;另一方面,以当前微服务、多服务的架构方式,如何共享Sessi...

    toddmark 评论0 收藏0
  • 深入理解Spring Cloud与服务构建【二】 - 2.2 Spring Cloud

    摘要:负载均衡组件是一个负载均衡组件,它通常和配合使用。和配合,很容易做到负载均衡,将请求根据负载均衡策略分配到不同的服务实例中。和配合,在消费服务时能够做到负载均衡。在默认的情况下,和相结合,能够做到负载均衡智能路由。 2.2.1 简介 Spring Cloud 是基于 Spring Boot 的。 Spring Boot 是由 Pivotal 团队提供的全新 Web 框架, 它主要的特点...

    Rocko 评论0 收藏0
  • 让ERP的服务更开放! ——用服务架构搭建的一套基于EBS的API服务系统

    摘要:每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通通常是基于的。在微服务架构下,故障会被隔离在单个服务中。 1. 源码下载地址 源码链接: https://github.com/samt007/xy... 这是用Spring Cloud微服务架构搭建的一套基于EBS的API服务系统如对本文有任何的疑问,请联系我:samt007@qq.com 2. Introduc...

    JouyPub 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<