Рефакторинг кода
This commit is contained in:
parent
5c89e14e7d
commit
20c67a04fc
|
|
@ -0,0 +1,51 @@
|
||||||
|
package ru.oa2.lti.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
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.security.oauth2.jwt.JwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||||
|
import org.springframework.web.client.RestClient;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(AppProperties.class)
|
||||||
|
public class AppConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
AppProperties appProperties;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JwtDecoder jwtDecoder() {
|
||||||
|
return NimbusJwtDecoder
|
||||||
|
.withJwkSetUri(appProperties.lmsUri() + "/lti/keys")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ObjectMapper getMapper() {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
mapper.registerModule(new JavaTimeModule());
|
||||||
|
mapper.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||||
|
|
||||||
|
return mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RestClient getRestClient(RestClient.Builder restClientBuilder) {
|
||||||
|
return restClientBuilder
|
||||||
|
.baseUrl(appProperties.lmsUri())
|
||||||
|
.defaultHeader(HttpHeaders.USER_AGENT, "LtiProvider")
|
||||||
|
.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package ru.oa2.lti.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "lti")
|
||||||
|
public record AppProperties(
|
||||||
|
String lmsUri
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
package ru.oa2.lti.config;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
|
||||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
|
||||||
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class JwtConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public JwtDecoder jwtDecoder() {
|
|
||||||
return NimbusJwtDecoder
|
|
||||||
.withJwkSetUri("http://openolat.local/lti/keys") //TODO адрес из application
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package ru.oa2.lti.config;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class MapperConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public ObjectMapper getMapper() {
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
mapper.registerModule(new JavaTimeModule());
|
|
||||||
mapper.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
|
||||||
|
|
||||||
return mapper;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -48,10 +48,9 @@ public class ResultController {
|
||||||
if (session == null) return ResponseEntity.status(401).build();
|
if (session == null) return ResponseEntity.status(401).build();
|
||||||
|
|
||||||
var ltiLogin = (LtiLogin) session.getAttribute("lti_login");
|
var ltiLogin = (LtiLogin) session.getAttribute("lti_login");
|
||||||
var assessToken = tokenService.exchangeForAccessToken(ltiLogin.getClientId(), "http://openolat.local/lti/token");
|
var assessToken = tokenService.exchangeForAccessToken(
|
||||||
|
ltiLogin.getClientId());
|
||||||
|
|
||||||
String userId = (String) session.getAttribute("lti_user_id");
|
|
||||||
UUID ltiContextId = (UUID) session.getAttribute("lti_context_id");
|
|
||||||
String idToken = (String) session.getAttribute("id_token");
|
String idToken = (String) session.getAttribute("id_token");
|
||||||
|
|
||||||
resultService.setResult(
|
resultService.setResult(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package ru.oa2.lti.service.jwt;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Scope {
|
||||||
|
public static String LINE_ITEM = "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem";
|
||||||
|
public static String SCOPE = "https://purl.imsglobal.org/spec/lti-ags/scope/scope";
|
||||||
|
public static String ENDPOINT = "https://purl.imsglobal.org/spec/lti-ags/claim/endpoint";
|
||||||
|
|
||||||
|
public static String scope(String... scopes) {
|
||||||
|
List<String> listScopes = new ArrayList<>(Arrays.asList(scopes));
|
||||||
|
return String.join(" ", listScopes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.client.RestClient;
|
import org.springframework.web.client.RestClient;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import ru.oa2.lti.config.AppProperties;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
@ -16,21 +17,24 @@ public class TokenService {
|
||||||
|
|
||||||
private final RestTemplate restTemplate = new RestTemplate();
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
private final RestClient restClient;
|
private final RestClient restClient;
|
||||||
|
private final AppProperties appProperties;
|
||||||
|
|
||||||
public TokenService(RestClient restClient) {
|
public TokenService(RestClient restClient, AppProperties appProperties) {
|
||||||
this.restClient = restClient;
|
this.restClient = restClient;
|
||||||
|
this.appProperties = appProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Вызывается после LTI-запуска
|
// Вызывается после LTI-запуска
|
||||||
public String exchangeForAccessToken(
|
public String exchangeForAccessToken(
|
||||||
String clientId,
|
String clientId) {
|
||||||
String tokenEndpoint) {
|
|
||||||
|
var endpointUrl = appProperties.lmsUri() + "/lti/token";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Генерируем client_assertion
|
// Генерируем client_assertion
|
||||||
String clientAssertion = JwtAssertionGenerator.generateClientAssertion(
|
String clientAssertion = JwtAssertionGenerator.generateClientAssertion(
|
||||||
clientId,
|
clientId,
|
||||||
tokenEndpoint,
|
endpointUrl,
|
||||||
"my-key-id-1" // должен совпадать с тем, что в JWKS
|
"my-key-id-1" // должен совпадать с тем, что в JWKS
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -39,7 +43,7 @@ public class TokenService {
|
||||||
body.add("grant_type", "client_credentials");
|
body.add("grant_type", "client_credentials");
|
||||||
body.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
|
body.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
|
||||||
body.add("client_assertion", clientAssertion);
|
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/scope https://purl.imsglobal.org/spec/lti-ags/claim/endpoint");
|
body.add("scope", Scope.scope(Scope.LINE_ITEM, Scope.SCOPE, Scope.ENDPOINT));
|
||||||
|
|
||||||
// Заголовки
|
// Заголовки
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
|
@ -48,7 +52,7 @@ public class TokenService {
|
||||||
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
|
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
|
||||||
|
|
||||||
// Отправляем запрос к OpenOLAT
|
// Отправляем запрос к OpenOLAT
|
||||||
ResponseEntity<Map> response = restTemplate.postForEntity(tokenEndpoint, request, Map.class);
|
ResponseEntity<Map> response = restTemplate.postForEntity(endpointUrl, request, Map.class);
|
||||||
|
|
||||||
if (response.getStatusCode() == HttpStatus.OK) {
|
if (response.getStatusCode() == HttpStatus.OK) {
|
||||||
Map<String, Object> responseBody = response.getBody();
|
Map<String, Object> responseBody = response.getBody();
|
||||||
|
|
@ -70,7 +74,7 @@ public class TokenService {
|
||||||
"&code=code1" +
|
"&code=code1" +
|
||||||
"&iss=" + iss +
|
"&iss=" + iss +
|
||||||
"&login_hint=" + loginHint +
|
"&login_hint=" + loginHint +
|
||||||
"&redirect_uri=http://openolat.local/tool/lti/redirect" +
|
"&redirect_uri=" + appProperties.lmsUri() + "/tool/lti/redirect" +
|
||||||
"<i_message_hint=" + ltiLoginHint)
|
"<i_message_hint=" + ltiLoginHint)
|
||||||
.retrieve();
|
.retrieve();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,3 +29,6 @@ logging:
|
||||||
org.hibernate.SQL: DEBUG
|
org.hibernate.SQL: DEBUG
|
||||||
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
||||||
|
|
||||||
|
|
||||||
|
lti:
|
||||||
|
lms_url: http://openolat.local
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package ru.oa2.lti;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import ru.oa2.lti.service.jwt.Scope;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||||
|
|
||||||
|
public class ScopeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void scopeTest() {
|
||||||
|
assertThat(Scope.scope(Scope.ENDPOINT, Scope.LINE_ITEM))
|
||||||
|
.isEqualTo("https://purl.imsglobal.org/spec/lti-ags/claim/endpoint https://purl.imsglobal.org/spec/lti-ags/scope/lineitem");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,11 +3,11 @@ package ru.oa2.lti.jwt;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import ru.oa2.lti.config.JwtConfig;
|
import ru.oa2.lti.config.AppConfig;
|
||||||
import ru.oa2.lti.service.jwt.JwtService;
|
import ru.oa2.lti.service.jwt.JwtService;
|
||||||
import ru.oa2.lti.service.jwt.JwtServiceImpl;
|
import ru.oa2.lti.service.jwt.JwtServiceImpl;
|
||||||
|
|
||||||
@SpringBootTest(classes = {JwtConfig.class, JwtServiceImpl.class})
|
@SpringBootTest(classes = {AppConfig.class, JwtServiceImpl.class})
|
||||||
public class IdTokenTest {
|
public class IdTokenTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue