第5章 Spring Security 的高级认证技术(2024 最新版)(上)

简介: 第5章 Spring Security 的高级认证技术(2024 最新版)

9526b1d6ced59dd11678343f8411959.png

5.1 OAuth2 和 OpenID Connect

在现代应用开发中,OAuth2 和 OpenID Connect (OIDC) 是保护资源和身份验证的关键技术。让我们先深入了解一下这两种技术的基础,然后通过实用案例来探索它们在实际开发中的应用。

5.1.1 基础知识详解

在深入探讨具体案例之前,让我们对 OAuth2 和 OpenID Connect (OIDC) 的基础知识进行更详尽的解析。这两个协议在现代网络安全和身份验证中扮演着至关重要的角色,为用户提供了一种安全、高效的方式来访问和分享他们在不同服务中的资源和信息。

OAuth2

OAuth2 是一个开放标准的授权协议,它允许第三方应用代表用户安全地执行操作,而无需用户共享其登录凭据。OAuth2 的设计围绕四种角色展开:

  1. 资源所有者:通常指的是用户,控制其数据的访问权限。
  2. 客户端:尝试代表资源所有者访问其在资源服务器上的受保护资源的第三方应用。
  3. 资源服务器:托管用户数据的服务器,响应客户端的资源请求。
  4. 授权服务器:验证资源所有者的身份并发放令牌给客户端,允许其访问资源服务器上的资源。

OAuth2 定义了四种授权流程,以适应不同的客户端类型和安全需求:

  • 授权码模式:适用于有自己后端服务的客户端,是最常用的流程。
  • 隐式授权模式:适用于纯前端应用,如单页应用(SPA)。
  • 密码凭证模式:允许用户直接向客户端提供其用户名和密码。
  • 客户端凭证模式:适用于客户端访问自己的资源,无需代表用户行动。

OpenID Connect

OpenID Connect 建立在 OAuth2 的基础上,是一个身份层,提供了用户身份验证的标准协议。它扩展了 OAuth2,允许客户端除了获取用户授权的资源访问令牌外,还能获取用户的身份信息。OIDC 引入了以下几个关键概念:

  • ID 令牌(ID Token):一个 JWT,包含了用户的身份信息。
  • 用户信息端点(UserInfo Endpoint):客户端可以使用访问令牌查询此端点以获取关于用户的更多信息。
  • 认证服务器(Authorization Server):验证用户身份并向客户端发放 ID 令牌和访问令牌。

OIDC 通过提供一个标准化的身份层,使得开发者能够在全球范围内实现互操作性强、安全可靠的用户身份验证解决方案。

结合 OAuth2 和 OIDC

结合 OAuth2 和 OIDC 不仅可以安全地授权第三方应用访问用户资源,还能验证用户的身份,这对于构建现代应用而言至关重要。这两个协议提供的机制,如令牌和授权流程,为用户提供了一个无缝且安全的登录体验,同时也保护了用户的数据安全。

通过掌握这些基础知识,开发者可以更好地理解如何利用 OAuth2 和 OpenID Connect 来构建安全、符合行业标准的认证和授权机制。接下来,我们将通过具体的案例来探索如何在实际开发中应用这些协议。

5.1.2 重点案例:使用 OAuth2 和 OpenID Connect 实现社交登录

在这个案例中,我们将探索如何利用 Spring Security 5 支持的 OAuth2 和 OpenID Connect 来实现 Google 社交登录,从而让用户能够使用他们的 Google 账户在我们的应用中登录。

案例 Demo

步骤 1: 添加依赖

首先,确保你的 pom.xml 包含 Spring Security 5 和 OAuth2 客户端的依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

步骤 2: 配置 OAuth2 客户端

application.ymlapplication.properties 文件中,配置 Google 作为 OAuth2 提供者的相关信息。你需要在 Google Cloud Platform 上创建一个 OAuth2 客户端,并获取客户端 ID 和密钥。

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            clientId: <YOUR_CLIENT_ID>
            clientSecret: <YOUR_CLIENT_SECRET>
            scope:
              - email
              - profile

步骤 3: 创建授权和回调控制器

虽然 Spring Security 和 OAuth2 客户端的自动配置已经处理了大部分工作,但你可能仍然需要创建一个控制器来处理登录成功后的逻辑,例如重定向用户或提取用户信息。

@Controller
public class OAuth2LoginController {
    @GetMapping("/loginSuccess")
    public String getLoginInfo(OAuth2AuthenticationToken authentication) {
        OAuth2AuthorizedClientService authorizedClientService; // 假设已注入
        OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient(
                authentication.getAuthorizedClientRegistrationId(), authentication.getName());
        String userInfoEndpointUri = client.getClientRegistration()
                .getProviderDetails().getUserInfoEndpoint().getUri();
        if (!StringUtils.isEmpty(userInfoEndpointUri)) {
            RestTemplate restTemplate = new RestTemplate();
            HttpHeaders headers = new HttpHeaders();
            headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + client.getAccessToken().getTokenValue());
            HttpEntity<String> entity = new HttpEntity<>("", headers);
            ResponseEntity<Map> response = restTemplate.exchange(userInfoEndpointUri, HttpMethod.GET, entity, Map.class);
            Map userAttributes = response.getBody();
            // 根据 userAttributes 处理用户信息
        }
        return "redirect:/home";
    }
}

在这个控制器中,我们使用 OAuth2AuthenticationToken 来获取 OAuth2AuthorizedClient,它包含了当前用户的 OAuth2 令牌信息。然后,我们可以使用这个令牌调用用户信息端点来获取用户的详细信息。

步骤 4: 配置安全性

确保你的 Spring Security 配置支持 OAuth2 登录,并正确处理登录后的重定向。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(authorizeRequests ->
                authorizeRequests
                    .anyRequest().authenticated()
            )
            .oauth2Login(oauth2Login ->
                oauth2Login
                    .loginPage("/login")
                    .defaultSuccessUrl("/loginSuccess", true)
            );
    }
}

通过这个配置,Spring Security 会自动处理 OAuth2 登录流程,并在成功认证后重定向到 /loginSuccess 路径。

通过实现这个案例,你可以为你的应用添加使用 Google 账户登录的功能,提供一种方便快捷的登录方式,同时也增强了应用的安全性。使用 OAuth2 和 OpenID Connect 实现社交登录,不仅简化了认证流程,还提高了用户体验。

5.1.3 拓展案例 1:访问受保护资源

在用户通过 OAuth2 和 OpenID Connect 实现社交登录之后,一个常见的需求是访问用户在社交平台上的受保护资源,比如访问用户的Google日历。在这个案例中,我们将探讨如何使用用户的授权信息来访问这些受保护资源。

案例 Demo

步骤 1: 获取用户授权

这一步已经在之前的社交登录案例中完成。在用户成功通过 Google 登录后,应用将获得一个访问令牌(Access Token),这个令牌允许应用代表用户访问其在 Google 的受保护资源。

步骤 2: 使用访问令牌访问受保护资源

假设我们想要获取用户的 Google 联系人列表。首先,需要确保在 Google Cloud Platform 上的客户端配置中请求了正确的权限(或作用域),例如 https://www.googleapis.com/auth/contacts.readonly

然后,我们可以使用获取到的访问令牌调用 Google 联系人 API。

@RestController
public class GoogleContactsController {
    @Autowired
    private OAuth2AuthorizedClientService authorizedClientService;
    @GetMapping("/contacts")
    public ResponseEntity<List> getGoogleContacts(@AuthenticationPrincipal OAuth2User oauth2User) {
        OAuth2AuthorizedClient client = authorizedClientService
                .loadAuthorizedClient("google", oauth2User.getName());
        String accessToken = client.getAccessToken().getTokenValue();
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(accessToken);
        HttpEntity<String> entity = new HttpEntity<>("parameters", headers);
        ResponseEntity<List> response = restTemplate.exchange(
                "https://people.googleapis.com/v1/people/me/connections?personFields=names,emailAddresses",
                HttpMethod.GET, entity, List.class);
        return response;
    }
}

在这个控制器中,我们首先从 OAuth2AuthorizedClientService 获取到代表用户会话的 OAuth2AuthorizedClient 对象,这个对象中包含了当前用户的访问令牌。然后,我们设置一个带有 Bearer 认证头的 HTTP 请求,使用 RestTemplate 发送请求到 Google 联系人 API,并返回响应。

注意:为了简化示例,这里直接将 List.class 作为响应类型。在实际应用中,你可能需要定义一个更具体的类来匹配 API 响应的结构。

测试访问受保护资源

启动应用,确保用户已通过 Google 登录,然后访问 /contacts 端点。应用将代表用户调用 Google 联系人 API,并返回用户的联系人列表。

通过这个案例,你可以看到如何在用户授权后,使用 OAuth2 访问令牌来获取用户在第三方服务上的受保护资源。这种能力极大地扩展了应用的功能,使得它们能够为用户提供更加个性化和丰富的服务。

5.1.4 拓展案例 2:实现自定义 OIDC 提供者

实现自定义的 OpenID Connect (OIDC) 提供者涉及到创建一个遵循 OpenID Connect 协议的认证服务器,该服务器能够处理认证请求、发放令牌,并提供用户信息。这个过程比较复杂且超出了简单的示例范围,但我可以概述关键步骤和概念,以及如何使用 Spring Security 5 支持的功能来实现它。

概述

实现自定义 OIDC 提供者通常涉及以下几个关键组件:

  1. 授权服务器:负责处理认证和授权请求,发放 ID 令牌和访问令牌。
  2. 用户信息端点:提供经过认证的客户端访问用户信息的接口。
  3. 客户端注册:允许第三方客户端注册并获取访问授权服务器所需的凭证。

步骤 1: 设置授权服务器

使用 Spring Security OAuth2 Boot 2.x 版本之前,你可以利用其提供的 @EnableAuthorizationServer 注解来快速创建一个授权服务器。但在最新的 Spring Security 5 中,推荐的做法是使用外部授权服务器,或者通过手动配置方式来实现 OIDC 功能。

对于自定义 OIDC 提供者,你需要创建几个关键的端点:

  • 授权端点:处理认证请求,生成授权码。
  • 令牌端点:交换授权码以获取令牌。
  • 用户信息端点:提供用户信息。

步骤 2: 实现用户信息端点

用户信息端点是 OIDC 提供者用来提供用户信息的接口。这通常是一个受保护的 API,只有在提供了有效的访问令牌时才能访问。

@RestController
@RequestMapping("/userinfo")
public class UserInfoController {
    @GetMapping
    public Map<String, Object> userInfo(@AuthenticationPrincipal OidcUser user) {
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put("sub", user.getSubject());
        userInfo.put("name", user.getFullName());
        // 添加更多用户信息
        return userInfo;
    }
}

在这个例子中,我们定义了一个简单的用户信息端点,它返回当前用户的一些基本信息。这些信息通常从 OidcUser 对象中提取,该对象代表了通过 OIDC 认证的用户。

步骤 3: 配置客户端注册

在自定义 OIDC 提供者中,你需要提供一种机制来注册和管理第三方客户端。这通常涉及到存储客户端ID、客户端密钥和重定向URI等信息。

虽然 Spring Security 框架为客户端注册提供了支持,但在自定义 OIDC 提供者的情况下,你可能需要手动管理这些信息,比如使用数据库来存储客户端详情。

注意

由于 Spring Security 5 放弃了内置的授权服务器支持,如果你需要构建完整的自定义 OIDC 提供者,可能需要依赖额外的库或服务,如 Keycloak,或者使用 Spring Security 的低级 API 自己实现相关功能。

这个拓展案例展示了如何开始构建一个自定义 OIDC 提供者的基础框架。实际上,这是一个复杂的过程,需要深入理解 OAuth2 和 OIDC 协议的细节。对于大多数应用来说,使用现成的解决方案(如 Keycloak 或 Auth0)可能是一个更实用和时间效率更高的选择。

5.2 使用 JWT

JSON Web Tokens (JWT) 是一种开放标准(RFC 7519),用于在网络应用环境间安全地传输声明(如认证信息)。JWTs 可以被签名(使用秘密或公钥/私钥对),甚至可以被加密。

5.2.1 基础知识详解

在深入探索 JWT 在现代认证机制中的应用之前,让我们先对 JWT 的基础知识进行更全面的解读。JSON Web Tokens(JWT)为在网络应用间安全地传递声明(claims)提供了一种紧凑且自包含的方式。理解 JWT 的结构和工作原理是关键,这将帮助我们更好地利用它在认证和授权过程中的强大功能。

JWT 结构

JWT 通常由三部分组成,每部分之间用点(.)分隔:

  1. Header(头部):
  • 包含了用于处理令牌的元数据,主要是令牌的类型(typ,通常是 JWT)和所使用的签名或加密算法(alg,例如 HS256、RS256)。
  • 示例:{"alg": "HS256", "typ": "JWT"}
  1. Payload(负载):
  • 包含所要传递的声明,声明是关于实体(通常指用户)和其他数据的陈述。这些声明可以是预定义的、公共的或是私有的。
  • 预定义的声明(Registered claims):如 iss(发行者)、exp(过期时间)、sub(主题)等。
  • 示例:{"sub": "1234567890", "name": "John Doe", "admin": true}
  1. Signature(签名):
  • 通过对 header 和 payload 部分进行编码,然后使用 header 中指定的算法和一个密钥进行签名来生成。
  • 签名的目的是为了验证消息的发送者是谁,以及确保消息在传输过程中未被篡改。

JWT 的优势和用途

  1. 自包含:
  • JWT 自包含了所有必要的信息,使得它无需回数据库查询即可完成认证和授权过程,这对于分布式微服务架构尤其重要。
  1. 跨语言支持:
  • 由于 JWT 是基于 JSON 的,它自然地被各种编程语言支持,这使得 JWT 在多种应用和服务间传递信息成为可能。
  1. 性能优异:
  • JWT 由于其自包含性,减少了需要查询数据库的次数,这对于需要高性能和低延迟的应用来说是一个重要优势。
  1. 易于传输:
  • JWT 由于其紧凑的结构,可以轻松地通过 URL、POST 参数或者在 HTTP 的 Header 中传输。

安全考虑

  • 虽然 JWT 的 Payload 是 Base64 编码的,这意味着它可以被任何人解码并读取,因此不应在 JWT 中包含任何敏感信息,除非它是加密的。
  • 使用 HTTPS 来防止令牌在传输过程中被拦截。
  • 确保 JWT 的有效期适当,以减少令牌被盗用的风险。

通过深入了解 JWT 的基础知识,我们为进一步探索如何在实际项目中应用 JWT 奠定了坚实的基础。JWT 不仅能够提高应用的安全性,还能在保持良好用户体验的同时简化认证和授权流程。

5.2.2 重点案例:使用 JWT 实现无状态认证

在这个案例中,我们将演示如何在 Spring Boot 应用中使用 JWT 来实现无状态认证机制。这种方式允许应用在无需保存用户会话状态的情况下,安全地识别和授权请求。

案例 Demo

假设我们正在开发一个简单的博客系统,其中用户需要登录后才能创建和编辑文章。以下是实现步骤:

步骤 1: 添加 JWT 依赖

首先,向 pom.xml 文件中添加 JWT 支持库的依赖,如 jjwt

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

步骤 2: 创建 JWT 工具类

接下来,创建一个 JwtUtil 类来生成和验证 JWT。

@Component
public class JwtUtil {
    private String secretKey = "secret"; // 在实际应用中,应从配置文件或环境变量中获取
    private long validityInMilliseconds = 3600000; // 1h
    public String createToken(String username) {
        Claims claims = Jwts.claims().setSubject(username);
        Date now = new Date();
        Date validity = new Date(now.getTime() + validityInMilliseconds);
        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(validity)
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
    }
    public String getUsername(String token) {
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
    }
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            throw new IllegalStateException("Expired or invalid JWT token");
        }
    }
}

步骤 3: 实现 JWT 请求过滤器

创建 JwtTokenFilter 类来验证每个请求的 JWT。

public class JwtTokenFilter extends OncePerRequestFilter {
    @Autowired
    private JwtUtil jwtUtil;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String token = request.getHeader("Authorization");
        if (token != null && jwtUtil.validateToken(token)) {
            Authentication auth = new UsernamePasswordAuthenticationToken(jwtUtil.getUsername(token), null, Collections.emptyList());
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        filterChain.doFilter(request, response);
    }
}

步骤 4: 配置 Spring Security

配置 Spring Security 以使用我们的 JWT 过滤器。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtTokenFilter jwtTokenFilter;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .httpBasic().disable()
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
            .and()
                .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

步骤 5: 实现登录端点

最后,实现一个简单的登录端点,用于验证用户凭据并返回 JWT。

@RestController
public class AuthenticationController {
    @Autowired
    private JwtUtil jwtUtil;
    // 假设有一个自定义的方法来验证用户凭据
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestParam String username, @RequestParam String password) {
        // 验证用户凭据(省略)
        String token = jwtUtil.createToken(username);
        return ResponseEntity.ok(token);
    }
}

通过这个案例,你可以看到使用 JWT 实现无状态认证的基本步骤。这种方法不仅提高了应用的安全性,还因其无状态特性,使得应用易于扩展。


第5章 Spring Security 的高级认证技术(2024 最新版)(下)+https://developer.aliyun.com/article/1487150

目录
相关文章
|
1月前
|
安全 Java 数据库
安全无忧!在 Spring Boot 3.3 中轻松实现 TOTP 双因素认证
【10月更文挑战第8天】在现代应用程序开发中,安全性是一个不可忽视的重要环节。随着技术的发展,双因素认证(2FA)已经成为增强应用安全性的重要手段之一。本文将详细介绍如何在 Spring Boot 3.3 中实现基于时间的一次性密码(TOTP)双因素认证,让你的应用安全无忧。
81 5
|
3月前
|
SQL Java 测试技术
在Spring boot中 使用JWT和过滤器实现登录认证
在Spring boot中 使用JWT和过滤器实现登录认证
225 0
|
3月前
|
开发框架 负载均衡 Java
当热门技术负载均衡遇上 Spring Boot,开发者的梦想与挑战在此碰撞,你准备好了吗?
【8月更文挑战第29天】在互联网应用开发中,负载均衡至关重要,可避免单服务器过载导致性能下降或崩溃。Spring Boot 作为流行框架,提供了强大的负载均衡支持,通过合理分配请求至多台服务器,提升系统可用性与可靠性,优化资源利用。本文通过示例展示了如何在 Spring Boot 中配置负载均衡,包括添加依赖、创建负载均衡的 `RestTemplate` 实例及服务接口调用等步骤,帮助开发者构建高效、稳定的应用。随着业务扩展,掌握负载均衡技术将愈发关键。
75 6
|
1月前
|
存储 Java API
简单两步,Spring Boot 写死的定时任务也能动态设置:技术干货分享
【10月更文挑战第4天】在Spring Boot开发中,定时任务通常通过@Scheduled注解来实现,这种方式简单直接,但存在一个显著的限制:任务的执行时间或频率在编译时就已经确定,无法在运行时动态调整。然而,在实际工作中,我们往往需要根据业务需求或外部条件的变化来动态调整定时任务的执行计划。本文将分享一个简单两步的解决方案,让你的Spring Boot应用中的定时任务也能动态设置,从而满足更灵活的业务需求。
84 4
|
2月前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
39 1
|
2月前
|
前端开发 安全 Java
技术进阶:使用Spring MVC构建适应未来的响应式Web应用
【9月更文挑战第2天】随着移动设备的普及,响应式设计至关重要。Spring MVC作为强大的Java Web框架,助力开发者创建适应多屏的应用。本文推荐使用Thymeleaf整合视图,通过简洁的HTML代码提高前端灵活性;采用`@ResponseBody`与`Callable`实现异步处理,优化应用响应速度;运用`@ControllerAdvice`统一异常管理,保持代码整洁;借助Jackson简化JSON处理;利用Spring Security增强安全性;并强调测试的重要性。遵循这些实践,将大幅提升开发效率和应用质量。
64 7
|
3月前
|
缓存 NoSQL Java
SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)
Spring Cache 是 Spring 提供的简易缓存方案,支持本地与 Redis 缓存。通过添加 `spring-boot-starter-data-redis` 和 `spring-boot-starter-cache` 依赖,并使用 `@EnableCaching` 开启缓存功能。JetCache 由阿里开源,功能更丰富,支持多级缓存和异步 API,通过引入 `jetcache-starter-redis` 依赖并配置 YAML 文件启用。Layering Cache 则提供分层缓存机制,需引入 `layering-cache-starter` 依赖并使用特定注解实现缓存逻辑。
974 1
SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)
|
3月前
|
Java Spring
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
|
2月前
|
JavaScript 前端开发 Java
【颠覆传统】Spring框架如何用WebSocket技术重塑实时通信格局?揭秘背后的故事与技术细节!
【9月更文挑战第4天】随着Web应用对实时交互需求的增长,传统的HTTP模型已无法满足现代应用的要求,特别是在需要持续、双向通信的场景下。WebSocket协议由此诞生,提供全双工通信渠道,使服务器与客户端能实时互发消息。作为Java开发中最受欢迎的框架之一,Spring通过其WebSocket模块支持这一协议,简化了WebSocket在Spring应用中的集成。
56 0
|
3月前
|
前端开发 Java Spring
Spring与Angular/React/Vue:当后端大佬遇上前端三杰,会擦出怎样的火花?一场技术的盛宴,你准备好了吗?
【8月更文挑战第31天】Spring框架与Angular、React、Vue等前端框架的集成是现代Web应用开发的核心。通过RESTful API、WebSocket及GraphQL等方式,Spring能与前端框架高效互动,提供快速且功能丰富的应用。RESTful API简单有效,适用于基本数据交互;WebSocket支持实时通信,适合聊天应用和数据监控;GraphQL则提供更精确的数据查询能力。开发者可根据需求选择合适的集成方式,提升用户体验和应用功能。
92 0