springboot怎么集成JWT实现身份认证

发布时间:2023-05-08 14:27:09 作者:zzz
来源:亿速云 阅读:76

Spring Boot 怎么集成 JWT 实现身份认证

目录

  1. 引言
  2. JWT 简介
  3. Spring Boot 集成 JWT 的准备工作
  4. JWT 的生成与解析
  5. Spring Security 集成 JWT
  6. JWT 在 Spring Boot 中的应用
  7. JWT 的安全性考虑
  8. JWT 的优缺点
  9. 总结
  10. 参考资料

引言

在现代的 Web 应用中,身份认证是一个非常重要的环节。传统的基于 Session 的认证方式虽然简单易用,但在分布式系统中存在一些问题,比如 Session 的存储和管理。为了解决这些问题,JWT(JSON Web Token)应运而生。JWT 是一种轻量级的认证机制,它可以在客户端和服务器之间安全地传输信息,并且不需要在服务器端存储 Session。

本文将详细介绍如何在 Spring Boot 中集成 JWT 实现身份认证。我们将从 JWT 的基本概念开始,逐步讲解如何在 Spring Boot 项目中生成和解析 JWT,并将其与 Spring Security 集成,最终实现一个完整的身份认证系统。

JWT 简介

JWT 的结构

JWT 由三部分组成,分别是 Header、Payload 和 Signature。这三部分通过 . 连接在一起,形成一个完整的 JWT。

JWT 的工作原理

JWT 的工作原理可以简单概括为以下几个步骤:

  1. 用户登录:用户通过提供用户名和密码进行登录。
  2. 生成 JWT:服务器验证用户凭证后,生成一个 JWT 并返回给客户端。
  3. 客户端存储 JWT:客户端将 JWT 存储在本地(通常是 localStorage 或 cookie)。
  4. 发送请求:客户端在每次请求时,将 JWT 放在请求头中发送给服务器。
  5. 验证 JWT:服务器收到请求后,验证 JWT 的有效性,并根据 JWT 中的信息进行授权。

Spring Boot 集成 JWT 的准备工作

创建 Spring Boot 项目

首先,我们需要创建一个 Spring Boot 项目。可以使用 Spring Initializr 快速生成一个项目骨架。选择以下依赖:

添加依赖

pom.xml 中添加以下依赖:

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

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

    <!-- Spring Boot Starter Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- H2 Database -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>

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

    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
</dependencies>

JWT 的生成与解析

生成 JWT

在生成 JWT 之前,我们需要定义一个工具类来处理 JWT 的生成和解析。以下是一个简单的 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 String SECRET_KEY = "secret";

    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.parser().setSigningKey(SECRET_KEY).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));
    }
}

解析 JWT

在上面的工具类中,我们已经实现了 JWT 的解析功能。通过 extractAllClaims 方法,我们可以从 JWT 中提取所有的声明。extractUsernameextractExpiration 方法分别用于提取用户名和过期时间。

Spring Security 集成 JWT

配置 Spring Security

接下来,我们需要配置 Spring Security 以支持 JWT 认证。首先,创建一个配置类 SecurityConfig

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService myUserDetailsService;

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

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

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable()
                .authorizeRequests().antMatchers("/authenticate").permitAll()
                .anyRequest().authenticated().and()
                .exceptionHandling().and().sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

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

import java.util.ArrayList;

@Service
public class MyUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return new User("foo", "foo", new ArrayList<>());
    }
}

实现 JWT 过滤器

为了实现 JWT 认证,我们需要创建一个过滤器来拦截请求并验证 JWT。以下是一个简单的 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 MyUserDetailsService userDetailsService;

    @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);
            username = jwtUtil.extractUsername(jwt);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

            UserDetails userDetails = this.userDetailsService.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);
    }
}

JWT 在 Spring Boot 中的应用

用户登录与 JWT 生成

在用户登录时,我们需要生成一个 JWT 并返回给客户端。以下是一个简单的登录控制器:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;

@RestController
public class AuthenticationController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private MyUserDetailsService userDetailsService;

    @Autowired
    private JwtUtil jwtUtil;

    @RequestMapping(value = "/authenticate", method = RequestMethod.POST)
    public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {

        try {
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
            );
        } catch (BadCredentialsException e) {
            throw new Exception("Incorrect username or password", e);
        }

        final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());

        final String jwt = jwtUtil.generateToken(userDetails);

        return ResponseEntity.ok(new AuthenticationResponse(jwt));
    }
}

保护 API 端点

在 Spring Security 配置中,我们已经将所有请求设置为需要认证。现在,我们可以创建一个受保护的 API 端点来测试 JWT 认证:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @RequestMapping({ "/hello" })
    public String firstPage() {
        return "Hello World";
    }
}

刷新 JWT

在某些情况下,我们可能需要刷新 JWT。可以通过以下方式实现:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;

@RestController
public class RefreshTokenController {

    @Autowired
    private MyUserDetailsService userDetailsService;

    @Autowired
    private JwtUtil jwtUtil;

    @RequestMapping(value = "/refresh", method = RequestMethod.POST)
    public ResponseEntity<?> refreshAuthenticationToken(@RequestHeader("Authorization") String token) throws Exception {

        String username = jwtUtil.extractUsername(token.substring(7));

        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        if (jwtUtil.validateToken(token.substring(7), userDetails)) {
            String newToken = jwtUtil.generateToken(userDetails);
            return ResponseEntity.ok(new AuthenticationResponse(newToken));
        } else {
            throw new Exception("Invalid token");
        }
    }
}

JWT 的安全性考虑

JWT 的存储

JWT 通常存储在客户端的 localStorage 或 cookie 中。为了增加安全性,建议将 JWT 存储在 HttpOnly 的 cookie 中,以防止 XSS 攻击。

JWT 的过期与刷新

JWT 应该设置一个合理的过期时间,以防止长期有效的 token 被滥用。可以通过刷新 token 机制来延长用户的会话时间。

JWT 的签名与加密

JWT 的签名算法应该选择安全的算法,如 HMAC SHA256 或 RSA。对于敏感信息,建议对 JWT 进行加密。

JWT 的优缺点

优点

缺点

总结

本文详细介绍了如何在 Spring Boot 中集成 JWT 实现身份认证。我们从 JWT 的基本概念开始,逐步讲解了如何在 Spring Boot 项目中生成和解析 JWT,并将其与 Spring Security 集成,最终实现了一个完整的身份认证系统。通过本文的学习,你应该能够在自己的 Spring Boot 项目中轻松集成 JWT,并实现安全的身份认证机制。

参考资料

推荐阅读:
  1. SpringBoot怎么整合spring-data-jpa
  2. spring、springmvc和springboot有什么不同

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

springboot jwt

上一篇:Springboot怎么整合Flowable6.x导出bpmn20

下一篇:SQL Server数据库怎么更改默认备份目录

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》