当前位置: 首页 > news >正文

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('登录失败,请重试');}
}
  1. 密码加密:使用BCrypt
  2. JWT认证:有状态的token管理
  3. 失败限制:防暴力破解
  4. 会话管理:Redis存储会话
  5. 操作日志:记录登录行为
  6. 参数验证:使用Bean Validation
  7. IP白名单:限制访问来源
http://www.xdnf.cn/news/1290223.html

相关文章:

  • SQL 合并两个时间段的销售数据:FULL OUTER JOIN + COALESCE
  • 测试环境下因网络环境变化导致集群无法正常使用解决办法
  • SQL注入学习笔记
  • LeetCode Day5 -- 栈、队列、堆
  • 前后端分离项目中Spring MVC的请求执行流程
  • 肖臻《区块链技术与应用》第十讲:深入解析硬分叉与软分叉
  • 用 Spring 思维快速上手 DDD——以 Kratos 为例的分层解读
  • provide()函数和inject()函数
  • 数据结构:后缀表达式:结合性 (Associativity) 与一元运算符 (Unary Operators)
  • ZKmall开源商城的容灾之道:多地域部署与故障切换如何守护电商系统
  • 21.Linux HTTPS服务
  • 【GESP】C++一级知识点之【集成开发环境】
  • 备战国赛算法讲解——马尔科夫链,2025国赛数学建模B题详细思路模型更新
  • UE5.3 C++ 动态多播实战总结
  • SQL 生成日期与产品的所有组合:CROSS JOIN(笛卡尔积)
  • JVM宝典
  • 每日五个pyecharts可视化图表-line:从入门到精通 (4)
  • 什么时候用WS(WebSocket),什么使用用SSE(Server-Sent Events)?
  • Pytest项目_day13(usefixture方法、params、ids)
  • 机器学习处理文本数据
  • linux 开机进入initramfs无法开机
  • 串口通信学习
  • 数据分析专栏记录之 -基础数学与统计知识
  • Spring-Cache 缓存数据
  • windows git安装步骤
  • XGBoost 的适用场景以及与 CNN、LSTM 的区别
  • 网络协议——HTTP协议
  • Linux服务:Apache 虚拟主机配置指南:多站点部署三种方式详解
  • 【超详细!题解|两种做法】洛谷P3196 [HNOI2008] 神奇的国度[MCS算法]
  • 深入剖析 React 合成事件:透过 onClick 看本质