[Spring Boot] Google OAuth2 Login

Spring Boot

Language :

Google Cloud Console

If there is no project, create a project.

Link

Set the OAuth consent screen

API & Services > OAuth consent Screen

  • User Type: Select External because login is used by all users.
  • Add Scope: Add API to use.Select userinfo.email, userinfo, profile, and openid because you only use login.
  • Test Users: Register by entering an email to test others while in test mode
  • Completion

Creating an OAuth 2.0 Client ID

APIs & Services > Credentials

  • Create Credentials > OAuth Client ID
  • Register URI: Register URI to receive Google login result code.
  • Verify the generated client ID and client security password

API documentation material

Call Google Login Window

Spring Boot Code

  • application.yml

yml

google:
  client:
    id: "Client ID"
    secret: "Client secret"
  auth:
    url: "https://oauth2.googleapis.com"
    redirect-url: "Approved redirect URI registered with OAuth client ID"
  • Provider

kotlin

@Component
class GoogleApiProvider(
    @Value("\${google.client.id}") private val clientId: String,
    @Value("\${google.client.secret}") private val clientSecret: String,
    @Value("\${google.auth.url}") private val authUrl: String,
    @Value("\${google.auth.redirect-url}") private val redirectUrl: String
) {

    /**
     * Google login screen URI
     */
    fun getGoogleAuthUrl(): String {
        return "https://accounts.google.com/o/oauth2/v2/auth?" +
            "scope=${URLEncoder.encode("openid email profile", Charsets.UTF_8)}" +
            "&access_type=offline" +
            "&response_type=code" +
            "&redirect_uri=$redirectUrl" +
            "&client_id=$clientId"
    }

}
  • Controller

kotlin

@RestController
@RequestMapping("/api/auth")
class AuthController(
    private val googleApiProvider: GoogleApiProvider
) {

    /**
     * Call Google Login Window
     */
    @GetMapping("/login/google-auth")
    fun redirectGoogleAuth(request: HttpServletRequest): ResponseEntity<Any> {
        val headers = HttpHeaders()
        headers.location = URI.create(googleApiProvider.getGoogleAuthUrl())
        return ResponseEntity(headers, HttpStatus.MOVED_PERMANENTLY)
    }

}

Testing

  • Search for http://localhost:8080/api/auth/login/google-auth in the browser address bar or create a button on the front screen to run it

Login Screen

  • When you log in, you will see the cscreen.If you press Continue here, it goes to the redirect_uri sent as a parameter when you call the Google login window.

consent screen

Redirect Processing / Login Processing

Spring Boot Code

  • Receive Token Response DTO

kotlin

data class GoogleTokenResponseDTO(
    @SerializedName("access_token")
    val accessToken: String,
    @SerializedName("expires_in")
    val expiresIn: String,
    @SerializedName("refresh_token")
    val refreshToken: String,
    val scope: String,
    @SerializedName("token_type")
    val tokenType: String
)
  • User Information Response DTO

kotlin

data class GoogleUserInfoResponseDTO(
    val sub: String,
    val name: String,
    @SerializedName("given_name")
    val givenName: String,
    val picture: String,
    val email: String,
    @SerializedName("email_verified")
    val emailVerified: String,
    val locale: String
)
  • Provider

kotlin

@Component
class GoogleApiProvider {

    // ...

    /**
     * Receive tokens
     */
    fun getToken(code: String): GoogleTokenResponseDTO {
        val url = "$authUrl/token"
        val param = LinkedMultiValueMap<String, String>()
        param["code"] = code
        param["client_id"] = clientId
        param["client_secret"] = clientSecret
        param["redirect_uri"] = redirectUrl
        param["grant_type"] = "authorization_code"

        val response = RestTemplate().postForObject(url, param, String::class.java)
            ?: throw GoogleApiException("token")
        return Gson().fromJson(response, GoogleTokenResponseDTO::class.java)
    }

    /**
     * Get user information
     */
    fun getUserInfo(accessToken: String): GoogleUserInfoResponseDTO {
        val url = "https://openidconnect.googleapis.com/v1/userinfo?access_token=$accessToken"

        val headers = HttpHeaders()
        headers.setBearerAuth(accessToken)

        val request = HttpEntity<MultiValueMap<String, String>>(LinkedMultiValueMap(), headers)

        val response = RestTemplate().exchange(url, HttpMethod.POST, request, String::class.java)
        return if (response.statusCode == HttpStatus.OK)
            Gson().fromJson(response.body, GoogleUserInfoResponseDTO::class.java)
        else throw GoogleApiException("user info")
    }

}
  • Controller

kotlin

@RestController
@RequestMapping("/api/auth")
class AuthController(
    private val authService: AuthService,
    private val googleApiProvider: GoogleApiProvider
) {

    // ...

    /**
     * Called after logging in to Google. Login Processing
     */
    @GetMapping("/login/google")
    fun loginGoogle(
        request: HttpServletRequest,
        @RequestParam code: String?,
        @RequestParam error: String?
    ): ResponseEntity<Any> {
        return code?.let {
            try {
                authService.loginGoogle(code)
                ResponseEntity.ok(HttpStatus.OK)
            } catch (e: Exception) {
                ResponseEntity.ok(HttpStatus.BAD_REQUEST)
            }
        } ?: ResponseEntity.ok(HttpStatus.BAD_REQUEST)
    }

}
  • Service

kotlin

@Service
class AuthService(
    private val googleApiProvider: GoogleApiProvider
) {

    fun loginGoogle(code: String) {
        // get token
        val token = googleApiProvider.getToken(code)
        // Look up user information
        val info = googleApiProvider.getUserInfo(token.accessToken)
        // Process login
    }

}

Testing

  • If you press Continue on the consent screen above, you will be logged in.

민갤

Back-End Developer

백엔드 개발자입니다.