# LIFF Utilities
[TOC]
###### tags: `LIFF`
---
```java
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.message.BasicNameValuePair;
import tw.pawnshops.service.Servant;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Future;
public class LineLoginUtils {
private static final URI LINE_VERIFY_TOKEN_URL = URI.create("https://api.line.me/oauth2/v2.1/verify");
@Data
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
private static class ErrorResponse {
private String error;
@JsonProperty("error_description")
private String errorDescription;
}
/**
* If the access token is valid, a 200 OK status code is returned with a
* JSON object that has the following information.
*/
@Data
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
private static class VerifyAccessTokenResponse {
/**
* Permissions granted to the access token. To learn more about
* scopes,
*/
private String scope;
/**
* Channel ID for which the access token is issued
*/
@JsonProperty("client_id")
private String clientId;
/**
* Number of seconds until the access token expires.
*/
@JsonProperty("expires_in")
private Long expiresIn;
}
/**
* The ID token payload is returned when the specified ID token is
* successfully verified.
*/
@Data
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public static class VerifyIdTokenResponse {
/**
* URL used to generate the ID token.
*/
private String iss;
/**
* User ID for which the ID token was generated.
*/
private String sub;
/**
* Channel ID
*/
private String aud;
/**
* The expiry date of the ID token in UNIX time.
*/
private Long exp;
/**
* Time when the ID token was generated in UNIX time.
*/
private Long iat;
/**
* Time the user was authenticated in UNIX time. Not included if
* the max_age value wasn't specified in the authorization
* request.
*/
@JsonProperty("auth_time")
private Long authTime;
/**
* The <code>nonce</code> value specified in the authorization
* URL. Not included if the <code>nonce</code> value wasn't
* specified in the authorization request.
*/
private String nonce;
/**
* A list of authentication methods used by the user. Not
* included in the payload under certain conditions.
* <p>
* One or more of:
* <ul>
* <li><code>pwd</code>: Log in with email and password</li>
* <li><code>lineautologin</code>: LINE automatic login
* (including through LINE SDK)</li>
* <li><code>lineqr</code>: Log in with QR code</li>
* <li><code>linesso</code>: Log in with single sign-on</li>
* </ul>
* </p>
*/
private List<String> amr;
/**
* User's display name. Not included if the <code>profile</code>
* scope wasn't specified in the authorization request.
*/
private String name;
/**
* User's profile image URL. Not included if the
* <code>profile</code> scope wasn't specified in the
* authorization request.
*/
private String picture;
/**
* User's email address. Not included if the <code>email</code>
* scope wasn't specified in the authorization request.
*/
private String email;
}
/**
* Verifies if an access token is valid.
*
* @param accessToken Access token
* @return If the access token is valid, a 200 OK status code is
* returned with a JSON object that has the following information. If
* the access token has expired, a 400 Bad Request HTTP status code and
* a JSON response are returned.
*/
public static Object verifyAccessToken(final String accessToken) {
Object valueObject;
try (CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault()) {
httpClient.start();
HttpGet httpGet = new HttpGet(
new URIBuilder(LINE_VERIFY_TOKEN_URL)
.addParameter(
"access_token",
accessToken
)
.build()
);
Future<HttpResponse> future = httpClient.execute(
httpGet,
null
);
HttpResponse httpResponse = future.get();
if (Objects.equals(200, httpResponse.getStatusLine().getStatusCode())) {
valueObject = Servant.JSON_MAPPER.readValue(
httpResponse.getEntity().getContent(),
VerifyAccessTokenResponse.class
);
} else {
valueObject = Servant.JSON_MAPPER.readValue(
httpResponse.getEntity().getContent(),
ErrorResponse.class
);
}
httpClient.close();
} catch (Exception exception) {
valueObject = new ErrorResponse();
}
return valueObject;
}
/**
* ID tokens are JSON web tokens (JWT) with information about the
* user.It's possible for an attacker to spoof an ID token. Use this
* call to verify that a received ID token is authentic, meaning you can
* use it to obtain the user's profile information and email.
*
* @param idToken ID token
* @param clientId Expected channel ID.
* @return The ID token payload is returned when the specified ID token
* is successfully verified; or a JSON object is returned when the
* specified ID token fails to be verified.
*/
public static Object verifyIdToken(final String idToken, final String clientId) {
Object valueObject;
try (CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault()) {
httpClient.start();
UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(
Arrays.asList(
new BasicNameValuePair(
"id_token",
idToken
),
new BasicNameValuePair(
"client_id",
clientId
)
)
);
HttpPost httpPost = new HttpPost(LINE_VERIFY_TOKEN_URL);
httpPost.addHeader(
HttpHeaders.CONTENT_TYPE,
"application/x-www-form-urlencoded"
);
httpPost.setEntity(urlEncodedFormEntity);
Future<HttpResponse> future = httpClient.execute(
httpPost,
null
);
HttpResponse httpResponse = future.get();
if (Objects.equals(200, httpResponse.getStatusLine().getStatusCode())) {
valueObject = Servant.JSON_MAPPER.readValue(
httpResponse.getEntity().getContent(),
VerifyIdTokenResponse.class
);
} else {
valueObject = Servant.JSON_MAPPER.readValue(
httpResponse.getEntity().getContent(),
ErrorResponse.class
);
}
httpClient.close();
} catch (Exception exception) {
valueObject = new ErrorResponse();
}
return valueObject;
}
}
```