一、整体流程梳理 🚀
这是一套 Spring Boot + Spring Security + JWT 的登录认证代码,核心流程是:
前端发送登录请求 → Controller 接收参数 → Service 层完成身份认证 → 生成 JWT 令牌返回给前端 → 前端后续请求携带 Token → 后端验证 Token 有效性。
二、Controller 层代码讲解(LoginController.java)
这是请求入口,负责接收前端的登录请求并调用业务逻辑。
java
package com.kob.backend.controller.user.account;
import com.kob.backend.service.user.account.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class LoginController {
@Autowired
private LoginService loginService;
@PostMapping("/user/account/token/")
public Map<String, String> getToken(@RequestParam Map<String, String> map) {
String username = map.get("username");
String password = map.get("password");
return loginService.getToken(username, password);
}
}
关键注解与代码解析:
-
@RestController- 标记这是一个控制器类 ,所有方法的返回值会直接作为 HTTP 响应体(相当于
@Controller + @ResponseBody)。 - 告诉 Spring:这个类要处理前端请求。
- 标记这是一个控制器类 ,所有方法的返回值会直接作为 HTTP 响应体(相当于
-
@Autowired- 依赖注入:Spring 会自动找到
LoginService的实现类(LoginServiceImpl)并注入进来,不用手动new对象。 - 作用:解耦,让 Controller 直接调用 Service 的业务方法。
- 依赖注入:Spring 会自动找到
-
@PostMapping("/user/account/token/")- 处理 POST 请求 ,请求路径是
/user/account/token/。 - 前端需要用 POST 方法访问这个地址,携带
username和password参数。
- 处理 POST 请求 ,请求路径是
-
getToken(@RequestParam Map<String, String> map)@RequestParam Map<String, String> map:把前端传来的请求参数(比如表单/URL 参数)封装成一个 Map,key是参数名,value是参数值。- 从 Map 中取出
username和password,传给 Service 层的getToken方法处理。 - 最终返回 Service 层的结果(包含 JWT 令牌的 Map)给前端。
三、Service 层代码讲解
3.1 LoginService 接口(LoginService.java)
接口定义业务方法,解耦接口与实现,符合面向接口编程思想。
java
package com.kob.backend.service.user.account;
import java.util.Map;
public interface LoginService {
/**
* 处理登录逻辑,生成JWT令牌
* @param username 用户名
* @param password 密码
* @return 包含令牌和状态的Map
*/
Map<String, String> getToken(String username, String password);
}
3.2 LoginServiceImpl 实现类(LoginServiceImpl.java)
这是业务逻辑层,负责完成身份认证和生成 JWT 令牌。
java
package com.kob.backend.service.user.account;
import com.kob.backend.pojo.User;
import com.kob.backend.service.utils.UserDetailsImpl;
import com.kob.backend.utils.JwtUtil;
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.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Override
public Map<String, String> getToken(String username, String password) {
// 1. 封装用户名密码为 Spring Security 认证令牌
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, password);
// 2. 调用认证管理器完成认证(失败会自动抛异常)
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 3. 从认证结果中获取用户信息
UserDetailsImpl loginUser = (UserDetailsImpl) authenticate.getPrincipal();
User user = loginUser.getUser();
// 4. 生成 JWT 令牌
String jwt = JwtUtil.createJWT(user.getId().toString());
// 5. 封装结果返回
Map<String, String> map = new HashMap<>();
map.put("error_message", "success");
map.put("token", jwt);
return map;
}
}
关键注解与代码解析:
-
@Service- 标记这是一个业务层组件,Spring 会将其纳入容器管理,方便 Controller 注入。
- 作用:处理核心业务逻辑,和数据库/安全框架交互。
-
@Autowired AuthenticationManager- 注入 Spring Security 的认证管理器,是处理用户名密码认证的核心组件。
-
UsernamePasswordAuthenticationToken- 把前端传来的
username和password封装成 Spring Security 能识别的认证令牌。 - 相当于把用户的登录凭证打包,交给认证管理器去验证。
- 把前端传来的
-
authenticationManager.authenticate(authenticationToken)- 执行认证:Spring Security 会调用你配置的
UserDetailsService去数据库查用户信息,对比密码是否正确。 - ✅ 认证成功:返回包含用户信息的
Authentication对象。 - ❌ 认证失败(用户名不存在/密码错误):自动抛出异常,Spring 会全局处理并返回错误响应,代码里不需要手动处理失败情况。
- 执行认证:Spring Security 会调用你配置的
-
authenticate.getPrincipal()- 从认证成功的结果中获取当前登录用户 ,强转为你自定义的
UserDetailsImpl(实现了 Spring Security 的UserDetails接口,用来封装用户信息)。 - 再从
UserDetailsImpl中拿到你自己的User实体类,获取用户 ID。
- 从认证成功的结果中获取当前登录用户 ,强转为你自定义的
-
JwtUtil.createJWT(user.getId().toString())- 调用工具类
JwtUtil,传入用户 ID,生成一个 JWT 令牌。 - JWT 是前后端分离认证的核心:前端拿到后,后续请求会在请求头里带上这个 Token,服务端验证 Token 有效性来确认用户身份,不需要存储 Session。
- 调用工具类
-
封装返回结果
- 用
HashMap封装结果:error_message: "success":告诉前端认证成功。token: jwt:返回生成的 JWT 令牌。
- 这个 Map 最终会被 Controller 返回给前端。
- 用
四、核心依赖(pom.xml)
实现该功能需要引入以下核心依赖,基于 Spring Boot 2.x/3.x 适配:
xml
<!-- Spring Boot Web 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security 安全框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT 核心依赖 (jjwt) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- MyBatis-Plus (操作数据库) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok (简化实体类) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
五、核心工具类/实体类补充
5.1 JWT 工具类(JwtUtil.java)
负责 JWT 令牌的生成、验证,是认证的核心工具类:
java
package com.kob.backend.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
@Component
public class JwtUtil {
// Token 过期时间:2小时 (毫秒)
private static final long EXPIRATION_TIME = 7200000;
// 密钥(建议配置在 application.yml 中,此处简化)
@Value("${jwt.secret}")
private String secret;
// 生成 JWT Token
public String createJWT(String userId) {
// 生成密钥
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
// 构建 Token
return Jwts.builder()
.setSubject(userId) // 存储用户ID
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 过期时间
.signWith(key, SignatureAlgorithm.HS256) // 签名算法
.compact();
}
// 验证 Token 并解析用户ID
public String parseJWT(String token) {
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
// 返回存储的用户ID
return claims.getSubject();
}
}
5.2 User 实体类(User.java)
对应数据库用户表,封装用户基础信息:
java
package com.kob.backend.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user") // 对应数据库表名
public class User {
@TableId(type = IdType.AUTO) // 自增主键
private Integer id; // 用户ID
private String username; // 用户名
private String password; // 加密后的密码
private String nickname; // 昵称(可选)
}
5.3 UserDetailsImpl(UserDetailsImpl.java)
实现 Spring Security 的 UserDetails 接口,封装用户信息供 Security 使用:
java
package com.kob.backend.service.utils;
import com.kob.backend.pojo.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
// 封装用户信息,适配 Spring Security
public class UserDetailsImpl implements UserDetails {
private final User user;
public UserDetailsImpl(User user) {
this.user = user;
}
public User getUser() {
return user;
}
// 权限控制(此处简化,返回空集合)
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptyList();
}
// 获取密码(Security 会用这个密码和前端传入的对比)
@Override
public String getPassword() {
return user.getPassword();
}
// 获取用户名
@Override
public String getUsername() {
return user.getUsername();
}
// 账号是否未过期
@Override
public boolean isAccountNonExpired() {
return true;
}
// 账号是否未锁定
@Override
public boolean isAccountNonLocked() {
return true;
}
// 凭证是否未过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 账号是否可用
@Override
public boolean isEnabled() {
return true;
}
}
5.4 UserDetailsService 实现类(UserDetailsServiceImpl.java)
Security 核心类,负责从数据库查询用户信息:
java
package com.kob.backend.service.utils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
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 UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
// Security 认证时自动调用,根据用户名查用户
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 从数据库查询用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
User user = userMapper.selectOne(queryWrapper);
// 2. 用户不存在则抛异常
if (user == null) {
throw new UsernameNotFoundException("用户名不存在!");
}
// 3. 封装成 UserDetails 返回
return new UserDetailsImpl(user);
}
}
六、Spring Security 核心配置(SecurityConfig.java)
配置认证管理器、密码加密、放行接口等核心规则:
java
package com.kob.backend.config;
import com.kob.backend.service.utils.UserDetailsServiceImpl;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity // 启用 Spring Security
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
// 密码加密器(必须配置,否则 Security 报错)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 配置认证管理器(供 Service 层调用)
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// 核心配置:放行接口、关闭Session、配置认证逻辑
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭跨域防护(前后端分离必备)
.cors().and().csrf().disable()
// 关闭 Session(JWT 是无状态认证)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 配置接口权限
.authorizeRequests()
// 登录接口放行,无需认证
.antMatchers("/user/account/token/").permitAll()
// 其他所有接口需要认证
.anyRequest().authenticated();
}
// 配置用户认证逻辑:指定用户信息来源和密码加密方式
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
七、全局异常处理(GlobalExceptionHandler.java)
统一处理认证失败等异常,返回友好提示给前端:
java
package com.kob.backend.config;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
// 全局异常处理器,处理 Controller 层异常
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理用户名不存在异常
@ExceptionHandler(UsernameNotFoundException.class)
public Map<String, String> handleUsernameNotFound(UsernameNotFoundException e) {
Map<String, String> map = new HashMap<>();
map.put("error_message", "用户名不存在");
return map;
}
// 处理密码错误异常
@ExceptionHandler(BadCredentialsException.class)
public Map<String, String> handleBadCredentials(BadCredentialsException e) {
Map<String, String> map = new HashMap<>();
map.put("error_message", "密码错误");
return map;
}
// 处理其他异常
@ExceptionHandler(Exception.class)
public Map<String, String> handleException(Exception e) {
Map<String, String> map = new HashMap<>();
map.put("error_message", "服务器内部错误");
return map;
}
}
八、application.yml 配置
核心配置文件,配置数据库、JWT 密钥等:
yaml
spring:
# 数据库配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/你的数据库名?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root # 你的数据库用户名
password: 123456 # 你的数据库密码
# 关闭 Spring Security 控制台密码
security:
user:
name: admin
password: admin
# JWT 配置
jwt:
secret: your-secret-key-1234567890abcdefghijklmnopqrstuvwxyz # 自定义密钥(建议至少32位)
# MyBatis-Plus 配置
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 下划线转驼峰
mapper-locations: classpath:mapper/*.xml # Mapper.xml 路径
type-aliases-package: com.kob.backend.pojo # 实体类包路径
九、完整请求流程
- 前端发送 POST 请求到
/user/account/token/,携带username和password。 LoginController接收参数,调用LoginService.getToken()。LoginService封装用户名密码为UsernamePasswordAuthenticationToken,交给AuthenticationManager认证。AuthenticationManager调用UserDetailsServiceImpl.loadUserByUsername()从数据库查询用户。- Security 自动对比数据库密码(BCrypt 加密)与前端传入密码,验证通过则返回
Authentication对象。 - Service 层从
Authentication中获取用户 ID,调用JwtUtil生成 Token。 - 封装
success+ Token 返回给 Controller,最终返回给前端。 - 前端存储 Token,后续请求在
Authorization请求头中携带Bearer + Token,后端验证 Token 有效性。
十、核心技术点总结 💡
| 技术/注解 | 作用 |
|---|---|
@RestController |
标记控制器,返回值直接作为 HTTP 响应体 |
@Service |
标记业务层组件,由 Spring 管理 |
@Autowired |
依赖注入,自动装配 Bean |
@PostMapping |
处理 POST 请求,指定请求路径 |
@RestControllerAdvice |
全局异常处理,统一返回格式 |
AuthenticationManager |
Spring Security 核心认证管理器 |
UserDetailsService |
自定义用户信息查询逻辑,对接数据库 |
BCryptPasswordEncoder |
密码加密/验证,保障密码安全 |
| JWT | 生成无状态的身份令牌,实现前后端分离认证 |
十一、注意事项 ⚠️
- 密码必须加密存储:数据库中不能存明文密码,注册/修改密码时需用
BCryptPasswordEncoder加密。 - JWT 密钥要足够复杂:建议至少 32 位随机字符串,避免被破解。
- Token 过期时间合理:过短影响用户体验,过长增加安全风险(建议 1-2 小时)。
- 敏感接口必须认证:除登录/注册外,其他接口需验证 Token 有效性。
- 异常处理要友好:避免直接返回堆栈信息给前端,统一返回
error_message。
总结
- 这套登录认证体系核心是Spring Security 完成身份验证 + JWT 生成无状态令牌,实现前后端分离下的安全认证。
- 核心流程分为:请求接收 → 身份认证 → 生成 Token → 返回结果,关键是
AuthenticationManager认证和 JWT 令牌生成。 - 必须配置
UserDetailsService对接数据库、PasswordEncoder加密密码、全局异常处理,才能保证功能完整且安全。