package com.tourguide.security; import com.fasterxml.jackson.databind.ObjectMapper; import com.tourguide.common.api.ApiResponse; import com.tourguide.modules.user.domain.ClientType; import com.tourguide.modules.user.domain.UserRole; import com.tourguide.modules.user.entity.AdminUser; import com.tourguide.modules.user.entity.GuideUser; import com.tourguide.modules.user.entity.MiniUser; import com.tourguide.modules.user.service.AdminUserService; import com.tourguide.modules.user.service.GuideUserService; import com.tourguide.modules.user.service.MiniUserService; import com.tourguide.modules.system.service.AdminPermissionService; import org.springframework.http.MediaType; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenService jwtTokenService; private final TokenSessionService tokenSessionService; private final AdminUserService adminUserService; private final AdminPermissionService adminPermissionService; private final MiniUserService miniUserService; private final GuideUserService guideUserService; private final ObjectMapper objectMapper; public JwtAuthenticationFilter(JwtTokenService jwtTokenService, TokenSessionService tokenSessionService, AdminUserService adminUserService, AdminPermissionService adminPermissionService, MiniUserService miniUserService, GuideUserService guideUserService, ObjectMapper objectMapper) { this.jwtTokenService = jwtTokenService; this.tokenSessionService = tokenSessionService; this.adminUserService = adminUserService; this.adminPermissionService = adminPermissionService; this.miniUserService = miniUserService; this.guideUserService = guideUserService; this.objectMapper = objectMapper; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = resolveToken(request); if (!StringUtils.hasText(token)) { filterChain.doFilter(request, response); return; } try { TokenPayload payload = jwtTokenService.parse(token); ClientType expectedClientType = resolveExpectedClientType(request.getRequestURI()); if (expectedClientType != null && payload.getClientType() != expectedClientType) { writeUnauthorized(response, "Token端类型不匹配"); return; } if (!tokenSessionService.isActive(payload)) { writeUnauthorized(response, "Token已失效"); return; } AppUserPrincipal principal = loadPrincipal(payload, request.getRequestURI()); if (!principal.getRole().equals(payload.getRole())) { writeUnauthorized(response, "Token权限信息已变更"); return; } UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(principal, token, principal.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); filterChain.doFilter(request, response); } catch (Exception exception) { SecurityContextHolder.clearContext(); writeUnauthorized(response, "Token无效或已过期"); } } /** 根据 Token 端类型读取对应用户表,任何端都不会回退查询其他用户表。 */ private AppUserPrincipal loadPrincipal(TokenPayload payload, String requestUri) { if (payload.getClientType() == ClientType.ADMIN) { AdminUser user = adminUserService.findEnabledById(payload.getUserId()); return new AppUserPrincipal(user.getId(), user.getUsername(), user.getRoleCode(), ClientType.ADMIN, adminPermissionService.getMenuCodes(user.getRoleCode())); } if (payload.getClientType() == ClientType.MINI) { MiniUser user = miniUserService.findEnabledById(payload.getUserId()); return new AppUserPrincipal(user.getId(), user.getUsername(), UserRole.MINI_USER.name(), ClientType.MINI); } GuideUser user = allowsDisabledGuide(requestUri) ? guideUserService.findWorkbenchAccessibleById(payload.getUserId()) : guideUserService.findEnabledById(payload.getUserId()); return new AppUserPrincipal(user.getId(), user.getUsername(), UserRole.GUIDE_USER.name(), ClientType.GUIDE); } private boolean allowsDisabledGuide(String requestUri) { return requestUri.startsWith("/api/guide/workbench") || requestUri.startsWith("/api/guide/notifications") || requestUri.equals("/api/guide/auth/logout"); } private ClientType resolveExpectedClientType(String uri) { if (uri.startsWith("/api/admin/")) { return ClientType.ADMIN; } if (uri.startsWith("/api/mini/")) { return ClientType.MINI; } if (uri.startsWith("/api/guide/")) { return ClientType.GUIDE; } return null; } private String resolveToken(HttpServletRequest request) { String authorization = request.getHeader("Authorization"); return StringUtils.hasText(authorization) && authorization.startsWith("Bearer ") ? authorization.substring(7) : null; } private void writeUnauthorized(HttpServletResponse response, String message) throws IOException { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); objectMapper.writeValue(response.getWriter(), ApiResponse.failure(401, message)); } }