Лучший способ выполнить аутентификацию Oauth2 с помощью Java Spring + React NativeJAVA

Программисты JAVA общаются здесь
Ответить
Anonymous
 Лучший способ выполнить аутентификацию Oauth2 с помощью Java Spring + React Native

Сообщение Anonymous »

У меня возникли проблемы с созданием конечной точки в моем бэкэнде /login, которая должна аутентифицировать пользователя с помощью Github Oauth2. Фронтенд делается на React Native, и я пытаюсь это сделать, но не могу

Код: Выделить всё

WebBrowser.maybeCompleteAuthSession();

const discovery = {
authorizationEndpoint: 'https://github.com/login/oauth/authorize',
tokenEndpoint: 'https://github.com/login/oauth/access_token',
};

const scheme = 'coderpg';
const redirectUri = makeRedirectUri({ scheme });

const TOKEN_STORAGE_KEY = '@coderpg:token';

// Configurar interceptor do axios
axios.interceptors.request.use(
async (config) => {
const token = await AsyncStorage.getItem(TOKEN_STORAGE_KEY);
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);

export function useAuth() {
const queryClient = useQueryClient();

const [request, response, promptAsync] = useAuthRequest(
{
clientId: ENV.GITHUB_CLIENT_ID,
scopes: ['user'],
redirectUri,
usePKCE: false,
},
discovery
);

const loginBackendMutation = useMutation({
mutationFn: async (code: string) => {
console.log('🚀 Enviando para o backend:', {
code: code.substring(0, 10) + '...',
redirectUri: redirectUri,
});

const response = await axios.post(`${ENV.API_BASE_URL}/auth/login`, {
code,
redirectUri: redirectUri,
});

console.log('Resposta do backend:', {
success: response.data?.success,
hasToken: !!response.data?.data?.token,
authenticated: response.data?.data?.authStatus?.authenticated,
});

return response.data;
},
onSuccess: async (response) => {
const token = response.data?.token;

if (token) {
await AsyncStorage.setItem(TOKEN_STORAGE_KEY, token);
console.log('💾 Token salvo com sucesso:', token.substring(0, 8) + '...');
}

// Invalida queries para recarregar status
queryClient.invalidateQueries({ queryKey: ['authStatus'] });
},
onError: (error: any) => {
console.error('Erro ao logar no backend:', {
message: error.message,
response: error.response?.data,
status: error.response?.status,
});
}
});

// Query para buscar status de autenticação
const { data: authStatus, isLoading, refetch } = useQuery({
queryKey: ['authStatus'],
queryFn: async () => {
const token = await AsyncStorage.getItem(TOKEN_STORAGE_KEY);

if (!token) {
console.log('⚠️ Sem token salvo no storage');
return { authenticated: false };
}

console.log('🔍 Verificando status com token:', token.substring(0, 8) + '...');

try {
const status = await getAuthStatusUseCase.execute();
console.log('✅ Status recebido:', {
authenticated: status.authenticated,
userId: status.userId,
needsOnBoarding: status.needsOnBoarding,
});
return status;
} catch (error: any) {
console.error('❌ Erro ao buscar status:', error.message);

// Se der 401, remove o token inválido
if (error.response?.status === 401) {
console.log('🗑️ Token inválido, removendo...');
await AsyncStorage.removeItem(TOKEN_STORAGE_KEY);
}

return { authenticated: false };
}
},
retry: false,
enabled: true, // ← IMPORTANTE: Sempre habilitado
});

const logoutMutation = useMutation({
mutationFn: async () => {
console.log('👋 Fazendo logout...');
await logoutUseCase.execute();
await AsyncStorage.removeItem(TOKEN_STORAGE_KEY);
},
onSuccess: () =>  {
console.log('✅ Logout realizado com sucesso');
queryClient.invalidateQueries({ queryKey: ['authStatus'] });
queryClient.clear();
},
});

// Efeito para processar resposta do OAuth
useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
console.log('✅ Código OAuth recebido do GitHub');
loginBackendMutation.mutate(code);
} else if (response?.type === 'error') {
console.error('❌ Erro no AuthSession:', response.error);
} else if (response?.type === 'cancel') {
console.log('⚠️ Login cancelado pelo usuário');
}
}, [response]);

const login = async () => {
console.log('🔐 Iniciando login com GitHub...');
console.log('📍 Redirect URI:', redirectUri);
await promptAsync();
};

const finalAuthStatus = loginBackendMutation.data?.data?.authStatus || authStatus;
const isAuthenticated = finalAuthStatus?.authenticated || false;

console.log('📊 Estado final:', {
isAuthenticated,
isLoading: isLoading || loginBackendMutation.isPending,
needsOnBoarding: finalAuthStatus?.needsOnBoarding,
});

return {
authStatus: finalAuthStatus,
isLoading: isLoading || loginBackendMutation.isPending,
isAuthenticated,
loginData: loginBackendMutation.data?.data,
login,
logout: () => logoutMutation.mutateAsync(),
isLoggingOut: logoutMutation.isPending,
refetchAuthStatus: refetch,
};
}

e o endpoint:

@PostMapping(AuthRoute.LOGIN)
public ResponseEntity authenticateWithGithub(
@Valid @RequestBody MobileAuthRequest request
) {
log.info("Mobile login attempt - redirectUri: {}", request.getRedirectUri());

Map tokenResponse = mobileAuthService
.exchangeCodeForToken(request.getCode(), request.getRedirectUri());

String accessToken = (String) tokenResponse.get("access_token");
String refreshToken = (String) tokenResponse.get("refresh_token");

if (accessToken == null || accessToken.isBlank()) {
throw new BusinessException("Failed to get access token from GitHub");
}

Map githubUser = mobileAuthService.getGitHubUser(accessToken);

Long githubId = ((Number) githubUser.get("id")).longValue();
String githubUsername = (String) githubUser.get("login");
String email = (String) githubUser.get("email");
String avatarUrl = (String) githubUser.get("avatar_url");
String bio = (String) githubUser.get("bio");
String location = (String) githubUser.get("location");
String website = (String) githubUser.get("blog");
Integer publicRepos = (Integer) githubUser.get("public_repos");
Integer followers = (Integer) githubUser.get("followers");
Integer following = (Integer) githubUser.get("following");
String createdAt = (String) githubUser.get("created_at");

Optional  existingUser = userRepository.findByGitHubId(githubId);
User user;
boolean isNewUser = false;

if (existingUser.isPresent()) {
user = existingUser.get();
user.setGithubUsername(githubUsername);
user.setAvatarUrl(avatarUrl);
user.setBio(bio);
user.setLocation(location);
user.setWebsite(website);
user.setGithubPublicRepos(publicRepos);
user.setGithubFollowers(followers);
user.setGithubFollowing(following);
user.setLastSyncAt(LocalDateTime.now());
user = userRepository.save(user);

log.info("Existing user logged in: {} ({})", user.getGithubUsername(), user.getId());
} else {
isNewUser = true;
User newUser = User.builder()
.githubId(githubId)
.githubUsername(githubUsername)
.name(null)
.email(email)
.avatarUrl(avatarUrl)
.bio(bio)
.location(location)
.website(website)
.classType(null)
.githubPublicRepos(publicRepos)
.githubFollowers(followers)
.githubFollowing(following)
.githubCreatedAt(createdAt != null ?
LocalDateTime.parse(createdAt.replace("Z", "")) : null)
.active(true)
.build();

Context createUserContext = new Context(newUser);
user = createUserPort.execute(createUserContext);

log.info("New user created: {} ({})", user.getGithubUsername(), user.getId());
}

LocalDateTime expiresAt = mobileAuthService.calculateExpiresAt(tokenResponse.get("expires_in"));

Context saveTokenContext = new Context();
saveTokenContext.putProperty("userId", user.getId());
saveTokenContext.putProperty("accessToken", accessToken);
saveTokenContext.putProperty("refreshToken", refreshToken);
saveTokenContext.putProperty("expiresAt", expiresAt);
saveGitHubTokenPort.execute(saveTokenContext);

boolean needsOnBoarding = user.getName() == null || user.getClassType() == null;

AuthStatusResponse authStatus = AuthStatusResponse.builder()
.authenticated(true)
.userId(user.getId())
.githubUsername(user.getGithubUsername())
.name(user.getName())
.avatarUrl(user.getAvatarUrl())
.hasValidGitHubToken(true)
.needsOnBoarding(needsOnBoarding)
.build();

LoginResponse loginResponse = LoginResponse.builder()
.token(user.getId().toString())
.authStatus(authStatus)
.build();

return ResponseEntity
.status(isNewUser ? HttpStatus.CREATED :  HttpStatus.OK)
.body(ApiResponse.success(loginResponse));
}

package samukadev.coderpg.security.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import samukadev.coderpg.core.persistence.UserRepositoryPort;
import samukadev.coderpg.domain.User;
import samukadev.coderpg.security.oauth.GitHubOAuthService;

import java.io.IOException;
import java.util.Collections;
import java.util.Optional;
import java.util.UUID;

@Slf4j
@Component
@RequiredArgsConstructor
public class TokenAuthenticationFilter extends OncePerRequestFilter {

private final UserRepositoryPort userRepository;
private final GitHubOAuthService gitHubOAuthService;

@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {

String requestPath = request.getRequestURI();

try {
String token = extractTokenFromRequest(request);

if (token != null && !token.isBlank()) {
log.debug("🔑 Token encontrado para {}: {}", requestPath, token.substring(0, 8) + "...");
authenticateUser(token, request);
} else {
log.debug("⚠️ Nenhum token encontrado para {}", requestPath);
}
} catch (Exception e) {
log.error("❌ Error authenticating user from token for {}: {}", requestPath, e.getMessage());
}

filterChain.doFilter(request, response);
}

private String extractTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");

if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}

return null;
}

private void authenticateUser(String userId, HttpServletRequest request) {
try {
log.debug("🔍 Tentando autenticar usuário com ID: {}", userId.substring(0, 8) + "...");

UUID userUuid = UUID.fromString(userId);
Optional userOpt = userRepository.get(userUuid);

if (userOpt.isEmpty()) {
log.warn("⚠️ User not found for ID: {}", userId.substring(0, 8) + "...");
return;
}

User user = userOpt.get();
log.debug("✅ Usuário encontrado: {} (githubId: {})", user.getGithubUsername(), user.getGithubId());

boolean hasValidToken = gitHubOAuthService.hasValidToken(user.getId());

if (!hasValidToken) {
log.warn("⚠️ User {} does not have a valid GitHub token", userId.substring(0, 8) + "...");
return;
}

log.debug("✅ Token do GitHub válido para usuário {}", user.getGithubUsername());

// IMPORTANTE: Coloca o githubId como "name"  da autenticação
// O AuthController usa authentication.getName() para buscar o user
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
user.getGithubId().toString(),  // ← githubId aqui
null,
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
);

authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);

log.info("✅ User {} authenticated successfully via token", userId.substring(0, 8) + "...");

} catch (IllegalArgumentException e) {
log.warn("⚠️ Invalid UUID format: {}", userId);
} catch (Exception e) {
log.error("❌ Error during authentication: {}", e.getMessage(), e);
}
}
}
Я знаю, это похоже на код ИИ, потому что это действительно так. Я работаю над этим уже много часов и до сих пор не могу, мне нужно было использовать ИИ, пока не сойду с ума.
Если вы знаете какой-нибудь способ сделать это, помогите мне, пожалуйста.
если вы хотите увидеть репозитории, вот они:
https://github.com/franklin-samuel/CodeRPG
https://github.com/franklin-samuel/CodeRPG-Mobile

Подробнее здесь: https://stackoverflow.com/questions/798 ... act-native
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «JAVA»