JSON Web Token(JWT)是一种用于在各方之间作为JSON对象安全传输信息的开放标准。JWT的使用在现代Web应用程序中越来越普遍,尤其是在实现认证和授权机制时。Spring Security作为Spring框架中的安全模块,提供了强大的支持来实现基于JWT的认证和授权。本文将详细介绍Spring Security中如何使用JWT,包括JWT的基本概念、生成和验证JWT的过程、在Spring Security中的集成、以及一些实际应用和最佳实践。
一、JWT基础概念 1.1 JWT简介 JWT(JSON Web Token)是一种紧凑且自包含的方式,用于在各方之间作为JSON对象传输信息。JWT被广泛用于认证和授权。
1.2 JWT的结构 JWT由三部分组成:Header、Payload和Signature。它们以点(.)分隔。
Header :通常包含两部分:令牌的类型(即JWT)和所使用的签名算法(如HMAC SHA256或RSA)。
Payload :包含声明(claims)。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型:注册声明、公共声明和私有声明。
Signature :用于验证消息在传输过程中是否未被更改。首先将Header和Payload进行Base64Url编码,然后将它们与密钥和签名算法进行签名。
示例JWT 1 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
1.3 JWT的优势
无状态 :JWT是无状态的,不需要在服务器端存储会话信息。
紧凑 :由于其紧凑性,JWT可以通过URL、POST参数或HTTP头进行传输。
自包含 :JWT包含所有必要的信息,使其能够自包含并且独立于其他系统进行验证。
二、生成和验证JWT 2.1 生成JWT 生成JWT的过程包括创建Header、Payload和Signature。以下是一个简单的生成JWT的示例。
示例代码:生成JWT 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import java.util.Date;public class JwtGenerator { private static final String SECRET_KEY = "mySecretKey" ; public static String generateToken () { return Jwts.builder() .setSubject("user123" ) .claim("name" , "John Doe" ) .setIssuedAt(new Date ()) .setExpiration(new Date (System.currentTimeMillis() + 3600000 )) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static void main (String[] args) { String jwt = generateToken(); System.out.println("Generated JWT: " + jwt); } }
2.2 验证JWT 验证JWT的过程包括解析JWT并验证其签名和声明。
示例代码:验证JWT 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureException;public class JwtValidator { private static final String SECRET_KEY = "mySecretKey" ; public static Claims validateToken (String token) { try { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } catch (SignatureException e) { throw new RuntimeException ("Invalid JWT signature" ); } } public static void main (String[] args) { String jwt = "your_jwt_here" ; Claims claims = validateToken(jwt); System.out.println("JWT claims: " + claims); } }
三、Spring Security中的JWT集成 3.1 引入依赖 首先,需要在项目中引入Spring Security和JWT的相关依赖。
示例代码:Maven依赖 1 2 3 4 5 6 7 8 9 10 11 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency > <dependency > <groupId > io.jsonwebtoken</groupId > <artifactId > jjwt</artifactId > <version > 0.9.1</version > </dependency > </dependencies >
3.2 配置Spring Security 需要配置Spring Security以支持JWT认证。包括定义过滤器、配置安全过滤链和实现用户认证逻辑。
示例代码:安全配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;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.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/auth/**" ).permitAll() .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } @Bean public JwtAuthenticationFilter jwtAuthenticationFilter () { return new JwtAuthenticationFilter (); } @Override protected void configure (AuthenticationManagerBuilder auth) throws Exception { } }
3.3 实现JWT过滤器 实现一个JWT过滤器,用于解析和验证JWT。
示例代码:JWT过滤器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;import org.springframework.util.StringUtils;import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class JwtAuthenticationFilter extends OncePerRequestFilter { private final UserDetailsService userDetailsService; public JwtAuthenticationFilter (UserDetailsService userDetailsService) { this .userDetailsService = userDetailsService; } @Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws java.io.IOException, javax.servlet.ServletException { String jwt = getJwtFromRequest(request); if (StringUtils.hasText(jwt) && validateToken(jwt)) { String username = getUsernameFromJWT(jwt); UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (userDetails != null ) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken (userDetails, null , userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource ().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } filterChain.doFilter(request, response); } private String getJwtFromRequest (HttpServletRequest request) { String bearerToken = request.getHeader("Authorization" ); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer " )) { return bearerToken.substring(7 ); } return null ; } private boolean validateToken (String token) { try { Jwts.parser().setSigningKey("mySecretKey" ).parseClaimsJws(token); return true ; } catch (Exception e) { return false ; } } private String getUsernameFromJWT (String token) { Claims claims = Jwts.parser().setSigningKey("mySecretKey" ).parseClaimsJws(token).getBody(); return claims.getSubject(); } }
3.4 实现用户认证逻辑 需要实现UserDetailsService接口,加载用户的详细信息。
示例代码:UserDetailsService实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { if ("user123" .equals(username)) { return new org .springframework.security.core.userdetails.User( "user123" , "{noop}password" , new ArrayList <>() ); } else { throw new UsernameNotFoundException ("User not found" ); } } }
四、JWT在实际应用中的使用 4.1 用户登录和JWT生成 用户登录成功后,生成JWT并返回给客户端。
示例代码:用户登录和JWT生成 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import org.springframework.beans.factory.annotation.Autowired;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.core.userdetails.UserDetails;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RestController;@RestController public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenProvider tokenProvider; @PostMapping("/api/auth/login") public String authenticateUser (@RequestBody LoginRequest loginRequest) { try { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken ( loginRequest.getUsername(), loginRequest.getPassword() ) ); UserDetails userDetails = (UserDetails) authentication.getPrincipal(); return tokenProvider.generateToken(userDetails); } catch (AuthenticationException e) { throw new RuntimeException ("Invalid username or password" ); } } }
示例代码:JWT生成类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.stereotype.Component;import java.util.Date;@Component public class JwtTokenProvider { private static final String SECRET_KEY = "mySecretKey" ; public String generateToken (UserDetails userDetails) { return Jwts.builder() .setSubject(userDetails.getUsername()) .setIssuedAt(new Date ()) .setExpiration(new Date (System.currentTimeMillis() + 3600000 )) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } }
示例代码:登录请求类 1 2 3 4 5 6 public class LoginRequest { private String username; private String password; }
4.2 JWT在请求中的使用 客户端在每次请求时,需要在HTTP头中包含JWT。
示例代码:客户端请求示例 1 2 3 4 5 6 7 8 9 10 11 const token = 'your_jwt_token_here' ;fetch ('/api/protected' , { method : 'GET' , headers : { 'Authorization' : 'Bearer ' + token } }) .then (response => response.json ()) .then (data => console .log (data)) .catch (error => console .error ('Error:' , error));
4.3 处理JWT过期和刷新 需要处理JWT的过期问题,并实现JWT的刷新机制。
示例代码:处理JWT过期和刷新 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import io.jsonwebtoken.Claims;import io.jsonwebtoken.ExpiredJwtException;import io.jsonwebtoken.Jwts;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RestController;@RestController public class TokenController { @Autowired private JwtTokenProvider tokenProvider; @Autowired private UserDetailsService userDetailsService; @PostMapping("/api/auth/refresh") public String refreshToken (@RequestBody String token) { try { Claims claims = Jwts.parser() .setSigningKey("mySecretKey" ) .parseClaimsJws(token) .getBody(); String username = claims.getSubject(); UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (tokenProvider.validateToken(token, userDetails)) { return tokenProvider.generateToken(userDetails); } else { throw new RuntimeException ("Invalid token" ); } } catch (ExpiredJwtException e) { throw new RuntimeException ("Token has expired" ); } } }
五、JWT安全最佳实践 5.1 使用强密钥 确保使用强密钥来签署JWT,以防止攻击者伪造令牌。
1 private static final String SECRET_KEY = "myVeryStrongSecretKeyThatIsHardToGuess" ;
5.2 短期有效性和刷新令牌 设置短期有效的JWT,并实现令牌刷新机制,以减少令牌被滥用的风险。
5.3 使用HTTPS 确保所有JWT的传输都使用HTTPS,以防止令牌在传输过程中被窃取。
5.4 避免在JWT中存储敏感数据 避免在JWT的Payload中存储敏感数据,因为Payload是可以被解码的。
5.5 定期轮换密钥 定期轮换签名密钥,以提高系统的安全性。
六、Spring Security JWT实战案例 6.1 构建基于JWT的Spring Boot应用 以下是一个完整的Spring Boot应用,展示了如何使用Spring Security和JWT进行用户认证和授权。
示例代码:Spring Boot应用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;@SpringBootApplication public class JwtDemoApplication { public static void main (String[] args) { SpringApplication.run(JwtDemoApplication.class, args); } @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } }
示例代码:用户实体类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; }
示例代码:用户存储库 1 2 3 4 5 6 7 import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;@Repository public interface UserRepository extends JpaRepository <User, Long> { User findByUsername (String username) ; }
示例代码:用户服务类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Service;@Service public class UserService { @Autowired private UserRepository userRepository; @Autowired private PasswordEncoder passwordEncoder; public User saveUser (User user) { user.setPassword(passwordEncoder.encode(user.getPassword())); return userRepository.save(user); } public User findByUsername (String username) { return userRepository.findByUsername(username); } }
示例代码:安全配置类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;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.crypto.password.PasswordEncoder;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserService userService; @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; @Autowired private PasswordEncoder passwordEncoder; @Override protected void configure (AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService::findByUsername).passwordEncoder(passwordEncoder); } @Override protected void configure (HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/auth/**" ).permitAll() .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } @Bean @Override public AuthenticationManager authenticationManagerBean () throws Exception { return super .authenticationManagerBean(); } }
示例代码:JWT过滤器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;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.core.userdetails.UserDetailsService;import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;import org.springframework.util.StringUtils;import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws java.io.IOException, javax.servlet.ServletException { String jwt = getJwtFromRequest(request); if (StringUtils.hasText(jwt) && validateToken(jwt)) { String username = getUsernameFromJWT(jwt); UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (userDetails != null ) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken (userDetails, null , userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource ().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } filterChain.doFilter(request, response); } private String getJwtFromRequest (HttpServletRequest request) { String bearerToken = request.getHeader("Authorization" ); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer " )) { return bearerToken.substring(7 ); } return null ; } private boolean validateToken (String token) { try { Jwts.parser().setSigningKey("mySecretKey" ).parseClaimsJws(token); return true ; } catch (Exception e) { return false ; } } private String getUsernameFromJWT (String token) { Claims claims = Jwts.parser().setSigningKey("mySecretKey" ).parseClaimsJws(token).getBody(); return claims.getSubject(); } }
示例代码:JWT生成类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.stereotype.Component;import java.util.Date;@Component public class JwtTokenProvider { private static final String SECRET_KEY = "mySecretKey" ; public String generateToken (UserDetails userDetails) { return Jwts.builder() .setSubject(userDetails.getUsername()) .setIssuedAt(new Date ()) .setExpiration(new Date (System.currentTimeMillis() + 3600000 )) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } }
示例代码:用户控制器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import org.springframework.beans.factory.annotation.Autowired;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.core.userdetails.UserDetails;import org.springframework.web.bind.annotation.*;@RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenProvider tokenProvider; @Autowired private UserService userService; @PostMapping("/register") public User registerUser (@RequestBody User user) { return userService.saveUser(user); } @PostMapping("/login") public String authenticateUser (@RequestBody LoginRequest loginRequest) { try { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken ( loginRequest.getUsername(), loginRequest.getPassword() ) ); UserDetails userDetails = (UserDetails) authentication.getPrincipal(); return tokenProvider.generateToken(userDetails); } catch (AuthenticationException e) { throw new RuntimeException ("Invalid username or password" ); } } }
示例代码:登录请求类 1 2 3 4 5 6 public class LoginRequest { private String username; private String password; }
七、总结 Spring Security与JWT的集成提供了一种强大且灵活的方式来实现基于令牌的认证和授权。本文详细介绍了JWT的基本概念、生成和验证过程、以及如何在Spring Security中集成JWT。通过实际案例和最佳实践,读者可以深入理解并应用JWT来保护Spring应用程序,确保其安全性和可靠性。