Skip to content

Commit

Permalink
management-service: oauth redesigned to use id token instead of auth …
Browse files Browse the repository at this point in the history
…token (#143)

* management-service: oauth redesigned to use id token instead of auth token

* management-service: final version of oauth :)

* management-service: refactor

* management-service: refactor

* management-service: refactor

* management-service: removed unused imports

* management-service: refactor
  • Loading branch information
MDybek authored Dec 4, 2024
1 parent cc086b4 commit 75862a1
Show file tree
Hide file tree
Showing 16 changed files with 271 additions and 412 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
package pl.pwr.zpi.auth.controller;

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import pl.pwr.zpi.auth.oauth2.OauthRefreshTokenService;
import pl.pwr.zpi.auth.oauth2.GoogleOauthTokenService;
import pl.pwr.zpi.auth.service.AuthenticationService;

import java.io.IOException;
Expand All @@ -19,7 +15,7 @@
public class AuthenticationController {

private final AuthenticationService service;
private final OauthRefreshTokenService oauthRefreshTokenService;
private final GoogleOauthTokenService oauthRefreshTokenService;

@GetMapping("/api/v1/auth/user-details")
public ResponseEntity<?> getUser(@CookieValue("authToken") String authToken) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package pl.pwr.zpi.auth;
package pl.pwr.zpi.auth.oauth2;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package pl.pwr.zpi.auth.oauth2;

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
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.beans.factory.annotation.Value;
import org.springframework.http.ResponseCookie;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import pl.pwr.zpi.security.cookie.CookieService;
import pl.pwr.zpi.user.data.User;
import pl.pwr.zpi.user.dto.Provider;
import pl.pwr.zpi.user.service.UserService;

import java.io.IOException;
import java.time.Instant;

@Component
@RequiredArgsConstructor
@Slf4j
public class GoogleOAuthLoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

@Value("${oauth2.google.redirect-uri}")
private String redirectUri;

private final UserService userService;
private final OAuth2AuthorizedClientService authorizedClientService;
private final CookieService cookieService;
private final GoogleOauthTokenService googleOauthTokenService;

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if (!(authentication instanceof OAuth2AuthenticationToken)) {
throw new ServletException("Unsupported authentication type.");
}

OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
String registrationId = oauthToken.getAuthorizedClientRegistrationId();
if (registrationId == null) {
throw new ServletException("Missing registration ID.");
}

OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient(registrationId, authentication.getName());
DefaultOidcUser oidcUser = (DefaultOidcUser) authentication.getPrincipal();
OAuth2RefreshToken refreshToken = authorizedClient.getRefreshToken();

if (refreshToken == null) {
throw new RuntimeException("Refresh token is null. Please contact the administrator.");
}

addCookiesToResponse(response, oidcUser.getIdToken().getTokenValue(), oidcUser.getIdToken().getExpiresAt(), refreshToken.getTokenValue());

createOrUpdateUser(oidcUser);

getRedirectStrategy().sendRedirect(request, response, redirectUri);
}

private void addCookiesToResponse(HttpServletResponse response, String authToken, Instant expiresAt, String refreshToken) {
ResponseCookie authCookie = cookieService.createAuthCookie(authToken, expiresAt);
ResponseCookie refreshCookie = cookieService.createRefreshCookie(refreshToken);

response.addHeader("Set-Cookie", authCookie.toString());
response.addHeader("Set-Cookie", refreshCookie.toString());
}

private void createOrUpdateUser(DefaultOidcUser oidcUser) {
Payload payload = googleOauthTokenService.decodeToken(oidcUser.getIdToken().getTokenValue());

String email = payload.getEmail();
String nickname = (String) payload.get("name");
String fallbackNickname = nickname != null ? nickname : email.split("@")[0];

if (userService.findByEmail(email).isEmpty()) {
saveNewUser(email, fallbackNickname);
}
}

private void saveNewUser(String email, String nickname) {
userService.saveUser(
User.builder()
.email(email)
.nickname(nickname)
.provider(Provider.GOOGLE)
.build()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package pl.pwr.zpi.auth.oauth2;

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.googleapis.auth.oauth2.GoogleRefreshTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Service;
import pl.pwr.zpi.security.cookie.CookieService;
import pl.pwr.zpi.utils.exception.AuthenticationException;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.time.Instant;
import java.util.Collections;

@Service
@Slf4j
public class GoogleOauthTokenService {

private final String googleClientId;
private final String googleClientSecret;
private final CookieService cookieService;
private final GsonFactory jsonFactory;
private final NetHttpTransport transport;
private final GoogleIdTokenVerifier verifier;

public GoogleOauthTokenService(@Value("${spring.security.oauth2.client.registration.google.client-id}") String googleClientId,
@Value("${spring.security.oauth2.client.registration.google.client-secret}") String googleClientSecret,
CookieService cookieService) {
this.cookieService = cookieService;
this.googleClientId = googleClientId;
this.googleClientSecret = googleClientSecret;
this.jsonFactory = GsonFactory.getDefaultInstance();
this.transport = new NetHttpTransport();
this.verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Collections.singletonList(googleClientId))
.build();
}

public ResponseCookie updateAuthToken(String refreshToken) {
var refreshedIdToken = refreshIdToken(refreshToken);
return cookieService.createAuthCookie(refreshedIdToken.getIdToken(), Instant.ofEpochSecond(refreshedIdToken.getExpiresInSeconds()));
}

public GoogleIdToken.Payload decodeToken(String token) {
try {
GoogleIdToken idToken = verifier.verify(token);
if (idToken == null) {
throw new AuthenticationException("Token validation failed");
}
return idToken.getPayload();
} catch (GeneralSecurityException | IOException e) {
throw new AuthenticationException("Token validation failed");
}
}

public void validateToken(String token) throws GeneralSecurityException, IOException {
verifier.verify(token);
}

private GoogleTokenResponse refreshIdToken(String refreshToken) {
try {
return new GoogleRefreshTokenRequest(
transport,
jsonFactory,
refreshToken,
googleClientId,
googleClientSecret)
.execute();

} catch (IOException e) {
throw new RuntimeException(e);
}

}
}

This file was deleted.

Loading

0 comments on commit 75862a1

Please sign in to comment.