
Springboot +JWT实现登录认证
Spring Boot + JWT实现登录认证,密码加密及Token校验全过程
项目简介
本项目是一个基于Spring Boot + JWT的前后端分离认证系统,使用拦截器而非过滤器进行JWT验证。项目包含用户注册、登录、JWT生成、Token验证、权限控制等完整功能。
JWT认证原理
通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。
JWT认证流程
-
前端登录请求:前端通过Web表单将用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探。
-
后端验证并生成Token:后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同
xxx.yyy.zzz
的字符串。 -
返回Token:后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可。
-
携带Token请求:前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)。
-
后端验证Token:后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等。
-
业务处理:验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果。
项目架构设计
核心组件
- 用户实体(User): 实现UserDetails接口,包含用户信息和权限
- JWT工具类(JwtUtils): 负责JWT的生成、解析和验证
- JWT拦截器(JwtInterceptor): 拦截请求,验证JWT token
- 认证控制器(AuthController): 处理登录和注册请求
- 用户服务(UserService): 处理用户相关业务逻辑
- 安全配置(SecurityConfig): Spring Security配置
- 全局异常处理器: 统一处理各种异常情况
技术栈
- Spring Boot 3.5.5: 主框架
- Spring Security: 安全框架
- Spring Data JPA: 数据访问层
- H2 Database: 内存数据库(便于演示)
- JJWT: JWT库
- BCrypt: 密码加密
详细实现流程
1. 用户实体设计
@Entity
@Table(name = "users")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String email;
@Enumerated(EnumType.STRING)
private Role role = Role.USER;
@Column(nullable = false)
private boolean enabled = true;
// 实现UserDetails接口方法
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton(new SimpleGrantedAuthority("ROLE_" + role.name()));
}
// 角色枚举
public enum Role {
USER, ADMIN
}
// 其他getter/setter方法...
}
关键点:
- 实现
UserDetails
接口,与Spring Security集成 - 使用BCrypt加密密码
- 支持USER和ADMIN两种角色
2. JWT工具类实现
@Component
public class JwtUtils {
@Value("${app.jwt.secret:mySecretKey}")
private String jwtSecret;
@Value("${app.jwt.expiration:86400000}") // 24小时
private int jwtExpirationMs;
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(jwtSecret.getBytes());
}
// 生成JWT Token
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
// 创建Token
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.claims(claims)
.subject(subject)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + jwtExpirationMs))
.signWith(getSigningKey())
.compact();
}
// 验证Token
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
// 从Token中获取用户名
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
// 其他工具方法...
}
关键功能:
- 使用HMAC SHA算法签名
- 支持Token过期时间配置
- 提供Token生成、验证、解析功能
3. JWT拦截器实现
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtils jwtUtils;
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取Authorization头
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7); // 移除"Bearer "前缀
try {
// 验证token
if (jwtUtils.validateToken(token)) {
String username = jwtUtils.getUsernameFromToken(token);
// 如果用户未认证,则进行认证
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 再次验证token与用户信息
if (jwtUtils.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
} catch (Exception e) {
logger.error("JWT认证过程中发生错误: {}", e.getMessage());
}
}
return true;
}
}
关键特性:
- 使用拦截器而非过滤器
- 自动从请求头提取JWT token
- 验证成功后设置Spring Security上下文
4. 认证控制器
@RestController
@RequestMapping("/api/auth")
@CrossOrigin(origins = "*", maxAge = 3600)
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Autowired
private JwtUtils jwtUtils;
@PostMapping("/login")
public ResponseEntity<ApiResponse<JwtResponse>> login(@Valid @RequestBody LoginRequest loginRequest) {
try {
// 认证用户
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
// 获取用户详情
User user = (User) authentication.getPrincipal();
// 生成JWT token
String jwt = jwtUtils.generateToken(user);
// 获取用户角色
List<String> roles = user.getAuthorities().stream()
.map(item -> item.getAuthority())
.collect(Collectors.toList());
JwtResponse jwtResponse = new JwtResponse(jwt, user.getUsername(), user.getEmail(), roles);
return ResponseEntity.ok(ApiResponse.success("登录成功", jwtResponse));
} catch (Exception e) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("用户名或密码错误"));
}
}
@PostMapping("/register")
public ResponseEntity<ApiResponse<String>> register(@Valid @RequestBody RegisterRequest registerRequest) {
try {
User user = userService.registerUser(registerRequest);
return ResponseEntity.ok(ApiResponse.success("用户注册成功!用户名: " + user.getUsername()));
} catch (RuntimeException e) {
return ResponseEntity.badRequest()
.body(ApiResponse.error(e.getMessage()));
}
}
}
登录流程:
- 接收用户名和密码
- 使用Spring Security进行认证
- 认证成功后生成JWT token
- 返回token和用户信息
5. Spring Security配置
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/h2-console/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(authenticationProvider());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 其他配置...
}
配置要点:
- 禁用CSRF(前后端分离项目)
- 设置无状态会话管理
- 配置公开接口路径
- 启用CORS支持
6. 拦截器注册配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private JwtInterceptor jwtInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/api/**") // 拦截所有API请求
.excludePathPatterns(
"/api/auth/**", // 排除认证相关接口
"/api/public/**", // 排除公共接口
"/h2-console/**" // 排除H2控制台
);
}
}
实际测试流程
1. 用户登录
请求:
POST http://localhost:8080/api/auth/login
Content-Type: application/json
{
"username": "user",
"password": "123456"
}
响应:
{
"success": true,
"message": "登录成功",
"data": {
"token": "eyJhbGciOiJIUzM4NCJ9.eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNzU3MzM5ODUyLCJleHAiOjE3NTc0MjYyNTJ9.mFfQgpO8iCEUQyHzdg9XOu_K8WZVwiZBVIFeazzYNbANwizDRqEn-hLtQy2DG9GI",
"type": "Bearer",
"username": "user",
"email": "user@example.com",
"roles": ["ROLE_USER"]
}
}
2. 携带Token访问受保护资源
请求:
GET http://localhost:8080/api/test/user
Authorization: Bearer eyJhbGciOiJIUzM4NCJ9.eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNzU3MzM5ODUyLCJleHAiOjE3NTc0MjYyNTJ9.mFfQgpO8iCEUQyHzdg9XOu_K8WZVwiZBVIFeazzYNbANwizDRqEn-hLtQy2DG9GI
响应:
{
"success": true,
"message": "用户内容访问成功!",
"data": "欢迎您,user!这是需要认证的内容。"
}
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 程序员Hanserwei
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果