AuthController类讲解
这是一个认证控制器,处理用户登录和登出的核心逻辑
package com.example.usermanagement.controller;import com.example.usermanagement.common.Result;
import com.example.usermanagement.entity.User;
import com.example.usermanagement.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.HashMap;
import java.util.Map;
import java.util.UUID;/*** 认证控制器*/
@RestController
@RequestMapping("/api/auth")
@CrossOrigin // 允许跨域
public class AuthController {private static final Logger log = LoggerFactory.getLogger(AuthController.class);@Autowiredprivate UserService userService;/*** 用户登录*/@PostMapping("/login")public Result<Map<String, Object>> login(@RequestBody Map<String, String> loginData) {try {String username = loginData.get("username");String password = loginData.get("password");// 查询用户User user = userService.getUserByUsername(username);if (user == null) {return Result.<Map<String, Object>>error("用户不存在");}// 验证密码(实际项目中应该加密比较)if (!password.equals(user.getPassword())) {return Result.<Map<String, Object>>error("密码错误");}// 检查用户状态if (user.getStatus() == 0) {return Result.<Map<String, Object>>error("账号已被禁用");}// 生成token(简单示例,实际项目应使用JWT)String token = UUID.randomUUID().toString();// 构建返回数据Map<String, Object> data = new HashMap<>();data.put("token", token);data.put("user", user);// 设置用户权限(示例)String role = "admin".equals(username) ? "admin" : "user";data.put("role", role);return Result.success(data);} catch (Exception e) {log.error("登录失败", e);return Result.<Map<String, Object>>error("登录失败:" + e.getMessage());}}/*** 退出登录*/@PostMapping("/logout")public Result<Void> logout() {// 实际项目中应该清除token等操作return Result.success();}
}
1. 类结构与注解解析
1.1 核心注解
@RestController
@RequestMapping("/api/auth")
@CrossOrigin
@RestController:
- 组合注解:
@Controller
+@ResponseBody
- 所有方法返回的都是JSON数据,不是视图页面
- 适用于RESTful API开发
@RequestMapping(“/api/auth”):
- 类级别的路径映射
- 所有方法的URL都以
/api/auth
开头 - 统一管理认证相关的接口
@CrossOrigin:
- 方法级别的跨域配置
- 作为全局
CorsConfig
的补充 - 确保前端能正常访问认证接口
1.2 依赖注入
@Autowired
private UserService userService;
- 注入用户业务服务
- 用于查询用户信息和验证
2. 登录接口深度解析
2.1 接口声明
@PostMapping("/login")
public Result<Map<String, Object>> login(@RequestBody Map<String, String> loginData)
HTTP方法选择:
- 使用
POST
而不是GET
- 原因:登录涉及敏感信息,不应出现在URL中
- 符合RESTful设计原则
参数设计:
@RequestBody
:从请求体解析JSON数据Map<String, String>
:灵活接收键值对数据- 支持扩展(如验证码、记住我等参数)
返回类型:
Result<Map<String, Object>>
:统一响应格式Map
包含token、用户信息、角色等多种数据
2.2 参数提取与验证
String username = loginData.get("username");
String password = loginData.get("password");
前端请求示例:
{"username": "admin","password": "123456"
}
// 添加参数验证
if (username == null || username.trim().isEmpty()) {return Result.error("用户名不能为空");
}
if (password == null || password.trim().isEmpty()) {return Result.error("密码不能为空");
}
2.3 用户查询与验证
User user = userService.getUserByUsername(username);if (user == null) {return Result.<Map<String, Object>>error("用户不存在");
}
- 先查询用户是否存在
- 提供明确的错误信息
- 为后续密码验证做准备
安全优化建议:
// 统一错误信息,防止用户枚举攻击
if (user == null) {return Result.error("用户名或密码错误");
}
2.4 密码验证逻辑
if (!password.equals(user.getPassword())) {return Result.<Map<String, Object>>error("密码错误");
}
当前实现问题:
- 明文密码比较,安全性差
- 没有防暴力破解机制
- 错误信息过于详细
生产环境改进:
// 使用BCrypt验证加密密码
if (!BCrypt.checkpw(password, user.getPassword())) {// 记录失败次数recordLoginFailure(username);return Result.error("用户名或密码错误");
}
2.5 账号状态检查
if (user.getStatus() == 0) {return Result.<Map<String, Object>>error("账号已被禁用");
}
状态码设计:
0
:禁用1
:正常- 可扩展:
2
待审核、3
锁定等
2.6 Token生成
String token = UUID.randomUUID().toString();
JWT改进方案:
// 使用JWT生成token
String token = Jwts.builder().setSubject(user.getId().toString()).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + 7200000)) // 2小时.signWith(SignatureAlgorithm.HS256, "your-secret-key").compact();
2.7 响应数据构建
Map<String, Object> data = new HashMap<>();
data.put("token", token);
data.put("user", user);String role = "admin".equals(username) ? "admin" : "user";
data.put("role", role);return Result.success(data);
响应格式:
{"code": 200,"message": "操作成功","data": {"token": "550e8400-e29b-41d4-a716-446655440000","user": {"id": 1,"username": "admin","email": "admin@example.com","status": 1},"role": "admin"}
}
安全优化:
// 敏感信息脱敏
User userInfo = new User();
userInfo.setId(user.getId());
userInfo.setUsername(user.getUsername());
userInfo.setEmail(user.getEmail());
userInfo.setStatus(user.getStatus());
// 不返回密码等敏感信息
data.put("user", userInfo);
3. 登出接口分析
3.1 简单实现
@PostMapping("/logout")
public Result<Void> logout() {return Result.success();
}
完整实现建议:
@PostMapping("/logout")
public Result<Void> logout(HttpServletRequest request) {try {// 1. 从请求头获取tokenString token = request.getHeader("Authorization");if (token != null && token.startsWith("Bearer ")) {token = token.substring(7);}// 2. 将token加入黑名单if (token != null) {tokenBlacklistService.addToBlacklist(token);}// 3. 清除Redis中的用户会话String userId = JwtUtils.getUserIdFromToken(token);redisTemplate.delete("user:session:" + userId);// 4. 记录登出日志log.info("用户登出成功,token: {}", token);return Result.success();} catch (Exception e) {log.error("登出失败", e);return Result.error("登出失败");}
}
4. 异常处理机制
4.1 统一异常捕获
try {// 登录逻辑
} catch (Exception e) {log.error("登录失败", e);return Result.<Map<String, Object>>error("登录失败:" + e.getMessage());
}
日志记录:
- 使用SLF4J记录错误信息
- 包含异常堆栈,便于排查问题
- 返回给前端的错误信息要脱敏
5. 生产环境安全增强
5.1 完整的认证控制器
@RestController
@RequestMapping("/api/auth")
@CrossOrigin
@Validated
public class AuthController {private static final Logger log = LoggerFactory.getLogger(AuthController.class);@Autowiredprivate UserService userService;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate JwtTokenUtil jwtTokenUtil;// 登录失败次数限制private static final int MAX_LOGIN_ATTEMPTS = 5;private static final long LOCK_TIME = 15 * 60 * 1000; // 15分钟@PostMapping("/login")public Result<Map<String, Object>> login(@RequestBody @Valid LoginRequest loginRequest,HttpServletRequest request) {try {String username = loginRequest.getUsername();String password = loginRequest.getPassword();String ip = getClientIpAddress(request);// 1. 检查账号是否被锁定if (isAccountLocked(username)) {return Result.error("账号已被锁定,请15分钟后重试");}// 2. 查询用户User user = userService.getUserByUsername(username);if (user == null) {recordLoginFailure(username, ip);return Result.error("用户名或密码错误");}// 3. 验证密码if (!BCrypt.checkpw(password, user.getPassword())) {recordLoginFailure(username, ip);return Result.error("用户名或密码错误");}// 4. 检查账号状态if (user.getStatus() == 0) {return Result.error("账号已被禁用");}// 5. 生成JWT tokenString token = jwtTokenUtil.generateToken(user);// 6. 存储用户会话到RedisstoreUserSession(user.getId(), token);// 7. 清除登录失败记录clearLoginFailures(username);// 8. 记录登录日志recordLoginSuccess(user, ip);// 9. 构建响应数据Map<String, Object> data = buildLoginResponse(user, token);return Result.success(data);} catch (Exception e) {log.error("登录异常", e);return Result.error("系统繁忙,请稍后重试");}}// 检查账号是否被锁定private boolean isAccountLocked(String username) {String key = "login:lock:" + username;return redisTemplate.hasKey(key);}// 记录登录失败private void recordLoginFailure(String username, String ip) {String key = "login:fail:" + username;String count = (String) redisTemplate.opsForValue().get(key);int failCount = count == null ? 0 : Integer.parseInt(count);failCount++;if (failCount >= MAX_LOGIN_ATTEMPTS) {// 锁定账号redisTemplate.opsForValue().set("login:lock:" + username, "locked", LOCK_TIME, TimeUnit.MILLISECONDS);redisTemplate.delete(key);log.warn("账号{}因多次登录失败被锁定,IP: {}", username, ip);} else {redisTemplate.opsForValue().set(key, String.valueOf(failCount), LOCK_TIME, TimeUnit.MILLISECONDS);}}
}
5.2 登录请求DTO
@Data
public class LoginRequest {@NotBlank(message = "用户名不能为空")@Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")private String username;@NotBlank(message = "密码不能为空")@Size(min = 6, max = 20, message = "密码长度必须在6-20之间")private String password;// 验证码private String captcha;// 记住我private Boolean rememberMe = false;
}
6. JWT Token管理
6.1 JWT工具类
@Component
public class JwtTokenUtil {private String secret = "mySecretKey";private int expiration = 7200; // 2小时public String generateToken(User user) {Map<String, Object> claims = new HashMap<>();claims.put("userId", user.getId());claims.put("username", user.getUsername());claims.put("role", getRoleByUsername(user.getUsername()));return Jwts.builder().setClaims(claims).setSubject(user.getUsername()).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)).signWith(SignatureAlgorithm.HS512, secret).compact();}public Boolean validateToken(String token, UserDetails userDetails) {final String username = getUsernameFromToken(token);return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));}
}
7. 前端集成示例
7.1 Vue.js登录实现
// 登录方法
async login(loginForm) {try {const response = await axios.post('/api/auth/login', {username: loginForm.username,password: loginForm.password});if (response.data.code === 200) {// 存储tokenlocalStorage.setItem('token', response.data.data.token);localStorage.setItem('user', JSON.stringify(response.data.data.user));// 设置axios默认headeraxios.defaults.headers.common['Authorization'] = 'Bearer ' + response.data.data.token;// 跳转到首页this.$router.push('/dashboard');} else {this.$message.error(response.data.message);}} catch (error) {this.$message.error('登录失败,请重试');}
}
- 密码加密:使用BCrypt
- JWT认证:有状态的token管理
- 失败限制:防暴力破解
- 会话管理:Redis存储会话
- 操作日志:记录登录行为
- 参数验证:使用Bean Validation
- IP白名单:限制访问来源