当前位置: 首页 > article >正文

微服务架构中处理用户认证信息的5种方案

方案一:网关层解析Token并存储于Redis

设计思路
  1. 集中式认证:所有认证逻辑集中在API网关中,减少每个微服务的负担。
  2. 缓存机制:使用Redis缓存解析后的用户信息,避免重复解析Token。
  3. 传递用户信息:将用户信息通过HTTP头或上下文(Context)传递给下游微服务。
实现步骤
  1. 网关层解析Token

    • 使用JWT库解析Token。
    • 提取用户信息(如ID、角色等)。
    • 将用户信息存储到Redis中,并设置过期时间。
  2. 缓存用户信息

    • 生成唯一的Key(如user:{userId}),将用户信息存储到Redis中。
    • 设置合理的过期时间,以确保数据新鲜度。
  3. 传递用户信息

    • 在请求转发时,通过HTTP头或上下文传递用户信息的标识(如用户ID)。
  4. 下游服务获取用户信息

    • 下游服务根据传递的用户ID从Redis中获取用户信息。
优点
  • 简化代码:所有认证逻辑集中在网关实现,减少了每个微服务需要编写的认证代码量。
  • 性能优化:通过缓存用户信息到Redis,减少重复解析Token的开销,提高响应速度。
  • 易于扩展:可以方便地在网关层添加额外的安全措施,如限流、黑白名单等。
缺点
  • 单点故障风险:如果网关出现问题,可能会影响整个系统的可用性。
  • 数据一致性挑战:需要确保Redis中的用户信息与实际数据库同步,尤其是在有频繁更新的情况下。
适用场景
  • 高并发场景下,对性能要求较高的系统。
  • 系统规模较大,需要集中管理安全策略的场景。
代码示例

网关层解析Token并存储到Redis

@RestController
public class GatewayController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/api")
    public ResponseEntity<?> handleRequest(@RequestHeader("Authorization") String token) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey("secret".getBytes())
                    .parseClaimsJws(token.replace("Bearer ", ""))
                    .getBody();

            String userId = claims.getSubject();
            String userRole = (String) claims.get("role");

            // 存储到Redis
            User user = new User(userId, userRole);
            redisTemplate.opsForValue().set("user:" + userId, user, 30, TimeUnit.MINUTES);

            // 转发请求
            HttpHeaders headers = new HttpHeaders();
            headers.add("X-User-ID", userId);
            HttpEntity<String> entity = new HttpEntity<>(headers);
            return restTemplate.exchange("http://downstream-service/api", HttpMethod.POST, entity, String.class);

        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid Token");
        }
    }
}

下游服务获取用户信息

@RestController
public class DownstreamServiceController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @PostMapping("/api")
    public ResponseEntity<?> handleApiRequest(@RequestHeader("X-User-ID") String userId) {
        User user = (User) redisTemplate.opsForValue().get("user:" + userId);
        if (user == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found");
        }
        // 处理业务逻辑
        return ResponseEntity.ok("Success");
    }
}

方案二:微服务内部拦截器存储于ThreadLocal

设计思路
  1. 分布式认证:每个微服务独立处理自己的认证逻辑。
  2. ThreadLocal存储:使用ThreadLocal变量存储当前线程中的用户信息,避免跨线程访问问题。
  3. 拦截器机制:通过自定义拦截器在请求到达控制器之前解析Token并存储用户信息。
实现步骤
  1. 创建拦截器

    • 实现HandlerInterceptor接口,在preHandle方法中解析Token并存储用户信息到ThreadLocal。
  2. 配置拦截器

    • 在Spring Boot应用中注册自定义拦截器。
  3. 业务逻辑中使用用户信息

    • 在需要的地方从ThreadLocal中获取用户信息。
优点
  • 分散风险:每个微服务独立处理自己的认证逻辑,降低了单一组件故障对整体系统的影响。
  • 实时性强:直接从Token中获取最新用户信息,无需担心缓存导致的数据不一致问题。
  • 灵活性高:各服务可以根据自身需求灵活调整认证逻辑。
缺点
  • 增加复杂度:需要在每个微服务中实现类似的认证逻辑,增加了开发和维护成本。
  • 性能考虑:如果Token包含大量信息或者频繁调用,解析Token可能会带来一定的性能开销。
适用场景
  • 对实时性和数据一致性要求较高的场景。
  • 各微服务需要独立处理认证逻辑的场景。
代码示例

创建拦截器

@Component
public class AuthInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        if (token != null && !token.isEmpty()) {
            try {
                Claims claims = Jwts.parser()
                        .setSigningKey("secret".getBytes())
                        .parseClaimsJws(token.replace("Bearer ", ""))
                        .getBody();

                String userId = claims.getSubject();
                String userRole = (String) claims.get("role");

                // 存储到ThreadLocal
                UserContextHolder.setUser(new User(userId, userRole));
                return true;
            } catch (Exception e) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return false;
            }
        } else {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 清理ThreadLocal
        UserContextHolder.clearUser();
    }
}

ThreadLocal存储类

public class UserContextHolder {
    private static final ThreadLocal<User> userHolder = new ThreadLocal<>();

    public static void setUser(User user) {
        userHolder.set(user);
    }

    public static User getUser() {
        return userHolder.get();
    }

    public static void clearUser() {
        userHolder.remove();
    }
}

配置拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/login", "/register");
    }
}

业务逻辑中使用用户信息

@RestController
public class BusinessController {

    @GetMapping("/business")
    public ResponseEntity<?> businessLogic() {
        User user = UserContextHolder.getUser();
        if (user == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized");
        }
        // 处理业务逻辑
        return ResponseEntity.ok("Success");
    }
}

方案三:使用分布式Session

设计思路
  1. 集中式会话管理:将用户的会话信息存储在一个集中式的存储系统(如Redis或数据库)中。
  2. 传递Session ID:通过HTTP头或Cookie传递Session ID,下游服务根据Session ID从集中式存储中获取用户信息。
实现步骤
  1. 生成Session ID

    • 用户登录时生成唯一的Session ID,并将其存储到集中式存储中(如Redis)。
  2. 传递Session ID

    • 将Session ID通过HTTP头或Cookie传递给下游服务。
  3. 下游服务获取用户信息

    • 下游服务根据Session ID从集中式存储中获取用户信息。
优点
  • 集中式会话管理:简化了认证逻辑;可以方便地实现会话共享和跨域支持。
  • 适合需要保持用户状态的应用场景:如购物车、用户偏好等。
缺点
  • 依赖于集中式存储:存在单点故障风险;需要额外的存储和管理开销。
适用场景
  • 需要保持用户会话状态的场景。
  • 跨域或多设备共享会话的场景。
代码示例

生成Session ID并存储到Redis

@RestController
public class AuthController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody UserLoginRequest loginRequest) {
        // 验证用户名和密码
        String userId = "user123"; // 假设验证成功
        String sessionId = UUID.randomUUID().toString();

        // 存储到Redis
        redisTemplate.opsForValue().set("session:" + sessionId, userId, 30, TimeUnit.MINUTES);

        // 返回Session ID
        return ResponseEntity.ok(new SessionResponse(sessionId));
    }
}

网关层传递Session ID

@RestController
public class GatewayController {

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/api")
    public ResponseEntity<?> handleRequest(@RequestHeader("X-Session-ID") String sessionId) {
        HttpHeaders headers = new HttpHeaders();
        headers.add("X-Session-ID", sessionId);
        HttpEntity<String> entity = new HttpEntity<>(headers);
        return restTemplate.exchange("http://downstream-service/api", HttpMethod.POST, entity, String.class);
    }
}

下游服务获取用户信息

@RestController
public class DownstreamServiceController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @PostMapping("/api")
    public ResponseEntity<?> handleApiRequest(@RequestHeader("X-Session-ID") String sessionId) {
        String userId = (String) redisTemplate.opsForValue().get("session:" + sessionId);
        if (userId == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid Session");
        }
        // 处理业务逻辑
        return ResponseEntity.ok("Success");
    }
}

方案四:基于OAuth2的分布式认证

设计思路
  1. 授权服务器:使用OAuth2协议,由专门的授权服务器负责用户认证和授权。
  2. 资源服务器:每个微服务作为资源服务器,通过访问令牌(Access Token)验证请求的合法性。
  3. JWT Token:使用JWT作为访问令牌,包含用户信息和权限信息。
实现步骤
  1. 配置授权服务器

    • 使用Spring Security OAuth2或其他框架配置授权服务器。
  2. 配置资源服务器

    • 每个微服务配置为资源服务器,验证JWT Token。
  3. 客户端请求

    • 客户端通过授权服务器获取JWT Token,并在每次请求时携带该Token。
优点
  • 标准的认证协议:广泛支持;安全性高,提供了多种授权模式。
  • 适合复杂的权限管理和多租户应用场景:如企业级应用。
缺点
  • 配置复杂度较高:需要熟悉OAuth2协议;对已有系统的改造较大。
适用场景
  • 复杂权限管理和多租户支持的场景。
  • 需要高度安全性的企业级应用。
代码示例

授权服务器配置

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client")
                .secret("{noop}secret")
                .authorizedGrantTypes("password", "refresh_token")
                .scopes("read", "write")
                .accessTokenValiditySeconds(3600)
                .refreshTokenValiditySeconds(2592000);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(new JwtTokenStore(accessTokenConverter()))
                .accessTokenConverter(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");
        return converter;
    }
}

资源服务器配置

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/api/**").authenticated();
    }
}

业务逻辑中使用用户信息

@RestController
public class BusinessController {

    @GetMapping("/business")
    public ResponseEntity<?> businessLogic(OAuth2Authentication authentication) {
        Map<String, Object> details = (Map<String, Object>) authentication.getUserAuthentication().getDetails();
        String userId = (String) details.get("sub");
        // 处理业务逻辑
        return ResponseEntity.ok("Success");
    }
}

方案五:使用API Gateway代理认证

设计思路
  1. 代理认证:API Gateway不仅转发请求,还负责验证和转换请求中的认证信息。
  2. 透明化认证:下游服务不需要直接处理认证信息,所有认证逻辑都由网关代理完成。
实现步骤
  1. 网关层验证Token

    • 网关层解析Token并提取用户信息。
  2. 转发请求

    • 将提取的用户信息以某种方式(如HTTP头)传递给下游服务。
  3. 下游服务直接使用用户信息

    • 下游服务直接从HTTP头中获取用户信息,无需再次验证。
优点
  • 透明化认证:下游服务无需处理认证逻辑;简化了微服务的开发和维护。
  • 适合需要快速集成认证功能的场景:如新项目或快速迭代的项目。
缺点
  • 网关成为系统的瓶颈:可能影响性能;缺乏灵活性,难以应对复杂的认证需求。
适用场景
  • 快速集成认证功能的场景。
  • 希望简化微服务开发和维护的场景。
代码示例

网关层验证Token并传递用户信息

@RestController
public class GatewayController {

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/api")
    public ResponseEntity<?> handleRequest(@RequestHeader("Authorization") String token) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey("secret".getBytes())
                    .parseClaimsJws(token.replace("Bearer ", ""))
                    .getBody();

            String userId = claims.getSubject();
            String userRole = (String) claims.get("role");

            HttpHeaders headers = new HttpHeaders();
            headers.add("X-User-ID", userId);
            headers.add("X-User-Role", userRole);
            HttpEntity<String> entity = new HttpEntity<>(headers);
            return restTemplate.exchange("http://downstream-service/api", HttpMethod.POST, entity, String.class);

        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid Token");
        }
    }
}

下游服务直接使用用户信息

@RestController
public class DownstreamServiceController {

    @PostMapping("/api")
    public ResponseEntity<?> handleApiRequest(@RequestHeader("X-User-ID") String userId,
                                              @RequestHeader("X-User-Role") String userRole) {
        // 直接使用用户信息
        return ResponseEntity.ok("Success");
    }
}

总结与选择建议

网关层解析Token并存储于Redis
  • 适用场景:高并发场景下,对性能要求较高的系统;系统规模较大,需要集中管理安全策略的场景。
  • 优点:集中式管理,简化了微服务内部的认证逻辑;减少了重复解析Token的开销,提升了性能;易于扩展。
  • 缺点:单点故障风险,需要高可用性设计;数据一致性挑战,需要确保Redis与数据库同步。
微服务内部拦截器存储于ThreadLocal
  • 适用场景:对实时性和数据一致性要求较高的场景;各微服务需要独立处理认证逻辑的场景。
  • 优点:分散风险,各服务独立处理认证逻辑;实时性强,直接从Token获取最新用户信息;灵活性高,各服务可以根据需求定制认证逻辑。
  • 缺点:增加了开发和维护成本,需要在每个服务中实现类似逻辑;可能存在性能开销,特别是在高并发场景下。
分布式Session
  • 适用场景:需要保持用户会话状态的场景;跨域或多设备共享会话的场景。
  • 优点:集中式会话管理,简化了认证逻辑;可以方便地实现会话共享和跨域支持;适合需要保持用户状态的应用场景。
  • 缺点:依赖于集中式存储,存在单点故障风险;需要额外的存储和管理开销。
基于OAuth2的分布式认证
  • 适用场景:复杂权限管理和多租户支持的场景;需要高度安全性的企业级应用。
  • 优点:标准的认证协议,广泛支持;安全性高,提供了多种授权模式;适合复杂的权限管理和多租户应用场景。
  • 缺点:配置复杂度较高,需要熟悉OAuth2协议;对已有系统的改造较大。
API Gateway代理认证
  • 适用场景:快速集成认证功能的场景;希望简化微服务开发和维护的场景。
  • 优点:透明化认证,下游服务无需处理认证逻辑;简化了微服务的开发和维护;适合需要快速集成认证功能的场景。
  • 缺点:网关成为系统的瓶颈,可能影响性能;缺乏灵活性,难以应对复杂的认证需求。

通过综合评估以上因素,你可以根据具体的业务需求和技术条件选择最适合的解决方案。每个方案都有其独特的适用场景和优缺点,合理选择和组合这些方案可以帮助你构建一个高效、安全且易于维护的微服务架构。


http://www.kler.cn/a/571884.html

相关文章:

  • 数据显示不符合用户阅读习惯
  • Virtual Box虚拟机安装Mac苹果Monterey和big sur版本实践
  • 解决跨域请求的问题(CORS)
  • # 【Unity】【游戏开发】赛车游戏中碰撞加速的实现方法
  • Ubuntu20.04双系统安装及软件安装(四):国内版火狐浏览器
  • Google Earth Engine中的Map对象
  • 数据结构:二叉树的链式结构及相关算法详解
  • 【TCP/IP协议栈】4. 传输层协议(TCP、UDP)
  • 【PHP】fastadmin中对addons进行路由重写
  • WSL下使用git克隆失败解决
  • 14. LangChain项目实战1——基于公司制度RAG回答机器人
  • 【开源免费】基于SpringBoot+Vue.JS信息技术知识赛系统(JAVA毕业设计)
  • 10分钟从零开始搭建机器人管理系统(飞算AI)
  • K8S学习之基础十:初始化容器和主容器
  • 【流行病学】Melodi-Presto因果关联工具
  • [特殊字符]在eclipse中导入JAVA的jar包方法
  • html | 预览一个颜色数组
  • 算法与数据结构(相交链表)
  • com.mysql.jdbc.Driver 和 com.mysql.cj.jdbc.Driver的区别
  • 火语言RPA--PDF提取图片