增加登录的异常,修复重复调用error

This commit is contained in:
dengqichen 2024-11-29 18:39:46 +08:00
parent e3aa3d1aca
commit 527b7563c0
7 changed files with 142 additions and 41 deletions

View File

@ -32,7 +32,12 @@ public enum ResponseCode {
DEPENDENCY_INJECTION_SERVICE_NOT_FOUND(1100, "dependency.injection.service.not.found"),
DEPENDENCY_INJECTION_REPOSITORY_NOT_FOUND(1101, "dependency.injection.repository.not.found"),
DEPENDENCY_INJECTION_CONVERTER_NOT_FOUND(1102, "dependency.injection.converter.not.found"),
DEPENDENCY_INJECTION_ENTITYPATH_FAILED(1103, "dependency.injection.entitypath.failed");
DEPENDENCY_INJECTION_ENTITYPATH_FAILED(1103, "dependency.injection.entitypath.failed"),
// JWT相关错误码 (2100-2199)
JWT_EXPIRED(2100, "jwt.token.expired"),
JWT_INVALID(2101, "jwt.token.invalid"),
JWT_MISSING(2102, "jwt.token.missing");
private final int code;

View File

@ -2,6 +2,8 @@ package com.qqchen.deploy.backend.framework.exception;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
@ -9,6 +11,8 @@ import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.security.SignatureException;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ -37,4 +41,16 @@ public class GlobalExceptionHandler {
log.error("Unexpected error occurred", e);
return Response.error(ResponseCode.ERROR);
}
@ExceptionHandler(ExpiredJwtException.class)
public Response<?> handleExpiredJwtException(ExpiredJwtException e) {
log.warn("JWT token expired", e);
return Response.error(ResponseCode.JWT_EXPIRED);
}
@ExceptionHandler({SignatureException.class, MalformedJwtException.class})
public Response<?> handleInvalidJwtException(Exception e) {
log.warn("Invalid JWT token", e);
return Response.error(ResponseCode.JWT_INVALID);
}
}

View File

@ -0,0 +1,29 @@
package com.qqchen.deploy.backend.framework.exception;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
/**
* JWT认证异常
*/
public class JwtAuthenticationException extends BusinessException {
public JwtAuthenticationException(ResponseCode errorCode) {
super(errorCode);
}
public JwtAuthenticationException(ResponseCode errorCode, Object[] args) {
super(errorCode, args);
}
public static JwtAuthenticationException expired() {
return new JwtAuthenticationException(ResponseCode.JWT_EXPIRED);
}
public static JwtAuthenticationException invalid() {
return new JwtAuthenticationException(ResponseCode.JWT_INVALID);
}
public static JwtAuthenticationException missing() {
return new JwtAuthenticationException(ResponseCode.JWT_MISSING);
}
}

View File

@ -1,19 +1,32 @@
package com.qqchen.deploy.backend.framework.security.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.context.TenantContext;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.exception.JwtAuthenticationException;
import com.qqchen.deploy.backend.framework.exception.SystemException;
import com.qqchen.deploy.backend.framework.security.util.JwtTokenUtil;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.security.SignatureException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
@ -43,37 +56,58 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
try {
// String tenantId = request.getHeader("X-Tenant-ID");
// if (tenantId != null && !tenantId.isEmpty()) {
// TenantContext.setTenantId(tenantId);
// }
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
String token = authHeader.substring(7);
String username = jwtTokenUtil.getUsernameFromToken(token);
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt)) {
String username = jwtTokenUtil.getUsernameFromToken(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(token, userDetails)) {
if (jwtTokenUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
} catch (ExpiredJwtException e) {
handleAuthenticationException(response, JwtAuthenticationException.expired());
} catch (SignatureException | MalformedJwtException e) {
handleAuthenticationException(response, JwtAuthenticationException.invalid());
} catch (Exception e) {
log.error("JWT authentication failed", e);
SecurityContextHolder.clearContext();
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
handleAuthenticationException(response, new SystemException("JWT authentication failed", e));
} finally {
TenantContext.clear();
}
}
private void handleAuthenticationException(HttpServletResponse response, Exception e) {
try {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
Response<?> errorResponse;
if (e instanceof BusinessException be) {
errorResponse = Response.error(be.getErrorCode());
} else {
errorResponse = Response.error(ResponseCode.UNAUTHORIZED);
}
new ObjectMapper().writeValue(response.getOutputStream(), errorResponse);
} catch (IOException ex) {
log.error("Failed to write error response", ex);
}
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (!StringUtils.hasText(bearerToken) || !bearerToken.startsWith("Bearer ")) {
return null;
}
return bearerToken.substring(7);
}
}

View File

@ -18,27 +18,34 @@ import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Component
@Slf4j
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final ObjectMapper objectMapper = new ObjectMapper();
private final ObjectMapper objectMapper;
public CustomAuthenticationEntryPoint(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
if (!isErrorDispatch(request)) {
log.error("Custom authentication entry point error", authException);
}
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
Response<?> result;
if (authException instanceof BadCredentialsException) {
result = Response.error(ResponseCode.UNAUTHORIZED, MessageUtils.getMessage(ResponseCode.UNAUTHORIZED.getMessageKey()));
} else if (authException instanceof InternalAuthenticationServiceException) {
result = Response.error(ResponseCode.USER_NOT_FOUND, MessageUtils.getMessage(ResponseCode.USER_NOT_FOUND.getMessageKey()));
} else if (authException instanceof InsufficientAuthenticationException) {
result = Response.error(ResponseCode.AUTH_REQUIRED, MessageUtils.getMessage(ResponseCode.AUTH_REQUIRED.getMessageKey()));
} else {
result = Response.error(ResponseCode.UNAUTHORIZED, MessageUtils.getMessage(ResponseCode.UNAUTHORIZED.getMessageKey()));
Response<?> errorResponse = Response.error(ResponseCode.UNAUTHORIZED);
objectMapper.writeValue(response.getOutputStream(), errorResponse);
}
log.error("Custom authentication entry point error", authException);
response.getWriter().write(objectMapper.writeValueAsString(result));
private boolean isErrorDispatch(HttpServletRequest request) {
String errorPath = "/error";
String requestUri = request.getRequestURI();
return requestUri != null && requestUri.equals(errorPath);
}
}

View File

@ -33,3 +33,8 @@ dependency.injection.service.not.found=找不到实体 {0} 对应的服务 (尝
dependency.injection.repository.not.found=找不到实体 {0} 对应的Repository: {1}
dependency.injection.converter.not.found=找不到实体 {0} 对应的Converter: {1}
dependency.injection.entitypath.failed=初始化实体 {0} 的EntityPath失败: {1}
# JWT相关
jwt.token.expired=登录已过期,请重新登录
jwt.token.invalid=无效的登录凭证
jwt.token.missing=未提供登录凭证

View File

@ -33,3 +33,8 @@ dependency.injection.service.not.found=No service found for entity {0} (tried be
dependency.injection.repository.not.found=No repository found for entity {0}: {1}
dependency.injection.converter.not.found=No converter found for entity {0}: {1}
dependency.injection.entitypath.failed=Failed to initialize EntityPath for entity {0}: {1}
# JWT
jwt.token.expired=Login expired, please login again
jwt.token.invalid=Invalid token
jwt.token.missing=No token provided