реализация взаимодейстия LMS и external tool по LTI 1.3 с отправкой

This commit is contained in:
Anton Dzyk 2025-12-18 21:03:51 +03:00
parent 8f96e5b3bb
commit 30bc91ee53
19 changed files with 457 additions and 76 deletions

View File

@ -0,0 +1,25 @@
package ru.oa2.lti.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.web.client.RestClient;
import java.nio.charset.StandardCharsets;
@Configuration
public class RestClientConfig {
@Bean
public RestClient getRestClient(RestClient.Builder restClientBuilder) {
return restClientBuilder
.baseUrl("http://openolat.local")
.defaultHeader(HttpHeaders.USER_AGENT, "SpringBootApp")
.defaultStatusHandler(HttpStatusCode::isError, (req, res) -> {
throw new RuntimeException("API error: " + res.getStatusCode() + " body: " +
new String(res.getBody().readAllBytes(), StandardCharsets.UTF_8)); //TODO business exception
})
.build();
}
}

View File

@ -4,12 +4,14 @@ import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import ru.oa2.lti.ApplicationService;
import ru.oa2.lti.model.LtiLogin;
import ru.oa2.lti.service.jwt.Payload;
import ru.oa2.lti.service.jwt.TokenService;
@Slf4j
@ -26,16 +28,17 @@ public class LoginController {
this.tokenService = tokenService;
}
@ResponseBody
@PostMapping("/login")
public String login(@RequestParam("client_id") String clientId,
@RequestParam("iss") String iss,
@RequestParam("login_hint") String loginHint,
@RequestParam("lti_deployment_id") String ltiDeploymentId,
@RequestParam("lti_message_hint") String ltiMessageHint,
@RequestParam("target_link_uri") String targetLinkUri,
HttpServletRequest request,
RedirectAttributes redirectAttributes,
ServletResponse servletResponse) {
@RequestParam("iss") String iss,
@RequestParam("login_hint") String loginHint,
@RequestParam("lti_deployment_id") String ltiDeploymentId,
@RequestParam("lti_message_hint") String ltiMessageHint,
@RequestParam("target_link_uri") String targetLinkUri,
HttpServletRequest request,
RedirectAttributes redirectAttributes,
ServletResponse servletResponse) {
var ltiLogin = LtiLogin.builder()
.clientId(clientId)
@ -49,62 +52,64 @@ public class LoginController {
log.info("BODY: {}", ltiLogin);
var payload = service.getPayload(ltiLogin);
var accessToken = tokenService.exchangeForAccessToken(ltiLogin.getClientId(), "http://openolat.local/lti/token");
// 4. Сохранить access token в сессии
HttpSession session = request.getSession();
session.setAttribute("lti_access_token", accessToken);
session.setAttribute("lti_context_id", payload.getContextId());
session.setAttribute("lti_user_id", ltiLogin.getClientId());
// 4. Сохранить access token в сессии
HttpSession session = request.getSession();
session.setAttribute("lti_login", ltiLogin);
session.setAttribute("lti_message_hint", Long.valueOf(ltiMessageHint));
session.setAttribute("lti_context_id", payload.getContextId());
session.setAttribute("lti_user_id", ltiLogin.getClientId());
session.setAttribute("payload", payload);
var data = service.getTask(payload.getContextId());
if (payload.getContextId() != null) {
var body = tokenService.ltiAuth(ltiMessageHint, iss, loginHint);
return body.getBody();
}
redirectAttributes.addFlashAttribute("description", data);
//TODO заполнять из data
redirectAttributes.addFlashAttribute("codeTitle", "Dockerfile:");
redirectAttributes.addFlashAttribute("inputCode", "FROM maven:3.9.9-eclipse-temurin-21-jammy AS build\n" +
"\n" +
"COPY . /build\n" +
"WORKDIR /build\n" +
"\n" +
"RUN --mount=type=cache,target=/root/.m2/repository,rw \\\n" +
"\tmvn clean package -DskipTests -B\n" +
"\n" +
"FROM eclipse-temurin:21-jdk AS extract\n" +
"\n" +
"COPY --from=build /build/target/lti-provider-*.jar app.jar\n" +
"RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted\n" +
"\n" +
"FROM eclipse-temurin:21-jre\n" +
"\n" +
"LABEL org.opencontainers.image.title=\"LTI Provider\"\n" +
"LABEL org.opencontainers.image.description=\"LTI провайдер для лабораторных по Docker и Kubernetes\"\n" +
"LABEL org.opencontainers.image.url=\"TODO\"\n" +
"LABEL org.opencontainers.image.source=\"TODO\"\n" +
"LABEL org.opencontainers.image.documentation=\"#TODO\"\n" +
"\n" +
"WORKDIR /opt\n" +
"\n" +
"COPY --from=extract extracted/dependencies/ ./\n" +
"COPY --from=extract extracted/spring-boot-loader/ ./\n" +
"COPY --from=extract extracted/snapshot-dependencies/ ./\n" +
"COPY --from=extract extracted/application/ ./\n" +
"\n" +
"ENV TZ=\"Europe/Moscow\"\n" +
"ENV JAVA_TOOL_OPTIONS=\"-Xmx1g -Xms1g\"\n" +
"\n" +
"ENTRYPOINT [\"java\", \"-jar\", \"/opt/app.jar\"]");
return "redirect:/tool/lti/docker-task";
return "redirect:/tool/lti/select";
}
@GetMapping("/docker-task")
public String showDockerTas(Model model) {
if (!model.containsAttribute("description")) {
return "redirect:/error";
@GetMapping("/task")
public String showDockerTas(Model model,
HttpServletRequest request) {
var session = request.getSession();
var payload = (Payload) session.getAttribute("payload");
if (payload != null) {
var data = service.getTask(payload.getContextId());
if (data == null) {
return "redirect:/error";
}
model.addAttribute("description", data);
model.addAttribute("codeTitle", "Dockerfile:");
model.addAttribute("inputCode", "FROM ...");
}
return "docker-task";
return "task";
}
@GetMapping("/select")
public String select(Model model) {
return "select";
}
@PostMapping("/redirect")
public String redirect(@RequestParam("id_token") String idToken,
@RequestParam("state") String state,
HttpServletRequest request) {
if (log.isDebugEnabled()) {
log.debug("/redirect idToken: {}, state: {}", idToken, state);
}
HttpSession session = request.getSession();
session.setAttribute("id_token", idToken);
session.setAttribute("state", state);
return "redirect:/tool/lti/task";
}
}

View File

@ -1,11 +1,22 @@
package ru.oa2.lti.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.client.RestClientBuilderConfigurer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestClient;
import ru.oa2.lti.ApplicationService;
import ru.oa2.lti.model.LtiLogin;
import ru.oa2.lti.service.jwt.JwtService;
import ru.oa2.lti.service.jwt.TokenService;
import ru.oa2.lti.service.results.ResultService;
import java.util.UUID;
@Slf4j
@RestController
@ -13,17 +24,44 @@ import ru.oa2.lti.ApplicationService;
public class ResultController {
final ApplicationService service;
final ResultService resultService;
final TokenService tokenService;
final JwtService jwtService;
public ResultController(ApplicationService applicationService) {
public ResultController(ApplicationService applicationService,
ResultService resultService,
TokenService tokenService,
JwtService jwtService) {
this.service = applicationService;
this.resultService = resultService;
this.tokenService = tokenService;
this.jwtService = jwtService;
}
@PostMapping("/docker")
public String result(@RequestBody String body) {
public ResponseEntity result(@RequestBody String body,
HttpServletRequest req) {
log.info("RESULT: {}", body);
HttpSession session = req.getSession(false);
if (session == null) return ResponseEntity.status(401).build();
var ltiLogin = (LtiLogin) session.getAttribute("lti_login");
var assessToken = tokenService.exchangeForAccessToken(ltiLogin.getClientId(), "http://openolat.local/lti/token");
String userId = (String) session.getAttribute("lti_user_id");
UUID ltiContextId = (UUID) session.getAttribute("lti_context_id");
String idToken = (String) session.getAttribute("id_token");
resultService.setResult(
assessToken,
jwtService.getTokenPayload(idToken)
);
//TODO возвращать json
return service.saveResult(body);
return ResponseEntity
.accepted()
.body(service.saveResult(body));
}
}

View File

@ -0,0 +1,27 @@
package ru.oa2.lti.service.jwt;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
@Data
public class IdTokenPayload {
LocalDateTime iat;
LocalDateTime exp;
String iss;
UUID aud;
UUID sub;
String messageType;
ResourceLink resourceLink;
List<String> roles;
TokenContext context;
NamesRoleService namesRoleService;
LaunchPresentation launchPresentation;
TokenEndpoint tokenEndpoint;
String version;
UUID deploymentId;
String targetLinkUri;
}

View File

@ -3,4 +3,6 @@ package ru.oa2.lti.service.jwt;
public interface JwtService {
Payload getPayload(String jwt);
IdTokenPayload getTokenPayload(String jwt);
}

View File

@ -1,8 +1,14 @@
package ru.oa2.lti.service.jwt;
import com.nimbusds.jose.shaded.gson.internal.LinkedTreeMap;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Service
@ -18,14 +24,89 @@ public class JwtServiceImpl implements JwtService {
public Payload getPayload(String token) {
var jwt = jwtDecoder.decode(token);
return Payload.builder()
.deploymentKey(jwt.getClaim("deploymentKey"))
.deploymentId(UUID.fromString(jwt.getClaim("deploymentId")))
.contextKey(jwt.getClaim("contextKey"))
.contextId(UUID.fromString(jwt.getClaim("contextId")))
.courseadmin(jwt.getClaim("courseadmin"))
.coach(jwt.getClaim("coach"))
.participant(jwt.getClaim("participant"))
.build();
var payload = new Payload();
for (var key : jwt.getClaims().entrySet()) {
switch (key.getKey()) {
case "deploymentKey" -> payload.setDeploymentKey((Long) key.getValue());
case "deploymentId" -> payload.setDeploymentId(UUID.fromString((String) key.getValue()));
case "contextKey" -> payload.setContextKey((Long) key.getValue());
case "contextId" -> payload.setContextId(UUID.fromString((String) key.getValue()));
case "courseadmin" -> payload.setCourseadmin((Boolean) key.getValue());
case "coach" -> payload.setCoach((Boolean) key.getValue());
case "participant" -> payload.setParticipant((Boolean) key.getValue());
}
}
return payload;
}
@Override
public IdTokenPayload getTokenPayload(String idToken) {
var jwt = jwtDecoder.decode(idToken);
var payload = new IdTokenPayload();
for (var el : jwt.getClaims().entrySet()) {
switch (el.getKey()) {
case "iat" -> payload.setIat(LocalDateTime.ofInstant((Instant) el.getValue(), ZoneId.systemDefault()));
case "exp" -> payload.setExp(LocalDateTime.ofInstant((Instant) el.getValue(), ZoneId.systemDefault()));
case "iss" -> payload.setIss((String) el.getValue());
case "aud" -> payload.setAud(UUID.fromString((String) ((ArrayList) el.getValue()).get(0)));
case "sub" -> payload.setSub(UUID.fromString((String) el.getValue()));
case "https://purl.imsglobal.org/spec/lti/claim/message_type"
-> payload.setMessageType((String) el.getValue());
case "https://purl.imsglobal.org/spec/lti/claim/resource_link"
-> {
var m = (LinkedTreeMap) el.getValue();
payload.setResourceLink(
new ResourceLink(UUID.fromString((String) m.get("id")), (String) m.get("title"))
);
}
case "https://purl.imsglobal.org/spec/lti/claim/roles" ->
payload.setRoles((List<String>) el.getValue());
case "https://purl.imsglobal.org/spec/lti/claim/context" -> {
var tc = (LinkedTreeMap) el.getValue();
payload.setContext(
new TokenContext(
UUID.fromString((String) tc.get("id")),
(String) tc.get("label"),
(String) tc.get("title"),
(List<String>) tc.get("type")
)
);
}
case "https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice" -> {
var nrs = (LinkedTreeMap) el.getValue();
payload.setNamesRoleService(
new NamesRoleService(
(String) nrs.get("context_memberships_url"),
(List<String>) nrs.get("service_versions")
)
);
}
case "https://purl.imsglobal.org/spec/lti/claim/launch_presentation" -> {
payload.setLaunchPresentation(new LaunchPresentation(
(String) ((LinkedTreeMap) el.getValue()).get("document_target")
));
}
case "https://purl.imsglobal.org/spec/lti-ags/claim/endpoint" -> {
var endpoint = (LinkedTreeMap) el.getValue();
payload.setTokenEndpoint(
new TokenEndpoint(
(List<String>) endpoint.get("scope"),
(String) endpoint.get("lineitems"),
(String) endpoint.get("lineitem")
)
);
}
case "https://purl.imsglobal.org/spec/lti/claim/version" ->
payload.setVersion((String) el.getValue());
case "https://purl.imsglobal.org/spec/lti/claim/deployment_id" ->
payload.setDeploymentId(UUID.fromString((String) el.getValue()));
case "https://purl.imsglobal.org/spec/lti/claim/target_link_uri" ->
payload.setTargetLinkUri((String) el.getValue());
}
}
return payload;
}
}

View File

@ -0,0 +1,11 @@
package ru.oa2.lti.service.jwt;
/*
"https://purl.imsglobal.org/spec/lti/claim/launch_presentation" : {
"document_target" : "iframe"
},
*/
public record LaunchPresentation(
String documentTarget
) {
}

View File

@ -0,0 +1,15 @@
package ru.oa2.lti.service.jwt;
import java.util.List;
/*
"https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice" : {
"context_memberships_url" : "http://openolat.local/lti/nrps/b0d12061-2564-47a0-9291-b0d226f1eefe/memberships/",
"service_versions" : [ "2.0" ]
},
*/
public record NamesRoleService(
String contextMembershipsUrl,
List<String>serviceVersions
) {
}

View File

@ -1,12 +1,12 @@
package ru.oa2.lti.service.jwt;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.UUID;
@Data
@Builder
@NoArgsConstructor
public class Payload {
private Long deploymentKey;
private UUID deploymentId;

View File

@ -0,0 +1,9 @@
package ru.oa2.lti.service.jwt;
import java.util.UUID;
public record ResourceLink(
UUID id,
String title
) {
}

View File

@ -0,0 +1,13 @@
package ru.oa2.lti.service.jwt;
import java.util.List;
import java.util.UUID;
public record TokenContext(
UUID id,
String label,
String title,
List<String> type
) {
}

View File

@ -0,0 +1,17 @@
package ru.oa2.lti.service.jwt;
import java.util.List;
/*
"https://purl.imsglobal.org/spec/lti-ags/claim/endpoint" : {
"scope" : [ "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly", "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", "https://purl.imsglobal.org/spec/lti-ags/scope/score" ],
"lineitems" : "http://openolat.local/lti/ags/b0d12061-2564-47a0-9291-b0d226f1eefe/context/112999233990970/lineitems/",
"lineitem" : "http://openolat.local/lti/ags/b0d12061-2564-47a0-9291-b0d226f1eefe/context/112999233990970/lineitems/112999233990979/lineitem"
},
*/
public record TokenEndpoint(
List<String> scope,
String lineitems,
String lineitem
) {
}

View File

@ -1,17 +1,25 @@
package ru.oa2.lti.service.jwt;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Slf4j
@Service
public class TokenService {
private final RestTemplate restTemplate = new RestTemplate();
private final RestClient restClient;
public TokenService(RestClient restClient) {
this.restClient = restClient;
}
// Вызывается после LTI-запуска
public String exchangeForAccessToken(
@ -31,7 +39,7 @@ public class TokenService {
body.add("grant_type", "client_credentials");
body.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
body.add("client_assertion", clientAssertion);
body.add("scope", "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem https://purl.imsglobal.org/spec/lti-ags/scope/result");
body.add("scope", "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem https://purl.imsglobal.org/spec/lti-ags/scope/scope https://purl.imsglobal.org/spec/lti-ags/claim/endpoint");
// Заголовки
HttpHeaders headers = new HttpHeaders();
@ -53,4 +61,24 @@ public class TokenService {
throw new RuntimeException("Не удалось получить access token", e);
}
}
public ResponseEntity<String> ltiAuth(String ltiLoginHint, String iss, String loginHint) {
var result = restClient
.get()
.uri("/lti/auth?" +
"state=start" +
"&code=code1" +
"&iss=" + iss +
"&login_hint=" + loginHint +
"&redirect_uri=http://openolat.local/tool/lti/redirect" +
"&lti_message_hint=" + ltiLoginHint)
.retrieve();
ResponseEntity<String> body = result.toEntity(String.class);
if (log.isDebugEnabled()) {
log.debug("lti/auth RESPONSE: {}", body);
}
return body;
}
}

View File

@ -0,0 +1,8 @@
package ru.oa2.lti.service.results;
public record Lineitems(
String id,
String label,
String resourceId
) {
}

View File

@ -0,0 +1,10 @@
package ru.oa2.lti.service.results;
import ru.oa2.lti.service.jwt.IdTokenPayload;
import java.util.UUID;
public interface ResultService {
void setResult(String accessToken, IdTokenPayload idToken);
}

View File

@ -0,0 +1,65 @@
package ru.oa2.lti.service.results;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import ru.oa2.lti.service.jwt.IdTokenPayload;
import java.util.UUID;
@Slf4j
@Service
public class ResultServiceImpl implements ResultService {
private final RestClient restClient;
private final ObjectMapper mapper;
public ResultServiceImpl(RestClient client) {
this.restClient = client;
this.mapper = new ObjectMapper();
}
@Override
public void setResult(String accessToken, IdTokenPayload idToken) {
// http://openolat.local/lti/ags/b0d12061-2564-47a0-9291-b0d226f1eefe/context/112999233990970/lineitems/112999233990979/lineitem
var resp = restClient.get()
.uri("http://openolat.local/lti/ags/b0d12061-2564-47a0-9291-b0d226f1eefe/context/112999233990970/lineitems/112999233990979/lineitem")
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", "application/vnd.ims.lis.v1.score+json")
.header("Accept", "application/vnd.ims.lis.v1.score+json")
.retrieve();
var respBody = resp.toEntity(String.class).getBody();
try {
Lineitems lineitems = mapper.readValue(respBody, Lineitems.class);
String resourceId = "112999233990970"; //TODO
String userId = "efc0b988-cfe0-4d00-9466-cf86fcf8f885"; //TODO
var result = restClient
.post()
.uri(String.format("/lti/ags/%s/context/%s/lineitems/laba/lineitem/scores/x",
idToken.getContext().id(), resourceId))
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", "application/vnd.ims.lis.v1.score+json")
.header("Accept", "application/vnd.ims.lis.v1.score+json")
.body(
String.format(
"{\"id\":\"%s\", \"userId\": \"%s\", \"scoreMaximum\": 1, \"label\":\"LTI page 1\", \"resourceId\": \"%s\"}",
lineitems.id(), userId, lineitems.resourceId()
)
)
.retrieve();
log.info("RESULT RESP: {}", result.toEntity(String.class));
} catch (Exception e) {
log.error(e.getMessage());
}
}
}

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h1>(.)_(.)</h1>
</body>
</html>

View File

@ -125,9 +125,6 @@
</div>
</div>
<!-- Подключаем FontAwesome для иконок -->
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
<script>
document.getElementById('runButton').addEventListener('click', function() {
const codeInput = document.getElementById('codeInput').value;

View File

@ -0,0 +1,24 @@
package ru.oa2.lti.jwt;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import ru.oa2.lti.config.JwtConfig;
import ru.oa2.lti.service.jwt.JwtService;
import ru.oa2.lti.service.jwt.JwtServiceImpl;
@SpringBootTest(classes = {JwtConfig.class, JwtServiceImpl.class})
public class IdTokenTest {
@Autowired
JwtService jwtService;
@Test
public void idTokenTest() {
//TODO как сделать вечный токен?
var token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjMyODYzM2M0LWQ0MmMtNDhjZi1hYmJkLTZhYzE4NGNiNDVkOSJ9.eyJpYXQiOjE3NjYwNjk2NDYsImV4cCI6MTc2NjA3MzI0NiwiaXNzIjoiaHR0cHM6Ly9vcGVub2xhdC5sb2NhbCIsImF1ZCI6IjFjYjhiZDg5LTMyYTAtNDM1MC1iZDlmLTExMDQ3NzRjOThiOCIsInN1YiI6ImVmYzBiOTg4LWNmZTAtNGQwMC05NDY2LWNmODZmY2Y4Zjg4NSIsImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvbHRpL2NsYWltL21lc3NhZ2VfdHlwZSI6Ikx0aVJlc291cmNlTGlua1JlcXVlc3QiLCJodHRwczovL3B1cmwuaW1zZ2xvYmFsLm9yZy9zcGVjL2x0aS9jbGFpbS9yZXNvdXJjZV9saW5rIjp7ImlkIjoiMzMyMWVjZTItMTQzYS00YzYzLTk2NGQtODQwNmI4MTlkZTk3IiwidGl0bGUiOiJEb2NrZXIifSwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9sdGkvY2xhaW0vcm9sZXMiOlsiaHR0cDovL3B1cmwuaW1zZ2xvYmFsLm9yZy92b2NhYi9saXMvdjIvbWVtYmVyc2hpcCNMZWFybmVyIl0sImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvbHRpL2NsYWltL2NvbnRleHQiOnsiaWQiOiJiMGQxMjA2MS0yNTY0LTQ3YTAtOTI5MS1iMGQyMjZmMWVlZmUiLCJsYWJlbCI6IkRvY2tlciIsInRpdGxlIjoiRG9ja2VyIiwidHlwZSI6WyJodHRwOi8vcHVybC5pbXNnbG9iYWwub3JnL3ZvY2FiL2xpcy92Mi9jb3Vyc2UjQ291cnNlU2VjdGlvbiJdfSwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9sdGktbnJwcy9jbGFpbS9uYW1lc3JvbGVzZXJ2aWNlIjp7ImNvbnRleHRfbWVtYmVyc2hpcHNfdXJsIjoiaHR0cDovL29wZW5vbGF0LmxvY2FsL2x0aS9ucnBzL2IwZDEyMDYxLTI1NjQtNDdhMC05MjkxLWIwZDIyNmYxZWVmZS9tZW1iZXJzaGlwcy8iLCJzZXJ2aWNlX3ZlcnNpb25zIjpbIjIuMCJdfSwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9sdGkvY2xhaW0vbGF1bmNoX3ByZXNlbnRhdGlvbiI6eyJkb2N1bWVudF90YXJnZXQiOiJpZnJhbWUifSwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9sdGktYWdzL2NsYWltL2VuZHBvaW50Ijp7InNjb3BlIjpbImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvbHRpLWFncy9zY29wZS9saW5laXRlbSIsImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvbHRpLWFncy9zY29wZS9saW5laXRlbS5yZWFkb25seSIsImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvbHRpLWFncy9zY29wZS9yZXN1bHQucmVhZG9ubHkiLCJodHRwczovL3B1cmwuaW1zZ2xvYmFsLm9yZy9zcGVjL2x0aS1hZ3Mvc2NvcGUvc2NvcmUiXSwibGluZWl0ZW1zIjoiaHR0cDovL29wZW5vbGF0LmxvY2FsL2x0aS9hZ3MvYjBkMTIwNjEtMjU2NC00N2EwLTkyOTEtYjBkMjI2ZjFlZWZlL2NvbnRleHQvMTEyOTk5MjMzOTkwOTcwL2xpbmVpdGVtcy8iLCJsaW5laXRlbSI6Imh0dHA6Ly9vcGVub2xhdC5sb2NhbC9sdGkvYWdzL2IwZDEyMDYxLTI1NjQtNDdhMC05MjkxLWIwZDIyNmYxZWVmZS9jb250ZXh0LzExMjk5OTIzMzk5MDk3MC9saW5laXRlbXMvMTEyOTk5MjMzOTkwOTc5L2xpbmVpdGVtIn0sImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvbHRpL2NsYWltL3ZlcnNpb24iOiIxLjMuMCIsImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvbHRpL2NsYWltL2RlcGxveW1lbnRfaWQiOiJhOWViZGM3My04MDBmLTRmNmYtOGQ0MS0xYzYyMTViYmUyN2IiLCJodHRwczovL3B1cmwuaW1zZ2xvYmFsLm9yZy9zcGVjL2x0aS9jbGFpbS90YXJnZXRfbGlua191cmkiOiJodHRwczovL29wZW5vbGF0LmxvY2FsIn0.BsB3_7_T53Uoj1YJCOeFP11Twm10wOEVNEadyR_jWp1sM5u9Xnkag_0bBkmQigHpXvXtK2wIwi2VguujYz6t5SoBREa48rCB-qilcs1glVG9vrMPHhwsd5qGbD1y8vtfDwn_LZsi9IfW6vmDsYNUj3zGKupRBCdz46C9cRAXvgWz8Wv-dE2QYqYJmN0S7OBhiSFAXYAIwYLdq07ia4b5ufgD6AQx_sptWHpzmKatlfG8K3bqgCLa6CWH2SgrAmoXxFwimrbel4D94feA9qxnwvN9oa1T2Hs_shQXpn9KpWN0xUEmcpXsaEOG-_xpR26kalVidsqDIGr3wW8XdF8UVA";
var idTokenPayload = jwtService.getTokenPayload(token);
//TODO добавить проверки
}
}