Compare commits

...

3 Commits

Author SHA1 Message Date
8307bc4906
Verify public clients are not issued refresh tokens 2025-04-15 14:42:12 +02:00
3822f1229c
Change the OAuth 2 / OIDC endpoint URLs.
A decision was made to not deploy as a drop-in replacement but rather migrate applications to the new authorzitanion server.
This means it is no longer necessary to maintain backwards-compatible URLs and can instead use more "standard" URLs.
Not super-critical since they should be discovered via metadata but still nice that the URLs map closer to what the endpoint is called in the various specifications.
2025-04-15 14:32:56 +02:00
09f2fe9430
Change the default JTE templates to be pre-compiled and switch to development mode only in the "dev" profile.
This is done so that the default artifact produced my `mvnw package` works out of the box without explicitly changing to pre-compiled templates.
2025-04-15 13:50:40 +02:00
5 changed files with 53 additions and 13 deletions

@ -1,2 +1,5 @@
se.su.dsv.oauth2.admin-entitlement=oauth2-admin
se.su.dsv.oauth2.developer-entitlement=oauth2-developer
gg.jte.templateLocation=src/main/resources/templates
gg.jte.developmentMode=true
gg.jte.usePrecompiledTemplates=false

@ -7,14 +7,14 @@ spring:
oauth2:
authorizationserver:
endpoint:
authorization-uri: /authorize
token-uri: /exchange
token-introspection-uri: /introspect
authorization-uri: /oauth2/authorize
token-uri: /oauth2/token
token-introspection-uri: /oauth2/introspect
oidc:
user-info-uri: /verify
user-info-uri: /oidc/userinfo
flyway:
baseline-on-migrate: true
gg:
jte:
templateLocation: src/main/resources/templates
developmentMode: true
developmentMode: false
usePrecompiledTemplates: true

@ -39,7 +39,7 @@ public class AuthorizationCodeFlowTest {
String principal = "user";
// 1. Authorize
MvcResult authorizationResult = mockMvc.perform(get("/authorize")
MvcResult authorizationResult = mockMvc.perform(get("/oauth2/authorize")
.with(remoteUser(principal))
.queryParam("response_type", "code")
.queryParam("client_id", CLIENT_ID)
@ -57,7 +57,7 @@ public class AuthorizationCodeFlowTest {
String code = matcher.group("code");
// 2. Code exchange
MvcResult codeExchangeResult = mockMvc.perform(post("/exchange")
MvcResult codeExchangeResult = mockMvc.perform(post("/oauth2/token")
.header("Authorization", "Basic " + CLIENT_AUTHORIZATION)
.param("grant_type", "authorization_code")
.param("code", code)
@ -75,7 +75,7 @@ public class AuthorizationCodeFlowTest {
String accessToken = jsonNode.get("access_token").asText();
// 3. Introspect
mockMvc.perform(post("/introspect")
mockMvc.perform(post("/oauth2/introspect")
.header("Authorization", "Basic " + CLIENT_AUTHORIZATION)
.param("token", accessToken))
.andExpect(status().isOk())

@ -20,6 +20,7 @@ import java.util.Set;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static se.su.dsv.oauth2.ShibbolethRequestProcessor.remoteUser;
@ -27,6 +28,8 @@ import static se.su.dsv.oauth2.ShibbolethRequestProcessor.remoteUser;
@SpringBootTest
@AutoConfigureMockMvc
public class PublicClientCodeFlowTest extends AbstractMetadataCodeFlowTest {
private static final String REDIRECT_URI = "http://localhost/public";
@ServiceConnection
static MariaDBContainer<?> mariaDBContainer = new MariaDBContainer<>("mariadb:10.11");
@ -37,7 +40,9 @@ public class PublicClientCodeFlowTest extends AbstractMetadataCodeFlowTest {
@BeforeEach
public void register_public_client() {
ClientData clientData = new ClientData("public-client", URI.create("http://localhost/public"), true, Set.of(), "admin@localhost");
Set<String> scopes = Set.of("openid", "offline_access");
ClientData clientData = new ClientData("public-client", URI.create(REDIRECT_URI), true,
scopes, "admin@localhost");
newClient = clientManagementService.createClient(() -> "admin", clientData);
}
@ -74,4 +79,37 @@ public class PublicClientCodeFlowTest extends AbstractMetadataCodeFlowTest {
verifyToken(tokenResponse.accessToken());
}
@Test
public void public_client_is_not_issued_refresh_tokens() throws Exception {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
String codeVerifier = "some-new-code-verifier";
byte[] codeChallengeDigest = sha256.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
String codeChallenge = Base64.getUrlEncoder()
.withoutPadding()
.encodeToString(codeChallengeDigest);
MvcResult mvcResult = mockMvc.perform(get(getAuthorizationEndpoint())
.with(remoteUser("random-end-user"))
.queryParam("response_type", "code")
.queryParam("client_id", newClient.clientId())
.queryParam("redirect_uri", REDIRECT_URI)
.queryParam("scope", "openid offline_access")
.queryParam("code_challenge", codeChallenge)
.queryParam("code_challenge_method", "S256"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrlPattern("http://localhost/public?code=*"))
.andReturn();
String code = extractCode(mvcResult.getResponse().getRedirectedUrl());
mockMvc.perform(post(getTokenEndpoint())
.param("grant_type", "authorization_code")
.param("code", code)
.param("redirect_uri", REDIRECT_URI)
.param("client_id", newClient.clientId())
.param("code_verifier", codeVerifier))
.andExpect(status().isOk())
.andExpect(jsonPath("$.refresh_token").doesNotExist());
}
}

@ -13,9 +13,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@SpringBootTest(classes = TestRegisteredClientConfiguration.class)
public class UserInfoEndpointTest extends AbstractMetadataCodeFlowTest {
// Checks for URL compatibility with the old OAuth 2.0 authorization server
@Test
public void user_info_endpoint_url_compatibility() throws Exception {
public void user_info_endpoint_url() throws Exception {
MvcResult mvcResult = mockMvc.perform(get("/.well-known/openid-configuration"))
.andExpect(status().isOk())
.andReturn();
@ -25,6 +24,6 @@ public class UserInfoEndpointTest extends AbstractMetadataCodeFlowTest {
String userinfoEndpoint = openidConfiguration.required("userinfo_endpoint").asText();
URI userInfoUri = URI.create(userinfoEndpoint);
assertEquals("/verify", userInfoUri.getPath());
assertEquals("/oidc/userinfo", userInfoUri.getPath());
}
}