auth-lifecycle.md 23 KB

认证功能生命周期详解

1. 控制器层 (Controller)

1.1 用户注册接口

接口定义UserAuthController.register()

@PostMapping("/register")
public ApiResult<UserRegisterResponse> register(@RequestBody @Validated UserRegisterRequest request) {
    return userAuthService.register(request);
}

功能职责

  • 接收用户注册请求
  • 参数校验(通过@Validated注解)
  • 调用服务层处理注册逻辑
  • 返回统一格式的注册结果

请求参数(UserRegisterRequest)

{
  "username": "enterprise_user1",
  "password": "Password@123",
  "confirmPassword": "Password@123",
  "mobile": "13812345678",
  "email": "test@example.com",
  "userType": "ENTERPRISE",
  "enterpriseInfo": {
    "enterpriseName": "测试企业有限公司",
    "creditCode": "91110000ABCDEFG123",
    "legalPerson": "张三",
    "contactPhone": "13812345678"
  }
}

响应结果(UserRegisterResponse)

{
  "code": 200,
  "message": "注册成功",
  "data": {
    "userId": "1234567890",
    "username": "enterprise_user1",
    "userType": "ENTERPRISE",
    "registrationTime": "2023-08-15T10:30:45Z"
  }
}

1.2 用户登录接口

接口定义UserAuthController.login()

@PostMapping("/login")
public ApiResult<LoginResponse> login(@RequestBody @Validated LoginRequest request) {
    return userAuthService.login(request);
}

功能职责

  • 接收用户登录请求
  • 参数校验
  • 调用服务层处理登录逻辑
  • 返回统一格式的登录结果(包含JWT令牌)

请求参数(LoginRequest)

{
  "username": "enterprise_user1",
  "password": "Password@123"
}

响应结果(LoginResponse)

{
  "code": 200,
  "message": "登录成功",
  "data": {
    "userId": "1234567890",
    "username": "enterprise_user1",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expiresIn": 3600
  }
}

1.3 刷新令牌接口

接口定义UserAuthController.refreshToken()

@PostMapping("/refresh-token")
public ApiResult<RefreshTokenResponse> refreshToken(@RequestBody @Validated RefreshTokenRequest request) {
    return userAuthService.refreshToken(request);
}

1.4 登出接口

接口定义UserAuthController.logout()

@PostMapping("/logout")
public ApiResult<Void> logout(@RequestHeader("Authorization") String token) {
    return userAuthService.logout(token);
}

2. 服务层 (Service)

2.1 用户认证服务

主要实现类UserAuthServiceImpl

2.1.1 注册业务流程 register()

@Override
@Transactional(rollbackFor = Exception.class)
public ApiResult<UserRegisterResponse> register(UserRegisterRequest request) {
    // 1. 检查用户名是否已存在
    if (userMapper.existsByUsername(request.getUsername())) {
        return ApiResult.failed("用户名已存在");
    }
    
    // 2. 检查手机号是否已注册
    if (userMapper.existsByMobile(request.getMobile())) {
        return ApiResult.failed("手机号已注册");
    }
    
    // 3. 密码加密
    String encryptedPassword = passwordEncoder.encode(request.getPassword());
    
    // 4. 构建用户对象
    User user = new User();
    user.setUserId(IdGenerator.generateId());
    user.setUsername(request.getUsername());
    user.setPassword(encryptedPassword);
    user.setMobile(request.getMobile());
    user.setEmail(request.getEmail());
    user.setUserType(request.getUserType());
    user.setStatus(UserStatus.NORMAL);
    user.setCreateTime(new Date());
    
    // 5. 保存用户信息
    userMapper.insert(user);
    
    // 6. 企业用户需保存企业信息
    if (UserType.ENTERPRISE.equals(request.getUserType())) {
        EnterpriseInfo enterpriseInfo = request.getEnterpriseInfo();
        enterpriseInfo.setUserId(user.getUserId());
        enterpriseInfo.setVerifyStatus(VerifyStatus.UNVERIFIED);
        enterpriseInfoMapper.insert(enterpriseInfo);
    }
    
    // 7. 分配默认角色权限
    roleService.assignDefaultRole(user.getUserId(), user.getUserType());
    
    // 8. 发送注册成功通知
    notificationService.sendRegistrationNotification(user);
    
    // 9. 构建返回结果
    UserRegisterResponse response = new UserRegisterResponse();
    response.setUserId(user.getUserId());
    response.setUsername(user.getUsername());
    response.setUserType(user.getUserType());
    response.setRegistrationTime(user.getCreateTime());
    
    return ApiResult.success("注册成功", response);
}

处理逻辑

  1. 用户名和手机号唯一性校验
  2. 密码加密存储
  3. 保存用户基本信息
  4. 保存企业附加信息(如适用)
  5. 分配默认角色和权限
  6. 发送注册成功通知
  7. 构建返回结果

2.1.2 登录业务流程 login()

@Override
public ApiResult<LoginResponse> login(LoginRequest request) {
    // 1. 根据用户名查询用户
    User user = userMapper.selectByUsername(request.getUsername());
    if (user == null) {
        return ApiResult.failed("用户名或密码错误");
    }
    
    // 2. 检查用户状态
    if (!UserStatus.NORMAL.equals(user.getStatus())) {
        return ApiResult.failed("账号已被禁用,请联系管理员");
    }
    
    // 3. 验证密码
    if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
        // 4. 记录登录失败次数
        loginFailService.recordFailedLogin(user.getUserId());
        return ApiResult.failed("用户名或密码错误");
    }
    
    // 5. 清除登录失败记录
    loginFailService.clearFailedLogin(user.getUserId());
    
    // 6. 生成JWT令牌
    String token = jwtTokenProvider.generateToken(user);
    String refreshToken = jwtTokenProvider.generateRefreshToken(user);
    
    // 7. 保存令牌信息
    tokenService.saveToken(user.getUserId(), token, refreshToken);
    
    // 8. 记录登录日志
    auditLogService.logLogin(user.getUserId(), request.getLoginIp(), LoginResult.SUCCESS);
    
    // 9. 构建返回结果
    LoginResponse response = new LoginResponse();
    response.setUserId(user.getUserId());
    response.setUsername(user.getUsername());
    response.setToken(token);
    response.setRefreshToken(refreshToken);
    response.setExpiresIn(jwtTokenProvider.getExpirationTime());
    
    return ApiResult.success("登录成功", response);
}

处理逻辑

  1. 验证用户存在性
  2. 检查用户状态(是否被禁用)
  3. 验证密码正确性
  4. 处理登录失败记录
  5. 生成JWT令牌和刷新令牌
  6. 保存令牌信息到缓存
  7. 记录登录日志
  8. 构建返回结果

2.2 JWT令牌服务

主要实现类JwtTokenProviderImpl

2.2.1 生成令牌 generateToken()

@Override
public String generateToken(User user) {
    Date now = new Date();
    Date expiryDate = new Date(now.getTime() + tokenExpirationMs);
    
    Map<String, Object> claims = new HashMap<>();
    claims.put("userId", user.getUserId());
    claims.put("username", user.getUsername());
    claims.put("userType", user.getUserType());
    
    return Jwts.builder()
        .setClaims(claims)
        .setIssuedAt(now)
        .setExpiration(expiryDate)
        .signWith(SignatureAlgorithm.HS256, jwtSecret)
        .compact();
}

2.2.2 验证令牌 validateToken()

@Override
public boolean validateToken(String token) {
    try {
        Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
        return true;
    } catch (SignatureException ex) {
        log.error("Invalid JWT signature");
    } catch (MalformedJwtException ex) {
        log.error("Invalid JWT token");
    } catch (ExpiredJwtException ex) {
        log.error("Expired JWT token");
    } catch (UnsupportedJwtException ex) {
        log.error("Unsupported JWT token");
    } catch (IllegalArgumentException ex) {
        log.error("JWT claims string is empty");
    }
    return false;
}

3. 数据访问层 (Mapper/Repository)

3.1 用户数据访问

主要实现类UserMapper

@Mapper
public interface UserMapper {
    /**
     * 插入用户
     */
    int insert(User user);
    
    /**
     * 根据用户名查询
     */
    User selectByUsername(String username);
    
    /**
     * 检查用户名是否存在
     */
    boolean existsByUsername(String username);
    
    /**
     * 检查手机号是否存在
     */
    boolean existsByMobile(String mobile);
    
    /**
     * 根据ID查询
     */
    User selectById(String userId);
    
    /**
     * 更新用户信息
     */
    int update(User user);
}

3.2 企业信息数据访问

主要实现类EnterpriseInfoMapper

@Mapper
public interface EnterpriseInfoMapper {
    /**
     * 插入企业信息
     */
    int insert(EnterpriseInfo enterpriseInfo);
    
    /**
     * 根据用户ID查询
     */
    EnterpriseInfo selectByUserId(String userId);
    
    /**
     * 根据统一社会信用代码查询
     */
    EnterpriseInfo selectByCreditCode(String creditCode);
    
    /**
     * 更新企业信息
     */
    int update(EnterpriseInfo enterpriseInfo);
}

4. 安全拦截层

4.1 JWT认证过滤器

主要实现类JwtAuthenticationFilter

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenProvider jwtTokenProvider;
    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            // 1. 从请求中获取JWT令牌
            String jwt = getJwtFromRequest(request);

            // 2. 验证令牌有效性
            if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) {
                // 3. 从令牌中获取用户ID
                String userId = jwtTokenProvider.getUserIdFromToken(jwt);

                // 4. 加载用户详情
                UserDetails userDetails = userDetailsService.loadUserById(userId);
                
                // 5. 创建认证对象
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                // 6. 设置认证上下文
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            logger.error("Could not set user authentication in security context", ex);
        }

        // 7. 继续过滤器链
        filterChain.doFilter(request, response);
    }

    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

处理逻辑

  1. 从HTTP请求头中提取JWT令牌
  2. 验证令牌的有效性
  3. 从令牌中解析用户信息
  4. 加载用户详情和权限
  5. 创建认证对象
  6. 设置Spring Security上下文
  7. 继续过滤器链处理

5. 实体类层 (Entity/Model)

5.1 用户实体

@Data
@Table(name = "t_user")
public class User implements Serializable {
    
    @Id
    private String userId;
    
    private String username;
    
    private String password;
    
    private String mobile;
    
    private String email;
    
    @Enumerated(EnumType.STRING)
    private UserType userType;
    
    @Enumerated(EnumType.STRING)
    private UserStatus status;
    
    private Date createTime;
    
    private Date updateTime;
}

5.2 企业信息实体

@Data
@Table(name = "t_enterprise_info")
public class EnterpriseInfo implements Serializable {
    
    @Id
    private String id;
    
    private String userId;
    
    private String enterpriseName;
    
    private String creditCode;
    
    private String legalPerson;
    
    private String contactPhone;
    
    @Enumerated(EnumType.STRING)
    private VerifyStatus verifyStatus;
    
    private Date createTime;
    
    private Date updateTime;
}

6. 异常处理

6.1 全局异常处理器

主要实现类GlobalExceptionHandler

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 处理参数验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResult<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        StringBuilder sb = new StringBuilder("参数校验失败:");
        
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
        }
        
        String msg = sb.toString();
        log.warn(msg);
        return ApiResult.validateFailed(msg);
    }

    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public ApiResult<String> handleBusinessException(BusinessException ex) {
        log.error("业务异常", ex);
        return ApiResult.failed(ex.getCode(), ex.getMessage());
    }

    /**
     * 处理未授权异常
     */
    @ExceptionHandler(AccessDeniedException.class)
    public ApiResult<String> handleAccessDeniedException(AccessDeniedException ex) {
        log.error("权限不足", ex);
        return ApiResult.forbidden("没有权限执行此操作");
    }

    /**
     * 处理其他未知异常
     */
    @ExceptionHandler(Exception.class)
    public ApiResult<String> handleException(Exception ex) {
        log.error("系统异常", ex);
        return ApiResult.failed("服务器内部错误,请稍后重试");
    }
}

7. 缓存层

7.1 令牌缓存服务

主要实现类TokenServiceImpl

@Service
@RequiredArgsConstructor
public class TokenServiceImpl implements TokenService {

    private final RedisTemplate<String, Object> redisTemplate;
    private final JwtTokenProvider jwtTokenProvider;

    private static final String TOKEN_KEY_PREFIX = "auth:token:";
    private static final String REFRESH_TOKEN_KEY_PREFIX = "auth:refresh_token:";

    @Override
    public void saveToken(String userId, String token, String refreshToken) {
        // 保存访问令牌,过期时间与JWT令牌一致
        String tokenKey = TOKEN_KEY_PREFIX + userId;
        redisTemplate.opsForValue().set(tokenKey, token, jwtTokenProvider.getExpirationTime(), TimeUnit.MILLISECONDS);
        
        // 保存刷新令牌,过期时间更长
        String refreshTokenKey = REFRESH_TOKEN_KEY_PREFIX + userId;
        redisTemplate.opsForValue().set(refreshTokenKey, refreshToken, jwtTokenProvider.getRefreshExpirationTime(), TimeUnit.MILLISECONDS);
    }

    @Override
    public String getToken(String userId) {
        String key = TOKEN_KEY_PREFIX + userId;
        Object token = redisTemplate.opsForValue().get(key);
        return token != null ? token.toString() : null;
    }

    @Override
    public String getRefreshToken(String userId) {
        String key = REFRESH_TOKEN_KEY_PREFIX + userId;
        Object refreshToken = redisTemplate.opsForValue().get(key);
        return refreshToken != null ? refreshToken.toString() : null;
    }

    @Override
    public void removeToken(String userId) {
        String tokenKey = TOKEN_KEY_PREFIX + userId;
        String refreshTokenKey = REFRESH_TOKEN_KEY_PREFIX + userId;
        
        redisTemplate.delete(tokenKey);
        redisTemplate.delete(refreshTokenKey);
    }
}

8. 日志审计

8.1 登录日志服务

主要实现类AuditLogServiceImpl

@Service
@RequiredArgsConstructor
public class AuditLogServiceImpl implements AuditLogService {

    private final AuditLogMapper auditLogMapper;

    @Override
    public void logLogin(String userId, String ip, LoginResult result) {
        AuditLog log = new AuditLog();
        log.setUserId(userId);
        log.setOperationType(OperationType.LOGIN);
        log.setOperationResult(result.toString());
        log.setIp(ip);
        log.setCreateTime(new Date());
        
        auditLogMapper.insert(log);
    }

    @Override
    public void logLogout(String userId, String ip) {
        AuditLog log = new AuditLog();
        log.setUserId(userId);
        log.setOperationType(OperationType.LOGOUT);
        log.setOperationResult(LoginResult.SUCCESS.toString());
        log.setIp(ip);
        log.setCreateTime(new Date());
        
        auditLogMapper.insert(log);
    }
}

9. 完整认证流程时序

┌──────────┐          ┌───────────────┐          ┌─────────────┐          ┌──────────┐          ┌─────────┐
│  客户端  │          │ 控制器层      │          │  服务层     │          │ 数据访问层│          │ 缓存层  │
└────┬─────┘          └───────┬───────┘          └──────┬──────┘          └────┬─────┘          └────┬────┘
     │                        │                          │                      │                     │
     │   1. 登录请求          │                          │                      │                     │
     │ ───────────────────────>                          │                      │                     │
     │                        │                          │                      │                     │
     │                        │  2. 调用登录服务         │                      │                     │
     │                        │ ─────────────────────────>                      │                     │
     │                        │                          │                      │                     │
     │                        │                          │  3. 查询用户         │                     │
     │                        │                          │ ─────────────────────>                     │
     │                        │                          │                      │                     │
     │                        │                          │  4. 返回用户数据     │                     │
     │                        │                          │ <─────────────────────                     │
     │                        │                          │                      │                     │
     │                        │                          │  5. 生成令牌         │                     │
     │                        │                          │ ─────────────────────────────────────────> │
     │                        │                          │                      │                     │
     │                        │                          │  6. 令牌缓存完成     │                     │
     │                        │                          │ <───────────────────────────────────────── │
     │                        │                          │                      │                     │
     │                        │  7. 返回登录结果         │                      │                     │
     │                        │ <─────────────────────────                      │                     │
     │                        │                          │                      │                     │
     │   8. 返回令牌          │                          │                      │                     │
     │ <───────────────────────                          │                      │                     │
     │                        │                          │                      │                     │
     │   9. 后续请求(带令牌)  │                          │                      │                     │
     │ ───────────────────────>                          │                      │                     │
     │                        │                          │                      │                     │
     │                        │  10.令牌校验            │                      │                     │
     │                        │ ─────────────────────────>                      │                     │
     │                        │                          │                      │                     │
     │                        │  11.校验通过            │                      │                     │
     │                        │ <─────────────────────────                      │                     │
     │                        │                          │                      │                     │
     │   12.返回请求结果      │                          │                      │                     │
     │ <───────────────────────                          │                      │                     │
     │                        │                          │                      │                     │