Token Endpoint

This commit is contained in:
Dusan Jakub
2023-09-15 14:06:27 +02:00
parent e703ca25a1
commit 38403ff828
8 changed files with 128 additions and 14 deletions

View File

@@ -40,6 +40,10 @@
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-qute</artifactId> <artifactId>quarkus-resteasy-reactive-qute</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId> <artifactId>quarkus-junit5</artifactId>

View File

@@ -1,10 +1,9 @@
package com.ysoft.geecon; package com.ysoft.geecon;
import com.ysoft.geecon.dto.AuthParams; import com.ysoft.geecon.dto.*;
import com.ysoft.geecon.dto.OAuthClient;
import com.ysoft.geecon.dto.User;
import com.ysoft.geecon.error.OAuthException; import com.ysoft.geecon.error.OAuthException;
import com.ysoft.geecon.repo.ClientsRepo; import com.ysoft.geecon.repo.ClientsRepo;
import com.ysoft.geecon.repo.SecureRandomStrings;
import com.ysoft.geecon.repo.SessionsRepo; import com.ysoft.geecon.repo.SessionsRepo;
import com.ysoft.geecon.repo.UsersRepo; import com.ysoft.geecon.repo.UsersRepo;
import io.quarkus.qute.CheckedTemplate; import io.quarkus.qute.CheckedTemplate;
@@ -17,6 +16,7 @@ import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.core.UriBuilder;
import java.util.List; import java.util.List;
import java.util.Optional;
@Path("/auth") @Path("/auth")
public class OAuthResource { public class OAuthResource {
@@ -40,7 +40,7 @@ public class OAuthResource {
public TemplateInstance get(AuthParams params) { public TemplateInstance get(AuthParams params) {
var client = validateClient(params); var client = validateClient(params);
String sessionId = sessionsRepo.newAuthorizationSession(params, client); String sessionId = sessionsRepo.newAuthorizationSession(params, client);
return Templates.login(params.getLoginHint(), sessionId, ""); return Templates.login(params.getLoginHint(), sessionId, "");
} }
@POST @POST
@@ -75,7 +75,34 @@ public class OAuthResource {
.queryParam("state", params.getState()) .queryParam("state", params.getState())
.build()) .build())
.build(); .build();
} }
@POST
@Path("/token")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public AccessTokenResponse token(TokenParams params) {
return switch (params.getGrantType()) {
case "authorization_code" -> redeemAuthorizationCode(params);
default -> throw new OAuthException("Unsupported grant type");
};
}
private AccessTokenResponse redeemAuthorizationCode(TokenParams params) {
validateClient(params);
var session = sessionsRepo.redeemAuthorizationCode(params.getCode())
.orElseThrow(() -> new OAuthException("Invalid code"));
String idToken = null;
return new AccessTokenResponse("Bearer",
8400,
SecureRandomStrings.alphanumeric(50),
session.scope(),
SecureRandomStrings.alphanumeric(50),
idToken
);
}
private User validateUser(String username, String password) { private User validateUser(String username, String password) {
return usersRepo.getUser(username) return usersRepo.getUser(username)
@@ -95,6 +122,17 @@ public class OAuthResource {
return client; return client;
} }
private OAuthClient validateClient(TokenParams params) {
var client = clientsRepo.getClient(params.getClientId())
.orElseThrow(() -> new RuntimeException("Not a valid client"));
if (!client.validateRedirectUri(params.getRedirectUri())) {
throw new RuntimeException("Invalid redirect URI");
}
if (!client.validateSecret(params.getClientSecret())) {
throw new RuntimeException("Invalid secret");
}
return client;
}
} }

View File

@@ -1,4 +1,4 @@
package com.ysoft.geecon.dto; package com.ysoft.geecon.dto;
public record AccessTokenResponse(String token, String scope, String idToken, long expiresIn) { public record AccessTokenResponse(String tokenType, long expiresIn, String accessToken, String scope, String refreshToken, String idToken) {
} }

View File

@@ -6,14 +6,10 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
public class AuthParams { public class AuthParams {
public enum ResponseType {
code
}
@RestQuery("login_hint") @RestQuery("login_hint")
String loginHint; String loginHint;
@RestQuery("response_type") @RestQuery("response_type")
ResponseType responseType; String responseType;
@RestQuery("client_id") @RestQuery("client_id")
String clientId; String clientId;
@RestQuery("redirect_uri") @RestQuery("redirect_uri")
@@ -31,11 +27,11 @@ public class AuthParams {
this.loginHint = loginHint; this.loginHint = loginHint;
} }
public ResponseType getResponseType() { public String getResponseType() {
return responseType; return responseType;
} }
public void setResponseType(ResponseType responseType) { public void setResponseType(String responseType) {
this.responseType = responseType; this.responseType = responseType;
} }

View File

@@ -14,4 +14,8 @@ public record AuthorizationSession(AuthParams params, OAuthClient client, User u
public AuthorizationSession withScopes(List<String> acceptedScopes) { public AuthorizationSession withScopes(List<String> acceptedScopes) {
return new AuthorizationSession(params, client, user, acceptedScopes); return new AuthorizationSession(params, client, user, acceptedScopes);
} }
public String scope() {
return acceptedScopes == null ? null : String.join(" ", acceptedScopes);
}
} }

View File

@@ -1,7 +1,13 @@
package com.ysoft.geecon.dto; package com.ysoft.geecon.dto;
import java.util.Objects;
public record OAuthClient(String clientId, String description, String clientSecret, String redirectUri) { public record OAuthClient(String clientId, String description, String clientSecret, String redirectUri) {
public boolean validateRedirectUri(String redirectUri) { public boolean validateRedirectUri(String redirectUri) {
return this.redirectUri != null && this.redirectUri.equals(redirectUri); return this.redirectUri != null && this.redirectUri.equals(redirectUri);
} }
public boolean validateSecret(String clientSecret) {
return Objects.equals(clientSecret, this.clientSecret);
}
} }

View File

@@ -0,0 +1,65 @@
package com.ysoft.geecon.dto;
import jakarta.ws.rs.FormParam;
import org.jboss.resteasy.reactive.RestQuery;
import java.util.Arrays;
import java.util.List;
public class TokenParams {
@FormParam("grant_type")
private String grantType;
@FormParam("client_id")
private String clientId;
@FormParam("client_secret")
private String clientSecret;
@FormParam("redirect_uri")
private String redirectUri;
@FormParam("code")
private String code;
public String getGrantType() {
return grantType;
}
public void setGrantType(String grantType) {
this.grantType = grantType;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public String getRedirectUri() {
return redirectUri;
}
public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}

View File

@@ -14,6 +14,7 @@ public class ClientsRepo {
public ClientsRepo() { public ClientsRepo() {
register(new OAuthClient("my-public-client", "Example public client", null, "https://localhost:8888/oauth_success")); register(new OAuthClient("my-public-client", "Example public client", null, "https://localhost:8888/oauth_success"));
register(new OAuthClient("oauthdebugger", "Example public client", null, "https://oauthdebugger.com/debug"));
} }
public Optional<OAuthClient> getClient(String clientId) { public Optional<OAuthClient> getClient(String clientId) {