I don't know how many brews it is, but this is an article on how to use OAuth. There are as many OAuth client libraries in Java as there are stars, including Google's own, but here I will touch OAuth without using the client library for study. In addition, I think that there are many good articles about the mechanism of OAuth itself, so I will not explain it here.
Using OAuth 2.0 for Web Server Applications OpenID Connect
OAuth
You need to register your OAuth credentials with Google.
Create Credentials-> OAuth Client ID-> Web Application
The name is appropriate. Added http: // localhost: 8080 / auth
to the approved redirect URI. The value added here will be the value that can be used as the redirect destination of the request.
This time we will only use it for development, so specify localhost. It should not be used in production.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.3.RELEASE")
}
}
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
dependencies {
compile(
'org.springframework.boot:spring-boot-devtools',
'org.springframework.boot:spring-boot-starter-web',
'org.springframework.boot:spring-boot-starter-thymeleaf',
'com.google.guava:guava:25.1-jre',
'com.google.http-client:google-http-client:1.23.0',
'com.google.http-client:google-http-client-gson:1.23.0',
'com.google.code.gson:gson:2.8.5',
)
}
Anything is fine, but this time I will use Spring. ~~ Guava is used for Base64 and ~~ HttpClient is used for HTTP request simplification. Use Gson to parse JSON.
Addendum: Base64 is supported from Java 8 It's completely out of focus.
Create a login screen.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<a th:href="${endpoint}">login</a>
</body>
</html>
private static final String clientId = checkNotNull( System.getenv( "CLIENT_ID" ) );
@GetMapping( "/login" )
public String login( Model model ) {
var endpoint = "https://accounts.google.com/o/oauth2/v2/auth?"
+ "client_id=" + clientId + "&"
+ "redirect_uri="
+ UrlEscapers.urlFormParameterEscaper().escape( "http://localhost:8080/auth" )
+ "&"
+ "access_type=" + "online" + "&"
+ "state=" + "test_state" + "&"
+ "response_type=" + "code" + "&"
+ "scope="
+ UrlEscapers.urlFormParameterEscaper().escape( "email profile" );
model.addAttribute( "endpoint", endpoint );
return "login";
}
https://accounts.google.com/o/oauth2/v2/auth
is the endpoint for the Google OAuth API. Set the required information in the URL query parameter.
parameter | |
---|---|
client_id |
An ID that identifies the client. Use the value you got in the developer console earlier. Here, it is obtained from the environment variable. |
redirect_uri |
The redirect destination. Use the value set in the developer console. |
access_type |
This time I will use a browser, soonline Is specified. |
state |
state The value itself can be set freely. MostlynonceIt is used for such as. This time it is simply a fixed value. |
response_type |
How to receive a response. On the server sidecode It is fixed. |
scope |
Specify what information you can get with this request. hereemail andprofile Is set. For available valuesdocumentSee. |
When the user follows the link generated here, they will be taken to the Google login screen. The first time you allow the application to access your user information, a confirmation dialog is displayed.
When the user completes the login process, they will be redirected to redirect_url
. You can request an access token using the code
included in the query parameter of the URL.
@GetMapping( "/auth" )
public String auth( HttpServletRequest request, Model model ) throws IOException {
var params = parseQueryParams( request );
checkState( Objects.equals( params.get( "state" ), "test_state" ) );
var code = URLDecoder.decode( checkNotNull( params.get( "code" ) ), StandardCharsets.UTF_8 );
// ...
Get the code
and state
from the query parameters. state
verifies that the value used in the previous request is returned as is. Note that you may need to decode the URL.
var response = httpPost(
"https://www.googleapis.com/oauth2/v4/token",
Map.of(
"code", code,
"client_id", clientId,
"client_secret", clientSecret,
"redirect_uri", "http://localhost:8080/auth",
"grant_type", "authorization_code"
)
).parseAsString();
var responseAsMap = parseJson( response );
Send a POST request to the URL for token acquisition. It was v2 a while ago, but for some reason this is v4. I don't care because it is as per the official document.
parameter | |
---|---|
code |
Obtained abovecode Is sent as it is. |
client_id |
Client ID. |
client_secret |
Client secret. Like the ID, set the one specified in the developer console. |
redirect_uri |
This is not a redirect destination, but for verification. Set it in the console and set the redirect destination used in the previous request again. |
grant_type |
Fixed valueauthorization_code 。 |
If the request is recognized as successful, a JWT-formatted token is returned.
The JSON Web Token is divided into three parts, the header, body, and signature separated by .
, so get each part.
var idTokenList = Splitter.on( "." ).splitToList( idToken );
var header = parseJson( base64ToStr( idTokenList.get( 0 ) ) );
var body = parseJson( base64ToStr( idTokenList.get( 1 ) ) );
var sign = idTokenList.get( 2 );
The header and body parts are Base64-encoded JSON. If you do not verify the result, you can get the value with just this, but since it is a big deal, let's also verify the token.
Get the Google public key to use for verification. Get the key location from https://accounts.google.com/.well-known/openid-configuration
.
var response = httpGet( "https://accounts.google.com/.well-known/openid-configuration" ).parseAsString();
var responseAsMap = parseNestedJson( response );
var jwks = responseAsMap.get( "jwks_uri" ).toString();
Send a GET request to this URI to get the JSON.
var keyResponse = httpGet( jwks ).parseAsString();
var keysResponseObj = parseJsonAs( keyResponse, Keys.class );
return keysResponseObj.keys.stream()
.filter( k -> k.kid.equals( kid ) )
.findAny()
.orElseThrow();
Multiple encryption keys are stored in the keys
array. Look for a kid
that matches the kid
in the header of the ID token.
var signature = Signature.getInstance( "SHA256withRSA" );
signature.initVerify( KeyFactory.getInstance( "RSA" ).generatePublic( new RSAPublicKeySpec(
new BigInteger( 1, base64ToByte( n ) ),
new BigInteger( 1, base64ToByte( e ) )
) ) );
signature.update( contentToVerify.getBytes( StandardCharsets.UTF_8 ) );
return signature.verify( base64ToByte( sign ) );
Convert the key JSON to Java format and make sure it is well known. Actually, the key format should also be obtained from JSON, but here it is a fixed value of RSA
.
After confirming the signature, the next step is to verify the parameters.
checkState( Set.of( "https://accounts.google.com", "accounts.google.com" ).contains( body.get( "iss" ) ) );
Make sure the issuer, ʻiss, is legitimate. For Google, it's either
https://accounts.google.com, ʻaccounts.google.com
.
checkState( Objects.equals( body.get( "aud" ), clientId ) );
Make sure the audience, ʻaud`, matches the client ID.
var now = System.currentTimeMillis();
checkState( now <= Long.parseLong( body.get( "exp" ) ) * 1000 + 1000 );
checkState( now >= Long.parseLong( body.get( "iat" ) ) * 1000 - 1000 );
Finally, make sure the key is within the expiration date and the signing time is valid. The value is in seconds. The reason for adding and subtracting 1000 is to absorb the time lag with the server.
Now that we have verified that the ID token is valid, we will get the value. Here, the email address, name, and URL of the face image are acquired and set in the model.
model.addAttribute( "email", id.get( "email" ) )
.addAttribute( "name", id.get( "name" ) )
.addAttribute( "picture", id.get( "picture" ) );
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Result</title>
</head>
<body>
<img th:src="${picture}">
<p th:text="'Email: ' + ${email}"></p>
<p th:text="'Name: ' + ${name}"></p>
<a th:href="${back}">back</a>
</body>
</html>
To use Google's various APIs, use the ʻaccess_token and
refresh_token` included in the JWT to send each request.
Go to localhost: 8080 / login
.
Go to Google and authenticate.
I got the face image, email address, and name.
Recommended Posts