在现代Web应用中,前后端分离的架构越来越流行。这种架构下,前端和后端通过API进行通信,因此需要一个可靠的认证机制来确保通信的安全性。JSON Web Token(JWT)是一种轻量级的认证机制,广泛应用于前后端分离的应用中。本文将详细介绍如何在Spring Boot项目中集成JWT,实现前后端的认证。
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。JWT通常用于身份验证和信息交换。它由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。
JWT的结构由三部分组成,用点(.
)分隔:
首先,我们需要创建一个Spring Boot项目。可以使用Spring Initializr来快速生成项目骨架。
在pom.xml
中添加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>
在Spring Boot中,我们可以使用io.jsonwebtoken
库来生成JWT。以下是一个简单的JWT生成示例:
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import java.security.Key; import java.util.Date; public class JwtUtil { private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256); private static final long EXPIRATION_TIME = 864_000_000; // 10 days public static String generateToken(String username) { return Jwts.builder() .setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SECRET_KEY) .compact(); } }
验证JWT的过程与生成JWT类似,我们需要使用相同的密钥来验证签名:
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; public class JwtUtil { public static String validateToken(String token) { Jws<Claims> claimsJws = Jwts.parserBuilder() .setSigningKey(SECRET_KEY) .build() .parseClaimsJws(token); return claimsJws.getBody().getSubject(); } }
在Spring Boot中,我们可以通过配置Spring Security来保护我们的API。以下是一个简单的Spring Security配置:
import org.springframework.context.annotation.Bean; 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; @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/public/**").permitAll() .anyRequest().authenticated() .and() .addFilter(new JwtAuthenticationFilter(authenticationManager())) .addFilter(new JwtAuthorizationFilter(authenticationManager())); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
我们需要自定义一个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; @Service public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 这里可以从数据库中加载用户信息 return User.withUsername(username) .password("password") .authorities("ROLE_USER") .build(); } }
我们需要创建两个过滤器:一个用于处理登录请求并生成JWT,另一个用于验证JWT并授权用户访问受保护的资源。
import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final AuthenticationManager authenticationManager; public JwtAuthenticationFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { try { UserCredentials credentials = new ObjectMapper().readValue(request.getInputStream(), UserCredentials.class); return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( credentials.getUsername(), credentials.getPassword(), new ArrayList<>()) ); } catch (IOException e) { throw new RuntimeException(e); } } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) { String token = JwtUtil.generateToken(authResult.getName()); response.addHeader("Authorization", "Bearer " + token); } }
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class JwtAuthorizationFilter extends BasicAuthenticationFilter { public JwtAuthorizationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String header = request.getHeader("Authorization"); if (header == null || !header.startsWith("Bearer ")) { chain.doFilter(request, response); return; } String token = header.replace("Bearer ", ""); String username = JwtUtil.validateToken(token); if (username != null) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>()); SecurityContextHolder.getContext().setAuthentication(authentication); } chain.doFilter(request, response); } }
Authorization: Bearer <token>
)。JWT的有效期通常较短,为了保持用户的登录状态,我们需要实现一个刷新Token的机制。当JWT即将过期时,前端可以请求一个新的JWT。
本文详细介绍了如何在Spring Boot项目中集成JWT,实现前后端的认证。通过JWT,我们可以实现无状态的认证机制,适用于前后端分离的架构。同时,我们还讨论了JWT的安全性考虑和最佳实践,以确保应用的安全性。希望本文能帮助你更好地理解和应用JWT。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。