跳转至

Java Spring Boot API 认证示例解析

简介

在当今的软件开发领域,API 安全性至关重要。Java Spring Boot 提供了强大且灵活的工具来实现 API 认证,确保只有经过授权的用户或系统能够访问 API 端点。本文将深入探讨 Java Spring Boot API 认证示例相关的基础概念、使用方法、常见实践以及最佳实践,帮助开发者更好地构建安全可靠的 API 服务。

目录

  1. 基础概念
  2. 使用方法
    • 引入依赖
    • 配置认证相关类
    • 创建认证逻辑
  3. 常见实践
    • 基于用户名和密码的认证
    • 使用 JWT 进行认证
  4. 最佳实践
    • 安全存储密码
    • 防止暴力破解
    • 定期更新认证策略
  5. 小结
  6. 参考资料

基础概念

认证(Authentication)

认证是确认用户或系统身份的过程。在 API 环境中,它回答“你是谁?”的问题。常见的认证方式包括用户名/密码组合、令牌(Token)等。

授权(Authorization)

授权决定已认证的用户或系统被允许执行哪些操作。它回答“你能做什么?”的问题。例如,一个用户可能被授权读取某些数据,但不允许修改。

安全上下文(Security Context)

安全上下文存储有关当前已认证用户的信息。Spring Security 使用安全上下文来管理用户的认证状态和相关权限。

使用方法

引入依赖

pom.xml 文件中添加 Spring Security 依赖:

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

配置认证相关类

创建一个配置类,例如 WebSecurityConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
           .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
           .logout()
                .permitAll();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

创建认证逻辑

创建一个简单的用户服务,例如 UserService.java

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.stereotype.Service;

@Service
public class UserService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 这里简单示例,实际应用中从数据库等数据源获取用户信息
        if ("user".equals(username)) {
            return User.withUsername("user")
                   .password("$2a$10$9dXf3Qy7Y399W89yL76c4u66Jd09e696l76X69f7z3z5976X99") // 加密后的密码
                   .roles("USER")
                   .build();
        } else {
            throw new UsernameNotFoundException("User not found");
        }
    }
}

常见实践

基于用户名和密码的认证

上述配置和代码展示了基本的用户名/密码认证。用户访问受保护的 API 端点时,系统会提示用户输入用户名和密码,通过 UserDetailsService 验证用户信息。

使用 JWT 进行认证

  1. 引入 JWT 依赖
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
  1. 创建 JWT 工具类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Component
public class JwtUtil {

    private static final String SECRET_KEY = "your-secret-key";

    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    private Claims extractAllClaims(String token) {
        return Jwts.parserBuilder().setSigningKey(SECRET_KEY).build()
               .parseClaimsJws(token).getBody();
    }

    private Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }

    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
               .setClaims(claims)
               .setSubject(subject)
               .setIssuedAt(new Date(System.currentTimeMillis()))
               .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
               .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
               .compact();
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername()) &&!isTokenExpired(token));
    }
}
  1. 配置 JWT 认证过滤器
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    @Autowired
    private UserService userService;

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        final String authorizationHeader = request.getHeader("Authorization");
        String username = null;
        String jwt = null;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            try {
                username = jwtUtil.extractUsername(jwt);
            } catch (ExpiredJwtException e) {
                // 处理过期令牌
            }
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userService.loadUserByUsername(username);
            if (jwtUtil.validateToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
}

最佳实践

安全存储密码

使用强大的密码哈希算法,如 BCrypt,避免存储明文密码。Spring Security 提供了 PasswordEncoder 接口来处理密码哈希和验证。

防止暴力破解

实施措施防止暴力破解密码,例如设置登录尝试次数限制、使用验证码或实施账户锁定策略。

定期更新认证策略

随着安全威胁的演变,定期审查和更新认证策略。这包括更新加密算法、增强密码复杂度要求等。

小结

本文详细介绍了 Java Spring Boot API 认证的基础概念、使用方法、常见实践和最佳实践。通过示例代码展示了基于用户名/密码和 JWT 的认证实现。开发者可以根据项目需求选择合适的认证方式,并遵循最佳实践来确保 API 的安全性。

参考资料