feat(auth):重构认证登录接口返回结构

- 修改登录接口返回类型为 AuthTokenResponse
- 新增 AuthTokenResponse DTO 类封装 token 和 tokenHead
- 调整 AuthService 接口及实现类返回值类型
- 移除 Controller 层手动构造返回数据逻辑
- 完善异常处理逻辑,区分不同认证失败场景
- 新增用户未启用状态的错误码和处理
- 添加全局异常处理器对授权拒绝异常的处理
This commit is contained in:
Chaos
2025-11-18 17:18:09 +08:00
parent 0527602d1c
commit 7e754b19d4
6 changed files with 47 additions and 28 deletions

View File

@@ -21,6 +21,9 @@ public enum ErrorCode {
PASSWORD_FORMAT_ERROR(400, "USER-104", "密码需6-20位字符组合"), PASSWORD_FORMAT_ERROR(400, "USER-104", "密码需6-20位字符组合"),
USER_NOT_EXISTS(404, "USER-104", "用户不存在"), USER_NOT_EXISTS(404, "USER-104", "用户不存在"),
USER_ID_INVALID(400, "USER-105", "用户ID无效"), USER_ID_INVALID(400, "USER-105", "用户ID无效"),
USER_NOT_EXISTS_OR_PASSWORD_WRONG(401, "USER-105", "用户名不存在或密码错误"),
USER_NOT_ENABLED(403, "USER-106", "用户未启用"),
USER_NOT_LOGIN(401, "USER-105", "请先登录"), USER_NOT_LOGIN(401, "USER-105", "请先登录"),
USER_BANNED(403, "USER-106", "账号已被封禁"), USER_BANNED(403, "USER-106", "账号已被封禁"),
EMAIL_EXISTS(409, "USER-107", "邮箱已注册"), EMAIL_EXISTS(409, "USER-107", "邮箱已注册"),
@@ -37,10 +40,11 @@ public enum ErrorCode {
COMMENT_TOO_LONG(400, "POST-206", "评论超过500字限制"), COMMENT_TOO_LONG(400, "POST-206", "评论超过500字限制"),
// ================== 系统/第三方 (300-399) ================== // ================== 系统/第三方 (300-399) ==================
SYSTEM_ERROR(500, "SYS-300", "系统错误"),
CAPTCHA_ERROR(400, "SYS-301", "验证码错误"), CAPTCHA_ERROR(400, "SYS-301", "验证码错误"),
SMS_SEND_FAILED(500, "SYS-302", "短信发送失败"), SMS_SEND_FAILED(500, "SYS-302", "短信发送失败"),
FILE_UPLOAD_FAILED(500, "SYS-303", "文件上传失败"), FILE_UPLOAD_FAILED(500, "SYS-303", "文件上传失败"),
RATE_LIMIT_EXCEEDED(429, "SYS-304", "操作过于频繁"), ; RATE_LIMIT_EXCEEDED(429, "SYS-304", "操作过于频繁");
private final int httpStatus; private final int httpStatus;
private final String code; // 业务错误码(领域-编号) private final String code; // 业务错误码(领域-编号)

View File

@@ -0,0 +1,10 @@
package cn.nopj.chaos_api.dto.response;
import lombok.Data;
@Data
public class AuthTokenResponse {
private String tokenHead;
private String token;
}

View File

@@ -3,22 +3,19 @@ package cn.nopj.chaos_api.controller;
import cn.nopj.chaos_api.converter.UserConverter; import cn.nopj.chaos_api.converter.UserConverter;
import cn.nopj.chaos_api.dto.request.AuthLoginRequest; import cn.nopj.chaos_api.dto.request.AuthLoginRequest;
import cn.nopj.chaos_api.dto.request.AuthRegisterRequest; import cn.nopj.chaos_api.dto.request.AuthRegisterRequest;
import cn.nopj.chaos_api.dto.response.AuthTokenResponse;
import cn.nopj.chaos_api.model.ApiResult; import cn.nopj.chaos_api.model.ApiResult;
import cn.nopj.chaos_api.service.AuthService; import cn.nopj.chaos_api.service.AuthService;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/** /**
* 认证管理 * 认证管理
* *
*/ */
@RestController @RestController
@@ -27,8 +24,7 @@ public class AuthController {
@Autowired @Autowired
private AuthService authService; private AuthService authService;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Autowired @Autowired
private UserConverter userConverter; private UserConverter userConverter;
@@ -49,15 +45,7 @@ public class AuthController {
* @return 登录结果 * @return 登录结果
*/ */
@PostMapping("/login") @PostMapping("/login")
public ApiResult<?> login(@RequestBody AuthLoginRequest authLoginRequest) { public ApiResult<AuthTokenResponse> login(@RequestBody AuthLoginRequest authLoginRequest) {
return ApiResult.success(authService.login(authLoginRequest.getUsername(), authLoginRequest.getPassword()));
String token = authService.login(authLoginRequest.getUsername(), authLoginRequest.getPassword());
Map<String, String> tokenMap = new HashMap<>();
tokenMap.put("token", token);
tokenMap.put("tokenHead", tokenHead);
return ApiResult.success(tokenMap);
} }
} }

View File

@@ -5,6 +5,7 @@ import cn.nopj.chaos_api.model.ApiResult;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
@@ -40,4 +41,10 @@ public class GlobalExceptionHandler {
log.error("服务器内部错误", ex); log.error("服务器内部错误", ex);
return ApiResult.failed("服务器内部错误,请联系管理员"); return ApiResult.failed("服务器内部错误,请联系管理员");
} }
@ExceptionHandler(AuthorizationDeniedException.class)
public ApiResult<?> handleAuthorizationDeniedException(AuthorizationDeniedException ex) {
return ApiResult.failed("权限不足,请求已登记");
}
} }

View File

@@ -1,6 +1,7 @@
package cn.nopj.chaos_api.service; package cn.nopj.chaos_api.service;
import cn.nopj.chaos_api.domain.entity.User; import cn.nopj.chaos_api.domain.entity.User;
import cn.nopj.chaos_api.dto.response.AuthTokenResponse;
public interface AuthService { public interface AuthService {
/** /**
@@ -12,5 +13,5 @@ public interface AuthService {
* 登录 * 登录
* @return 生成的 JWT token * @return 生成的 JWT token
*/ */
String login(String username, String password); AuthTokenResponse login(String username, String password);
} }

View File

@@ -4,12 +4,16 @@ package cn.nopj.chaos_api.service.impl;
import cn.nopj.chaos_api.common.constants.ErrorCode; import cn.nopj.chaos_api.common.constants.ErrorCode;
import cn.nopj.chaos_api.common.exceotion.BizException; import cn.nopj.chaos_api.common.exceotion.BizException;
import cn.nopj.chaos_api.domain.entity.User; import cn.nopj.chaos_api.domain.entity.User;
import cn.nopj.chaos_api.dto.response.AuthTokenResponse;
import cn.nopj.chaos_api.mapper.UserMapper; import cn.nopj.chaos_api.mapper.UserMapper;
import cn.nopj.chaos_api.service.AuthService; import cn.nopj.chaos_api.service.AuthService;
import cn.nopj.chaos_api.util.JwtTokenUtil; import cn.nopj.chaos_api.util.JwtTokenUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@@ -30,7 +34,8 @@ public class AuthServiceImpl implements AuthService {
private JwtTokenUtil jwtTokenUtil; private JwtTokenUtil jwtTokenUtil;
@Autowired @Autowired
private AuthenticationManager authenticationManager; private AuthenticationManager authenticationManager;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Override @Override
public User register(User user) { public User register(User user) {
@@ -49,7 +54,7 @@ public class AuthServiceImpl implements AuthService {
} }
@Override @Override
public String login(String username, String password) { public AuthTokenResponse login(String username, String password) {
// 尝试进行身份验证 // 尝试进行身份验证
try{ try{
Authentication authentication = authenticationManager.authenticate( Authentication authentication = authenticationManager.authenticate(
@@ -57,20 +62,24 @@ public class AuthServiceImpl implements AuthService {
); );
// 将认证结果保存在 SecurityContextHolder 中 // 将认证结果保存在 SecurityContextHolder 中
SecurityContextHolder.getContext().setAuthentication(authentication); SecurityContextHolder.getContext().setAuthentication(authentication);
// 获取用户详情 // 获取用户详情
UserDetails userDetails = (UserDetails) authentication.getPrincipal(); UserDetails userDetails = (UserDetails) authentication.getPrincipal();
if (!userDetails.isEnabled()) { if (!userDetails.isEnabled()) {
return null; throw new BizException(ErrorCode.USER_NOT_ENABLED);
} }
// 生成 JWT AuthTokenResponse res = new AuthTokenResponse();
return jwtTokenUtil.generateToken(userDetails); res.setToken(jwtTokenUtil.generateToken(userDetails));
res.setTokenHead(tokenHead);
return res;
}catch (BadCredentialsException e) {
throw new BizException(ErrorCode.USER_NOT_EXISTS_OR_PASSWORD_WRONG);
} catch (DisabledException e) {
throw new BizException(ErrorCode.USER_NOT_ENABLED);
} catch (Exception e) { } catch (Exception e) {
throw new BizException(ErrorCode.SYSTEM_ERROR);
throw new BizException(ErrorCode.USER_NOT_EXISTS); }
}
} }
} }