增加登录的异常,修复重复调用error
This commit is contained in:
parent
e3aa3d1aca
commit
527b7563c0
@ -32,7 +32,12 @@ public enum ResponseCode {
|
|||||||
DEPENDENCY_INJECTION_SERVICE_NOT_FOUND(1100, "dependency.injection.service.not.found"),
|
DEPENDENCY_INJECTION_SERVICE_NOT_FOUND(1100, "dependency.injection.service.not.found"),
|
||||||
DEPENDENCY_INJECTION_REPOSITORY_NOT_FOUND(1101, "dependency.injection.repository.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_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;
|
private final int code;
|
||||||
|
|
||||||
|
|||||||
@ -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.api.Response;
|
||||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
|
import io.jsonwebtoken.MalformedJwtException;
|
||||||
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.context.MessageSource;
|
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.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
import java.security.SignatureException;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
public class GlobalExceptionHandler {
|
public class GlobalExceptionHandler {
|
||||||
@ -37,4 +41,16 @@ public class GlobalExceptionHandler {
|
|||||||
log.error("Unexpected error occurred", e);
|
log.error("Unexpected error occurred", e);
|
||||||
return Response.error(ResponseCode.ERROR);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,19 +1,32 @@
|
|||||||
package com.qqchen.deploy.backend.framework.security.filter;
|
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.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 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.FilterChain;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -43,37 +56,58 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
|
||||||
try {
|
try {
|
||||||
// String tenantId = request.getHeader("X-Tenant-ID");
|
String jwt = getJwtFromRequest(request);
|
||||||
// if (tenantId != null && !tenantId.isEmpty()) {
|
if (StringUtils.hasText(jwt)) {
|
||||||
// TenantContext.setTenantId(tenantId);
|
String username = jwtTokenUtil.getUsernameFromToken(jwt);
|
||||||
// }
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||||
|
|
||||||
if (jwtTokenUtil.validateToken(token, userDetails)) {
|
if (jwtTokenUtil.validateToken(jwt, userDetails)) {
|
||||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
||||||
userDetails, null, userDetails.getAuthorities());
|
userDetails, null, userDetails.getAuthorities());
|
||||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
chain.doFilter(request, response);
|
chain.doFilter(request, response);
|
||||||
|
} catch (ExpiredJwtException e) {
|
||||||
|
handleAuthenticationException(response, JwtAuthenticationException.expired());
|
||||||
|
} catch (SignatureException | MalformedJwtException e) {
|
||||||
|
handleAuthenticationException(response, JwtAuthenticationException.invalid());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("JWT authentication failed", e);
|
log.error("JWT authentication failed", e);
|
||||||
SecurityContextHolder.clearContext();
|
handleAuthenticationException(response, new SystemException("JWT authentication failed", e));
|
||||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
|
||||||
} finally {
|
} finally {
|
||||||
TenantContext.clear();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -18,27 +18,34 @@ import org.springframework.stereotype.Component;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@Component
|
||||||
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||||
|
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public CustomAuthenticationEntryPoint(ObjectMapper objectMapper) {
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@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.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||||
Response<?> result;
|
|
||||||
if (authException instanceof BadCredentialsException) {
|
Response<?> errorResponse = Response.error(ResponseCode.UNAUTHORIZED);
|
||||||
result = Response.error(ResponseCode.UNAUTHORIZED, MessageUtils.getMessage(ResponseCode.UNAUTHORIZED.getMessageKey()));
|
objectMapper.writeValue(response.getOutputStream(), errorResponse);
|
||||||
} 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()));
|
|
||||||
}
|
}
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,3 +33,8 @@ dependency.injection.service.not.found=找不到实体 {0} 对应的服务 (尝
|
|||||||
dependency.injection.repository.not.found=找不到实体 {0} 对应的Repository: {1}
|
dependency.injection.repository.not.found=找不到实体 {0} 对应的Repository: {1}
|
||||||
dependency.injection.converter.not.found=找不到实体 {0} 对应的Converter: {1}
|
dependency.injection.converter.not.found=找不到实体 {0} 对应的Converter: {1}
|
||||||
dependency.injection.entitypath.failed=初始化实体 {0} 的EntityPath失败: {1}
|
dependency.injection.entitypath.failed=初始化实体 {0} 的EntityPath失败: {1}
|
||||||
|
|
||||||
|
# JWT相关
|
||||||
|
jwt.token.expired=登录已过期,请重新登录
|
||||||
|
jwt.token.invalid=无效的登录凭证
|
||||||
|
jwt.token.missing=未提供登录凭证
|
||||||
@ -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.repository.not.found=No repository found for entity {0}: {1}
|
||||||
dependency.injection.converter.not.found=No converter 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}
|
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
|
||||||
Loading…
Reference in New Issue
Block a user