第1章 Spring Security 概述(2024 最新版)(上)+https://developer.aliyun.com/article/1487138
1.2.3 拓展案例 1:表单登录与数据库用户存储
在这个案例中,我们将演示如何结合表单登录和数据库用户存储在 Spring Boot 应用中实现用户认证。这将涉及从数据库中加载用户信息,并根据这些信息进行认证。
案例 Demo
假设我们有一个应用程序,其中用户信息存储在数据库中。我们需要实现一个表单登录页面,用户可以使用其数据库中的凭据进行登录。
实现步骤:
- 添加依赖:
在pom.xml
中添加spring-boot-starter-data-jpa
和数据库相关的依赖,例如spring-boot-starter-data-jpa
和h2
(作为示例数据库)。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
- 配置数据源:
在application.properties
中配置数据库连接信息。对于 H2 数据库,可以使用内存模式。
spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
- 创建用户模型和仓库:
定义一个用户实体类和一个继承JpaRepository
的接口。
import javax.persistence.Entity; import javax.persistence.Id; @Entity public class User { @Id private String username; private String password; // 省略 getter 和 setter } import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, String> { }
- 实现 UserDetailsService:
实现UserDetailsService
接口,从数据库中加载用户信息。
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.beans.factory.annotation.Autowired; public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findById(username) .orElseThrow(() -> new UsernameNotFoundException("User not found")); return new User(user.getUsername(), user.getPassword(), new ArrayList<>()); } }
- 配置 Spring Security:
配置WebSecurityConfigurerAdapter
,使用自定义的UserDetailsService
和合适的密码编码器。
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; @Autowired private CustomUserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(new BCryptPasswordEncoder()); }
- 创建登录表单:
创建一个简单的 HTML 登录表单。 - 运行和测试:
运行应用程序,并通过访问http://localhost:8080/login
来测试登录功能。
通过这个案例,您可以了解如何结合表单登录和数据库用户存储来实现认证功能。这在实际开发中非常常见,为应用程序提供了更大的灵活性和安全性。
1.2.4 拓展案例 2:整合 OAuth2
在这个案例中,我们将演示如何在 Spring Boot 应用中整合 OAuth2,实现第三方登录。我们将以 Google 作为 OAuth2 提供商的例子来实现这一功能。
案例 Demo
假设我们有一个 Spring Boot 应用,需要让用户可以通过他们的 Google 账户登录。
实现步骤:
- 添加 OAuth2 客户端依赖:
在pom.xml
中添加spring-boot-starter-oauth2-client
依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency>
- 配置 OAuth2 客户端:
在application.properties
或application.yml
中配置 Google 的 OAuth2 客户端信息。你需要替换client-id
和client-secret
为你在 Google API Console 中获取的实际值。
spring.security.oauth2.client.registration.google.client-id=your-google-client-id spring.security.oauth2.client.registration.google.client-secret=your-google-client-secret spring.security.oauth2.client.registration.google.scope=profile,email
- 安全配置:
修改你的SecurityConfig
类,以支持 OAuth2 登录。
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2Login(); }
- 这里我们配置了所有请求都需要认证,并且启用了 OAuth2 登录。
- 获取用户信息:
创建一个控制器来处理登录后的用户信息。
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class UserController { @GetMapping("/user") public String user(OAuth2AuthenticationToken token) { // 这里可以获取到用户信息 // token.getPrincipal().getAttributes(); return "user"; } }
- 运行和测试:
启动应用程序,并尝试通过访问http://localhost:8080/user
使用 Google 账户登录。检查用户信息是否正确获取。
通过这个案例,您可以看到在 Spring Boot 应用中整合 OAuth2 并不复杂。这使得应用可以利用第三方服务提供商的强大认证机制,同时提高了用户体验。
1.3 Spring Security 的发展历程
1.3.1 基础知识详解
Spring Security 的演变
- 从 Acegi 到 Spring Security:
- Acegi Security: 最初作为一个独立的安全框架,Acegi Security 为 Spring 应用提供了强大的安全性支持。
- Spring Security 2.0: Acegi Security 被正式纳入 Spring 框架,并更名为 Spring Security。这个版本主要专注于简化配置,并引入了新的命名空间和注解驱动的安全配置。
- 功能增强和改进:
- Spring Security 3.0: 引入了多项新特性,包括对注解的支持,使得开发者可以更灵活地配置安全策略。
- Spring Security 4.0: 进一步增强了对 Java 配置的支持,减少了对 XML 的依赖。同时,增强了对 REST API 的安全支持。
- 适应现代应用的需求:
- Spring Security 5.0: 标志着对现代应用安全的重大更新,包括对 OAuth2 和 OpenID Connect 的支持,以及对 Reactive 应用的安全支持。
核心特性的演进
- 认证和授权: 从基本的表单登录和 HTTP 基本认证到支持 LDAP、OAuth2、OpenID Connect 和 JWT 等多样化认证方式。
- 安全性控制的细化: 引入方法级别的安全控制,允许在不同方法上应用不同的安全策略。
- 对 RESTful API 的支持: 随着 RESTful API 在现代应用中的普及,Spring Security 加强了对无状态 REST API 的安全性支持,例如通过 JWT 实现无状态认证。
- 易用性和灵活性: 通过提供更多的 Java 配置选项,减少了对 XML 的依赖,使得配置更加灵活和易于理解。
- 响应式编程的支持: 为适应 Spring WebFlux 和响应式编程模型的兴起,Spring Security 5 引入了对响应式应用的支持。
通过理解 Spring Security 的发展历程和其核心特性的演进,开发者可以更好地把握其使用方式和最佳实践,从而为应用程序提供强大而灵活的安全解决方案。
1.3.2 主要案例:表达式驱动的安全性
在这个案例中,我们将展示如何在 Spring Boot 应用中使用 Spring Security 的表达式驱动安全性,以实现更细粒度的访问控制。
案例 Demo
假设我们正在开发一个网上书店应用,需要实现以下安全需求:只有管理员可以添加书籍,而注册用户可以浏览书籍。
实现步骤:
- 创建 Spring Boot 应用程序:
使用 Spring Initializr 创建一个新的 Spring Boot 项目,并添加spring-boot-starter-web
和spring-boot-starter-security
依赖。 - 配置安全性:
创建一个继承自WebSecurityConfigurerAdapter
的配置类并开启方法级安全。
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { // 配置用户认证逻辑 }
- 设置用户角色和权限:
在configure(AuthenticationManagerBuilder auth)
方法中设置用户角色和权限。
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("admin").password("{noop}admin123").roles("ADMIN") .and() .withUser("user").password("{noop}user123").roles("USER"); }
- 使用安全表达式:
在你的服务类中使用安全注解来限制方法访问。
import org.springframework.security.access.prepost.PreAuthorize; @Service public class BookService { @PreAuthorize("hasRole('ADMIN')") public void addBook(Book book) { // 逻辑添加书籍 } @PreAuthorize("hasAnyRole('USER', 'ADMIN')") public List<Book> listBooks() { // 返回书籍列表 } }
- 创建控制器:
实现一个控制器,调用BookService
的方法。
@RestController public class BookController { @Autowired private BookService bookService; @PostMapping("/books") public ResponseEntity<String> addBook(@RequestBody Book book) { bookService.addBook(book); return ResponseEntity.ok("Book added successfully"); } @GetMapping("/books") public ResponseEntity<List<Book>> getBooks() { return ResponseEntity.ok(bookService.listBooks()); } }
- 测试安全性:
运行应用程序,并使用不同角色的用户尝试访问/books
的 GET 和 POST 方法。
通过这个案例,您可以看到如何在实际项目中使用 Spring Security 的表达式驱动安全性来实现基于角色的细粒度访问控制。这种方法为应用程序提供了强大的安全性和灵活性。
1.3.3 拓展案例 1:OAuth2 集成
在这个案例中,我们将展示如何在 Spring Boot 应用中整合 OAuth2,以便用户可以使用外部服务提供商进行认证。我们以集成 GitHub 作为 OAuth2 提供商为例。
案例 Demo
假设我们有一个 Spring Boot 应用,我们希望用户能够使用他们的 GitHub 账户登录。
实现步骤:
- 添加 OAuth2 客户端依赖:
在pom.xml
中添加spring-boot-starter-oauth2-client
依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency>
- 配置 OAuth2 客户端:
在application.properties
或application.yml
中配置 GitHub 的 OAuth2 客户端信息。你需要在 GitHub 创建 OAuth 应用以获取client-id
和client-secret
。
spring.security.oauth2.client.registration.github.client-id=your-github-client-id spring.security.oauth2.client.registration.github.client-secret=your-github-client-secret spring.security.oauth2.client.registration.github.scope=user:email
- 安全配置:
修改你的SecurityConfig
类,以支持 OAuth2 登录。
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2Login(); }
- 这里我们配置了所有请求都需要认证,并且启用了 OAuth2 登录。
- 创建用户信息端点:
创建一个控制器来显示登录用户的信息。
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @GetMapping("/user") public Map<String, Object> user(@AuthenticationPrincipal OAuth2User principal) { return Collections.singletonMap("name", principal.getAttribute("name")); } }
- 运行和测试:
启动应用程序,并尝试访问http://localhost:8080/user
。你应该能够看到一个重定向到 GitHub 的登录页面。使用你的 GitHub 凭据登录后,应用将显示你的 GitHub 用户名。
通过这个案例,你可以了解到如何在 Spring Boot 应用中实现 OAuth2 集成,从而允许用户使用外部服务提供商的账户进行认证,这对于提升用户体验和应用安全性都非常有帮助。
1.3.4 拓展案例 2:JWT 认证
在这个案例中,我们将演示如何在 Spring Boot 应用中使用 JSON Web Tokens (JWT) 实现无状态认证。这对于保护 REST API 尤其重要。
案例 Demo
假设我们正在开发一个提供 REST API 的应用程序,我们希望通过 JWT 来认证和授权用户。
实现步骤:
- 添加 JWT 依赖:
在pom.xml
中添加处理 JWT 的依赖库,例如java-jwt
。
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.3</version> </dependency>
- 实现 JWT Token 服务:
创建一个服务类来生成和验证 JWT。
import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import java.util.Date; public class JwtTokenService { private static final String SECRET = "YourSecretKey"; public String generateToken(String username) { return JWT.create() .withSubject(username) .withExpiresAt(new Date(System.currentTimeMillis() + 86400000)) // 1 day .sign(Algorithm.HMAC256(SECRET)); } public String validateTokenAndGetUsername(String token) { return JWT.require(Algorithm.HMAC256(SECRET)) .build() .verify(token) .getSubject(); } }
- 创建 JWT 过滤器:
创建一个 JWT 过滤器来拦截请求并验证 JWT。
import javax.servlet.FilterChain; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; public class JwtFilter extends OncePerRequestFilter { private JwtTokenService jwtTokenService = new JwtTokenService(); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); String username = jwtTokenService.validateTokenAndGetUsername(token); if (username != null) { UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null, null); SecurityContextHolder.getContext().setAuthentication(auth); } } filterChain.doFilter(request, response); } }
- 配置 Spring Security:
在WebSecurityConfigurerAdapter
中配置 JWT 过滤器。
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .addFilter(new JwtFilter()) .authorizeRequests() .antMatchers("/api/protected").authenticated() .antMatchers("/api/public").permitAll(); }
- 创建测试端点:
创建一些测试用的 RESTful 端点。
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { @GetMapping("/api/protected") public String protectedHello() { return "Hello from protected API"; } @GetMapping("/api/public") public String publicHello() { return "Hello from public API"; } }
- 运行和测试:
启动应用程序,并使用生成的 JWT 尝试访问/api/protected
。没有有效 JWT 的请求应该被拒绝。
通过这个案例,您可以看到如何在 Spring Boot 应用中实现基于 JWT 的认证。JWT 认证提供了一种有效的方式来保护 REST API,确保只有拥有有效令牌的用户才能访问受保护的资源。