编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

SringSecurity认证——SpringSecurity集成JWT保姆级教程

wxchong 2025-04-24 09:29:25 开源技术 11 ℃ 0 评论

Spring Security是一个功能强大且高度可定制的安全框架,用于保护基于Spring的应用程序。它提供了多种认证方式,以满足不同场景下的安全需求。以下是对Spring Security主要认证方式的概述:

  1. 表单登录(Form Login):这是最常见的认证方式,用户通过浏览器访问受保护的资源时,会被重定向到一个登录页面。用户在登录页面输入用户名和密码,然后提交表单。Spring Security拦截表单提交请求,验证用户名和密码,成功后创建安全上下文,允许用户访问受保护的资源。
  2. HTTP基本认证(HTTP Basic Authentication):一种简单的认证机制,客户端在请求头中包含用户名和密码(Base64编码)。通常用于测试或API认证,不适用于交互式Web应用,因为用户名和密码会在每次请求中暴露。
  3. HTTP摘要认证(HTTP Digest Authentication):对HTTP基本认证的一种改进,使用MD5摘要算法对用户名、密码和请求细节进行加密。提供了比基本认证更高的安全性,但仍需要客户端存储密码。
  4. 记住我(Remember Me):允许用户在登录时选择“记住我”选项,以便在后续访问时无需重新输入用户名和密码。通常通过设置一个持久的cookie来实现,该cookie包含用户的唯一标识符,而不是密码。
  5. OAuth2/OpenID Connect:OAuth2是一个用于授权的行业标准协议,允许用户授权第三方应用访问他们在其他服务上的信息,而无需将用户名和密码提供给第三方。OpenID Connect是OAuth2的扩展,用于身份验证,提供了标准的方式来验证用户的身份并获取关于用户的信息。
  6. CAS(Central Authentication Service):一种企业级的单点登录解决方案,允许用户在一个地方登录,然后访问多个应用而无需重新登录。CAS服务器负责处理认证请求,并将认证结果返回给客户端应用。
  7. JWT(JSON Web Tokens):一种用于双方之间安全传输信息的简洁的、URL安全的令牌标准。JWT通常用于身份验证和信息交换,因为它们可以在客户端和服务器之间安全地传输用户信息。
  8. LDAP(Lightweight Directory Access Protocol):一种用于访问和维护分布式目录信息服务的协议。LDAP常用于存储用户和密码信息,Spring Security提供了对LDAP认证的支持。
  9. 自定义认证:Spring Security允许开发者实现自定义的认证逻辑,以满足特定的安全需求。这通常涉及实现AuthenticationProvider接口,并在Spring Security配置中注册该提供程序。

这些认证方式可以单独使用,也可以组合使用,以构建满足特定安全需求的多因素认证系统。在选择认证方式时,应考虑应用的安全性需求、用户体验以及现有基础架构的兼容性,其中JWT(JSON Web Tokens)是使用较多的一种,今天就SpringSecurity集成JWT展开完整概述如下:

JWT (JSON Web Token) 简述

JWT 是一种开放标准 (RFC 7519),用于在网络应用环境间安全地传输信息。它是一种紧凑且自包含的方式,用于在各方之间传递声明(claims),这些声明通常是为了验证用户的身份,也可以被用来交换其他类型的信息。

一个典型的JWT由三部分组成,它们通过点号(.)分隔:

  1. 头部 (Header):包含了令牌的元数据,如使用的签名算法。
  2. 载荷 (Payload):包含了声明,即要传达的数据。这些声明可以是预定义的(如iss、exp等)或自定义的。
  3. 签名 (Signature):用于验证消息在传输过程中没有被更改,并且,对于私有密钥签名的令牌来说,还可以确认发送方的身份。
  4. 示例
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Spring Security 集成 JWT 的作用和意义

  1. 无状态认证:JWT使得认证过程无状态化,服务器端不需要存储会话信息,这有助于提高系统的可扩展性,特别是对于微服务架构或分布式系统。
  2. 跨域资源共享 (CORS):客户端可以在每次请求时携带JWT作为身份验证信息,无需依赖于服务器端的Session,这对单页应用(SPA)或者前后端分离的应用尤为重要。
  3. 简化移动端开发:移动端应用可以通过存储JWT来保持用户的登录状态,简化了移动端的安全实现。
  4. 增强安全性:JWT可以包含签名以确保内容未被篡改,并可以设置过期时间来限制其有效期限。
  5. 自包含:JWT令牌中包含了所有必要的用户信息,减少了对数据库查询的需求,提升了性能。
  6. 易于传播:JWT可以通过HTTP头部轻松传播,方便API间的通信。
  7. 支持多种平台:几乎所有的编程语言都有相应的库来处理JWT,促进了不同技术栈之间的互操作性。
  8. 细粒度访问控制:可以在JWT中编码权限信息,允许更精细的访问控制决策。
  9. 提高用户体验:通过记住我功能或刷新令牌机制,可以让用户长时间保持登录状态。

Spring Security 集成 JWT 的主要步骤

  1. 添加必要的依赖:确保项目中包含Spring Security和JWT相关的库。
  2. 配置安全设置:创建或修改SecurityConfig类,禁用Session管理并启用无状态认证。
  3. 实现JWT工具类:创建一个工具类来处理JWT的生成、解析和验证。
  4. 创建JWT过滤器:实现一个自定义的JwtTokenFilter来拦截请求并解析JWT。
  5. 配置用户详情服务:实现UserDetailsService接口来加载用户特定的数据。
  6. 创建认证管理器:配置AuthenticationManager以支持用户名密码认证。
  7. 登录控制器:创建一个控制器来处理登录请求,并返回JWT给客户端。
  8. 保护API端点:使用Spring Security配置来保护需要认证的API端点。

完整代码示例

1. 添加依赖 (Maven)

<dependencies>
    <!-- Spring Boot Starter Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!-- JJWT for JWT token generation and validation -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>{版本号根据自己的spring框架版本进行选择}</version>
    </dependency>

    <!-- Other dependencies like spring-boot-starter-web, etc. -->
</dependencies>

2. 自定义SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable() // 禁用CSRF防护,对于REST API来说通常不需要
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 禁用Session
            .and()
            .authorizeRequests()
                .antMatchers("/auth/login").permitAll() // 登录路径无需认证
                .anyRequest().authenticated() // 其他所有请求都需要认证
            .and()
            .apply(new JwtConfigurer(jwtTokenProvider)); // /**关键代码**/应用JWT配置
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

3. 添加JWT工具类 (JwtTokenProvider)

@Component
public class JwtTokenProvider {

    private final String secret = "YourSecretKey"; // 秘钥用于签名JWT
    private final long validityInMilliseconds = 3600000; // 令牌有效期(1小时)

    @Autowired
    private UserDetailsService userDetailsService;

    public String createToken(String username, List<String> roles) {
        Claims claims = Jwts.claims().setSubject(username);
        claims.put("roles", roles);

        Date now = new Date();
        Date validity = new Date(now.getTime() + validityInMilliseconds);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(validity)
                .signWith(SignatureAlgorithm.HS256, secret.getBytes())
                .compact();
    }

    public Authentication getAuthentication(String token) {
        UserDetails userDetails = this.userDetailsService.loadUserByUsername(getUsername(token));
        return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
    }

    public String getUsername(String token) {
        return Jwts.parser()
                .setSigningKey(secret.getBytes())
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }

    public boolean validateToken(String token) {
        try {
            Jws<Claims> claims = Jwts.parser()
                    .setSigningKey(secret.getBytes())
                    .parseClaimsJws(token);

            if (claims.getBody().getExpiration().before(new Date())) {
                return false;
            }
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            throw new InvalidJwtAuthenticationException("Expired or invalid JWT token");
        }
    }
}

4. 添加JWT过滤器 (JwtTokenFilter)

public class JwtTokenFilter extends OncePerRequestFilter {

    private final JwtTokenProvider jwtTokenProvider;

    public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String token = resolveToken(request);
        try {
            if (token != null && jwtTokenProvider.validateToken(token)) {
                Authentication auth = jwtTokenProvider.getAuthentication(token);
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        } catch (InvalidJwtAuthenticationException ex) {
            // Handle expired or invalid JWT token
        }

        filterChain.doFilter(request, response);
    }

    private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

5. 配置JWT (JwtConfigurer)

public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    private final JwtTokenProvider jwtTokenProvider;

    public JwtConfigurer(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider);
        http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

6. 添加用户详情服务接口自定义实现类 (CustomUserDetailsService)

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //自己根据实际情况实现查询数据库获取用户信息,并构建用户信息
      	User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));

        return org.springframework.security.core.userdetails.User.builder()
                .username(user.getUsername())
                .password(user.getPassword())
                .authorities(user.getRoles().toArray(new SimpleGrantedAuthority[0]))
                .build();
    }
}

7. 创建认证管理器 (AuthConfiguration)

@Configuration
public class AuthConfiguration {

    @Autowired
    private DataSource dataSource;

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

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
}

8. 登录控制器 (AuthController)

@RestController
@RequestMapping("/auth")
public class AuthController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @PostMapping("/login")
    public ResponseEntity<?> authenticate(@RequestBody LoginRequest loginRequest) {
        try {
            Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                    loginRequest.getUsername(),
                    loginRequest.getPassword()
                )
            );

            SecurityContextHolder.getContext().setAuthentication(authentication);
            String jwt = jwtTokenProvider.createToken(authentication.getName(), getRolesFromAuthentication(authentication));

            return ResponseEntity.ok(new JwtResponse(jwt));
        } catch (AuthenticationException e) {
            throw new BadCredentialsException("Invalid username/password supplied");
        }
    }

    private List<String> getRolesFromAuthentication(Authentication authentication) {
        return authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());
    }
}

// DTOs for requests and responses
class LoginRequest {
    private String username;
    private String password;
    // getters and setters
}

class JwtResponse {
    private String jwt;
    // constructor, getters and setters
}

以上代码提供了一个完整的框架,用于将JWT集成到Spring Security中。请注意,在实际应用中,你可能需要根据具体需求调整代码,比如使用更复杂的密钥管理、刷新令牌机制、更好的异常处理等。此外,确保遵循最佳实践来保护你的应用程序,如HTTPS协议的使用。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表