From 1bfe2b3d9ba8cb11960d4d421c59eacda4bdb5b3 Mon Sep 17 00:00:00 2001 From: chani2104 Date: Sat, 4 Jul 2026 13:53:41 +0900 Subject: [PATCH 01/55] =?UTF-8?q?feat:=20Analysis=20Topic=20=EA=B5=AC?= =?UTF-8?q?=EB=8F=85=20=EB=B3=80=EA=B2=BD=20/=20=EC=9A=B4=EB=B0=98=20?= =?UTF-8?q?=EB=8F=84=EC=B0=A9=20=EC=A0=95=EB=B3=B4=20API=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=20/=20AGV=20Simulation=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/client/AssemblyArrivalClient.java | 4 +++ .../aims/backend/config/RestClientConfig.java | 4 +++ .../backend/controller/AgvTestController.java | 4 +++ .../dashboard/AgvTestController.java | 28 ------------------- .../ManufacturingEventTestController.java | 23 --------------- .../scheduler/AgvSimulatorScheduler.java | 20 ------------- .../dashboard/scheduler/SimulationClock.java | 25 ----------------- .../dto/dashboard/AgvArrivalRequest.java | 4 +++ .../dto/dashboard/AgvDispatchTestRequest.java | 4 +++ .../dto/kafka/ManufacturingAnalysisEvent.java | 4 +++ .../AgvAnalysisAggregationService.java | 4 +++ .../ManufacturingAnalysisConsumer.java | 4 +++ 12 files changed, 32 insertions(+), 96 deletions(-) create mode 100644 src/main/java/com/aims/backend/client/AssemblyArrivalClient.java create mode 100644 src/main/java/com/aims/backend/config/RestClientConfig.java create mode 100644 src/main/java/com/aims/backend/controller/AgvTestController.java delete mode 100644 src/main/java/com/aims/backend/controller/dashboard/AgvTestController.java delete mode 100644 src/main/java/com/aims/backend/controller/dashboard/ManufacturingEventTestController.java delete mode 100644 src/main/java/com/aims/backend/domain/dashboard/scheduler/AgvSimulatorScheduler.java delete mode 100644 src/main/java/com/aims/backend/domain/dashboard/scheduler/SimulationClock.java create mode 100644 src/main/java/com/aims/backend/dto/dashboard/AgvArrivalRequest.java create mode 100644 src/main/java/com/aims/backend/dto/dashboard/AgvDispatchTestRequest.java create mode 100644 src/main/java/com/aims/backend/dto/kafka/ManufacturingAnalysisEvent.java create mode 100644 src/main/java/com/aims/backend/service/dashboard/AgvAnalysisAggregationService.java create mode 100644 src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java diff --git a/src/main/java/com/aims/backend/client/AssemblyArrivalClient.java b/src/main/java/com/aims/backend/client/AssemblyArrivalClient.java new file mode 100644 index 0000000..fe76f91 --- /dev/null +++ b/src/main/java/com/aims/backend/client/AssemblyArrivalClient.java @@ -0,0 +1,4 @@ +package com.aims.backend.client; + +public class AssemblyArrivalClient { +} diff --git a/src/main/java/com/aims/backend/config/RestClientConfig.java b/src/main/java/com/aims/backend/config/RestClientConfig.java new file mode 100644 index 0000000..65ede68 --- /dev/null +++ b/src/main/java/com/aims/backend/config/RestClientConfig.java @@ -0,0 +1,4 @@ +package com.aims.backend.config; + +public class RestClientConfig { +} diff --git a/src/main/java/com/aims/backend/controller/AgvTestController.java b/src/main/java/com/aims/backend/controller/AgvTestController.java new file mode 100644 index 0000000..3995553 --- /dev/null +++ b/src/main/java/com/aims/backend/controller/AgvTestController.java @@ -0,0 +1,4 @@ +package com.aims.backend.controller; + +public class AgvTestController { +} diff --git a/src/main/java/com/aims/backend/controller/dashboard/AgvTestController.java b/src/main/java/com/aims/backend/controller/dashboard/AgvTestController.java deleted file mode 100644 index 8db17f3..0000000 --- a/src/main/java/com/aims/backend/controller/dashboard/AgvTestController.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.aims.backend.controller.dashboard; - -import com.aims.backend.domain.dashboard.enums.ProcessCode; -import com.aims.backend.service.dashboard.AgvSimulationService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RequestMapping("/api/test/agv") -@RestController -@RequiredArgsConstructor -public class AgvTestController { - - private final AgvSimulationService agvSimulationService; - - @PostMapping("/dispatch") - public ResponseEntity dispatch( - @RequestParam Long carMasterId, - @RequestParam ProcessCode processCode - ) { - agvSimulationService.dispatchAgv( - carMasterId, - processCode - ); - - return ResponseEntity.ok().build(); - } -} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/controller/dashboard/ManufacturingEventTestController.java b/src/main/java/com/aims/backend/controller/dashboard/ManufacturingEventTestController.java deleted file mode 100644 index 838a8e2..0000000 --- a/src/main/java/com/aims/backend/controller/dashboard/ManufacturingEventTestController.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.aims.backend.controller.dashboard; - -import com.aims.backend.dto.dashboard.ManufacturingEventRequest; -import com.aims.backend.service.dashboard.AgvSimulationService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/api/test/manufacturing-events") /* 테스트 */ -@RequiredArgsConstructor -public class ManufacturingEventTestController { - - private final AgvSimulationService agvSimulationService; - - @PostMapping - public ResponseEntity receiveManufacturingEvent( - @RequestBody ManufacturingEventRequest request - ) { - agvSimulationService.handleManufacturingEvent(request); - return ResponseEntity.ok().build(); - } -} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/domain/dashboard/scheduler/AgvSimulatorScheduler.java b/src/main/java/com/aims/backend/domain/dashboard/scheduler/AgvSimulatorScheduler.java deleted file mode 100644 index 998c1c6..0000000 --- a/src/main/java/com/aims/backend/domain/dashboard/scheduler/AgvSimulatorScheduler.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.aims.backend.domain.dashboard.scheduler; - -import com.aims.backend.service.dashboard.AgvSimulationService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@RequiredArgsConstructor -public class AgvSimulatorScheduler { - - private final AgvSimulationService agvSimulationService; - - @Scheduled(fixedRate = 3000) - public void run() { - agvSimulationService.updateAgvProgress(); - } -} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/domain/dashboard/scheduler/SimulationClock.java b/src/main/java/com/aims/backend/domain/dashboard/scheduler/SimulationClock.java deleted file mode 100644 index 988dcf5..0000000 --- a/src/main/java/com/aims/backend/domain/dashboard/scheduler/SimulationClock.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.aims.backend.domain.dashboard.scheduler; - -import lombok.Getter; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; - -@Getter -@Component -public class SimulationClock { - - private LocalDateTime currentTime = LocalDateTime.of( - 2026, - 6, - 1, - 8, - 0, - 0 - ); - - public void tickSeconds(long seconds) { - this.currentTime = this.currentTime.plusSeconds(seconds); - } - -} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/dto/dashboard/AgvArrivalRequest.java b/src/main/java/com/aims/backend/dto/dashboard/AgvArrivalRequest.java new file mode 100644 index 0000000..52a08db --- /dev/null +++ b/src/main/java/com/aims/backend/dto/dashboard/AgvArrivalRequest.java @@ -0,0 +1,4 @@ +package com.aims.backend.dto.dashboard; + +public class AgvArrivalRequest { +} diff --git a/src/main/java/com/aims/backend/dto/dashboard/AgvDispatchTestRequest.java b/src/main/java/com/aims/backend/dto/dashboard/AgvDispatchTestRequest.java new file mode 100644 index 0000000..37d6499 --- /dev/null +++ b/src/main/java/com/aims/backend/dto/dashboard/AgvDispatchTestRequest.java @@ -0,0 +1,4 @@ +package com.aims.backend.dto.dashboard; + +public class AgvDispatchTest { +} diff --git a/src/main/java/com/aims/backend/dto/kafka/ManufacturingAnalysisEvent.java b/src/main/java/com/aims/backend/dto/kafka/ManufacturingAnalysisEvent.java new file mode 100644 index 0000000..11a74e8 --- /dev/null +++ b/src/main/java/com/aims/backend/dto/kafka/ManufacturingAnalysisEvent.java @@ -0,0 +1,4 @@ +package com.aims.backend.dto.kafka; + +public class ManufacturingAnalysisEvent { +} diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvAnalysisAggregationService.java b/src/main/java/com/aims/backend/service/dashboard/AgvAnalysisAggregationService.java new file mode 100644 index 0000000..a2030ad --- /dev/null +++ b/src/main/java/com/aims/backend/service/dashboard/AgvAnalysisAggregationService.java @@ -0,0 +1,4 @@ +package com.aims.backend.service.dashboard; + +public class AgvAnalysisAggregationService { +} diff --git a/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java b/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java new file mode 100644 index 0000000..0ace36d --- /dev/null +++ b/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java @@ -0,0 +1,4 @@ +package com.aims.backend.service.dashboard; + +public class ManufacturingAnalysisConsumer { +} From 1d66e18326128469675a93493391d97c0e99faea Mon Sep 17 00:00:00 2001 From: chani2104 Date: Sat, 4 Jul 2026 13:59:10 +0900 Subject: [PATCH 02/55] =?UTF-8?q?feat:=20Analysis=20Topic=20=EA=B5=AC?= =?UTF-8?q?=EB=8F=85=20=EB=B3=80=EA=B2=BD=20/=20=EC=9A=B4=EB=B0=98=20?= =?UTF-8?q?=EB=8F=84=EC=B0=A9=20=EC=A0=95=EB=B3=B4=20API=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=20/=20AGV=20Simulation=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/client/AssemblyArrivalClient.java | 62 ++- .../aims/backend/config/RestClientConfig.java | 12 +- .../backend/controller/AgvTestController.java | 31 +- .../domain/alert/AlertActionStatus.java | 3 +- .../backend/domain/alert/AlertSeverity.java | 3 +- .../domain/dashboard/AgvOperation.java | 37 +- .../domain/dashboard/enums/AgvStatus.java | 9 +- .../dto/dashboard/AgvArrivalRequest.java | 15 +- .../dto/dashboard/AgvDispatchTestRequest.java | 18 +- .../dto/dashboard/AgvOperationResponse.java | 44 +- .../dto/dashboard/AgvRealtimeState.java | 70 +++- .../dto/kafka/ManufacturingAnalysisEvent.java | 39 +- .../AgvAnalysisAggregationService.java | 152 ++++++- .../dashboard/AgvRealtimeRedisService.java | 40 +- .../dashboard/AgvSimulationService.java | 385 +++++++++++++----- .../service/dashboard/DashboardService.java | 11 +- .../ManufacturingAnalysisConsumer.java | 65 ++- .../dashboard/ManufacturingEventConsumer.java | 2 +- src/main/resources/application.yaml | 4 + 19 files changed, 823 insertions(+), 179 deletions(-) diff --git a/src/main/java/com/aims/backend/client/AssemblyArrivalClient.java b/src/main/java/com/aims/backend/client/AssemblyArrivalClient.java index fe76f91..0dae786 100644 --- a/src/main/java/com/aims/backend/client/AssemblyArrivalClient.java +++ b/src/main/java/com/aims/backend/client/AssemblyArrivalClient.java @@ -1,4 +1,64 @@ package com.aims.backend.client; +import com.aims.backend.domain.dashboard.enums.ProcessCode; +import com.aims.backend.dto.dashboard.AgvArrivalRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClient; + +import java.time.LocalDateTime; + +@Slf4j +@Component +@RequiredArgsConstructor public class AssemblyArrivalClient { -} + + private final RestClient restClient; + + @Value("${external.assembly-url}") + private String assemblyUrl; + + /** + * AGV 도착 정보를 Assembly Service로 전달 + */ + public void notifyAgvArrived( + String eventId + ) { + + AgvArrivalRequest request = + AgvArrivalRequest.builder() + .eventId(eventId) + .build(); + + String url = + assemblyUrl + "/api/internal/agv-arrivals"; + + log.info(""" + + ============================== + Assembly 도착 API 요청 + + url={} + eventId={} + + ============================== + + """, + url, + eventId + ); + + restClient.post() + .uri(url) + .body(request) + .retrieve() + .toBodilessEntity(); + + log.info( + "[ASSEMBLY ARRIVAL SUCCESS] eventId={}", + eventId + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/config/RestClientConfig.java b/src/main/java/com/aims/backend/config/RestClientConfig.java index 65ede68..01c01af 100644 --- a/src/main/java/com/aims/backend/config/RestClientConfig.java +++ b/src/main/java/com/aims/backend/config/RestClientConfig.java @@ -1,4 +1,14 @@ package com.aims.backend.config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestClient; + +@Configuration public class RestClientConfig { -} + + @Bean + public RestClient restClient() { + return RestClient.create(); + } +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/controller/AgvTestController.java b/src/main/java/com/aims/backend/controller/AgvTestController.java index 3995553..adb4b74 100644 --- a/src/main/java/com/aims/backend/controller/AgvTestController.java +++ b/src/main/java/com/aims/backend/controller/AgvTestController.java @@ -1,4 +1,33 @@ package com.aims.backend.controller; +import com.aims.backend.domain.dashboard.enums.ProcessCode; +import com.aims.backend.dto.dashboard.AgvDispatchTestRequest; +import com.aims.backend.service.dashboard.AgvSimulationService; +import lombok.RequiredArgsConstructor; +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; + +@RestController +@RequestMapping("/api/test/agv") +@RequiredArgsConstructor public class AgvTestController { -} + + private final AgvSimulationService agvSimulationService; + + @PostMapping("/dispatch") + public ResponseEntity dispatch( + @RequestBody AgvDispatchTestRequest request + ) { + + agvSimulationService.dispatchAgv( + request.getEventId(), + request.getCarMasterId(), + ProcessCode.valueOf(request.getProcessCode()) + ); + + return ResponseEntity.ok().build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/domain/alert/AlertActionStatus.java b/src/main/java/com/aims/backend/domain/alert/AlertActionStatus.java index fef0187..9117703 100644 --- a/src/main/java/com/aims/backend/domain/alert/AlertActionStatus.java +++ b/src/main/java/com/aims/backend/domain/alert/AlertActionStatus.java @@ -3,5 +3,6 @@ public enum AlertActionStatus { COMPLETED, INCOMPLETE, - NOT_NEEDED + NOT_NEEDED, + PENDING, } diff --git a/src/main/java/com/aims/backend/domain/alert/AlertSeverity.java b/src/main/java/com/aims/backend/domain/alert/AlertSeverity.java index 184879b..482a51f 100644 --- a/src/main/java/com/aims/backend/domain/alert/AlertSeverity.java +++ b/src/main/java/com/aims/backend/domain/alert/AlertSeverity.java @@ -2,5 +2,6 @@ public enum AlertSeverity { DANGER, - CAUTION + CAUTION, + WARNING } diff --git a/src/main/java/com/aims/backend/domain/dashboard/AgvOperation.java b/src/main/java/com/aims/backend/domain/dashboard/AgvOperation.java index c1d2dd1..6a1a468 100644 --- a/src/main/java/com/aims/backend/domain/dashboard/AgvOperation.java +++ b/src/main/java/com/aims/backend/domain/dashboard/AgvOperation.java @@ -18,6 +18,9 @@ public class AgvOperation extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(name = "event_id") + private String eventId; + @Column(name = "car_master_id") private Long carMasterId; @@ -40,11 +43,13 @@ public class AgvOperation extends BaseEntity { private Integer laneNo; public void dispatch( + String eventId, Long carMasterId, ProcessCode from, ProcessCode to, String routeCode ) { + this.eventId = eventId; this.carMasterId = carMasterId; this.currentProcess = from; this.targetProcess = to; @@ -52,11 +57,15 @@ public void dispatch( this.agvStatus = AgvStatus.MOVING; } + public void changeToUnloading() { + this.currentProcess = this.targetProcess; + this.agvStatus = AgvStatus.UNLOADING; + } + public void changeToReturning() { - ProcessCode arrivedProcess = this.targetProcess; - ProcessCode homeProcess = this.currentProcess; + ProcessCode arrivedProcess = this.currentProcess; + ProcessCode homeProcess = getHomeProcessByRouteCode(this.routeCode); - this.carMasterId = null; this.currentProcess = arrivedProcess; this.targetProcess = homeProcess; this.agvStatus = AgvStatus.RETURNING; @@ -64,13 +73,31 @@ public void changeToReturning() { public void changeToWaiting( ProcessCode homeProcess, - ProcessCode nextProcess, + ProcessCode nextTarget, String routeCode ) { + this.eventId = null; this.carMasterId = null; this.currentProcess = homeProcess; - this.targetProcess = nextProcess; + this.targetProcess = nextTarget; this.routeCode = routeCode; this.agvStatus = AgvStatus.WAITING; } + + private ProcessCode getHomeProcessByRouteCode(String routeCode) { + if ("PRESS_BODY".equals(routeCode)) { + return ProcessCode.PRESS; + } + if ("BODY_PAINT".equals(routeCode)) { + return ProcessCode.BODY; + } + if ("PAINT_ASSEMBLY".equals(routeCode)) { + return ProcessCode.PAINT; + } + if ("ASSEMBLY_INSPECTION".equals(routeCode)) { + return ProcessCode.ASSEMBLY; + } + + return this.currentProcess; + } } \ No newline at end of file diff --git a/src/main/java/com/aims/backend/domain/dashboard/enums/AgvStatus.java b/src/main/java/com/aims/backend/domain/dashboard/enums/AgvStatus.java index d83afa6..7a949ce 100644 --- a/src/main/java/com/aims/backend/domain/dashboard/enums/AgvStatus.java +++ b/src/main/java/com/aims/backend/domain/dashboard/enums/AgvStatus.java @@ -1,7 +1,8 @@ package com.aims.backend.domain.dashboard.enums; public enum AgvStatus { - WAITING, /** 대기중 */ - MOVING, /** 이동중 */ - RETURNING /** 복귀중 */ -} + WAITING, + MOVING, + UNLOADING, + RETURNING +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/dto/dashboard/AgvArrivalRequest.java b/src/main/java/com/aims/backend/dto/dashboard/AgvArrivalRequest.java index 52a08db..2143a6c 100644 --- a/src/main/java/com/aims/backend/dto/dashboard/AgvArrivalRequest.java +++ b/src/main/java/com/aims/backend/dto/dashboard/AgvArrivalRequest.java @@ -1,4 +1,17 @@ package com.aims.backend.dto.dashboard; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor public class AgvArrivalRequest { -} + + private String eventId; +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/dto/dashboard/AgvDispatchTestRequest.java b/src/main/java/com/aims/backend/dto/dashboard/AgvDispatchTestRequest.java index 37d6499..f37009f 100644 --- a/src/main/java/com/aims/backend/dto/dashboard/AgvDispatchTestRequest.java +++ b/src/main/java/com/aims/backend/dto/dashboard/AgvDispatchTestRequest.java @@ -1,4 +1,18 @@ package com.aims.backend.dto.dashboard; -public class AgvDispatchTest { -} +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AgvDispatchTestRequest { + + private String eventId; + + private Long carMasterId; + + private String processCode; + +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/dto/dashboard/AgvOperationResponse.java b/src/main/java/com/aims/backend/dto/dashboard/AgvOperationResponse.java index c849fe5..321f597 100644 --- a/src/main/java/com/aims/backend/dto/dashboard/AgvOperationResponse.java +++ b/src/main/java/com/aims/backend/dto/dashboard/AgvOperationResponse.java @@ -1,17 +1,37 @@ package com.aims.backend.dto.dashboard; +import lombok.AllArgsConstructor; +import lombok.Getter; + import java.time.LocalDateTime; -public record AgvOperationResponse( - Long id, - Long carMasterId, - String agvStatus, - String currentProcess, - String targetProcess, - Double progressRate, - Integer delaySeconds, - String routeCode, - Integer laneNo, - LocalDateTime updatedAt -) { +@Getter +@AllArgsConstructor +public class AgvOperationResponse { + + private Long agvId; + + private String eventId; + + private Long carMasterId; + + private String agvStatus; + + private String currentProcess; + + private String targetProcess; + + private Double progressRate; + + private Integer delaySeconds; + + private LocalDateTime startedAt; + + private LocalDateTime expectedArrivalTime; + + private String routeCode; + + private Integer laneNo; + + private LocalDateTime updatedAt; } \ No newline at end of file diff --git a/src/main/java/com/aims/backend/dto/dashboard/AgvRealtimeState.java b/src/main/java/com/aims/backend/dto/dashboard/AgvRealtimeState.java index db5f53f..56eff2c 100644 --- a/src/main/java/com/aims/backend/dto/dashboard/AgvRealtimeState.java +++ b/src/main/java/com/aims/backend/dto/dashboard/AgvRealtimeState.java @@ -1,7 +1,13 @@ package com.aims.backend.dto.dashboard; -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.Duration; +import java.time.LocalDateTime; @Getter @Setter @@ -12,17 +18,65 @@ public class AgvRealtimeState { private Long agvId; + private String eventId; + + private Long carMasterId; + + /** + * MOVING / RETURNING + */ + private String status; + + private String currentProcess; + + private String targetProcess; + + /** + * 누적 증가값이 아니라 startedAt, expectedArrivalTime 기준으로 계산된 값 + */ private Double progressRate; private Integer delaySeconds; - public void increaseProgress(double amount) { - double current = progressRate == null ? 0.0 : progressRate; - this.progressRate = Math.min(100.0, current + amount); + private LocalDateTime startedAt; + + private LocalDateTime expectedArrivalTime; + + public static AgvRealtimeState empty(Long agvId) { + return AgvRealtimeState.builder() + .agvId(agvId) + .progressRate(0.0) + .delaySeconds(0) + .build(); } - @JsonIgnore - public boolean isArrived() { - return progressRate != null && progressRate >= 100.0; + public void calculateProgress(LocalDateTime now) { + if (startedAt == null || expectedArrivalTime == null) { + this.progressRate = 0.0; + return; + } + + long totalMillis = Duration.between( + startedAt, + expectedArrivalTime + ).toMillis(); + + long elapsedMillis = Duration.between( + startedAt, + now + ).toMillis(); + + if (totalMillis <= 0) { + this.progressRate = 100.0; + return; + } + + double progress = + ((double) elapsedMillis / totalMillis) * 100.0; + + this.progressRate = Math.max( + 0.0, + Math.min(100.0, progress) + ); } } \ No newline at end of file diff --git a/src/main/java/com/aims/backend/dto/kafka/ManufacturingAnalysisEvent.java b/src/main/java/com/aims/backend/dto/kafka/ManufacturingAnalysisEvent.java index 11a74e8..0aa6756 100644 --- a/src/main/java/com/aims/backend/dto/kafka/ManufacturingAnalysisEvent.java +++ b/src/main/java/com/aims/backend/dto/kafka/ManufacturingAnalysisEvent.java @@ -1,4 +1,39 @@ package com.aims.backend.dto.kafka; -public class ManufacturingAnalysisEvent { -} +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record ManufacturingAnalysisEvent( + + String analysisId, + + String eventId, + + Long carMasterId, + + String processCode, + + String riskLevel, + + String analysisType, + + AnalysisResult analysisResult + +) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record AnalysisResult( + + boolean isAbnormal, + + boolean isBottleneck, + + boolean isQualityDefect, + + boolean isEquipmentFault, + + boolean isSequenceError + + ) { + } +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvAnalysisAggregationService.java b/src/main/java/com/aims/backend/service/dashboard/AgvAnalysisAggregationService.java index a2030ad..ab14b55 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvAnalysisAggregationService.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvAnalysisAggregationService.java @@ -1,4 +1,154 @@ package com.aims.backend.service.dashboard; +import com.aims.backend.dto.kafka.ManufacturingAnalysisEvent; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@Service +@RequiredArgsConstructor public class AgvAnalysisAggregationService { -} + + private static final String PREFIX = "agv:analysis:"; + + private static final String BOTTLENECK = + "BOTTLENECK_ANALYSIS"; + + private static final String DEFECT_TRANSFER = + "DEFECT_TRANSFER_PREDICTION"; + + private static final String PROCESS_RISK = + "PROCESS_RISK_ANALYSIS"; + + private final RedisTemplate redisTemplate; + + public Optional collect( + ManufacturingAnalysisEvent event + ) { + + String key = PREFIX + event.eventId(); + + @SuppressWarnings("unchecked") + Map result = + (Map) redisTemplate + .opsForValue() + .get(key); + + if (result == null) { + result = new HashMap<>(); + } + + // 현재 분석 결과 저장 + result.put( + event.analysisType(), + event.analysisResult().isAbnormal() + ); + + redisTemplate.opsForValue().set( + key, + result, + Duration.ofMinutes(30) + ); + + // 3개의 분석이 모두 도착했는지 확인 + boolean completed = + result.containsKey(BOTTLENECK) + && result.containsKey(DEFECT_TRANSFER) + && result.containsKey(PROCESS_RISK); + + if (!completed) { + + log.debug( + "[AGV WAIT] eventId={} analysisType={} ({}/3)", + event.eventId(), + event.analysisType(), + result.size() + ); + + return Optional.empty(); + } + + boolean bottleneckAbnormal = + result.get(BOTTLENECK); + + boolean defectTransferAbnormal = + result.get(DEFECT_TRANSFER); + + boolean processRiskAbnormal = + result.get(PROCESS_RISK); + + boolean abnormal = + bottleneckAbnormal + || defectTransferAbnormal + || processRiskAbnormal; + + if (abnormal) { + + StringBuilder reasons = new StringBuilder(); + + if (bottleneckAbnormal) { + reasons.append("BOTTLENECK_ANALYSIS "); + } + + if (defectTransferAbnormal) { + reasons.append("DEFECT_TRANSFER_PREDICTION "); + } + + if (processRiskAbnormal) { + reasons.append("PROCESS_RISK_ANALYSIS "); + } + + log.info(""" + + ============================== + AGV 출발 취소 + + eventId={} + + 이상 분석 : {} + + BOTTLENECK_ANALYSIS : {} + DEFECT_TRANSFER_PREDICTION : {} + PROCESS_RISK_ANALYSIS : {} + + ============================== + + """, + event.eventId(), + reasons.toString().trim(), + bottleneckAbnormal, + defectTransferAbnormal, + processRiskAbnormal + ); + + } else { + + log.info(""" + + ============================== + AGV 출발 가능 + + eventId={} + + 모든 AI 분석 정상 + + ============================== + + """, + event.eventId() + ); + } + + // 최종 판단 완료 → Redis 삭제 + redisTemplate.delete(key); + + return Optional.of(abnormal); + } +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvRealtimeRedisService.java b/src/main/java/com/aims/backend/service/dashboard/AgvRealtimeRedisService.java index 55ae499..4d8fccf 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvRealtimeRedisService.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvRealtimeRedisService.java @@ -10,16 +10,16 @@ @RequiredArgsConstructor public class AgvRealtimeRedisService { - private final RedisTemplate redisTemplate; - private final ObjectMapper objectMapper = - new ObjectMapper(); - private static final String KEY_PREFIX = "agv:realtime:"; + private final RedisTemplate redisTemplate; + private final ObjectMapper objectMapper; + /** * Redis 저장 */ public void save(AgvRealtimeState state) { + redisTemplate.opsForValue().set( makeKey(state.getAgvId()), state @@ -36,7 +36,7 @@ public AgvRealtimeState get(Long agvId) { .get(makeKey(agvId)); if (value == null) { - return defaultState(agvId); + return AgvRealtimeState.empty(agvId); } return objectMapper.convertValue( @@ -46,36 +46,20 @@ public AgvRealtimeState get(Long agvId) { } /** - * 진행률 초기화 + * Redis 삭제 */ - public void reset(Long agvId) - { - AgvRealtimeState state = - AgvRealtimeState.builder() - .agvId(agvId) - .progressRate(0.0) - .delaySeconds(0) - .build(); + public void delete(Long agvId) { - save(state); + redisTemplate.delete( + makeKey(agvId) + ); } /** - * Redis 삭제 + * Redis Key 생성 */ - public void delete(Long agvId) { - redisTemplate.delete(makeKey(agvId)); - } - private String makeKey(Long agvId) { - return KEY_PREFIX + agvId; - } - private AgvRealtimeState defaultState(Long agvId) { - return AgvRealtimeState.builder() - .agvId(agvId) - .progressRate(0.0) - .delaySeconds(0) - .build(); + return KEY_PREFIX + agvId; } } \ No newline at end of file diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java b/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java index 091635b..ceb5763 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java @@ -1,5 +1,6 @@ package com.aims.backend.service.dashboard; +import com.aims.backend.client.AssemblyArrivalClient; import com.aims.backend.domain.dashboard.AgvOperation; import com.aims.backend.domain.dashboard.enums.AgvStatus; import com.aims.backend.domain.dashboard.enums.ProcessCode; @@ -7,14 +8,18 @@ import com.aims.backend.dto.dashboard.AgvRealtimeState; import com.aims.backend.dto.dashboard.ManufacturingEventRequest; import com.aims.backend.repository.dashboard.AgvOperationRepository; +import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.hc.client5.http.RouteInfo; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; +import java.util.concurrent.*; @Slf4j @Service @@ -24,51 +29,46 @@ public class AgvSimulationService { private final AgvOperationRepository agvOperationRepository; private final AgvRealtimeRedisService agvRealtimeRedisService; private final SimpMessagingTemplate messagingTemplate; + private final TransactionTemplate transactionTemplate; + private final AssemblyArrivalClient assemblyArrivalClient; - private static final double PROGRESS_STEP = 5.0; + private final ScheduledExecutorService executorService = + Executors.newScheduledThreadPool(10); + + private final Map> scheduledTasks = + new ConcurrentHashMap<>(); + + private static final int MOVE_DURATION_SECONDS = 30; + private static final int UNLOADING_DURATION_SECONDS = 5; + private static final int RETURN_DURATION_SECONDS = 30; private static final Map ROUTES = Map.of( ProcessCode.PRESS, - new RouteInfo( - ProcessCode.PRESS, - ProcessCode.BODY, - "PRESS_BODY" - ), + new RouteInfo(ProcessCode.PRESS, ProcessCode.BODY, "PRESS_BODY"), ProcessCode.BODY, - new RouteInfo( - ProcessCode.BODY, - ProcessCode.PAINT, - "BODY_PAINT" - ), + new RouteInfo(ProcessCode.BODY, ProcessCode.PAINT, "BODY_PAINT"), ProcessCode.PAINT, - new RouteInfo( - ProcessCode.PAINT, - ProcessCode.ASSEMBLY, - "PAINT_ASSEMBLY" - ), + new RouteInfo(ProcessCode.PAINT, ProcessCode.ASSEMBLY, "PAINT_ASSEMBLY"), ProcessCode.ASSEMBLY, - new RouteInfo( - ProcessCode.ASSEMBLY, - ProcessCode.INSPECTION, - "ASSEMBLY_INSPECTION" - ) + new RouteInfo(ProcessCode.ASSEMBLY, ProcessCode.INSPECTION, "ASSEMBLY_INSPECTION") ); - @Transactional - public void handleManufacturingEvent(ManufacturingEventRequest request) { - ProcessCode currentProcess = ProcessCode.valueOf(request.getProcessCode()); + /*public void handleManufacturingEvent(ManufacturingEventRequest request) { + ProcessCode currentProcess = + ProcessCode.valueOf(request.getProcessCode()); dispatchAgv( + request.getEventId(), request.getCarMasterId(), currentProcess ); - } + }*/ - @Transactional public void dispatchAgv( + String eventId, Long carMasterId, ProcessCode currentProcess ) { @@ -76,137 +76,291 @@ public void dispatchAgv( if (routeInfo == null) { log.info( - "[AGV DISPATCH SKIP] 운반 대상 공정 아님. process={}", + "[AGV DISPATCH SKIP] 운반 대상 공정 아님. eventId={}, process={}", + eventId, currentProcess ); return; } - AgvOperation agv = agvOperationRepository - .findFirstByRouteCodeAndAgvStatusOrderByLaneNoAsc( - routeInfo.routeCode(), - AgvStatus.WAITING - ) - .orElseThrow(() -> new IllegalStateException( - "대기 중인 AGV가 없습니다. routeCode=" + routeInfo.routeCode() - )); + AgvOperation agv = transactionTemplate.execute(status -> { + AgvOperation selectedAgv = + agvOperationRepository + .findFirstByRouteCodeAndAgvStatusOrderByLaneNoAsc( + routeInfo.routeCode(), + AgvStatus.WAITING + ) + .orElseThrow(() -> new IllegalStateException( + "대기 중인 AGV가 없습니다. routeCode=" + routeInfo.routeCode() + )); + + selectedAgv.dispatch( + eventId, + carMasterId, + routeInfo.from(), + routeInfo.to(), + routeInfo.routeCode() + ); + + return agvOperationRepository.save(selectedAgv); + }); - agv.dispatch( + if (agv == null) { + throw new IllegalStateException("AGV 출발 처리 실패"); + } + + startMovingSession( + agv.getId(), + eventId, carMasterId, - routeInfo.from(), - routeInfo.to(), - routeInfo.routeCode() + routeInfo ); + } - agvRealtimeRedisService.reset(agv.getId()); + private void startMovingSession( + Long agvId, + String eventId, + Long carMasterId, + RouteInfo routeInfo + ) { + cancelScheduledTask(agvId); - agvOperationRepository.save(agv); + LocalDateTime startedAt = LocalDateTime.now(); + LocalDateTime expectedArrivalTime = + startedAt.plusSeconds(MOVE_DURATION_SECONDS); + + AgvRealtimeState state = + AgvRealtimeState.builder() + .agvId(agvId) + .eventId(eventId) + .carMasterId(carMasterId) + .status(AgvStatus.MOVING.name()) + .currentProcess(routeInfo.from().name()) + .targetProcess(routeInfo.to().name()) + .progressRate(0.0) + .delaySeconds(0) + .startedAt(startedAt) + .expectedArrivalTime(expectedArrivalTime) + .build(); + + agvRealtimeRedisService.save(state); log.info( - "[AGV DISPATCH] agvId={}, carMasterId={}, {} -> {}, routeCode={}", - agv.getId(), + "[AGV MOVING START] agvId={}, eventId={}, carMasterId={}, {} -> {}, expectedArrival={}", + agvId, + eventId, carMasterId, routeInfo.from(), routeInfo.to(), - routeInfo.routeCode() + expectedArrivalTime ); sendAgvStatus(); - } - @Transactional - public void updateAgvProgress() { - List activeAgvs = - agvOperationRepository.findByAgvStatusIn( - List.of( - AgvStatus.MOVING, - AgvStatus.RETURNING - ) + ScheduledFuture task = + executorService.schedule( + () -> handleMovingArrived( + agvId, + eventId, + carMasterId, + routeInfo + ), + MOVE_DURATION_SECONDS, + TimeUnit.SECONDS ); - for (AgvOperation agv : activeAgvs) { - AgvRealtimeState state = - agvRealtimeRedisService.get(agv.getId()); + scheduledTasks.put(agvId, task); + } - state.increaseProgress(PROGRESS_STEP); + private void handleMovingArrived( + Long agvId, + String eventId, + Long carMasterId, + RouteInfo routeInfo + ) { + scheduledTasks.remove(agvId); - if (!state.isArrived()) { - agvRealtimeRedisService.save(state); + transactionTemplate.executeWithoutResult(status -> { + AgvOperation agv = + agvOperationRepository.findById(agvId) + .orElseThrow(() -> new IllegalStateException( + "AGV를 찾을 수 없습니다. agvId=" + agvId + )); - log.debug( - "[AGV PROGRESS] agvId={}, status={}, progress={}", - agv.getId(), - agv.getAgvStatus(), - state.getProgressRate() - ); + agv.changeToUnloading(); + agvOperationRepository.save(agv); + }); - continue; - } + log.info( + "[AGV ARRIVED / UNLOADING START] agvId={}, eventId={}, carMasterId={}, arrivedProcess={}", + agvId, + eventId, + carMasterId, + routeInfo.to() + ); - if (agv.getAgvStatus() == AgvStatus.MOVING) { - handleMovingArrived(agv); - } else if (agv.getAgvStatus() == AgvStatus.RETURNING) { - handleReturningArrived(agv); - } + try { + assemblyArrivalClient.notifyAgvArrived(eventId); + } catch (Exception e) { + log.warn( + "[ASSEMBLY ARRIVAL FAILED] AGV 흐름은 계속 진행합니다. eventId={}", + eventId, + e + ); } - sendAgvStatus(); + startUnloadingSession( + agvId, + eventId, + carMasterId, + routeInfo + ); } - private void handleMovingArrived(AgvOperation agv) { - ProcessCode arrivedProcess = agv.getTargetProcess(); - ProcessCode homeProcess = agv.getCurrentProcess(); + private void startUnloadingSession( + Long agvId, + String eventId, + Long carMasterId, + RouteInfo routeInfo + ) { + cancelScheduledTask(agvId); - agv.changeToReturning(); + LocalDateTime startedAt = LocalDateTime.now(); + LocalDateTime expectedEndTime = + startedAt.plusSeconds(UNLOADING_DURATION_SECONDS); - agvRealtimeRedisService.reset(agv.getId()); + AgvRealtimeState state = + AgvRealtimeState.builder() + .agvId(agvId) + .eventId(eventId) + .carMasterId(carMasterId) + .status(AgvStatus.UNLOADING.name()) + .currentProcess(routeInfo.to().name()) + .targetProcess(routeInfo.to().name()) + .progressRate(100.0) + .delaySeconds(0) + .startedAt(startedAt) + .expectedArrivalTime(expectedEndTime) + .build(); + + agvRealtimeRedisService.save(state); log.info( - "[AGV ARRIVED] agvId={}, arrived={}, returningTo={}", - agv.getId(), - arrivedProcess, - homeProcess + "[AGV UNLOADING] agvId={}, eventId={}, duration={}s, expectedEnd={}", + agvId, + eventId, + UNLOADING_DURATION_SECONDS, + expectedEndTime ); - } - private void handleReturningArrived(AgvOperation agv) { - ProcessCode homeProcess = agv.getTargetProcess(); + sendAgvStatus(); - RouteInfo routeInfo = ROUTES.get(homeProcess); + ScheduledFuture task = + executorService.schedule( + () -> startReturningSession( + agvId, + routeInfo + ), + UNLOADING_DURATION_SECONDS, + TimeUnit.SECONDS + ); - if (routeInfo == null) { - agv.changeToWaiting( - homeProcess, - homeProcess, - null - ); + scheduledTasks.put(agvId, task); + } - agvRealtimeRedisService.reset(agv.getId()); + private void startReturningSession( + Long agvId, + RouteInfo routeInfo + ) { + cancelScheduledTask(agvId); - log.info( - "[AGV WAITING] agvId={}, home={}", - agv.getId(), - homeProcess - ); + transactionTemplate.executeWithoutResult(status -> { + AgvOperation agv = + agvOperationRepository.findById(agvId) + .orElseThrow(() -> new IllegalStateException( + "AGV를 찾을 수 없습니다. agvId=" + agvId + )); - return; - } + agv.changeToReturning(); + agvOperationRepository.save(agv); + }); - agv.changeToWaiting( - routeInfo.from(), + LocalDateTime startedAt = LocalDateTime.now(); + LocalDateTime expectedArrivalTime = + startedAt.plusSeconds(RETURN_DURATION_SECONDS); + + AgvRealtimeState state = + AgvRealtimeState.builder() + .agvId(agvId) + .eventId(null) + .carMasterId(null) + .status(AgvStatus.RETURNING.name()) + .currentProcess(routeInfo.to().name()) + .targetProcess(routeInfo.from().name()) + .progressRate(0.0) + .delaySeconds(0) + .startedAt(startedAt) + .expectedArrivalTime(expectedArrivalTime) + .build(); + + agvRealtimeRedisService.save(state); + + log.info( + "[AGV RETURNING START] agvId={}, {} -> {}, expectedArrival={}", + agvId, routeInfo.to(), - routeInfo.routeCode() + routeInfo.from(), + expectedArrivalTime ); - agvRealtimeRedisService.reset(agv.getId()); + sendAgvStatus(); + + ScheduledFuture task = + executorService.schedule( + () -> handleReturningArrived( + agvId, + routeInfo + ), + RETURN_DURATION_SECONDS, + TimeUnit.SECONDS + ); + + scheduledTasks.put(agvId, task); + } + + private void handleReturningArrived( + Long agvId, + RouteInfo routeInfo + ) { + scheduledTasks.remove(agvId); + + transactionTemplate.executeWithoutResult(status -> { + AgvOperation agv = + agvOperationRepository.findById(agvId) + .orElseThrow(() -> new IllegalStateException( + "AGV를 찾을 수 없습니다. agvId=" + agvId + )); + + agv.changeToWaiting( + routeInfo.from(), + routeInfo.to(), + routeInfo.routeCode() + ); + + agvOperationRepository.save(agv); + }); + + agvRealtimeRedisService.delete(agvId); log.info( "[AGV RETURN COMPLETE] agvId={}, waitingAt={}, nextTarget={}, routeCode={}", - agv.getId(), + agvId, routeInfo.from(), routeInfo.to(), routeInfo.routeCode() ); + + sendAgvStatus(); } private void sendAgvStatus() { @@ -226,20 +380,39 @@ private AgvOperationResponse toResponse(AgvOperation agv) { AgvRealtimeState state = agvRealtimeRedisService.get(agv.getId()); + state.calculateProgress(LocalDateTime.now()); + return new AgvOperationResponse( agv.getId(), + state.getEventId(), agv.getCarMasterId(), agv.getAgvStatus().name(), agv.getCurrentProcess().name(), agv.getTargetProcess().name(), state.getProgressRate(), state.getDelaySeconds(), + state.getStartedAt(), + state.getExpectedArrivalTime(), agv.getRouteCode(), agv.getLaneNo(), agv.getUpdatedAt() ); } + private void cancelScheduledTask(Long agvId) { + ScheduledFuture task = + scheduledTasks.remove(agvId); + + if (task != null && !task.isDone()) { + task.cancel(false); + } + } + + @PreDestroy + public void shutdown() { + executorService.shutdownNow(); + } + private record RouteInfo( ProcessCode from, ProcessCode to, diff --git a/src/main/java/com/aims/backend/service/dashboard/DashboardService.java b/src/main/java/com/aims/backend/service/dashboard/DashboardService.java index 2d6b4cd..d1cad98 100644 --- a/src/main/java/com/aims/backend/service/dashboard/DashboardService.java +++ b/src/main/java/com/aims/backend/service/dashboard/DashboardService.java @@ -148,9 +148,8 @@ public AgvStatusSummaryResponse getAgvStatusSummary() { agvOperationRepository.count(); long movingCount = - agvOperationRepository.countByAgvStatus( - AgvStatus.MOVING - ); + agvOperationRepository.countByAgvStatus(AgvStatus.MOVING) + + agvOperationRepository.countByAgvStatus(AgvStatus.UNLOADING); long waitingCount = agvOperationRepository.countByAgvStatus( @@ -207,8 +206,11 @@ private AgvOperationResponse toResponse( agv.getId() ); + realtimeState.calculateProgress(LocalDateTime.now()); + return new AgvOperationResponse( agv.getId(), + realtimeState.getEventId(), agv.getCarMasterId(), agv.getAgvStatus().name(), @@ -218,6 +220,9 @@ private AgvOperationResponse toResponse( realtimeState.getProgressRate(), realtimeState.getDelaySeconds(), + realtimeState.getStartedAt(), + realtimeState.getExpectedArrivalTime(), + agv.getRouteCode(), agv.getLaneNo(), agv.getUpdatedAt() diff --git a/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java b/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java index 0ace36d..531cf16 100644 --- a/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java +++ b/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java @@ -1,4 +1,67 @@ package com.aims.backend.service.dashboard; +import com.aims.backend.domain.dashboard.enums.ProcessCode; +import com.aims.backend.dto.kafka.ManufacturingAnalysisEvent; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +@RequiredArgsConstructor +@Slf4j public class ManufacturingAnalysisConsumer { -} + + private final ObjectMapper objectMapper; + private final AgvSimulationService agvSimulationService; + private final AgvAnalysisAggregationService aggregationService; + + @KafkaListener( + topics = "factory.manufacturing.analysis", + groupId = "main-agv-group" + ) + public void consume(String message) { + + try { + + ManufacturingAnalysisEvent event = + objectMapper.readValue( + message, + ManufacturingAnalysisEvent.class + ); + + Optional result = + aggregationService.collect(event); + + // 아직 3개가 안 모임 + if (result.isEmpty()) { + return; + } + + // 하나라도 abnormal + if (result.get()) { + + log.info( + "[AGV SKIP] eventId={}", + event.eventId() + ); + + return; + } + + // 모두 정상 + agvSimulationService.dispatchAgv( + event.eventId(), + event.carMasterId(), + ProcessCode.valueOf(event.processCode()) + ); + + } catch (Exception e) { + + log.error("Analysis Kafka 처리 실패", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/service/dashboard/ManufacturingEventConsumer.java b/src/main/java/com/aims/backend/service/dashboard/ManufacturingEventConsumer.java index 3c47aed..08ecad9 100644 --- a/src/main/java/com/aims/backend/service/dashboard/ManufacturingEventConsumer.java +++ b/src/main/java/com/aims/backend/service/dashboard/ManufacturingEventConsumer.java @@ -18,7 +18,7 @@ public class ManufacturingEventConsumer { private final ObjectMapper objectMapper; private final AgvSimulationService agvSimulationService; - @KafkaListener( + /*@KafkaListener( topics = "factory.manufacturing.raw", groupId = "main-agv-group" ) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 3de3c72..d67a683 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -81,6 +81,7 @@ app: group-id: ${KAFKA_GROUP_ID:main-dev} auto-offset-reset: ${KAFKA_AUTO_OFFSET_RESET:earliest} listeners-enabled: ${KAFKA_LISTENERS_ENABLED:true} + analysis-topic: factory.manufacturing.analysis security-protocol: SASL_SSL sasl-mechanism: AWS_MSK_IAM @@ -95,3 +96,6 @@ jwt: expiration: access: ${JWT_ACCESS_EXPIRATION:3600000} refresh: ${JWT_REFRESH_EXPIRATION:1209600000} + +external: + assembly-url: ${ASSEMBLY_SERVICE_URL:http://localhost:8082} From b1f38dcf9cb264dd9f4adafbb22c7fa9f11b1c8d Mon Sep 17 00:00:00 2001 From: haseokyung6 Date: Tue, 7 Jul 2026 15:14:48 +0900 Subject: [PATCH 03/55] =?UTF-8?q?refactor:=20gitAction=EA=B3=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=83=9C=EA=B7=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-backend.yml | 212 ++++++++++++++++++++++----- 1 file changed, 178 insertions(+), 34 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index c9dbf70..76738f9 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -17,8 +17,8 @@ on: workflow_dispatch: concurrency: - group: backend-${{ github.ref_name }} - cancel-in-progress: true + group: backend-gitops-dev + cancel-in-progress: false env: AWS_REGION: ap-northeast-2 @@ -26,7 +26,9 @@ env: ECR_REPOSITORY: aims/backend NAMESPACE: aims-project BACKEND_CONTEXT: . + INFRA_REPOSITORY: SK-Rookies-AIMS/infra + INFRA_BRANCH: dev INFRA_MANIFEST_PATH: k8s/backend/kustomization.yaml jobs: @@ -53,47 +55,113 @@ jobs: - name: Ensure ECR repository exists run: | - aws ecr describe-repositories --repository-names "$ECR_REPOSITORY" --region "$AWS_REGION" >/dev/null 2>&1 || aws ecr create-repository --repository-name "$ECR_REPOSITORY" --region "$AWS_REGION" --image-scanning-configuration scanOnPush=true --image-tag-mutability MUTABLE + set -euo pipefail + + if ! aws ecr describe-repositories \ + --repository-names "$ECR_REPOSITORY" \ + --region "$AWS_REGION" \ + > /dev/null 2>&1; then + + aws ecr create-repository \ + --repository-name "$ECR_REPOSITORY" \ + --region "$AWS_REGION" \ + --image-scanning-configuration scanOnPush=true \ + --image-tag-mutability MUTABLE + fi - name: Build and push Docker image id: build-image run: | + set -euo pipefail + IMAGE_TAG="${GITHUB_REF_NAME}-${GITHUB_SHA::12}" IMAGE_URI="${{ steps.login-ecr.outputs.registry }}/${ECR_REPOSITORY}:${IMAGE_TAG}" BRANCH_IMAGE_URI="${{ steps.login-ecr.outputs.registry }}/${ECR_REPOSITORY}:${GITHUB_REF_NAME}" - docker build -t "$IMAGE_URI" -t "$BRANCH_IMAGE_URI" "$BACKEND_CONTEXT" + echo "Building image: $IMAGE_URI" + + docker build \ + -t "$IMAGE_URI" \ + -t "$BRANCH_IMAGE_URI" \ + "$BACKEND_CONTEXT" + docker push "$IMAGE_URI" docker push "$BRANCH_IMAGE_URI" echo "image_uri=$IMAGE_URI" >> "$GITHUB_OUTPUT" + echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT" - name: Checkout infra repository uses: actions/checkout@v4 with: repository: ${{ env.INFRA_REPOSITORY }} - ref: ${{ github.ref_name }} + ref: ${{ env.INFRA_BRANCH }} token: ${{ secrets.INFRA_REPO_TOKEN }} path: infra fetch-depth: 0 - name: Update kubeconfig and ensure namespace - if: github.ref_name == 'dev' run: | - aws eks update-kubeconfig --region "$AWS_REGION" --name "$EKS_CLUSTER_NAME" - kubectl create namespace "$NAMESPACE" --dry-run=client -o yaml | kubectl apply -f - + set -euo pipefail + + aws eks update-kubeconfig \ + --region "$AWS_REGION" \ + --name "$EKS_CLUSTER_NAME" + + kubectl create namespace "$NAMESPACE" \ + --dry-run=client \ + -o yaml | + kubectl apply -f - - name: Load backend runtime parameters from SSM - if: github.ref_name == 'dev' run: | - RDS_SECRET_ARN=$(aws ssm get-parameter --name "/aims/dev/rds/secret-arn" --region "$AWS_REGION" --query "Parameter.Value" --output text) - RDS_HOST=$(aws ssm get-parameter --name "/aims/dev/backend/rds-host" --region "$AWS_REGION" --query "Parameter.Value" --output text) - RDS_PORT=$(aws ssm get-parameter --name "/aims/dev/backend/rds-port" --region "$AWS_REGION" --query "Parameter.Value" --output text) - MAIN_DB_NAME=$(aws ssm get-parameter --name "/aims/dev/backend/main-db-name" --region "$AWS_REGION" --query "Parameter.Value" --output text) - SAMPLE_DB_NAME=$(aws ssm get-parameter --name "/aims/dev/backend/sample-db-name" --region "$AWS_REGION" --query "Parameter.Value" --output text) - JWT_SECRET_KEY=$(aws ssm get-parameter --name "/aims/dev/backend/jwt-secret-key" --region "$AWS_REGION" --with-decryption --query "Parameter.Value" --output text) - - for VALUE in "$RDS_SECRET_ARN" "$RDS_HOST" "$RDS_PORT" "$MAIN_DB_NAME" "$SAMPLE_DB_NAME" "$JWT_SECRET_KEY"; do + set -euo pipefail + + RDS_SECRET_ARN=$(aws ssm get-parameter \ + --name "/aims/dev/rds/secret-arn" \ + --region "$AWS_REGION" \ + --query "Parameter.Value" \ + --output text) + + RDS_HOST=$(aws ssm get-parameter \ + --name "/aims/dev/backend/rds-host" \ + --region "$AWS_REGION" \ + --query "Parameter.Value" \ + --output text) + + RDS_PORT=$(aws ssm get-parameter \ + --name "/aims/dev/backend/rds-port" \ + --region "$AWS_REGION" \ + --query "Parameter.Value" \ + --output text) + + MAIN_DB_NAME=$(aws ssm get-parameter \ + --name "/aims/dev/backend/main-db-name" \ + --region "$AWS_REGION" \ + --query "Parameter.Value" \ + --output text) + + SAMPLE_DB_NAME=$(aws ssm get-parameter \ + --name "/aims/dev/backend/sample-db-name" \ + --region "$AWS_REGION" \ + --query "Parameter.Value" \ + --output text) + + JWT_SECRET_KEY=$(aws ssm get-parameter \ + --name "/aims/dev/backend/jwt-secret-key" \ + --region "$AWS_REGION" \ + --with-decryption \ + --query "Parameter.Value" \ + --output text) + + for VALUE in \ + "$RDS_SECRET_ARN" \ + "$RDS_HOST" \ + "$RDS_PORT" \ + "$MAIN_DB_NAME" \ + "$SAMPLE_DB_NAME" \ + "$JWT_SECRET_KEY"; do + if [ -z "$VALUE" ] || [ "$VALUE" = "None" ]; then echo "Required backend SSM parameter is empty" exit 1 @@ -111,19 +179,30 @@ jobs: echo "JWT_SECRET_KEY=$JWT_SECRET_KEY" >> "$GITHUB_ENV" - name: Create or update backend Kubernetes Secret - if: github.ref_name == 'dev' run: | - SECRET_JSON=$(aws secretsmanager get-secret-value --secret-id "$RDS_SECRET_ARN" --region "$AWS_REGION" --query "SecretString" --output text) + set -euo pipefail - DB_USERNAME=$(echo "$SECRET_JSON" | jq -r '.username') - DB_PASSWORD=$(echo "$SECRET_JSON" | jq -r '.password') + SECRET_JSON=$(aws secretsmanager get-secret-value \ + --secret-id "$RDS_SECRET_ARN" \ + --region "$AWS_REGION" \ + --query "SecretString" \ + --output text) - if [ -z "$DB_USERNAME" ] || [ "$DB_USERNAME" = "null" ] || [ -z "$DB_PASSWORD" ] || [ "$DB_PASSWORD" = "null" ]; then - echo "RDS username or password is missing" + DB_USERNAME=$(echo "$SECRET_JSON" | jq -r ".username") + DB_PASSWORD=$(echo "$SECRET_JSON" | jq -r ".password") + + if [ -z "$DB_USERNAME" ] || [ "$DB_USERNAME" = "null" ]; then + echo "RDS username is missing" + exit 1 + fi + + if [ -z "$DB_PASSWORD" ] || [ "$DB_PASSWORD" = "null" ]; then + echo "RDS password is missing" exit 1 fi MAIN_DB_JDBC_URL="jdbc:mysql://$RDS_HOST:$RDS_PORT/$MAIN_DB_NAME?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul&characterEncoding=UTF-8" + SAMPLE_DB_JDBC_URL="jdbc:mysql://$RDS_HOST:$RDS_PORT/$SAMPLE_DB_NAME?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul&characterEncoding=UTF-8" echo "::add-mask::$DB_USERNAME" @@ -131,22 +210,81 @@ jobs: echo "::add-mask::$MAIN_DB_JDBC_URL" echo "::add-mask::$SAMPLE_DB_JDBC_URL" - kubectl create secret generic backend-rds-secret -n "$NAMESPACE" --from-literal=MAIN_DB_JDBC_URL="$MAIN_DB_JDBC_URL" --from-literal=MAIN_DB_USERNAME="$DB_USERNAME" --from-literal=MAIN_DB_PASSWORD="$DB_PASSWORD" --from-literal=SAMPLE_DB_JDBC_URL="$SAMPLE_DB_JDBC_URL" --from-literal=SAMPLE_DB_USERNAME="$DB_USERNAME" --from-literal=SAMPLE_DB_PASSWORD="$DB_PASSWORD" --from-literal=JWT_SECRET_KEY="$JWT_SECRET_KEY" --dry-run=client -o yaml | kubectl apply -f - + kubectl create secret generic backend-rds-secret \ + --namespace "$NAMESPACE" \ + --from-literal=MAIN_DB_JDBC_URL="$MAIN_DB_JDBC_URL" \ + --from-literal=MAIN_DB_USERNAME="$DB_USERNAME" \ + --from-literal=MAIN_DB_PASSWORD="$DB_PASSWORD" \ + --from-literal=SAMPLE_DB_JDBC_URL="$SAMPLE_DB_JDBC_URL" \ + --from-literal=SAMPLE_DB_USERNAME="$DB_USERNAME" \ + --from-literal=SAMPLE_DB_PASSWORD="$DB_PASSWORD" \ + --from-literal=JWT_SECRET_KEY="$JWT_SECRET_KEY" \ + --dry-run=client \ + -o yaml | + kubectl apply -f - - name: Update backend image in GitOps repository env: IMAGE_URI: ${{ steps.build-image.outputs.image_uri }} run: | + set -euo pipefail + MANIFEST="infra/${INFRA_MANIFEST_PATH}" - test -f "$MANIFEST" + if [ ! -f "$MANIFEST" ]; then + echo "Manifest file not found: $MANIFEST" + exit 1 + fi + IMAGE_TAG="${IMAGE_URI##*:}" - - sed -i -E \ - "s|^([[:space:]]*)newTag:.*$|\1newTag: ${IMAGE_TAG}|" \ - "$MANIFEST" - - grep -n "newTag:" "$MANIFEST" + IMAGE_NAME="${IMAGE_URI%:*}" + TEMP_FILE="${MANIFEST}.tmp" + + echo "Manifest: $MANIFEST" + echo "Image name: $IMAGE_NAME" + echo "Image tag: $IMAGE_TAG" + + awk \ + -v image_name="$IMAGE_NAME" \ + -v image_tag="$IMAGE_TAG" \ + ' + BEGIN { + in_target = 0 + updated = 0 + } + + { + if ($1 == "-" && $2 == "name:") { + in_target = ($3 == image_name) + } + + if (in_target && $1 == "newTag:") { + match($0, /^[[:space:]]*/) + indent = substr($0, 1, RLENGTH) + + print indent "newTag: " image_tag + + updated = 1 + in_target = 0 + next + } + + print + } + + END { + if (!updated) { + print "Backend image block or newTag was not found" > "/dev/stderr" + exit 42 + } + } + ' \ + "$MANIFEST" > "$TEMP_FILE" + + mv "$TEMP_FILE" "$MANIFEST" + + echo "Updated backend image:" + grep -n -A 2 -B 1 "$IMAGE_NAME" "$MANIFEST" cd infra @@ -160,12 +298,18 @@ jobs: exit 0 fi - git commit -m "chore(backend): deploy ${GITHUB_REF_NAME}-${GITHUB_SHA::12}" + git diff --cached + + git commit \ + -m "chore(backend): deploy ${GITHUB_REF_NAME}-${GITHUB_SHA::12}" for ATTEMPT in 1 2 3; do - git pull --rebase origin "$GITHUB_REF_NAME" + echo "GitOps push attempt: $ATTEMPT" + + git pull --rebase origin "$INFRA_BRANCH" - if git push origin "HEAD:$GITHUB_REF_NAME"; then + if git push origin "HEAD:$INFRA_BRANCH"; then + echo "GitOps image update pushed successfully" exit 0 fi From 42e4b1966dc40ffc874ff615a19d4ae73d7242e1 Mon Sep 17 00:00:00 2001 From: chani2104 Date: Tue, 7 Jul 2026 16:22:05 +0900 Subject: [PATCH 04/55] =?UTF-8?q?fix:=20=C3=AC=20AGV=20eat=C2=B4=EB=8F=99?= =?UTF-8?q?=20=C3=A3=C2=85=EC=83=81=ED=83=9C=20WebSocket=20=EC=97=B0=C3=AA?= =?UTF-8?q?->=20=ED=94=84=EB=A1=A0=ED=8A=B8=20=ED=94=84=EB=A1=A0=ED=8A=B8?= =?UTF-8?q?=EC=97=94=EB=93=9C=20=EC=97=B0=EA=B2=B0,=20AGV=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/controller/AgvTestController.java | 33 ---- .../dto/dashboard/AgvDispatchTestRequest.java | 18 -- .../dto/dashboard/DispatchRequest.java | 4 + .../AgvAnalysisAggregationService.java | 154 ------------------ .../dashboard/AgvDispatchQueueService.java | 4 + .../dashboard/AnalysisDuplicateService.java | 4 + 6 files changed, 12 insertions(+), 205 deletions(-) delete mode 100644 src/main/java/com/aims/backend/controller/AgvTestController.java delete mode 100644 src/main/java/com/aims/backend/dto/dashboard/AgvDispatchTestRequest.java create mode 100644 src/main/java/com/aims/backend/dto/dashboard/DispatchRequest.java delete mode 100644 src/main/java/com/aims/backend/service/dashboard/AgvAnalysisAggregationService.java create mode 100644 src/main/java/com/aims/backend/service/dashboard/AgvDispatchQueueService.java create mode 100644 src/main/java/com/aims/backend/service/dashboard/AnalysisDuplicateService.java diff --git a/src/main/java/com/aims/backend/controller/AgvTestController.java b/src/main/java/com/aims/backend/controller/AgvTestController.java deleted file mode 100644 index adb4b74..0000000 --- a/src/main/java/com/aims/backend/controller/AgvTestController.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.aims.backend.controller; - -import com.aims.backend.domain.dashboard.enums.ProcessCode; -import com.aims.backend.dto.dashboard.AgvDispatchTestRequest; -import com.aims.backend.service.dashboard.AgvSimulationService; -import lombok.RequiredArgsConstructor; -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; - -@RestController -@RequestMapping("/api/test/agv") -@RequiredArgsConstructor -public class AgvTestController { - - private final AgvSimulationService agvSimulationService; - - @PostMapping("/dispatch") - public ResponseEntity dispatch( - @RequestBody AgvDispatchTestRequest request - ) { - - agvSimulationService.dispatchAgv( - request.getEventId(), - request.getCarMasterId(), - ProcessCode.valueOf(request.getProcessCode()) - ); - - return ResponseEntity.ok().build(); - } -} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/dto/dashboard/AgvDispatchTestRequest.java b/src/main/java/com/aims/backend/dto/dashboard/AgvDispatchTestRequest.java deleted file mode 100644 index f37009f..0000000 --- a/src/main/java/com/aims/backend/dto/dashboard/AgvDispatchTestRequest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.aims.backend.dto.dashboard; - -import lombok.*; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class AgvDispatchTestRequest { - - private String eventId; - - private Long carMasterId; - - private String processCode; - -} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/dto/dashboard/DispatchRequest.java b/src/main/java/com/aims/backend/dto/dashboard/DispatchRequest.java new file mode 100644 index 0000000..d0d714e --- /dev/null +++ b/src/main/java/com/aims/backend/dto/dashboard/DispatchRequest.java @@ -0,0 +1,4 @@ +package com.aims.backend.dto.dashboard; + +public class DispatchRequest { +} diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvAnalysisAggregationService.java b/src/main/java/com/aims/backend/service/dashboard/AgvAnalysisAggregationService.java deleted file mode 100644 index ab14b55..0000000 --- a/src/main/java/com/aims/backend/service/dashboard/AgvAnalysisAggregationService.java +++ /dev/null @@ -1,154 +0,0 @@ -package com.aims.backend.service.dashboard; - -import com.aims.backend.dto.kafka.ManufacturingAnalysisEvent; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -@Slf4j -@Service -@RequiredArgsConstructor -public class AgvAnalysisAggregationService { - - private static final String PREFIX = "agv:analysis:"; - - private static final String BOTTLENECK = - "BOTTLENECK_ANALYSIS"; - - private static final String DEFECT_TRANSFER = - "DEFECT_TRANSFER_PREDICTION"; - - private static final String PROCESS_RISK = - "PROCESS_RISK_ANALYSIS"; - - private final RedisTemplate redisTemplate; - - public Optional collect( - ManufacturingAnalysisEvent event - ) { - - String key = PREFIX + event.eventId(); - - @SuppressWarnings("unchecked") - Map result = - (Map) redisTemplate - .opsForValue() - .get(key); - - if (result == null) { - result = new HashMap<>(); - } - - // 현재 분석 결과 저장 - result.put( - event.analysisType(), - event.analysisResult().isAbnormal() - ); - - redisTemplate.opsForValue().set( - key, - result, - Duration.ofMinutes(30) - ); - - // 3개의 분석이 모두 도착했는지 확인 - boolean completed = - result.containsKey(BOTTLENECK) - && result.containsKey(DEFECT_TRANSFER) - && result.containsKey(PROCESS_RISK); - - if (!completed) { - - log.debug( - "[AGV WAIT] eventId={} analysisType={} ({}/3)", - event.eventId(), - event.analysisType(), - result.size() - ); - - return Optional.empty(); - } - - boolean bottleneckAbnormal = - result.get(BOTTLENECK); - - boolean defectTransferAbnormal = - result.get(DEFECT_TRANSFER); - - boolean processRiskAbnormal = - result.get(PROCESS_RISK); - - boolean abnormal = - bottleneckAbnormal - || defectTransferAbnormal - || processRiskAbnormal; - - if (abnormal) { - - StringBuilder reasons = new StringBuilder(); - - if (bottleneckAbnormal) { - reasons.append("BOTTLENECK_ANALYSIS "); - } - - if (defectTransferAbnormal) { - reasons.append("DEFECT_TRANSFER_PREDICTION "); - } - - if (processRiskAbnormal) { - reasons.append("PROCESS_RISK_ANALYSIS "); - } - - log.info(""" - - ============================== - AGV 출발 취소 - - eventId={} - - 이상 분석 : {} - - BOTTLENECK_ANALYSIS : {} - DEFECT_TRANSFER_PREDICTION : {} - PROCESS_RISK_ANALYSIS : {} - - ============================== - - """, - event.eventId(), - reasons.toString().trim(), - bottleneckAbnormal, - defectTransferAbnormal, - processRiskAbnormal - ); - - } else { - - log.info(""" - - ============================== - AGV 출발 가능 - - eventId={} - - 모든 AI 분석 정상 - - ============================== - - """, - event.eventId() - ); - } - - // 최종 판단 완료 → Redis 삭제 - redisTemplate.delete(key); - - return Optional.of(abnormal); - } -} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchQueueService.java b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchQueueService.java new file mode 100644 index 0000000..eaf3974 --- /dev/null +++ b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchQueueService.java @@ -0,0 +1,4 @@ +package com.aims.backend.service.dashboard; + +public class AgvDispatchQueueService { +} diff --git a/src/main/java/com/aims/backend/service/dashboard/AnalysisDuplicateService.java b/src/main/java/com/aims/backend/service/dashboard/AnalysisDuplicateService.java new file mode 100644 index 0000000..b0aa65c --- /dev/null +++ b/src/main/java/com/aims/backend/service/dashboard/AnalysisDuplicateService.java @@ -0,0 +1,4 @@ +package com.aims.backend.service.dashboard; + +public class AnalysisDuplicateService { +} From b2c53a9850fe0c71181483a1d23415b84dd7c6b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=B8?= <91dlwnsgh@naver.com> Date: Tue, 7 Jul 2026 16:34:24 +0900 Subject: [PATCH 05/55] =?UTF-8?q?DB=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=EB=B3=84=20=EB=8B=B4=EB=8B=B9=EC=97=85=EB=AC=B4,=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EC=84=A4=EB=B9=84=20=EC=83=81=ED=83=9C,?= =?UTF-8?q?=20=EC=84=A4=EB=B9=84=20=EC=83=81=ED=83=9C=20=EC=9A=94=EC=95=BD?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/aims/backend/domain/dashboard/Equipment.java | 3 --- .../aims/backend/domain/dashboard/enums/OperationStatus.java | 5 ++--- .../com/aims/backend/service/dashboard/DashboardService.java | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/aims/backend/domain/dashboard/Equipment.java b/src/main/java/com/aims/backend/domain/dashboard/Equipment.java index fbdd368..b997a20 100644 --- a/src/main/java/com/aims/backend/domain/dashboard/Equipment.java +++ b/src/main/java/com/aims/backend/domain/dashboard/Equipment.java @@ -42,9 +42,6 @@ public class Equipment { @Column(name = "current_status") private OperationStatus currentStatus; - @Column(name = "health_status") - private String healthStatus; - @Column(name = "last_fault_time") private LocalDateTime lastFaultTime; diff --git a/src/main/java/com/aims/backend/domain/dashboard/enums/OperationStatus.java b/src/main/java/com/aims/backend/domain/dashboard/enums/OperationStatus.java index 4260117..ce44500 100644 --- a/src/main/java/com/aims/backend/domain/dashboard/enums/OperationStatus.java +++ b/src/main/java/com/aims/backend/domain/dashboard/enums/OperationStatus.java @@ -12,13 +12,12 @@ public enum OperationStatus { RUNNING("가동"), - IDLE("대기"), STOPPED("정지"), FAULT("고장"), - - MAINTENANCE("정비"); + + WARNING("경고"); /** diff --git a/src/main/java/com/aims/backend/service/dashboard/DashboardService.java b/src/main/java/com/aims/backend/service/dashboard/DashboardService.java index 2d6b4cd..3b5342b 100644 --- a/src/main/java/com/aims/backend/service/dashboard/DashboardService.java +++ b/src/main/java/com/aims/backend/service/dashboard/DashboardService.java @@ -79,10 +79,8 @@ public OverallStatusResponse getOverallStatus() { .mapToDouble(equipment -> { switch (equipment.getCurrentStatus()) { case RUNNING: return 5.0; - case IDLE: return 4.0; - case MAINTENANCE: return 3.0; + case WARNING: return 4.0; case STOPPED: return 0.0; - case FAULT: return 0.0; default: return 0.0; } }).sum(); From f2679422a3a87c7ff729a15f414f128abd4ab2ff Mon Sep 17 00:00:00 2001 From: chani2104 Date: Tue, 7 Jul 2026 16:46:41 +0900 Subject: [PATCH 06/55] =?UTF-8?q?feat:=20Analysis=20Topic=20=EA=B5=AC?= =?UTF-8?q?=EB=8F=85=20=EB=B3=80=EA=B2=BD=20/=20=EC=9A=B4=EB=B0=98=20?= =?UTF-8?q?=EB=8F=84=EC=B0=A9=20=EC=A0=95=EB=B3=B4=20API=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=20/=20AGV=20Simulation=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aims/backend/config/SecurityConfig.java | 4 +- .../aims/backend/config/WebSocketConfig.java | 7 +- .../backend/domain/dashboard/Equipment.java | 4 +- .../dto/dashboard/DispatchRequest.java | 10 +- .../dashboard/AgvDispatchQueueService.java | 120 +++++++++++++++- .../dashboard/AgvSimulationService.java | 135 +++++++++++------- .../dashboard/AnalysisDuplicateService.java | 26 +++- .../ManufacturingAnalysisConsumer.java | 54 +++++-- 8 files changed, 289 insertions(+), 71 deletions(-) diff --git a/src/main/java/com/aims/backend/config/SecurityConfig.java b/src/main/java/com/aims/backend/config/SecurityConfig.java index 7c08353..45c9224 100644 --- a/src/main/java/com/aims/backend/config/SecurityConfig.java +++ b/src/main/java/com/aims/backend/config/SecurityConfig.java @@ -51,7 +51,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/api/auth/login", "/api/auth/signup", "/api/auth/refresh", - "/api/event/**" + "/api/event/**", + "/api/main/process-flow", + "/ws/**" ).permitAll() .anyRequest().authenticated() ) diff --git a/src/main/java/com/aims/backend/config/WebSocketConfig.java b/src/main/java/com/aims/backend/config/WebSocketConfig.java index 5fa5226..f825f58 100644 --- a/src/main/java/com/aims/backend/config/WebSocketConfig.java +++ b/src/main/java/com/aims/backend/config/WebSocketConfig.java @@ -9,13 +9,18 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { + public void registerStompEndpoints( + StompEndpointRegistry registry + ) { + registry.addEndpoint("/ws") .setAllowedOriginPatterns("*"); + } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.enableSimpleBroker("/topic"); registry.setApplicationDestinationPrefixes("/app"); } diff --git a/src/main/java/com/aims/backend/domain/dashboard/Equipment.java b/src/main/java/com/aims/backend/domain/dashboard/Equipment.java index fbdd368..d888657 100644 --- a/src/main/java/com/aims/backend/domain/dashboard/Equipment.java +++ b/src/main/java/com/aims/backend/domain/dashboard/Equipment.java @@ -42,8 +42,8 @@ public class Equipment { @Column(name = "current_status") private OperationStatus currentStatus; - @Column(name = "health_status") - private String healthStatus; + /*@Column(name = "health_status") + private String healthStatus;*/ @Column(name = "last_fault_time") private LocalDateTime lastFaultTime; diff --git a/src/main/java/com/aims/backend/dto/dashboard/DispatchRequest.java b/src/main/java/com/aims/backend/dto/dashboard/DispatchRequest.java index d0d714e..02e7d26 100644 --- a/src/main/java/com/aims/backend/dto/dashboard/DispatchRequest.java +++ b/src/main/java/com/aims/backend/dto/dashboard/DispatchRequest.java @@ -1,4 +1,10 @@ package com.aims.backend.dto.dashboard; -public class DispatchRequest { -} +import com.aims.backend.domain.dashboard.enums.ProcessCode; + +public record DispatchRequest( + String eventId, + Long carMasterId, + ProcessCode processCode +) { +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchQueueService.java b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchQueueService.java index eaf3974..fe3a015 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchQueueService.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchQueueService.java @@ -1,4 +1,122 @@ package com.aims.backend.service.dashboard; +import com.aims.backend.dto.dashboard.DispatchRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +@Slf4j +@Service public class AgvDispatchQueueService { -} + + private final Map> queues = + new ConcurrentHashMap<>(); + + private final Map> queuedEventIds = + new ConcurrentHashMap<>(); + + public synchronized boolean offer( + String routeCode, + DispatchRequest request + ) { + Queue queue = + queues.computeIfAbsent( + routeCode, + key -> new ConcurrentLinkedQueue<>() + ); + + Set eventIds = + queuedEventIds.computeIfAbsent( + routeCode, + key -> ConcurrentHashMap.newKeySet() + ); + + if (!eventIds.add(request.eventId())) { + log.debug( + "[AGV QUEUE][{}] duplicated eventId={} ignored", + routeCode, + request.eventId() + ); + return false; + } + + queue.offer(request); + + log.warn( + "[AGV QUEUE][{}] queued eventId={}, process={}, queueSize={}, waitingEvents={}", + routeCode, + request.eventId(), + request.processCode(), + queue.size(), + getWaitingEventIds(routeCode) + ); + + return true; + } + + public synchronized Optional poll( + String routeCode + ) { + Queue queue = + queues.get(routeCode); + + if (queue == null || queue.isEmpty()) { + return Optional.empty(); + } + + DispatchRequest request = + queue.poll(); + + if (request == null) { + return Optional.empty(); + } + + Set eventIds = + queuedEventIds.get(routeCode); + + if (eventIds != null) { + eventIds.remove(request.eventId()); + } + + log.info( + "[AGV QUEUE][{}] dispatch eventId={}, process={}, remainQueue={}", + routeCode, + request.eventId(), + request.processCode(), + getWaitingEventIds(routeCode) + ); + + return Optional.of(request); + } + + public synchronized List getWaitingEventIds( + String routeCode + ) { + Queue queue = + queues.get(routeCode); + + if (queue == null) { + return List.of(); + } + + return queue.stream() + .map(DispatchRequest::eventId) + .toList(); + } + + public synchronized int size( + String routeCode + ) { + Queue queue = + queues.get(routeCode); + + return queue == null ? 0 : queue.size(); + } +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java b/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java index ceb5763..dacfafb 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java @@ -6,12 +6,11 @@ import com.aims.backend.domain.dashboard.enums.ProcessCode; import com.aims.backend.dto.dashboard.AgvOperationResponse; import com.aims.backend.dto.dashboard.AgvRealtimeState; -import com.aims.backend.dto.dashboard.ManufacturingEventRequest; +import com.aims.backend.dto.dashboard.DispatchRequest; import com.aims.backend.repository.dashboard.AgvOperationRepository; import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.hc.client5.http.RouteInfo; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.support.TransactionTemplate; @@ -19,7 +18,11 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Map; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; @Slf4j @Service @@ -31,6 +34,8 @@ public class AgvSimulationService { private final SimpMessagingTemplate messagingTemplate; private final TransactionTemplate transactionTemplate; private final AssemblyArrivalClient assemblyArrivalClient; + private final AgvDispatchQueueService dispatchQueueService; + private final Object dispatchLock = new Object(); private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10); @@ -56,65 +61,79 @@ public class AgvSimulationService { new RouteInfo(ProcessCode.ASSEMBLY, ProcessCode.INSPECTION, "ASSEMBLY_INSPECTION") ); - /*public void handleManufacturingEvent(ManufacturingEventRequest request) { - ProcessCode currentProcess = - ProcessCode.valueOf(request.getProcessCode()); - - dispatchAgv( - request.getEventId(), - request.getCarMasterId(), - currentProcess - ); - }*/ - public void dispatchAgv( String eventId, Long carMasterId, ProcessCode currentProcess ) { - RouteInfo routeInfo = ROUTES.get(currentProcess); - if (routeInfo == null) { - log.info( - "[AGV DISPATCH SKIP] 운반 대상 공정 아님. eventId={}, process={}", - eventId, - currentProcess - ); - return; - } + synchronized (dispatchLock) { - AgvOperation agv = transactionTemplate.execute(status -> { - AgvOperation selectedAgv = - agvOperationRepository - .findFirstByRouteCodeAndAgvStatusOrderByLaneNoAsc( - routeInfo.routeCode(), - AgvStatus.WAITING + RouteInfo routeInfo = ROUTES.get(currentProcess); + + if (routeInfo == null) { + log.info( + "[AGV DISPATCH SKIP] 운반 대상 공정 아님. eventId={}, process={}", + eventId, + currentProcess + ); + return; + } + + AgvOperation agv = transactionTemplate.execute(status -> { + + AgvOperation selectedAgv = + agvOperationRepository + .findFirstByRouteCodeAndAgvStatusOrderByLaneNoAsc( + routeInfo.routeCode(), + AgvStatus.WAITING + ) + .orElse(null); + + if (selectedAgv == null) { + + dispatchQueueService.offer( + routeInfo.routeCode(), + new DispatchRequest( + eventId, + carMasterId, + currentProcess ) - .orElseThrow(() -> new IllegalStateException( - "대기 중인 AGV가 없습니다. routeCode=" + routeInfo.routeCode() - )); + ); + + return null; + } + + selectedAgv.dispatch( + eventId, + carMasterId, + routeInfo.from(), + routeInfo.to(), + routeInfo.routeCode() + ); + + return agvOperationRepository.save(selectedAgv); + }); + + if (agv == null) { + + log.info( + "[AGV DISPATCH] queued eventId={}, process={}, routeCode={}", + eventId, + currentProcess, + routeInfo.routeCode() + ); + + return; + } - selectedAgv.dispatch( + startMovingSession( + agv.getId(), eventId, carMasterId, - routeInfo.from(), - routeInfo.to(), - routeInfo.routeCode() + routeInfo ); - - return agvOperationRepository.save(selectedAgv); - }); - - if (agv == null) { - throw new IllegalStateException("AGV 출발 처리 실패"); } - - startMovingSession( - agv.getId(), - eventId, - carMasterId, - routeInfo - ); } private void startMovingSession( @@ -361,6 +380,22 @@ private void handleReturningArrived( ); sendAgvStatus(); + + dispatchQueueService.poll(routeInfo.routeCode()) + .ifPresent(request -> { + log.info( + "[AGV QUEUE] retry eventId={}, process={}, routeCode={}", + request.eventId(), + request.processCode(), + routeInfo.routeCode() + ); + + dispatchAgv( + request.eventId(), + request.carMasterId(), + request.processCode() + ); + }); } private void sendAgvStatus() { @@ -380,8 +415,6 @@ private AgvOperationResponse toResponse(AgvOperation agv) { AgvRealtimeState state = agvRealtimeRedisService.get(agv.getId()); - state.calculateProgress(LocalDateTime.now()); - return new AgvOperationResponse( agv.getId(), state.getEventId(), diff --git a/src/main/java/com/aims/backend/service/dashboard/AnalysisDuplicateService.java b/src/main/java/com/aims/backend/service/dashboard/AnalysisDuplicateService.java index b0aa65c..5831f6d 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AnalysisDuplicateService.java +++ b/src/main/java/com/aims/backend/service/dashboard/AnalysisDuplicateService.java @@ -1,4 +1,28 @@ package com.aims.backend.service.dashboard; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.time.Duration; + +@Service +@RequiredArgsConstructor public class AnalysisDuplicateService { -} + + private static final String PREFIX = "agv:analysis:processed:"; + + private final RedisTemplate redisTemplate; + + public boolean isFirstProcess(String eventId) { + + Boolean success = + redisTemplate.opsForValue().setIfAbsent( + PREFIX + eventId, + "1", + Duration.ofMinutes(1) + ); + + return Boolean.TRUE.equals(success); + } +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java b/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java index 531cf16..dc46b30 100644 --- a/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java +++ b/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java @@ -8,16 +8,17 @@ import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; -import java.util.Optional; - @Component @RequiredArgsConstructor @Slf4j public class ManufacturingAnalysisConsumer { + private static final String PROCESS_RISK_ANALYSIS = + "PROCESS_RISK_ANALYSIS"; + private final ObjectMapper objectMapper; private final AgvSimulationService agvSimulationService; - private final AgvAnalysisAggregationService aggregationService; + private final AnalysisDuplicateService duplicateService; @KafkaListener( topics = "factory.manufacturing.analysis", @@ -33,26 +34,55 @@ public void consume(String message) { ManufacturingAnalysisEvent.class ); - Optional result = - aggregationService.collect(event); + log.info( + "[ANALYSIS] eventId={}, type={}, abnormal={}, raw={}", + event.eventId(), + event.analysisType(), + event.analysisResult().isAbnormal(), + message + ); + + // Process Risk Analysis만 처리 + if (!PROCESS_RISK_ANALYSIS.equals(event.analysisType())) { + + log.debug( + "[ANALYSIS IGNORE] eventId={}, type={}", + event.eventId(), + event.analysisType() + ); - // 아직 3개가 안 모임 - if (result.isEmpty()) { return; } - // 하나라도 abnormal - if (result.get()) { + // 동일 eventId 중복 처리 방지 + if (!duplicateService.isFirstProcess(event.eventId())) { - log.info( - "[AGV SKIP] eventId={}", + log.debug( + "[ANALYSIS DUPLICATE] eventId={}", event.eventId() ); return; } - // 모두 정상 + // 위험 공정이면 AGV 출발하지 않음 + if (event.analysisResult().isAbnormal()) { + + log.info( + "[AGV SKIP] eventId={}, process={}", + event.eventId(), + event.processCode() + ); + + return; + } + + log.info( + "[AGV DISPATCH] eventId={}, process={}", + event.eventId(), + event.processCode() + ); + agvSimulationService.dispatchAgv( event.eventId(), event.carMasterId(), From 09512fe88e078bc534cfc3f92566afd11abff9b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=B8?= <91dlwnsgh@naver.com> Date: Thu, 9 Jul 2026 13:35:11 +0900 Subject: [PATCH 07/55] =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20=EC=A1=B0=EC=B9=98=20?= =?UTF-8?q?=EC=8B=9C=20=EC=83=98=ED=94=8CDB=20=EC=9E=A5=EB=B9=84=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/alert/AlertEventQueryService.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/aims/backend/service/alert/AlertEventQueryService.java b/src/main/java/com/aims/backend/service/alert/AlertEventQueryService.java index 3efa21c..b78282c 100644 --- a/src/main/java/com/aims/backend/service/alert/AlertEventQueryService.java +++ b/src/main/java/com/aims/backend/service/alert/AlertEventQueryService.java @@ -2,11 +2,15 @@ import com.aims.backend.common.status.ErrorStatus; import com.aims.backend.domain.alert.AlertEvent; +import com.aims.backend.domain.dashboard.Equipment; +import com.aims.backend.domain.dashboard.enums.OperationStatus; import com.aims.backend.dto.alert.AlertActionUpdateRequest; import com.aims.backend.dto.alert.AlertEventResponse; import com.aims.backend.dto.alert.AlertSearchRequest; import com.aims.backend.exception.GeneralException; import com.aims.backend.repository.alert.AlertEventRepository; +import com.aims.backend.repository.sample.EquipmentRepository; + import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; @@ -38,6 +42,7 @@ public class AlertEventQueryService { ); private final AlertEventRepository alertEventRepository; + private final EquipmentRepository equipmentRepository; @Transactional(readOnly = true) public Page getAlerts(AlertSearchRequest request) { @@ -74,8 +79,7 @@ public AlertEventResponse updateAction( AlertActionUpdateRequest request ) { - AlertEvent alertEvent = - getAlertEvent(logNo); + AlertEvent alertEvent = getAlertEvent(logNo); alertEvent.updateAction( request.getActionBy(), @@ -83,6 +87,20 @@ public AlertEventResponse updateAction( request.getReason() ); + if (alertEvent.getEquipmentId() != null) { + Equipment equipment = equipmentRepository.findById(alertEvent.getEquipmentId()) + .orElseThrow(() -> new GeneralException( + ErrorStatus.NOT_FOUND, + "Equipment not found. id=" + alertEvent.getEquipmentId() + )); + + //테스트용 로그 + System.out.println("Updating equipment status to RUNNING for equipment ID: " + equipment.getId()); + equipment.setCurrentStatus(OperationStatus.RUNNING); + equipmentRepository.save(equipment); + System.out.println("Equipment status updated to RUNNING for equipment currensStatus: " + equipment.getCurrentStatus()); + } + return AlertEventResponse.from(alertEvent); } From 21916b575a263eac63535d9cca7567cfb3b373eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=B8?= <91dlwnsgh@naver.com> Date: Thu, 9 Jul 2026 13:40:19 +0900 Subject: [PATCH 08/55] =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/aims/backend/service/alert/AlertEventQueryService.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/aims/backend/service/alert/AlertEventQueryService.java b/src/main/java/com/aims/backend/service/alert/AlertEventQueryService.java index b78282c..f6c6e76 100644 --- a/src/main/java/com/aims/backend/service/alert/AlertEventQueryService.java +++ b/src/main/java/com/aims/backend/service/alert/AlertEventQueryService.java @@ -94,11 +94,8 @@ public AlertEventResponse updateAction( "Equipment not found. id=" + alertEvent.getEquipmentId() )); - //테스트용 로그 - System.out.println("Updating equipment status to RUNNING for equipment ID: " + equipment.getId()); equipment.setCurrentStatus(OperationStatus.RUNNING); equipmentRepository.save(equipment); - System.out.println("Equipment status updated to RUNNING for equipment currensStatus: " + equipment.getCurrentStatus()); } return AlertEventResponse.from(alertEvent); From 5a935aef1103ccb9757a7d3cb787bde94cda0223 Mon Sep 17 00:00:00 2001 From: hyein0514 Date: Thu, 9 Jul 2026 17:11:24 +0900 Subject: [PATCH 09/55] =?UTF-8?q?refactor:=EC=9B=B9=EC=86=8C=EC=BC=93=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/aims/backend/config/WebSocketConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/aims/backend/config/WebSocketConfig.java b/src/main/java/com/aims/backend/config/WebSocketConfig.java index f825f58..8a8cc4b 100644 --- a/src/main/java/com/aims/backend/config/WebSocketConfig.java +++ b/src/main/java/com/aims/backend/config/WebSocketConfig.java @@ -14,7 +14,8 @@ public void registerStompEndpoints( ) { registry.addEndpoint("/ws") - .setAllowedOriginPatterns("*"); + .setAllowedOriginPatterns("*") + .withSockJS(); } From 0cf48522095c414676a707b1f91c2da7df65fd43 Mon Sep 17 00:00:00 2001 From: chani2104 Date: Fri, 10 Jul 2026 14:06:09 +0900 Subject: [PATCH 10/55] =?UTF-8?q?feat:=20Redis=20Queue=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=9A=B0=EC=84=A0=EC=88=9C?= =?UTF-8?q?=EC=9C=84=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/service/dashboard/AgvDispatchRedisService.java | 4 ++++ .../aims/backend/service/dashboard/AgvDispatchScheduler.java | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 src/main/java/com/aims/backend/service/dashboard/AgvDispatchRedisService.java create mode 100644 src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchRedisService.java b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchRedisService.java new file mode 100644 index 0000000..c64d9b6 --- /dev/null +++ b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchRedisService.java @@ -0,0 +1,4 @@ +package com.aims.backend.service.dashboard; + +public class AgvDispatchRedisService { +} diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java new file mode 100644 index 0000000..26a2b74 --- /dev/null +++ b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java @@ -0,0 +1,4 @@ +package com.aims.backend.service.dashboard; + +public class AgvDispatchScheduler { +} From 5f0e31644237ce925f457dabc61b979c345f5925 Mon Sep 17 00:00:00 2001 From: chani2104 Date: Fri, 10 Jul 2026 14:06:19 +0900 Subject: [PATCH 11/55] =?UTF-8?q?feat:=20Redis=20Queue=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=9A=B0=EC=84=A0=EC=88=9C?= =?UTF-8?q?=EC=9C=84=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/AgvOperationRepository.java | 23 ++- .../dashboard/AgvDispatchQueueService.java | 155 ++++++++++-------- .../dashboard/AgvDispatchRedisService.java | 96 +++++++++++ .../dashboard/AgvDispatchScheduler.java | 75 +++++++++ .../dashboard/AgvSimulationService.java | 124 ++++++-------- .../ManufacturingAnalysisConsumer.java | 23 ++- 6 files changed, 338 insertions(+), 158 deletions(-) diff --git a/src/main/java/com/aims/backend/repository/dashboard/AgvOperationRepository.java b/src/main/java/com/aims/backend/repository/dashboard/AgvOperationRepository.java index 8e32541..3439683 100644 --- a/src/main/java/com/aims/backend/repository/dashboard/AgvOperationRepository.java +++ b/src/main/java/com/aims/backend/repository/dashboard/AgvOperationRepository.java @@ -2,11 +2,9 @@ import com.aims.backend.domain.dashboard.AgvOperation; import com.aims.backend.domain.dashboard.enums.AgvStatus; -import com.aims.backend.dto.dashboard.AgvStatusCountResponse; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; - -import java.util.List; +import org.springframework.data.repository.query.Param; import java.util.List; import java.util.Optional; @@ -21,4 +19,21 @@ Optional findFirstByRouteCodeAndAgvStatusOrderByLaneNoAsc( String routeCode, AgvStatus agvStatus ); -} \ No newline at end of file + + @Query( + value = """ + SELECT * + FROM agv_operation + WHERE route_code = :routeCode + AND agv_status = :agvStatus + ORDER BY lane_no ASC + LIMIT 1 + FOR UPDATE SKIP LOCKED + """, + nativeQuery = true + ) + Optional findFirstWaitingAgvForUpdateSkipLocked( + @Param("routeCode") String routeCode, + @Param("agvStatus") String agvStatus + ); +} diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchQueueService.java b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchQueueService.java index fe3a015..a76e0ab 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchQueueService.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchQueueService.java @@ -1,44 +1,53 @@ package com.aims.backend.service.dashboard; +import com.aims.backend.domain.dashboard.enums.ProcessCode; import com.aims.backend.dto.dashboard.DispatchRequest; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.Queue; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; @Slf4j @Service +@RequiredArgsConstructor public class AgvDispatchQueueService { - private final Map> queues = - new ConcurrentHashMap<>(); + private final AgvDispatchRedisService redisService; - private final Map> queuedEventIds = - new ConcurrentHashMap<>(); + private static final List ROUTE_CODES = List.of( + "PRESS_BODY", + "BODY_PAINT", + "PAINT_ASSEMBLY", + "ASSEMBLY_INSPECTION" + ); - public synchronized boolean offer( + public boolean offer(DispatchRequest request) { + String routeCode = resolveRouteCode(request.processCode()); + + if (routeCode == null) { + log.info( + "[AGV QUEUE SKIP] 운반 대상 공정 아님. eventId={}, process={}", + request.eventId(), + request.processCode() + ); + return false; + } + + return offer(routeCode, request); + } + + public boolean offer( String routeCode, DispatchRequest request ) { - Queue queue = - queues.computeIfAbsent( - routeCode, - key -> new ConcurrentLinkedQueue<>() - ); - - Set eventIds = - queuedEventIds.computeIfAbsent( - routeCode, - key -> ConcurrentHashMap.newKeySet() - ); - - if (!eventIds.add(request.eventId())) { + Boolean added = redisService.addEventId( + routeCode, + request.eventId() + ); + + if (!Boolean.TRUE.equals(added)) { log.debug( "[AGV QUEUE][{}] duplicated eventId={} ignored", routeCode, @@ -47,76 +56,84 @@ public synchronized boolean offer( return false; } - queue.offer(request); + try { + redisService.pushLast(routeCode, request); + } catch (Exception e) { + redisService.removeEventId(routeCode, request.eventId()); + throw e; + } - log.warn( + log.info( "[AGV QUEUE][{}] queued eventId={}, process={}, queueSize={}, waitingEvents={}", routeCode, request.eventId(), request.processCode(), - queue.size(), + size(routeCode), getWaitingEventIds(routeCode) ); return true; } - public synchronized Optional poll( - String routeCode + public void requeueFirst( + String routeCode, + DispatchRequest request ) { - Queue queue = - queues.get(routeCode); - - if (queue == null || queue.isEmpty()) { - return Optional.empty(); - } - - DispatchRequest request = - queue.poll(); - - if (request == null) { - return Optional.empty(); - } + redisService.pushFirst(routeCode, request); - Set eventIds = - queuedEventIds.get(routeCode); - - if (eventIds != null) { - eventIds.remove(request.eventId()); - } - - log.info( - "[AGV QUEUE][{}] dispatch eventId={}, process={}, remainQueue={}", + log.debug( + "[AGV QUEUE][{}] requeued eventId={}, process={}, queueSize={}", routeCode, request.eventId(), request.processCode(), - getWaitingEventIds(routeCode) + size(routeCode) ); - - return Optional.of(request); } - public synchronized List getWaitingEventIds( - String routeCode - ) { - Queue queue = - queues.get(routeCode); + public Optional poll(String routeCode) { + Optional request = redisService.popFirst(routeCode); - if (queue == null) { - return List.of(); - } + request.ifPresent(value -> { + redisService.removeEventId(routeCode, value.eventId()); - return queue.stream() + log.info( + "[AGV QUEUE][{}] poll eventId={}, process={}, remainQueue={}", + routeCode, + value.eventId(), + value.processCode(), + getWaitingEventIds(routeCode) + ); + }); + + return request; + } + + public List getWaitingEventIds(String routeCode) { + return redisService.findAll(routeCode) + .stream() .map(DispatchRequest::eventId) .toList(); } - public synchronized int size( - String routeCode - ) { - Queue queue = - queues.get(routeCode); + public int size(String routeCode) { + return redisService.size(routeCode); + } + + public List routeCodes() { + return ROUTE_CODES; + } + + public String resolveRouteCode(ProcessCode processCode) { + if (processCode == null) { + return null; + } - return queue == null ? 0 : queue.size(); + return switch (processCode) { + case PRESS -> "PRESS_BODY"; + case BODY -> "BODY_PAINT"; + case PAINT -> "PAINT_ASSEMBLY"; + case ASSEMBLY -> "ASSEMBLY_INSPECTION"; + default -> null; + }; } -} \ No newline at end of file +} diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchRedisService.java b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchRedisService.java index c64d9b6..48d1d9b 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchRedisService.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchRedisService.java @@ -1,4 +1,100 @@ package com.aims.backend.service.dashboard; +import com.aims.backend.dto.dashboard.DispatchRequest; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor public class AgvDispatchRedisService { + + private static final String QUEUE_KEY_PREFIX = "agv:dispatch:queue:"; + private static final String EVENT_SET_KEY_PREFIX = "agv:dispatch:event-ids:"; + + private final RedisTemplate redisTemplate; + private final ObjectMapper objectMapper; + + public Boolean addEventId( + String routeCode, + String eventId + ) { + return redisTemplate.opsForSet() + .add(makeEventSetKey(routeCode), eventId) == 1L; + } + + public void removeEventId( + String routeCode, + String eventId + ) { + redisTemplate.opsForSet() + .remove(makeEventSetKey(routeCode), eventId); + } + + public void pushLast( + String routeCode, + DispatchRequest request + ) { + redisTemplate.opsForList() + .rightPush(makeQueueKey(routeCode), request); + } + + public void pushFirst( + String routeCode, + DispatchRequest request + ) { + redisTemplate.opsForList() + .leftPush(makeQueueKey(routeCode), request); + } + + public Optional popFirst(String routeCode) { + Object value = redisTemplate.opsForList() + .leftPop(makeQueueKey(routeCode)); + + if (value == null) { + return Optional.empty(); + } + + return Optional.of( + objectMapper.convertValue( + value, + DispatchRequest.class + ) + ); + } + + public List findAll(String routeCode) { + List values = redisTemplate.opsForList() + .range(makeQueueKey(routeCode), 0, -1); + + if (values == null || values.isEmpty()) { + return List.of(); + } + + return values.stream() + .map(value -> objectMapper.convertValue( + value, + DispatchRequest.class + )) + .toList(); + } + + public int size(String routeCode) { + Long size = redisTemplate.opsForList() + .size(makeQueueKey(routeCode)); + + return size == null ? 0 : size.intValue(); + } + + private String makeQueueKey(String routeCode) { + return QUEUE_KEY_PREFIX + routeCode; + } + + private String makeEventSetKey(String routeCode) { + return EVENT_SET_KEY_PREFIX + routeCode; + } } diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java index 26a2b74..0840eaf 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java @@ -1,4 +1,79 @@ package com.aims.backend.service.dashboard; +import com.aims.backend.dto.dashboard.DispatchRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.concurrent.atomic.AtomicBoolean; + +@Slf4j +@Component +@RequiredArgsConstructor +@ConditionalOnProperty( + prefix = "app.agv.dispatch-scheduler", + name = "enabled", + havingValue = "true", + matchIfMissing = true +) public class AgvDispatchScheduler { + + private final AgvDispatchQueueService dispatchQueueService; + private final AgvSimulationService agvSimulationService; + private final AtomicBoolean running = new AtomicBoolean(false); + + @Scheduled(fixedDelayString = "${app.agv.dispatch-scheduler.fixed-delay-ms:1000}") + public void dispatchQueuedEvents() { + if (!running.compareAndSet(false, true)) { + log.debug("이전 AGV Dispatch Scheduler 작업이 진행 중이므로 이번 실행을 건너뜁니다."); + return; + } + + try { + for (String routeCode : dispatchQueueService.routeCodes()) { + dispatchOne(routeCode); + } + } finally { + running.set(false); + } + } + + private void dispatchOne(String routeCode) { + dispatchQueueService.poll(routeCode) + .ifPresent(request -> dispatch(routeCode, request)); + } + + private void dispatch( + String routeCode, + DispatchRequest request + ) { + try { + boolean dispatched = agvSimulationService.dispatchAgv( + request.eventId(), + request.carMasterId(), + request.processCode() + ); + + if (!dispatched) { + dispatchQueueService.requeueFirst(routeCode, request); + + log.debug( + "[AGV DISPATCH SCHEDULER][{}] 사용 가능한 AGV 없음. eventId={} 재대기", + routeCode, + request.eventId() + ); + } + } catch (Exception e) { + dispatchQueueService.requeueFirst(routeCode, request); + + log.error( + "[AGV DISPATCH SCHEDULER][{}] 배정 실패. eventId={} 재대기", + routeCode, + request.eventId(), + e + ); + } + } } diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java b/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java index dacfafb..3804662 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java @@ -6,7 +6,6 @@ import com.aims.backend.domain.dashboard.enums.ProcessCode; import com.aims.backend.dto.dashboard.AgvOperationResponse; import com.aims.backend.dto.dashboard.AgvRealtimeState; -import com.aims.backend.dto.dashboard.DispatchRequest; import com.aims.backend.repository.dashboard.AgvOperationRepository; import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; @@ -34,8 +33,6 @@ public class AgvSimulationService { private final SimpMessagingTemplate messagingTemplate; private final TransactionTemplate transactionTemplate; private final AssemblyArrivalClient assemblyArrivalClient; - private final AgvDispatchQueueService dispatchQueueService; - private final Object dispatchLock = new Object(); private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10); @@ -61,79 +58,66 @@ public class AgvSimulationService { new RouteInfo(ProcessCode.ASSEMBLY, ProcessCode.INSPECTION, "ASSEMBLY_INSPECTION") ); - public void dispatchAgv( + public boolean dispatchAgv( String eventId, Long carMasterId, ProcessCode currentProcess ) { - synchronized (dispatchLock) { + RouteInfo routeInfo = ROUTES.get(currentProcess); - RouteInfo routeInfo = ROUTES.get(currentProcess); - - if (routeInfo == null) { - log.info( - "[AGV DISPATCH SKIP] 운반 대상 공정 아님. eventId={}, process={}", - eventId, - currentProcess - ); - return; - } - - AgvOperation agv = transactionTemplate.execute(status -> { - - AgvOperation selectedAgv = - agvOperationRepository - .findFirstByRouteCodeAndAgvStatusOrderByLaneNoAsc( - routeInfo.routeCode(), - AgvStatus.WAITING - ) - .orElse(null); + if (routeInfo == null) { + log.info( + "[AGV DISPATCH SKIP] 운반 대상 공정 아님. eventId={}, process={}", + eventId, + currentProcess + ); + return true; + } - if (selectedAgv == null) { + AgvOperation agv = transactionTemplate.execute(status -> { - dispatchQueueService.offer( - routeInfo.routeCode(), - new DispatchRequest( - eventId, - carMasterId, - currentProcess + AgvOperation selectedAgv = + agvOperationRepository + .findFirstWaitingAgvForUpdateSkipLocked( + routeInfo.routeCode(), + AgvStatus.WAITING.name() ) - ); - - return null; - } + .orElse(null); - selectedAgv.dispatch( - eventId, - carMasterId, - routeInfo.from(), - routeInfo.to(), - routeInfo.routeCode() - ); - - return agvOperationRepository.save(selectedAgv); - }); - - if (agv == null) { - - log.info( - "[AGV DISPATCH] queued eventId={}, process={}, routeCode={}", - eventId, - currentProcess, - routeInfo.routeCode() - ); - - return; + if (selectedAgv == null) { + return null; } - startMovingSession( - agv.getId(), + selectedAgv.dispatch( eventId, carMasterId, - routeInfo + routeInfo.from(), + routeInfo.to(), + routeInfo.routeCode() + ); + + return agvOperationRepository.save(selectedAgv); + }); + + if (agv == null) { + log.info( + "[AGV DISPATCH WAIT] 사용 가능한 AGV 없음. eventId={}, process={}, routeCode={}", + eventId, + currentProcess, + routeInfo.routeCode() ); + return false; } + + startMovingSession( + agv.getId(), + eventId, + carMasterId, + routeInfo + ); + + return true; } private void startMovingSession( @@ -380,22 +364,6 @@ private void handleReturningArrived( ); sendAgvStatus(); - - dispatchQueueService.poll(routeInfo.routeCode()) - .ifPresent(request -> { - log.info( - "[AGV QUEUE] retry eventId={}, process={}, routeCode={}", - request.eventId(), - request.processCode(), - routeInfo.routeCode() - ); - - dispatchAgv( - request.eventId(), - request.carMasterId(), - request.processCode() - ); - }); } private void sendAgvStatus() { @@ -415,6 +383,8 @@ private AgvOperationResponse toResponse(AgvOperation agv) { AgvRealtimeState state = agvRealtimeRedisService.get(agv.getId()); + state.calculateProgress(LocalDateTime.now()); + return new AgvOperationResponse( agv.getId(), state.getEventId(), @@ -452,4 +422,4 @@ private record RouteInfo( String routeCode ) { } -} \ No newline at end of file +} diff --git a/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java b/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java index dc46b30..a25aa79 100644 --- a/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java +++ b/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java @@ -1,6 +1,7 @@ package com.aims.backend.service.dashboard; import com.aims.backend.domain.dashboard.enums.ProcessCode; +import com.aims.backend.dto.dashboard.DispatchRequest; import com.aims.backend.dto.kafka.ManufacturingAnalysisEvent; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; @@ -17,7 +18,7 @@ public class ManufacturingAnalysisConsumer { "PROCESS_RISK_ANALYSIS"; private final ObjectMapper objectMapper; - private final AgvSimulationService agvSimulationService; + private final AgvDispatchQueueService dispatchQueueService; private final AnalysisDuplicateService duplicateService; @KafkaListener( @@ -77,16 +78,22 @@ public void consume(String message) { return; } - log.info( - "[AGV DISPATCH] eventId={}, process={}", + ProcessCode processCode = + ProcessCode.valueOf(event.processCode()); + + DispatchRequest request = new DispatchRequest( event.eventId(), - event.processCode() + event.carMasterId(), + processCode ); - agvSimulationService.dispatchAgv( + boolean queued = dispatchQueueService.offer(request); + + log.info( + "[AGV QUEUE REQUEST] eventId={}, process={}, queued={}", event.eventId(), - event.carMasterId(), - ProcessCode.valueOf(event.processCode()) + processCode, + queued ); } catch (Exception e) { @@ -94,4 +101,4 @@ public void consume(String message) { log.error("Analysis Kafka 처리 실패", e); } } -} \ No newline at end of file +} From 46666ed16ba53b855c0d33137b7c379dd2055f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=B8?= <91dlwnsgh@naver.com> Date: Fri, 10 Jul 2026 16:35:39 +0900 Subject: [PATCH 12/55] =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=A1=B0?= =?UTF-8?q?=EC=B9=98=20=ED=83=80=EC=9E=84=EB=9D=BC=EC=9D=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alert/AlertEventController.java | 10 +++ .../backend/domain/alert/ActionCategory.java | 10 +++ .../backend/domain/alert/ActionTimeline.java | 61 +++++++++++++++++++ .../dto/alert/ActionTimelineResponse.java | 36 +++++++++++ .../alert/ActionTimelineRepository.java | 13 ++++ .../sample/EquipmentRepository.java | 2 +- .../alert/ActionTimelineQueryService.java | 24 ++++++++ 7 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/aims/backend/domain/alert/ActionCategory.java create mode 100644 src/main/java/com/aims/backend/domain/alert/ActionTimeline.java create mode 100644 src/main/java/com/aims/backend/dto/alert/ActionTimelineResponse.java create mode 100644 src/main/java/com/aims/backend/repository/alert/ActionTimelineRepository.java create mode 100644 src/main/java/com/aims/backend/service/alert/ActionTimelineQueryService.java diff --git a/src/main/java/com/aims/backend/controller/alert/AlertEventController.java b/src/main/java/com/aims/backend/controller/alert/AlertEventController.java index f84d7e2..274e7eb 100644 --- a/src/main/java/com/aims/backend/controller/alert/AlertEventController.java +++ b/src/main/java/com/aims/backend/controller/alert/AlertEventController.java @@ -4,7 +4,9 @@ import com.aims.backend.dto.alert.AlertActionUpdateRequest; import com.aims.backend.dto.alert.AlertEventResponse; import com.aims.backend.dto.alert.AlertSearchRequest; +import com.aims.backend.dto.alert.ActionTimelineResponse; import com.aims.backend.service.alert.AlertEventQueryService; +import com.aims.backend.service.alert.ActionTimelineQueryService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -16,12 +18,15 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RestController @RequestMapping("/api/event") @RequiredArgsConstructor public class AlertEventController { private final AlertEventQueryService alertEventQueryService; + private final ActionTimelineQueryService actionTimelineQueryService; @GetMapping public ApiResponse> getAlerts(@ModelAttribute AlertSearchRequest request) { @@ -45,4 +50,9 @@ public ApiResponse updateAction( ) { return ApiResponse.success(alertEventQueryService.updateAction(logNo, request)); } + + @GetMapping("/{logNo}/action-timeline") + public ApiResponse> getActionTimeline(@PathVariable String logNo) { + return ApiResponse.success(actionTimelineQueryService.getTimeline(logNo)); + } } diff --git a/src/main/java/com/aims/backend/domain/alert/ActionCategory.java b/src/main/java/com/aims/backend/domain/alert/ActionCategory.java new file mode 100644 index 0000000..14970c1 --- /dev/null +++ b/src/main/java/com/aims/backend/domain/alert/ActionCategory.java @@ -0,0 +1,10 @@ +package com.aims.backend.domain.alert; + +public enum ActionCategory { + CHECK, + REPAIR, + CHANGE, + CLEAN, + ADJUST, + RESTART +} diff --git a/src/main/java/com/aims/backend/domain/alert/ActionTimeline.java b/src/main/java/com/aims/backend/domain/alert/ActionTimeline.java new file mode 100644 index 0000000..ada197b --- /dev/null +++ b/src/main/java/com/aims/backend/domain/alert/ActionTimeline.java @@ -0,0 +1,61 @@ +package com.aims.backend.domain.alert; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import com.aims.backend.domain.user.UserRole; + +import java.time.LocalDateTime; + +import com.aims.backend.domain.alert.ActionCategory; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Table(name = "action_timeline") +public class ActionTimeline { + + @Id + @Column(name = "action_id", nullable = false, length = 20) + private String actionId; + + @Column(name = "log_no", nullable = false, length = 20) + private String logNo; + + @Column(name = "emp_no", nullable = false, length = 20) + private String empNo; + + @Column(name = "emp_name", nullable = false, length = 20) + private String empName; + + @Enumerated(EnumType.STRING) + @Column(name = "emp_role", nullable = false, length = 20) + private UserRole empRole; + + @Column(name = "action_time", nullable = false) + private LocalDateTime actionTime; + + @Column(name = "action_content", nullable = false, length = 20) + private String actionContent; + + @Enumerated(EnumType.STRING) + @Column(name = "action_category", nullable = false, length = 20) + private ActionCategory actionCategory; + + @Column(name = "action_result", nullable = false, length = 20) + private String actionResult; + + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/dto/alert/ActionTimelineResponse.java b/src/main/java/com/aims/backend/dto/alert/ActionTimelineResponse.java new file mode 100644 index 0000000..94b0ace --- /dev/null +++ b/src/main/java/com/aims/backend/dto/alert/ActionTimelineResponse.java @@ -0,0 +1,36 @@ +package com.aims.backend.dto.alert; + +import java.time.LocalDateTime; + +import com.aims.backend.domain.alert.ActionCategory; +import com.aims.backend.domain.alert.ActionTimeline; +import com.aims.backend.domain.user.UserRole; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class ActionTimelineResponse { + private String actionId; + private String empNo; + private String empName; + private UserRole empRole; + private LocalDateTime actionTime; + private String actionContent; + private ActionCategory actionCategory; + private String actionResult; + + public static ActionTimelineResponse from(ActionTimeline entity) { + return ActionTimelineResponse.builder() + .actionId(entity.getActionId()) + .empNo(entity.getEmpNo()) + .empName(entity.getEmpName()) + .empRole(entity.getEmpRole()) + .actionTime(entity.getActionTime()) + .actionContent(entity.getActionContent()) + .actionCategory(entity.getActionCategory()) + .actionResult(entity.getActionResult()) + .build(); + } +} diff --git a/src/main/java/com/aims/backend/repository/alert/ActionTimelineRepository.java b/src/main/java/com/aims/backend/repository/alert/ActionTimelineRepository.java new file mode 100644 index 0000000..80515c8 --- /dev/null +++ b/src/main/java/com/aims/backend/repository/alert/ActionTimelineRepository.java @@ -0,0 +1,13 @@ +package com.aims.backend.repository.alert; + +import org.springframework.stereotype.Repository; + +import com.aims.backend.domain.alert.ActionTimeline; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + + +@Repository +public interface ActionTimelineRepository extends JpaRepository { + List findByLogNoOrderByActionTimeAsc(String logNo); +} diff --git a/src/main/java/com/aims/backend/repository/sample/EquipmentRepository.java b/src/main/java/com/aims/backend/repository/sample/EquipmentRepository.java index f9e1445..afa8c1f 100644 --- a/src/main/java/com/aims/backend/repository/sample/EquipmentRepository.java +++ b/src/main/java/com/aims/backend/repository/sample/EquipmentRepository.java @@ -14,7 +14,7 @@ public interface EquipmentRepository extends JpaRepository { @Query("SELECT e.currentStatus, COUNT(e) FROM Equipment e GROUP BY e.currentStatus") List countAllByCurrentStatus(); - @Query("SELECT e.processCode, COUNT(e) FROM Equipment e WHERE e.currentStatus IN ('RUNNING', 'IDLE') GROUP BY e.processCode") + @Query("SELECT e.processCode, COUNT(e) FROM Equipment e WHERE e.currentStatus IN ('RUNNING', 'WARNING') GROUP BY e.processCode") List countActiveByProcessCode(); @Query("SELECT e.processCode, COUNT(e) FROM Equipment e GROUP BY e.processCode") diff --git a/src/main/java/com/aims/backend/service/alert/ActionTimelineQueryService.java b/src/main/java/com/aims/backend/service/alert/ActionTimelineQueryService.java new file mode 100644 index 0000000..c6e71fb --- /dev/null +++ b/src/main/java/com/aims/backend/service/alert/ActionTimelineQueryService.java @@ -0,0 +1,24 @@ +package com.aims.backend.service.alert; + +import com.aims.backend.dto.alert.ActionTimelineResponse; +import com.aims.backend.repository.alert.ActionTimelineRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ActionTimelineQueryService { + private final ActionTimelineRepository actionTimelineRepository; + + @Transactional() + public List getTimeline(String logNo) { + + return actionTimelineRepository + .findByLogNoOrderByActionTimeAsc(logNo) + .stream() + .map(ActionTimelineResponse::from) + .toList(); + } +} From 8ccb14f2f9df6d915b212ab16cb6b0e618490a41 Mon Sep 17 00:00:00 2001 From: haseokyung6 Date: Fri, 10 Jul 2026 18:33:29 +0900 Subject: [PATCH 13/55] fix: redis test --- src/main/resources/application.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 3de3c72..fefb9e1 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -19,8 +19,8 @@ spring: format_sql: true data: redis: - host: ${REDIS_HOST:localhost} - port: ${REDIS_PORT:6379} + host: aims-dev-redis.80mdpk.ng.0001.apn2.cache.amazonaws.com + port: 6379 timeout: ${REDIS_TIMEOUT:3s} kafka: bootstrap-servers: ${MSK_BOOTSTRAP_SERVERS} From 3c76c57957c62456bd10260ec697fab9e35b840c Mon Sep 17 00:00:00 2001 From: haseokyung6 Date: Mon, 13 Jul 2026 10:58:26 +0900 Subject: [PATCH 14/55] refactor: add redis secret key --- .github/workflows/deploy-backend.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 76738f9..96d14c7 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -154,13 +154,27 @@ jobs: --query "Parameter.Value" \ --output text) + REDIS_HOST=$(aws ssm get-parameter \ + --name "/aims/dev/redis/host" \ + --region "$AWS_REGION" \ + --query "Parameter.Value" \ + --output text) + + REDIS_PORT=$(aws ssm get-parameter \ + --name "/aims/dev/redis/port" \ + --region "$AWS_REGION" \ + --query "Parameter.Value" \ + --output text) + for VALUE in \ "$RDS_SECRET_ARN" \ "$RDS_HOST" \ "$RDS_PORT" \ "$MAIN_DB_NAME" \ "$SAMPLE_DB_NAME" \ - "$JWT_SECRET_KEY"; do + "$JWT_SECRET_KEY" \ + "$REDIS_HOST" \ + "$REDIS_PORT"; do if [ -z "$VALUE" ] || [ "$VALUE" = "None" ]; then echo "Required backend SSM parameter is empty" @@ -177,6 +191,8 @@ jobs: echo "MAIN_DB_NAME=$MAIN_DB_NAME" >> "$GITHUB_ENV" echo "SAMPLE_DB_NAME=$SAMPLE_DB_NAME" >> "$GITHUB_ENV" echo "JWT_SECRET_KEY=$JWT_SECRET_KEY" >> "$GITHUB_ENV" + echo "REDIS_HOST=$REDIS_HOST" >> "$GITHUB_ENV" + echo "REDIS_PORT=$REDIS_PORT" >> "$GITHUB_ENV" - name: Create or update backend Kubernetes Secret run: | @@ -219,6 +235,8 @@ jobs: --from-literal=SAMPLE_DB_USERNAME="$DB_USERNAME" \ --from-literal=SAMPLE_DB_PASSWORD="$DB_PASSWORD" \ --from-literal=JWT_SECRET_KEY="$JWT_SECRET_KEY" \ + --from-literal=REDIS_HOST="$REDIS_HOST" \ + --from-literal=REDIS_PORT="$REDIS_PORT" \ --dry-run=client \ -o yaml | kubectl apply -f - From c917673327e1a011bea46e9f8d91cff486246cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=B8?= <91dlwnsgh@naver.com> Date: Mon, 13 Jul 2026 14:07:42 +0900 Subject: [PATCH 15/55] =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=ED=83=80?= =?UTF-8?q?=EC=9E=84=EB=9D=BC=EC=9D=B8=20=EC=B6=94=EA=B0=80=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alert/AlertEventController.java | 34 +++++++++++- .../backend/domain/alert/ActionTimeline.java | 7 ++- .../alert/ActionTimelineCreateRequest.java | 33 +++++++++++ .../dto/alert/ActionTimelineResponse.java | 2 +- .../alert/ActionTimelineQueryService.java | 24 -------- .../service/alert/ActionTimelineService.java | 55 +++++++++++++++++++ 6 files changed, 125 insertions(+), 30 deletions(-) create mode 100644 src/main/java/com/aims/backend/dto/alert/ActionTimelineCreateRequest.java delete mode 100644 src/main/java/com/aims/backend/service/alert/ActionTimelineQueryService.java create mode 100644 src/main/java/com/aims/backend/service/alert/ActionTimelineService.java diff --git a/src/main/java/com/aims/backend/controller/alert/AlertEventController.java b/src/main/java/com/aims/backend/controller/alert/AlertEventController.java index 274e7eb..2e90708 100644 --- a/src/main/java/com/aims/backend/controller/alert/AlertEventController.java +++ b/src/main/java/com/aims/backend/controller/alert/AlertEventController.java @@ -1,19 +1,25 @@ package com.aims.backend.controller.alert; import com.aims.backend.common.response.ApiResponse; +import com.aims.backend.config.jwt.TokenProvider; import com.aims.backend.dto.alert.AlertActionUpdateRequest; import com.aims.backend.dto.alert.AlertEventResponse; import com.aims.backend.dto.alert.AlertSearchRequest; import com.aims.backend.dto.alert.ActionTimelineResponse; +import com.aims.backend.dto.alert.ActionTimelineCreateRequest; import com.aims.backend.service.alert.AlertEventQueryService; -import com.aims.backend.service.alert.ActionTimelineQueryService; +import com.aims.backend.service.alert.ActionTimelineService; +import com.aims.backend.service.alert.ActionTimelineService; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; +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; @@ -26,7 +32,8 @@ public class AlertEventController { private final AlertEventQueryService alertEventQueryService; - private final ActionTimelineQueryService actionTimelineQueryService; + private final ActionTimelineService actionTimelineService; + private final TokenProvider tokenProvider; @GetMapping public ApiResponse> getAlerts(@ModelAttribute AlertSearchRequest request) { @@ -53,6 +60,27 @@ public ApiResponse updateAction( @GetMapping("/{logNo}/action-timeline") public ApiResponse> getActionTimeline(@PathVariable String logNo) { - return ApiResponse.success(actionTimelineQueryService.getTimeline(logNo)); + return ApiResponse.success(actionTimelineService.getTimeline(logNo)); + } + + @PostMapping("/{logNo}/action-timeline") + public ApiResponse createActionTimeline( + @PathVariable String logNo, + @Valid @RequestBody ActionTimelineCreateRequest request, + HttpServletRequest httpServletRequest + ) { + + + String accessToken = TokenProvider.resolveToken(httpServletRequest); + if (accessToken == null) { + return ApiResponse.failure("Access token is missing or invalid.", null); + } + + Authentication authentication = tokenProvider.getAuthentication(accessToken); + if (authentication == null || !(authentication.getPrincipal() instanceof TokenProvider.JwtPrincipal principal)) { + return ApiResponse.failure("Invalid authentication principal.", null); + } + + return ApiResponse.success(actionTimelineService.createTimeline(logNo, request, principal)); } } diff --git a/src/main/java/com/aims/backend/domain/alert/ActionTimeline.java b/src/main/java/com/aims/backend/domain/alert/ActionTimeline.java index ada197b..947b537 100644 --- a/src/main/java/com/aims/backend/domain/alert/ActionTimeline.java +++ b/src/main/java/com/aims/backend/domain/alert/ActionTimeline.java @@ -4,6 +4,8 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Table; import jakarta.persistence.Id; import lombok.AccessLevel; @@ -27,8 +29,9 @@ public class ActionTimeline { @Id - @Column(name = "action_id", nullable = false, length = 20) - private String actionId; + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "action_id") + private Long actionId; @Column(name = "log_no", nullable = false, length = 20) private String logNo; diff --git a/src/main/java/com/aims/backend/dto/alert/ActionTimelineCreateRequest.java b/src/main/java/com/aims/backend/dto/alert/ActionTimelineCreateRequest.java new file mode 100644 index 0000000..2f821d8 --- /dev/null +++ b/src/main/java/com/aims/backend/dto/alert/ActionTimelineCreateRequest.java @@ -0,0 +1,33 @@ +package com.aims.backend.dto.alert; + +import com.aims.backend.domain.alert.ActionCategory; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ActionTimelineCreateRequest { + + @NotNull(message = "조치 시간은 필수 입력 값입니다.") + private LocalDateTime actionTime; + + @NotBlank(message = "조치 내용은 필수 입력 값입니다.") + @Size(max = 20, message = "조치 내용은 최대 20자까지 입력 가능합니다.") + private String actionContent; + + @NotNull(message = "조치 카테고리는 필수 입력 값입니다.") + private ActionCategory actionCategory; + + @NotBlank(message = "조치 결과는 필수 입력 값입니다.") + @Size(max = 20, message = "조치 결과는 최대 20자까지 입력 가능합니다.") + private String actionResult; +} diff --git a/src/main/java/com/aims/backend/dto/alert/ActionTimelineResponse.java b/src/main/java/com/aims/backend/dto/alert/ActionTimelineResponse.java index 94b0ace..45af99d 100644 --- a/src/main/java/com/aims/backend/dto/alert/ActionTimelineResponse.java +++ b/src/main/java/com/aims/backend/dto/alert/ActionTimelineResponse.java @@ -12,7 +12,7 @@ @Builder @Getter public class ActionTimelineResponse { - private String actionId; + private Long actionId; private String empNo; private String empName; private UserRole empRole; diff --git a/src/main/java/com/aims/backend/service/alert/ActionTimelineQueryService.java b/src/main/java/com/aims/backend/service/alert/ActionTimelineQueryService.java deleted file mode 100644 index c6e71fb..0000000 --- a/src/main/java/com/aims/backend/service/alert/ActionTimelineQueryService.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.aims.backend.service.alert; - -import com.aims.backend.dto.alert.ActionTimelineResponse; -import com.aims.backend.repository.alert.ActionTimelineRepository; -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import java.util.List; - -@Service -@RequiredArgsConstructor -public class ActionTimelineQueryService { - private final ActionTimelineRepository actionTimelineRepository; - - @Transactional() - public List getTimeline(String logNo) { - - return actionTimelineRepository - .findByLogNoOrderByActionTimeAsc(logNo) - .stream() - .map(ActionTimelineResponse::from) - .toList(); - } -} diff --git a/src/main/java/com/aims/backend/service/alert/ActionTimelineService.java b/src/main/java/com/aims/backend/service/alert/ActionTimelineService.java new file mode 100644 index 0000000..efee0d3 --- /dev/null +++ b/src/main/java/com/aims/backend/service/alert/ActionTimelineService.java @@ -0,0 +1,55 @@ +package com.aims.backend.service.alert; + +import com.aims.backend.config.jwt.TokenProvider; +import com.aims.backend.domain.alert.ActionTimeline; +import com.aims.backend.domain.user.UserRole; +import com.aims.backend.dto.alert.ActionTimelineCreateRequest; +import com.aims.backend.dto.alert.ActionTimelineResponse; +import com.aims.backend.repository.alert.ActionTimelineRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import java.util.UUID; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ActionTimelineService { + private final ActionTimelineRepository actionTimelineRepository; + + @Transactional() + public List getTimeline(String logNo) { + + return actionTimelineRepository + .findByLogNoOrderByActionTimeAsc(logNo) + .stream() + .map(ActionTimelineResponse::from) + .toList(); + } + + + @Transactional + public ActionTimelineResponse createTimeline( + String logNo, + ActionTimelineCreateRequest request, + TokenProvider.JwtPrincipal principal + ) { + + ActionTimeline actionTimeline = ActionTimeline.builder() + .logNo(logNo) + .empNo(principal.EmpNo().toString()) + .empName(principal.name()) + .empRole(UserRole.valueOf(principal.role())) + .actionTime(request.getActionTime()) + .actionContent(request.getActionContent()) + .actionCategory(request.getActionCategory()) + .actionResult(request.getActionResult()) + .createdAt(LocalDateTime.now()) + .build(); + + ActionTimeline saved = actionTimelineRepository.save(actionTimeline); + return ActionTimelineResponse.from(saved); + } +} From 7cac72bbc4a81032c45651ac7385cfa5974f28e0 Mon Sep 17 00:00:00 2001 From: hyein0514 Date: Mon, 13 Jul 2026 14:38:01 +0900 Subject: [PATCH 16/55] =?UTF-8?q?feat:=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9A=B0=EC=84=A0=EC=88=9C=EC=9C=84=20=EC=A7=80=EC=88=98=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aims/backend/config/WebSocketConfig.java | 1 + .../alert/AlertEventController.java | 15 +++ .../alert/AlertPrioritySummaryResponse.java | 18 +++ .../alert/AlertEventRepository.java | 39 ++++++ .../alert/AlertPrioritySummaryService.java | 103 ++++++++++++++++ .../alert/AlertEventRepositoryTest.java | 74 ++++++++++++ .../AlertPrioritySummaryServiceTest.java | 111 ++++++++++++++++++ src/test/resources/application.yaml | 3 + 8 files changed, 364 insertions(+) create mode 100644 src/main/java/com/aims/backend/dto/alert/AlertPrioritySummaryResponse.java create mode 100644 src/main/java/com/aims/backend/service/alert/AlertPrioritySummaryService.java create mode 100644 src/test/java/com/aims/backend/service/alert/AlertPrioritySummaryServiceTest.java diff --git a/src/main/java/com/aims/backend/config/WebSocketConfig.java b/src/main/java/com/aims/backend/config/WebSocketConfig.java index 8a8cc4b..bd15e02 100644 --- a/src/main/java/com/aims/backend/config/WebSocketConfig.java +++ b/src/main/java/com/aims/backend/config/WebSocketConfig.java @@ -19,6 +19,7 @@ public void registerStompEndpoints( } + @Override public void configureMessageBroker(MessageBrokerRegistry registry) { diff --git a/src/main/java/com/aims/backend/controller/alert/AlertEventController.java b/src/main/java/com/aims/backend/controller/alert/AlertEventController.java index 274e7eb..2e0df4a 100644 --- a/src/main/java/com/aims/backend/controller/alert/AlertEventController.java +++ b/src/main/java/com/aims/backend/controller/alert/AlertEventController.java @@ -3,11 +3,15 @@ import com.aims.backend.common.response.ApiResponse; import com.aims.backend.dto.alert.AlertActionUpdateRequest; import com.aims.backend.dto.alert.AlertEventResponse; +import com.aims.backend.dto.alert.AlertPrioritySummaryResponse; import com.aims.backend.dto.alert.AlertSearchRequest; import com.aims.backend.dto.alert.ActionTimelineResponse; +import com.aims.backend.service.alert.AlertPrioritySummaryService; import com.aims.backend.service.alert.AlertEventQueryService; import com.aims.backend.service.alert.ActionTimelineQueryService; import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.GetMapping; @@ -16,23 +20,34 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.validation.annotation.Validated; import java.util.List; @RestController @RequestMapping("/api/event") @RequiredArgsConstructor +@Validated public class AlertEventController { private final AlertEventQueryService alertEventQueryService; private final ActionTimelineQueryService actionTimelineQueryService; + private final AlertPrioritySummaryService alertPrioritySummaryService; @GetMapping public ApiResponse> getAlerts(@ModelAttribute AlertSearchRequest request) { return ApiResponse.success(alertEventQueryService.getAlerts(request)); } + @GetMapping("/priority-summary") + public ApiResponse getPrioritySummary( + @RequestParam(defaultValue = "7") @Min(1) @Max(365) int days + ) { + return ApiResponse.success(alertPrioritySummaryService.getPrioritySummary(days)); + } + @GetMapping("/{logNo}") public ApiResponse getAlert(@PathVariable String logNo) { return ApiResponse.success(alertEventQueryService.getAlert(logNo)); diff --git a/src/main/java/com/aims/backend/dto/alert/AlertPrioritySummaryResponse.java b/src/main/java/com/aims/backend/dto/alert/AlertPrioritySummaryResponse.java new file mode 100644 index 0000000..42561a8 --- /dev/null +++ b/src/main/java/com/aims/backend/dto/alert/AlertPrioritySummaryResponse.java @@ -0,0 +1,18 @@ +package com.aims.backend.dto.alert; + +import java.math.BigDecimal; + +public record AlertPrioritySummaryResponse( + + int periodDays, + + BigDecimal averagePriorityScore, + + BigDecimal averageRiskScore, + + BigDecimal averageOccurrencePercentage, + + BigDecimal actionCompletionRate + +) { +} diff --git a/src/main/java/com/aims/backend/repository/alert/AlertEventRepository.java b/src/main/java/com/aims/backend/repository/alert/AlertEventRepository.java index eabc340..90bcc94 100644 --- a/src/main/java/com/aims/backend/repository/alert/AlertEventRepository.java +++ b/src/main/java/com/aims/backend/repository/alert/AlertEventRepository.java @@ -7,11 +7,31 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.Optional; public interface AlertEventRepository extends JpaRepository, JpaSpecificationExecutor { + interface PrioritySummaryProjection { + + long getTotalCount(); + + BigDecimal getPriorityScoreSum(); + + long getPriorityScoreCount(); + + BigDecimal getRiskScoreSum(); + + long getRiskScoreCount(); + + BigDecimal getOccurrenceScoreSum(); + + long getOccurrenceScoreCount(); + + long getCompletedCount(); + } + boolean existsByEventId(String eventId); Optional findByEventId(String eventId); @@ -39,4 +59,23 @@ long countByEventKeyAndActionStatus( String eventKey, AlertActionStatus actionStatus ); + + @Query(""" + SELECT COUNT(e) AS totalCount, + SUM(e.priorityScore) AS priorityScoreSum, + COUNT(e.priorityScore) AS priorityScoreCount, + SUM(e.riskScore) AS riskScoreSum, + COUNT(e.riskScore) AS riskScoreCount, + SUM(e.occurrenceScore) AS occurrenceScoreSum, + COUNT(e.occurrenceScore) AS occurrenceScoreCount, + SUM(CASE WHEN e.actionStatus = :completedStatus THEN 1 ELSE 0 END) AS completedCount + FROM AlertEvent e + WHERE e.createdAt >= :from + AND e.createdAt < :to + """) + PrioritySummaryProjection findPrioritySummary( + @Param("from") LocalDateTime from, + @Param("to") LocalDateTime to, + @Param("completedStatus") AlertActionStatus completedStatus + ); } diff --git a/src/main/java/com/aims/backend/service/alert/AlertPrioritySummaryService.java b/src/main/java/com/aims/backend/service/alert/AlertPrioritySummaryService.java new file mode 100644 index 0000000..bcbcb30 --- /dev/null +++ b/src/main/java/com/aims/backend/service/alert/AlertPrioritySummaryService.java @@ -0,0 +1,103 @@ +package com.aims.backend.service.alert; + +import com.aims.backend.domain.alert.AlertActionStatus; +import com.aims.backend.dto.alert.AlertPrioritySummaryResponse; +import com.aims.backend.repository.alert.AlertEventRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +public class AlertPrioritySummaryService { + + private static final BigDecimal ONE_HUNDRED = new BigDecimal("100"); + + private final AlertEventRepository alertEventRepository; + + @Transactional(readOnly = true) + public AlertPrioritySummaryResponse getPrioritySummary(int days) { + LocalDateTime to = LocalDateTime.now(); + LocalDateTime from = to.minusDays(days); + + AlertEventRepository.PrioritySummaryProjection summary = + alertEventRepository.findPrioritySummary(from, to, AlertActionStatus.COMPLETED); + + if (summary == null || summary.getTotalCount() == 0) { + return emptyResponse(days); + } + + BigDecimal averagePriorityScore = + average(summary.getPriorityScoreSum(), summary.getPriorityScoreCount(), 2); + BigDecimal averageRiskScore = + average(summary.getRiskScoreSum(), summary.getRiskScoreCount(), 1); + BigDecimal averageOccurrencePercentage = + percentage(summary.getOccurrenceScoreSum(), summary.getOccurrenceScoreCount()); + BigDecimal actionCompletionRate = + rate(summary.getCompletedCount(), summary.getTotalCount()); + + return new AlertPrioritySummaryResponse( + days, + averagePriorityScore, + averageRiskScore, + averageOccurrencePercentage, + actionCompletionRate + ); + } + + private AlertPrioritySummaryResponse emptyResponse(int days) { + return new AlertPrioritySummaryResponse( + days, + BigDecimal.ZERO, + BigDecimal.ZERO, + BigDecimal.ZERO, + BigDecimal.ZERO + ); + } + + private BigDecimal average(BigDecimal sum, long count, int scale) { + if (sum == null || count == 0) { + return BigDecimal.ZERO; + } + + return sum.divide(BigDecimal.valueOf(count), scale, RoundingMode.HALF_UP); + } + + private BigDecimal percentage(BigDecimal sum, long count) { + if (sum == null || count == 0) { + return BigDecimal.ZERO; + } + + BigDecimal percentage = + sum.multiply(ONE_HUNDRED) + .divide(BigDecimal.valueOf(count), 0, RoundingMode.HALF_UP); + return clampPercentage(percentage); + } + + private BigDecimal rate(long completedCount, long totalCount) { + if (totalCount == 0) { + return BigDecimal.ZERO; + } + + BigDecimal percentage = + BigDecimal.valueOf(completedCount) + .multiply(ONE_HUNDRED) + .divide(BigDecimal.valueOf(totalCount), 0, RoundingMode.HALF_UP); + return clampPercentage(percentage); + } + + private BigDecimal clampPercentage(BigDecimal value) { + if (value.compareTo(BigDecimal.ZERO) < 0) { + return BigDecimal.ZERO; + } + if (value.compareTo(ONE_HUNDRED) > 0) { + return ONE_HUNDRED; + } + + return value.setScale(0, RoundingMode.HALF_UP); + } +} diff --git a/src/test/java/com/aims/backend/repository/alert/AlertEventRepositoryTest.java b/src/test/java/com/aims/backend/repository/alert/AlertEventRepositoryTest.java index f5622e0..161138a 100644 --- a/src/test/java/com/aims/backend/repository/alert/AlertEventRepositoryTest.java +++ b/src/test/java/com/aims/backend/repository/alert/AlertEventRepositoryTest.java @@ -68,4 +68,78 @@ void savesAndFindsAlertEventByEventId() { assertThat(saved.getScoreCalculatedAt()).isEqualTo(scoreCalculatedAt); }); } + + @Test + void aggregatesScoresAndCompletedActionsWithinPeriod() { + LocalDateTime from = LocalDateTime.of(2026, 7, 6, 12, 0); + LocalDateTime to = LocalDateTime.of(2026, 7, 13, 12, 0); + + alertEventRepository.save(summaryEvent( + "summary-1", from.plusDays(1), "100.00", "80.00", "0.5000", AlertActionStatus.COMPLETED + )); + alertEventRepository.save(summaryEvent( + "summary-2", from.plusDays(2), "200.00", null, "1.0000", AlertActionStatus.NOT_NEEDED + )); + alertEventRepository.save(summaryEvent( + "summary-3", from.minusSeconds(1), "999.00", "99.00", "0.9000", AlertActionStatus.COMPLETED + )); + alertEventRepository.save(summaryEvent( + "summary-4", from.plusDays(3), null, "40.00", null, AlertActionStatus.INCOMPLETE + )); + alertEventRepository.flush(); + setCreatedAt("AL-summary-1", from.plusDays(1)); + setCreatedAt("AL-summary-2", from.plusDays(2)); + setCreatedAt("AL-summary-3", from.minusSeconds(1)); + setCreatedAt("AL-summary-4", from.plusDays(3)); + entityManager.clear(); + + AlertEventRepository.PrioritySummaryProjection summary = + alertEventRepository.findPrioritySummary(from, to, AlertActionStatus.COMPLETED); + + assertThat(summary.getTotalCount()).isEqualTo(3); + assertThat(summary.getPriorityScoreSum()).isEqualByComparingTo(new BigDecimal("300.00")); + assertThat(summary.getPriorityScoreCount()).isEqualTo(2); + assertThat(summary.getRiskScoreSum()).isEqualByComparingTo(new BigDecimal("120.00")); + assertThat(summary.getRiskScoreCount()).isEqualTo(2); + assertThat(summary.getOccurrenceScoreSum()).isEqualByComparingTo(new BigDecimal("1.5000")); + assertThat(summary.getOccurrenceScoreCount()).isEqualTo(2); + assertThat(summary.getCompletedCount()).isEqualTo(1); + } + + private AlertEvent summaryEvent( + String suffix, + LocalDateTime createdAt, + String priorityScore, + String riskScore, + String occurrenceScore, + AlertActionStatus actionStatus + ) { + return AlertEvent.builder() + .logNo("AL-" + suffix) + .eventId("event-" + suffix) + .alertType(AlertType.PROCESS) + .processCode(ProcessCode.PAINT) + .eventKey("PROCESS:PAINT:" + suffix) + .riskScore(decimal(riskScore)) + .occurrenceScore(decimal(occurrenceScore)) + .detectionScore(BigDecimal.ZERO) + .priorityScore(decimal(priorityScore)) + .severity(AlertSeverity.CAUTION) + .title("summary test") + .contents("summary test") + .actionStatus(actionStatus) + .createdAt(createdAt) + .build(); + } + + private BigDecimal decimal(String value) { + return value == null ? null : new BigDecimal(value); + } + + private void setCreatedAt(String logNo, LocalDateTime createdAt) { + entityManager.createNativeQuery("UPDATE alert_event SET created_at = :createdAt WHERE log_no = :logNo") + .setParameter("createdAt", createdAt) + .setParameter("logNo", logNo) + .executeUpdate(); + } } diff --git a/src/test/java/com/aims/backend/service/alert/AlertPrioritySummaryServiceTest.java b/src/test/java/com/aims/backend/service/alert/AlertPrioritySummaryServiceTest.java new file mode 100644 index 0000000..fe6beff --- /dev/null +++ b/src/test/java/com/aims/backend/service/alert/AlertPrioritySummaryServiceTest.java @@ -0,0 +1,111 @@ +package com.aims.backend.service.alert; + +import com.aims.backend.domain.alert.AlertActionStatus; +import com.aims.backend.dto.alert.AlertPrioritySummaryResponse; +import com.aims.backend.repository.alert.AlertEventRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class AlertPrioritySummaryServiceTest { + + @Mock + private AlertEventRepository alertEventRepository; + + @Mock + private AlertEventRepository.PrioritySummaryProjection projection; + + private AlertPrioritySummaryService service; + + @BeforeEach + void setUp() { + service = new AlertPrioritySummaryService(alertEventRepository); + } + + @Test + void calculatesPrioritySummary() { + when(alertEventRepository.findPrioritySummary( + any(LocalDateTime.class), + any(LocalDateTime.class), + eq(AlertActionStatus.COMPLETED) + )).thenReturn(projection); + when(projection.getTotalCount()).thenReturn(4L); + when(projection.getCompletedCount()).thenReturn(3L); + when(projection.getPriorityScoreSum()).thenReturn(new BigDecimal("748.504")); + when(projection.getPriorityScoreCount()).thenReturn(4L); + when(projection.getRiskScoreSum()).thenReturn(new BigDecimal("329.40")); + when(projection.getRiskScoreCount()).thenReturn(4L); + when(projection.getOccurrenceScoreSum()).thenReturn(new BigDecimal("2.7000")); + when(projection.getOccurrenceScoreCount()).thenReturn(4L); + + AlertPrioritySummaryResponse response = service.getPrioritySummary(7); + + assertThat(response.periodDays()).isEqualTo(7); + assertThat(response.averagePriorityScore()).isEqualByComparingTo(new BigDecimal("187.13")); + assertThat(response.averageRiskScore()).isEqualByComparingTo(new BigDecimal("82.4")); + assertThat(response.averageOccurrencePercentage()).isEqualByComparingTo(new BigDecimal("68")); + assertThat(response.actionCompletionRate()).isEqualByComparingTo(new BigDecimal("75")); + } + + @Test + void returnsZerosWhenThereAreNoEvents() { + when(alertEventRepository.findPrioritySummary( + any(LocalDateTime.class), + any(LocalDateTime.class), + eq(AlertActionStatus.COMPLETED) + )).thenReturn(projection); + when(projection.getTotalCount()).thenReturn(0L); + + AlertPrioritySummaryResponse response = service.getPrioritySummary(7); + + assertThat(response.averagePriorityScore()).isZero(); + assertThat(response.averageRiskScore()).isZero(); + assertThat(response.averageOccurrencePercentage()).isZero(); + assertThat(response.actionCompletionRate()).isZero(); + } + + @Test + void returnsZeroOnlyForMissingScoreAverages() { + when(alertEventRepository.findPrioritySummary( + any(LocalDateTime.class), + any(LocalDateTime.class), + eq(AlertActionStatus.COMPLETED) + )).thenReturn(projection); + when(projection.getTotalCount()).thenReturn(2L); + + AlertPrioritySummaryResponse response = service.getPrioritySummary(7); + + assertThat(response.averagePriorityScore()).isZero(); + assertThat(response.averageRiskScore()).isZero(); + assertThat(response.averageOccurrencePercentage()).isZero(); + } + + @Test + void clampsPercentagesToValidRange() { + when(alertEventRepository.findPrioritySummary( + any(LocalDateTime.class), + any(LocalDateTime.class), + eq(AlertActionStatus.COMPLETED) + )).thenReturn(projection); + when(projection.getTotalCount()).thenReturn(2L); + when(projection.getCompletedCount()).thenReturn(3L); + when(projection.getOccurrenceScoreSum()).thenReturn(new BigDecimal("2.4000")); + when(projection.getOccurrenceScoreCount()).thenReturn(2L); + + AlertPrioritySummaryResponse response = service.getPrioritySummary(7); + + assertThat(response.averageOccurrencePercentage()).isEqualByComparingTo(new BigDecimal("100")); + assertThat(response.actionCompletionRate()).isEqualByComparingTo(new BigDecimal("100")); + } +} diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml index b232b15..90b36a6 100644 --- a/src/test/resources/application.yaml +++ b/src/test/resources/application.yaml @@ -45,3 +45,6 @@ app: maximum-pool-size: 3 minimum-idle: 1 pool-name: test-sample-db-pool + +external: + assembly-url: http://localhost:8082 From eb5d53c1527bb5b9987a0417a133470aedaa53b4 Mon Sep 17 00:00:00 2001 From: kimgeon Date: Mon, 13 Jul 2026 16:04:30 +0900 Subject: [PATCH 17/55] feat : S3 Img URL ADD --- src/main/java/com/aims/backend/domain/alert/AlertEvent.java | 3 +++ .../java/com/aims/backend/dto/alert/AlertEventResponse.java | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/main/java/com/aims/backend/domain/alert/AlertEvent.java b/src/main/java/com/aims/backend/domain/alert/AlertEvent.java index ab97bf7..ee9a935 100644 --- a/src/main/java/com/aims/backend/domain/alert/AlertEvent.java +++ b/src/main/java/com/aims/backend/domain/alert/AlertEvent.java @@ -81,6 +81,9 @@ public class AlertEvent { @Column(name = "score_calculated_at") private LocalDateTime scoreCalculatedAt; + @Column(name = "image_url") + private String imageUrl; + @CreationTimestamp @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt; diff --git a/src/main/java/com/aims/backend/dto/alert/AlertEventResponse.java b/src/main/java/com/aims/backend/dto/alert/AlertEventResponse.java index 42aedc6..ceb4a24 100644 --- a/src/main/java/com/aims/backend/dto/alert/AlertEventResponse.java +++ b/src/main/java/com/aims/backend/dto/alert/AlertEventResponse.java @@ -30,6 +30,7 @@ public class AlertEventResponse { private String actionStatus; private String reason; private LocalDateTime scoreCalculatedAt; + private String imageUrl; private LocalDateTime createdAt; private LocalDateTime resolvedAt; @@ -52,6 +53,7 @@ public static AlertEventResponse from(AlertEvent alertEvent) { .actionStatus(alertEvent.getActionStatus() == null ? null : alertEvent.getActionStatus().name()) .reason(alertEvent.getReason()) .scoreCalculatedAt(alertEvent.getScoreCalculatedAt()) + .imageUrl(alertEvent.getImageUrl()) .createdAt(alertEvent.getCreatedAt()) .resolvedAt(alertEvent.getResolvedAt()) .build(); From 922cf1be82f495544501352b650aef73d5b84110 Mon Sep 17 00:00:00 2001 From: chani2104 Date: Mon, 13 Jul 2026 17:07:00 +0900 Subject: [PATCH 18/55] =?UTF-8?q?feat:=20Redis=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 + .../aims/backend/config/ShedLockConfig.java | 25 ++ .../dashboard/AgvRealtimeRedisService.java | 70 ++- .../dashboard/AgvSimulationService.java | 419 +++++++++++------- .../dashboard/AgvTransportStateScheduler.java | 69 +++ .../ManufacturingAnalysisConsumer.java | 3 +- src/main/resources/application-dev.yaml | 12 +- src/main/resources/application-local.yaml | 30 +- src/main/resources/application-prod.yaml | 12 +- src/main/resources/application.yaml | 11 + 10 files changed, 475 insertions(+), 180 deletions(-) create mode 100644 src/main/java/com/aims/backend/config/ShedLockConfig.java create mode 100644 src/main/java/com/aims/backend/service/dashboard/AgvTransportStateScheduler.java diff --git a/build.gradle b/build.gradle index 4132fda..8b79147 100644 --- a/build.gradle +++ b/build.gradle @@ -43,6 +43,10 @@ dependencies { // Redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' + // ShedLock + implementation "net.javacrumbs.shedlock:shedlock-spring:7.7.0" + implementation "net.javacrumbs.shedlock:shedlock-provider-redis-spring:7.7.0" + // Cache implementation 'org.springframework.boot:spring-boot-starter-cache' diff --git a/src/main/java/com/aims/backend/config/ShedLockConfig.java b/src/main/java/com/aims/backend/config/ShedLockConfig.java new file mode 100644 index 0000000..5f280bc --- /dev/null +++ b/src/main/java/com/aims/backend/config/ShedLockConfig.java @@ -0,0 +1,25 @@ +package com.aims.backend.config; + +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider; +import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; + +@Configuration +@EnableSchedulerLock(defaultLockAtMostFor = "PT30S") +public class ShedLockConfig { + + private static final String ENVIRONMENT = "aims-backend"; + + @Bean + public LockProvider lockProvider( + RedisConnectionFactory redisConnectionFactory + ) { + return new RedisLockProvider( + redisConnectionFactory, + ENVIRONMENT + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvRealtimeRedisService.java b/src/main/java/com/aims/backend/service/dashboard/AgvRealtimeRedisService.java index 4d8fccf..574641f 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvRealtimeRedisService.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvRealtimeRedisService.java @@ -3,37 +3,36 @@ import com.aims.backend.dto.dashboard.AgvRealtimeState; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ScanOptions; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.List; + +@Slf4j @Service @RequiredArgsConstructor public class AgvRealtimeRedisService { private static final String KEY_PREFIX = "agv:realtime:"; + private static final String KEY_PATTERN = KEY_PREFIX + "*"; private final RedisTemplate redisTemplate; private final ObjectMapper objectMapper; - /** - * Redis 저장 - */ public void save(AgvRealtimeState state) { - redisTemplate.opsForValue().set( makeKey(state.getAgvId()), state ); } - /** - * Redis 조회 - */ public AgvRealtimeState get(Long agvId) { - - Object value = - redisTemplate.opsForValue() - .get(makeKey(agvId)); + Object value = redisTemplate.opsForValue() + .get(makeKey(agvId)); if (value == null) { return AgvRealtimeState.empty(agvId); @@ -45,21 +44,48 @@ public AgvRealtimeState get(Long agvId) { ); } - /** - * Redis 삭제 - */ - public void delete(Long agvId) { + public List findAll() { + List states = new ArrayList<>(); - redisTemplate.delete( - makeKey(agvId) - ); + ScanOptions options = ScanOptions.scanOptions() + .match(KEY_PATTERN) + .count(100) + .build(); + + try (Cursor cursor = redisTemplate.scan(options)) { + while (cursor.hasNext()) { + String key = cursor.next(); + Object value = redisTemplate.opsForValue().get(key); + + if (value == null) { + continue; + } + + try { + states.add( + objectMapper.convertValue( + value, + AgvRealtimeState.class + ) + ); + } catch (IllegalArgumentException e) { + log.warn( + "[AGV REDIS READ SKIP] 역직렬화 실패. key={}", + key, + e + ); + } + } + } + + return states; } - /** - * Redis Key 생성 - */ - private String makeKey(Long agvId) { + public void delete(Long agvId) { + redisTemplate.delete(makeKey(agvId)); + } + private String makeKey(Long agvId) { return KEY_PREFIX + agvId; } } \ No newline at end of file diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java b/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java index 3804662..960789e 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java @@ -7,7 +7,6 @@ import com.aims.backend.dto.dashboard.AgvOperationResponse; import com.aims.backend.dto.dashboard.AgvRealtimeState; import com.aims.backend.repository.dashboard.AgvOperationRepository; -import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.messaging.simp.SimpMessagingTemplate; @@ -17,11 +16,6 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; @Slf4j @Service @@ -34,28 +28,38 @@ public class AgvSimulationService { private final TransactionTemplate transactionTemplate; private final AssemblyArrivalClient assemblyArrivalClient; - private final ScheduledExecutorService executorService = - Executors.newScheduledThreadPool(10); - - private final Map> scheduledTasks = - new ConcurrentHashMap<>(); - private static final int MOVE_DURATION_SECONDS = 30; private static final int UNLOADING_DURATION_SECONDS = 5; private static final int RETURN_DURATION_SECONDS = 30; private static final Map ROUTES = Map.of( ProcessCode.PRESS, - new RouteInfo(ProcessCode.PRESS, ProcessCode.BODY, "PRESS_BODY"), + new RouteInfo( + ProcessCode.PRESS, + ProcessCode.BODY, + "PRESS_BODY" + ), ProcessCode.BODY, - new RouteInfo(ProcessCode.BODY, ProcessCode.PAINT, "BODY_PAINT"), + new RouteInfo( + ProcessCode.BODY, + ProcessCode.PAINT, + "BODY_PAINT" + ), ProcessCode.PAINT, - new RouteInfo(ProcessCode.PAINT, ProcessCode.ASSEMBLY, "PAINT_ASSEMBLY"), + new RouteInfo( + ProcessCode.PAINT, + ProcessCode.ASSEMBLY, + "PAINT_ASSEMBLY" + ), ProcessCode.ASSEMBLY, - new RouteInfo(ProcessCode.ASSEMBLY, ProcessCode.INSPECTION, "ASSEMBLY_INSPECTION") + new RouteInfo( + ProcessCode.ASSEMBLY, + ProcessCode.INSPECTION, + "ASSEMBLY_INSPECTION" + ) ); public boolean dispatchAgv( @@ -63,7 +67,6 @@ public boolean dispatchAgv( Long carMasterId, ProcessCode currentProcess ) { - RouteInfo routeInfo = ROUTES.get(currentProcess); if (routeInfo == null) { @@ -76,7 +79,6 @@ public boolean dispatchAgv( } AgvOperation agv = transactionTemplate.execute(status -> { - AgvOperation selectedAgv = agvOperationRepository .findFirstWaitingAgvForUpdateSkipLocked( @@ -120,31 +122,96 @@ public boolean dispatchAgv( return true; } + /** + * ShedLock이 적용된 상태 전이 Scheduler가 호출합니다. + * + * Redis의 status만 신뢰하지 않고 DB의 실제 AGV 상태를 함께 확인하여 + * Pod가 상태 전이 중 종료된 경우에도 다음 Pod가 흐름을 복구할 수 있게 합니다. + */ + public void advanceExpiredState(AgvRealtimeState redisState) { + if (redisState == null || redisState.getAgvId() == null) { + return; + } + + Long agvId = redisState.getAgvId(); + + AgvOperation agv = agvOperationRepository.findById(agvId) + .orElse(null); + + if (agv == null) { + log.warn( + "[AGV STATE RECOVERY] DB에 AGV가 없어 Redis 상태를 삭제합니다. agvId={}", + agvId + ); + agvRealtimeRedisService.delete(agvId); + return; + } + + RouteInfo routeInfo = findRouteInfo(agv.getRouteCode()); + + if (routeInfo == null) { + log.error( + "[AGV STATE RECOVERY FAILED] 알 수 없는 routeCode. agvId={}, routeCode={}", + agvId, + agv.getRouteCode() + ); + return; + } + + switch (agv.getAgvStatus()) { + case MOVING -> handleMovingArrived(agvId, routeInfo); + + case UNLOADING -> { + if (AgvStatus.UNLOADING.name() + .equals(redisState.getStatus())) { + startReturningSession(agvId, routeInfo); + } else { + recoverUnloadingSession(agv, routeInfo); + } + } + + case RETURNING -> { + if (AgvStatus.RETURNING.name() + .equals(redisState.getStatus())) { + handleReturningArrived(agvId, routeInfo); + } else { + recoverReturningSession(agvId, routeInfo); + } + } + + case WAITING -> { + log.info( + "[AGV STALE REDIS STATE DELETE] agvId={}, redisStatus={}", + agvId, + redisState.getStatus() + ); + agvRealtimeRedisService.delete(agvId); + } + } + } + private void startMovingSession( Long agvId, String eventId, Long carMasterId, RouteInfo routeInfo ) { - cancelScheduledTask(agvId); - LocalDateTime startedAt = LocalDateTime.now(); LocalDateTime expectedArrivalTime = startedAt.plusSeconds(MOVE_DURATION_SECONDS); - AgvRealtimeState state = - AgvRealtimeState.builder() - .agvId(agvId) - .eventId(eventId) - .carMasterId(carMasterId) - .status(AgvStatus.MOVING.name()) - .currentProcess(routeInfo.from().name()) - .targetProcess(routeInfo.to().name()) - .progressRate(0.0) - .delaySeconds(0) - .startedAt(startedAt) - .expectedArrivalTime(expectedArrivalTime) - .build(); + AgvRealtimeState state = AgvRealtimeState.builder() + .agvId(agvId) + .eventId(eventId) + .carMasterId(carMasterId) + .status(AgvStatus.MOVING.name()) + .currentProcess(routeInfo.from().name()) + .targetProcess(routeInfo.to().name()) + .progressRate(0.0) + .delaySeconds(0) + .startedAt(startedAt) + .expectedArrivalTime(expectedArrivalTime) + .build(); agvRealtimeRedisService.save(state); @@ -159,63 +226,53 @@ private void startMovingSession( ); sendAgvStatus(); - - ScheduledFuture task = - executorService.schedule( - () -> handleMovingArrived( - agvId, - eventId, - carMasterId, - routeInfo - ), - MOVE_DURATION_SECONDS, - TimeUnit.SECONDS - ); - - scheduledTasks.put(agvId, task); } private void handleMovingArrived( Long agvId, - String eventId, - Long carMasterId, RouteInfo routeInfo ) { - scheduledTasks.remove(agvId); + ArrivalContext context = transactionTemplate.execute(status -> { + AgvOperation agv = agvOperationRepository.findById(agvId) + .orElseThrow(() -> new IllegalStateException( + "AGV를 찾을 수 없습니다. agvId=" + agvId + )); + + if (agv.getAgvStatus() != AgvStatus.MOVING) { + return null; + } - transactionTemplate.executeWithoutResult(status -> { - AgvOperation agv = - agvOperationRepository.findById(agvId) - .orElseThrow(() -> new IllegalStateException( - "AGV를 찾을 수 없습니다. agvId=" + agvId - )); + String eventId = agv.getEventId(); + Long carMasterId = agv.getCarMasterId(); agv.changeToUnloading(); agvOperationRepository.save(agv); + + return new ArrivalContext(eventId, carMasterId); }); + if (context == null) { + log.debug( + "[AGV MOVING ARRIVAL SKIP] 이미 다른 상태로 전이됨. agvId={}", + agvId + ); + return; + } + log.info( "[AGV ARRIVED / UNLOADING START] agvId={}, eventId={}, carMasterId={}, arrivedProcess={}", agvId, - eventId, - carMasterId, + context.eventId(), + context.carMasterId(), routeInfo.to() ); - try { - assemblyArrivalClient.notifyAgvArrived(eventId); - } catch (Exception e) { - log.warn( - "[ASSEMBLY ARRIVAL FAILED] AGV 흐름은 계속 진행합니다. eventId={}", - eventId, - e - ); - } + notifyArrival(context.eventId()); startUnloadingSession( agvId, - eventId, - carMasterId, + context.eventId(), + context.carMasterId(), routeInfo ); } @@ -226,25 +283,22 @@ private void startUnloadingSession( Long carMasterId, RouteInfo routeInfo ) { - cancelScheduledTask(agvId); - LocalDateTime startedAt = LocalDateTime.now(); LocalDateTime expectedEndTime = startedAt.plusSeconds(UNLOADING_DURATION_SECONDS); - AgvRealtimeState state = - AgvRealtimeState.builder() - .agvId(agvId) - .eventId(eventId) - .carMasterId(carMasterId) - .status(AgvStatus.UNLOADING.name()) - .currentProcess(routeInfo.to().name()) - .targetProcess(routeInfo.to().name()) - .progressRate(100.0) - .delaySeconds(0) - .startedAt(startedAt) - .expectedArrivalTime(expectedEndTime) - .build(); + AgvRealtimeState state = AgvRealtimeState.builder() + .agvId(agvId) + .eventId(eventId) + .carMasterId(carMasterId) + .status(AgvStatus.UNLOADING.name()) + .currentProcess(routeInfo.to().name()) + .targetProcess(routeInfo.to().name()) + .progressRate(100.0) + .delaySeconds(0) + .startedAt(startedAt) + .expectedArrivalTime(expectedEndTime) + .build(); agvRealtimeRedisService.save(state); @@ -257,54 +311,58 @@ private void startUnloadingSession( ); sendAgvStatus(); - - ScheduledFuture task = - executorService.schedule( - () -> startReturningSession( - agvId, - routeInfo - ), - UNLOADING_DURATION_SECONDS, - TimeUnit.SECONDS - ); - - scheduledTasks.put(agvId, task); } private void startReturningSession( Long agvId, RouteInfo routeInfo ) { - cancelScheduledTask(agvId); - - transactionTemplate.executeWithoutResult(status -> { - AgvOperation agv = - agvOperationRepository.findById(agvId) - .orElseThrow(() -> new IllegalStateException( - "AGV를 찾을 수 없습니다. agvId=" + agvId - )); + Boolean changed = transactionTemplate.execute(status -> { + AgvOperation agv = agvOperationRepository.findById(agvId) + .orElseThrow(() -> new IllegalStateException( + "AGV를 찾을 수 없습니다. agvId=" + agvId + )); + + if (agv.getAgvStatus() != AgvStatus.UNLOADING) { + return false; + } agv.changeToReturning(); agvOperationRepository.save(agv); + return true; }); + if (!Boolean.TRUE.equals(changed)) { + log.debug( + "[AGV RETURNING START SKIP] UNLOADING 상태가 아님. agvId={}", + agvId + ); + return; + } + + saveReturningState(agvId, routeInfo); + } + + private void saveReturningState( + Long agvId, + RouteInfo routeInfo + ) { LocalDateTime startedAt = LocalDateTime.now(); LocalDateTime expectedArrivalTime = startedAt.plusSeconds(RETURN_DURATION_SECONDS); - AgvRealtimeState state = - AgvRealtimeState.builder() - .agvId(agvId) - .eventId(null) - .carMasterId(null) - .status(AgvStatus.RETURNING.name()) - .currentProcess(routeInfo.to().name()) - .targetProcess(routeInfo.from().name()) - .progressRate(0.0) - .delaySeconds(0) - .startedAt(startedAt) - .expectedArrivalTime(expectedArrivalTime) - .build(); + AgvRealtimeState state = AgvRealtimeState.builder() + .agvId(agvId) + .eventId(null) + .carMasterId(null) + .status(AgvStatus.RETURNING.name()) + .currentProcess(routeInfo.to().name()) + .targetProcess(routeInfo.from().name()) + .progressRate(0.0) + .delaySeconds(0) + .startedAt(startedAt) + .expectedArrivalTime(expectedArrivalTime) + .build(); agvRealtimeRedisService.save(state); @@ -317,32 +375,21 @@ private void startReturningSession( ); sendAgvStatus(); - - ScheduledFuture task = - executorService.schedule( - () -> handleReturningArrived( - agvId, - routeInfo - ), - RETURN_DURATION_SECONDS, - TimeUnit.SECONDS - ); - - scheduledTasks.put(agvId, task); } private void handleReturningArrived( Long agvId, RouteInfo routeInfo ) { - scheduledTasks.remove(agvId); - - transactionTemplate.executeWithoutResult(status -> { - AgvOperation agv = - agvOperationRepository.findById(agvId) - .orElseThrow(() -> new IllegalStateException( - "AGV를 찾을 수 없습니다. agvId=" + agvId - )); + Boolean changed = transactionTemplate.execute(status -> { + AgvOperation agv = agvOperationRepository.findById(agvId) + .orElseThrow(() -> new IllegalStateException( + "AGV를 찾을 수 없습니다. agvId=" + agvId + )); + + if (agv.getAgvStatus() != AgvStatus.RETURNING) { + return false; + } agv.changeToWaiting( routeInfo.from(), @@ -351,8 +398,17 @@ private void handleReturningArrived( ); agvOperationRepository.save(agv); + return true; }); + if (!Boolean.TRUE.equals(changed)) { + log.debug( + "[AGV RETURN COMPLETE SKIP] RETURNING 상태가 아님. agvId={}", + agvId + ); + return; + } + agvRealtimeRedisService.delete(agvId); log.info( @@ -366,6 +422,75 @@ private void handleReturningArrived( sendAgvStatus(); } + /** + * DB는 UNLOADING으로 변경됐지만 Redis가 MOVING에 머문 채 Pod가 종료된 경우 복구합니다. + * 도착 API는 eventId 기준 멱등 처리가 되어 있어야 안전합니다. + */ + private void recoverUnloadingSession( + AgvOperation agv, + RouteInfo routeInfo + ) { + log.warn( + "[AGV UNLOADING RECOVERY] agvId={}, eventId={}", + agv.getId(), + agv.getEventId() + ); + + notifyArrival(agv.getEventId()); + + startUnloadingSession( + agv.getId(), + agv.getEventId(), + agv.getCarMasterId(), + routeInfo + ); + } + + /** + * DB는 RETURNING으로 변경됐지만 Redis가 이전 상태에 머문 채 Pod가 종료된 경우 + * 복귀 타이머를 Redis에 다시 생성합니다. + */ + private void recoverReturningSession( + Long agvId, + RouteInfo routeInfo + ) { + log.warn( + "[AGV RETURNING RECOVERY] agvId={}", + agvId + ); + + saveReturningState(agvId, routeInfo); + } + + private void notifyArrival(String eventId) { + if (eventId == null || eventId.isBlank()) { + log.warn("[ASSEMBLY ARRIVAL SKIP] eventId가 없습니다."); + return; + } + + try { + assemblyArrivalClient.notifyAgvArrived(eventId); + } catch (Exception e) { + log.warn( + "[ASSEMBLY ARRIVAL FAILED] AGV 흐름은 계속 진행합니다. eventId={}", + eventId, + e + ); + } + } + + private RouteInfo findRouteInfo(String routeCode) { + if (routeCode == null || routeCode.isBlank()) { + return null; + } + + return ROUTES.values() + .stream() + .filter(route -> route.routeCode().equals(routeCode)) + .findFirst() + .orElse(null); + } + private void sendAgvStatus() { List response = agvOperationRepository.findAll() @@ -402,24 +527,16 @@ private AgvOperationResponse toResponse(AgvOperation agv) { ); } - private void cancelScheduledTask(Long agvId) { - ScheduledFuture task = - scheduledTasks.remove(agvId); - - if (task != null && !task.isDone()) { - task.cancel(false); - } - } - - @PreDestroy - public void shutdown() { - executorService.shutdownNow(); - } - private record RouteInfo( ProcessCode from, ProcessCode to, String routeCode ) { } + + private record ArrivalContext( + String eventId, + Long carMasterId + ) { + } } diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvTransportStateScheduler.java b/src/main/java/com/aims/backend/service/dashboard/AgvTransportStateScheduler.java new file mode 100644 index 0000000..aeb9d83 --- /dev/null +++ b/src/main/java/com/aims/backend/service/dashboard/AgvTransportStateScheduler.java @@ -0,0 +1,69 @@ +package com.aims.backend.service.dashboard; + +import com.aims.backend.dto.dashboard.AgvRealtimeState; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.javacrumbs.shedlock.core.LockAssert; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +@Slf4j +@Component +@RequiredArgsConstructor +@ConditionalOnProperty( + prefix = "app.agv.transport-scheduler", + name = "enabled", + havingValue = "true", + matchIfMissing = true +) +public class AgvTransportStateScheduler { + + private final AgvRealtimeRedisService agvRealtimeRedisService; + private final AgvSimulationService agvSimulationService; + + @Scheduled( + fixedDelayString = + "${app.agv.transport-scheduler.fixed-delay-ms:1000}" + ) + @SchedulerLock( + name = "agvTransportStateScheduler", + lockAtMostFor = "PT20S", + lockAtLeastFor = "PT0.5S" + ) + public void advanceExpiredStates() { + LockAssert.assertLocked(); + + LocalDateTime now = LocalDateTime.now(); + + for (AgvRealtimeState state : agvRealtimeRedisService.findAll()) { + if (!isExpired(state, now)) { + continue; + } + + try { + agvSimulationService.advanceExpiredState(state); + } catch (Exception e) { + log.error( + "[AGV STATE SCHEDULER FAILED] agvId={}, redisStatus={}", + state.getAgvId(), + state.getStatus(), + e + ); + } + } + } + + private boolean isExpired( + AgvRealtimeState state, + LocalDateTime now + ) { + return state != null + && state.getAgvId() != null + && state.getExpectedArrivalTime() != null + && !state.getExpectedArrivalTime().isAfter(now); + } +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java b/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java index a25aa79..4075a24 100644 --- a/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java +++ b/src/main/java/com/aims/backend/service/dashboard/ManufacturingAnalysisConsumer.java @@ -23,7 +23,8 @@ public class ManufacturingAnalysisConsumer { @KafkaListener( topics = "factory.manufacturing.analysis", - groupId = "main-agv-group" + groupId = "${app.kafka.consumer.agv-group-id}", + concurrency = "2" ) public void consume(String message) { diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 9cd7912..3e79417 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -4,6 +4,7 @@ spring: host: ${REDIS_HOST:localhost} port: ${REDIS_PORT:6379} timeout: ${REDIS_TIMEOUT:3s} + kafka: bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092} @@ -11,21 +12,28 @@ app: redis: cache: ttl: ${REDIS_CACHE_TTL:10m} + opensearch: scheme: ${OPENSEARCH_SCHEME:http} host: ${OPENSEARCH_HOST:localhost} port: ${OPENSEARCH_PORT:9200} + kafka: bootstrap-servers: - ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092} + group-id: ${KAFKA_GROUP_ID:assembly-dev} auto-offset-reset: ${KAFKA_AUTO_OFFSET_RESET:earliest} + consumer: + agv-group-id: ${AGV_CONSUMER_GROUP_ID:main-agv-group} + logging: level: root: info - com.aims.assembly: debug + com.aims.backend: debug org.hibernate.SQL: debug + file: path: ${LOG_PATH:logs/dev} @@ -34,4 +42,4 @@ management: redis: enabled: true kafka: - enabled: false + enabled: false \ No newline at end of file diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 72e5121..c8db95b 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -1,12 +1,38 @@ logging: level: root: info - com.aims.assembly: debug + com.aims.backend: debug org.hibernate.SQL: WARN org.hibernate.orm.jdbc.bind: WARN + file: path: ${LOG_PATH:logs/local} +spring: + data: + redis: + host: localhost + port: 16379 + timeout: 3s + + kafka: + consumer: + auto-offset-reset: latest + +app: + + kafka: + consumer: + agv-group-id: main-agv-group-local + + agv: + dispatch-scheduler: + enabled: false # 배차 스케줄러 중지 + + transport-scheduler: + enabled: true # ShedLock 운행 스케줄러만 실행 + fixed-delay-ms: 1000 + management: health: redis: @@ -16,4 +42,4 @@ management: cors: allowedOrigins: - - http://localhost:5173 + - http://localhost:5173 \ No newline at end of file diff --git a/src/main/resources/application-prod.yaml b/src/main/resources/application-prod.yaml index f9b84fd..bc6e639 100644 --- a/src/main/resources/application-prod.yaml +++ b/src/main/resources/application-prod.yaml @@ -4,6 +4,7 @@ spring: host: ${REDIS_HOST:localhost} port: ${REDIS_PORT:6379} timeout: ${REDIS_TIMEOUT:3s} + kafka: bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092} @@ -11,20 +12,27 @@ app: redis: cache: ttl: ${REDIS_CACHE_TTL:30m} + opensearch: scheme: ${OPENSEARCH_SCHEME:http} host: ${OPENSEARCH_HOST:localhost} port: ${OPENSEARCH_PORT:9200} + kafka: bootstrap-servers: - ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092} + group-id: ${KAFKA_GROUP_ID:assembly-prod} auto-offset-reset: ${KAFKA_AUTO_OFFSET_RESET:latest} + consumer: + agv-group-id: ${AGV_CONSUMER_GROUP_ID:main-agv-group} + logging: level: root: warn - com.aims.assembly: info + com.aims.backend: info + file: path: ${LOG_PATH:logs/prod} @@ -33,4 +41,4 @@ management: redis: enabled: true kafka: - enabled: false + enabled: false \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 40341b3..9759ec4 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -69,6 +69,14 @@ app: maximum-pool-size: ${SAMPLE_DB_MAXIMUM_POOL_SIZE:5} minimum-idle: ${SAMPLE_DB_MINIMUM_IDLE:1} pool-name: ${SAMPLE_DB_POOL_NAME:sample-db-pool} + agv: + dispatch-scheduler: + enabled: true + fixed-delay-ms: 1000 + + transport-scheduler: + enabled: true + fixed-delay-ms: 1000 redis: cache: ttl: ${REDIS_CACHE_TTL:10m} @@ -83,6 +91,9 @@ app: listeners-enabled: ${KAFKA_LISTENERS_ENABLED:true} analysis-topic: factory.manufacturing.analysis + consumer: + agv-group-id: ${AGV_CONSUMER_GROUP_ID:main-agv-group} + security-protocol: SASL_SSL sasl-mechanism: AWS_MSK_IAM sasl-jaas-config: software.amazon.msk.auth.iam.IAMLoginModule required; From 51936dc7545c92d52ce0212cc9c5f298ffaef012 Mon Sep 17 00:00:00 2001 From: kimgeon Date: Mon, 13 Jul 2026 17:28:24 +0900 Subject: [PATCH 19/55] fix : save service mapper add --- null | 0 .../aims/backend/service/alert/AlertEventSaveService.java | 5 +++++ ...40\225 \354\235\264\354\203\201 \352\260\220\354\247\200" | 0 3 files changed, 5 insertions(+) create mode 100644 null create mode 100644 "\352\263\265\354\240\225 \354\235\264\354\203\201 \352\260\220\354\247\200" diff --git a/null b/null new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java b/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java index 5410bbd..687966c 100644 --- a/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java +++ b/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java @@ -131,6 +131,8 @@ private CalculatedAlert toCalculatedAlert(JsonNode root) { defaultContents(alertType, processCode, equipmentId, text(root, "contents", "message", "description")); String eventKey = eventKey(root, alertType, processCode, equipmentId, title, eventId); + String imageUrl = + text(root, "imageUrl", "image_url"); BigDecimal riskScore = score(root, BigDecimal.ZERO, BigDecimal.valueOf(100), "riskScore", "risk_score"); if (riskScore == null) { @@ -173,6 +175,7 @@ private CalculatedAlert toCalculatedAlert(JsonNode root) { severity, title, contents, + imageUrl, AlertActionStatus.INCOMPLETE, scoreCalculatedAt ); @@ -241,6 +244,7 @@ private AlertEvent toAlertEvent(CalculatedAlert calculatedAlert) { .severity(calculatedAlert.severity()) .title(truncate(calculatedAlert.title(), 100)) .contents(truncate(calculatedAlert.contents(), 500)) + .imageUrl(truncate(calculatedAlert.imageUrl(), 500)) .actionStatus(calculatedAlert.actionStatus()) .scoreCalculatedAt(calculatedAlert.scoreCalculatedAt()) .build(); @@ -578,6 +582,7 @@ private record CalculatedAlert( AlertSeverity severity, String title, String contents, + String imageUrl, AlertActionStatus actionStatus, LocalDateTime scoreCalculatedAt ) { diff --git "a/\352\263\265\354\240\225 \354\235\264\354\203\201 \352\260\220\354\247\200" "b/\352\263\265\354\240\225 \354\235\264\354\203\201 \352\260\220\354\247\200" new file mode 100644 index 0000000..e69de29 From f11af09215b8addee8c3a0a950070586aeacb360 Mon Sep 17 00:00:00 2001 From: kimgeon Date: Tue, 14 Jul 2026 08:41:57 +0900 Subject: [PATCH 20/55] fix : alerteventsaveservice update --- .../com/aims/backend/service/alert/AlertEventSaveService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java b/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java index 687966c..a4d1a55 100644 --- a/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java +++ b/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java @@ -244,7 +244,7 @@ private AlertEvent toAlertEvent(CalculatedAlert calculatedAlert) { .severity(calculatedAlert.severity()) .title(truncate(calculatedAlert.title(), 100)) .contents(truncate(calculatedAlert.contents(), 500)) - .imageUrl(truncate(calculatedAlert.imageUrl(), 500)) + .imageUrl(calculatedAlert.imageUrl()) .actionStatus(calculatedAlert.actionStatus()) .scoreCalculatedAt(calculatedAlert.scoreCalculatedAt()) .build(); From 385b59603e81e4cb34fdc1686312e755896b6b30 Mon Sep 17 00:00:00 2001 From: mmije0ng Date: Tue, 14 Jul 2026 09:42:20 +0900 Subject: [PATCH 21/55] =?UTF-8?q?fix:=20=ED=94=84=EB=A0=88=EC=8A=A4&?= =?UTF-8?q?=EC=B0=A8=EC=B2=B4=20=EC=9D=B4=EC=83=81=ED=83=90=EC=A7=80=20rea?= =?UTF-8?q?sons=20=EB=B0=98=ED=99=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- META-INF/MANIFEST.MF | 3 + .../io.jsonwebtoken/jjwt-api/pom.properties | 3 + .../maven/io.jsonwebtoken/jjwt-api/pom.xml | 53 + io/jsonwebtoken/ClaimJwtException.java | 98 ++ io/jsonwebtoken/Claims.java | 166 ++ io/jsonwebtoken/ClaimsBuilder.java | 29 + io/jsonwebtoken/ClaimsMutator.java | 270 ++++ io/jsonwebtoken/Clock.java | 33 + io/jsonwebtoken/CompressionCodec.java | 70 + io/jsonwebtoken/CompressionCodecResolver.java | 50 + io/jsonwebtoken/CompressionCodecs.java | 56 + io/jsonwebtoken/CompressionException.java | 46 + io/jsonwebtoken/ExpiredJwtException.java | 48 + io/jsonwebtoken/Header.java | 166 ++ io/jsonwebtoken/HeaderMutator.java | 131 ++ io/jsonwebtoken/Identifiable.java | 92 ++ io/jsonwebtoken/IncorrectClaimException.java | 52 + io/jsonwebtoken/InvalidClaimException.java | 86 ++ io/jsonwebtoken/Jwe.java | 65 + io/jsonwebtoken/JweHeader.java | 170 ++ io/jsonwebtoken/JweHeaderMutator.java | 116 ++ io/jsonwebtoken/Jws.java | 66 + io/jsonwebtoken/JwsHeader.java | 107 ++ io/jsonwebtoken/Jwt.java | 96 ++ io/jsonwebtoken/JwtBuilder.java | 1056 +++++++++++++ io/jsonwebtoken/JwtException.java | 43 + io/jsonwebtoken/JwtHandler.java | 102 ++ io/jsonwebtoken/JwtHandlerAdapter.java | 98 ++ io/jsonwebtoken/JwtParser.java | 422 +++++ io/jsonwebtoken/JwtParserBuilder.java | 826 ++++++++++ io/jsonwebtoken/JwtVisitor.java | 68 + io/jsonwebtoken/Jwts.java | 1077 +++++++++++++ io/jsonwebtoken/Locator.java | 41 + io/jsonwebtoken/LocatorAdapter.java | 112 ++ io/jsonwebtoken/MalformedJwtException.java | 43 + io/jsonwebtoken/MissingClaimException.java | 55 + io/jsonwebtoken/PrematureJwtException.java | 50 + io/jsonwebtoken/ProtectedHeader.java | 85 + io/jsonwebtoken/ProtectedHeaderMutator.java | 117 ++ io/jsonwebtoken/ProtectedJwt.java | 37 + io/jsonwebtoken/RequiredTypeException.java | 44 + io/jsonwebtoken/SignatureAlgorithm.java | 656 ++++++++ io/jsonwebtoken/SignatureException.java | 47 + io/jsonwebtoken/SigningKeyResolver.java | 75 + .../SigningKeyResolverAdapter.java | 123 ++ io/jsonwebtoken/SupportedJwtVisitor.java | 200 +++ io/jsonwebtoken/UnsupportedJwtException.java | 47 + io/jsonwebtoken/io/AbstractDeserializer.java | 84 + io/jsonwebtoken/io/AbstractSerializer.java | 75 + io/jsonwebtoken/io/Base64.java | 681 ++++++++ io/jsonwebtoken/io/Base64Decoder.java | 41 + io/jsonwebtoken/io/Base64Encoder.java | 41 + io/jsonwebtoken/io/Base64Support.java | 33 + io/jsonwebtoken/io/Base64UrlDecoder.java | 29 + io/jsonwebtoken/io/Base64UrlEncoder.java | 29 + io/jsonwebtoken/io/CodecException.java | 43 + io/jsonwebtoken/io/CompressionAlgorithm.java | 65 + io/jsonwebtoken/io/Decoder.java | 35 + io/jsonwebtoken/io/Decoders.java | 41 + io/jsonwebtoken/io/DecodingException.java | 43 + .../io/DeserializationException.java | 43 + io/jsonwebtoken/io/Deserializer.java | 48 + io/jsonwebtoken/io/Encoder.java | 35 + io/jsonwebtoken/io/Encoders.java | 41 + io/jsonwebtoken/io/EncodingException.java | 34 + .../io/ExceptionPropagatingDecoder.java | 60 + .../io/ExceptionPropagatingEncoder.java | 60 + io/jsonwebtoken/io/IOException.java | 46 + io/jsonwebtoken/io/Parser.java | 68 + io/jsonwebtoken/io/ParserBuilder.java | 54 + io/jsonwebtoken/io/SerialException.java | 43 + .../io/SerializationException.java | 43 + io/jsonwebtoken/io/Serializer.java | 51 + io/jsonwebtoken/lang/Arrays.java | 119 ++ io/jsonwebtoken/lang/Assert.java | 558 +++++++ io/jsonwebtoken/lang/Builder.java | 32 + io/jsonwebtoken/lang/Classes.java | 416 +++++ io/jsonwebtoken/lang/CollectionMutator.java | 61 + io/jsonwebtoken/lang/Collections.java | 576 +++++++ io/jsonwebtoken/lang/Conjunctor.java | 33 + io/jsonwebtoken/lang/DateFormats.java | 98 ++ .../lang/InstantiationException.java | 34 + io/jsonwebtoken/lang/MapMutator.java | 75 + io/jsonwebtoken/lang/Maps.java | 94 ++ io/jsonwebtoken/lang/NestedCollection.java | 32 + io/jsonwebtoken/lang/Objects.java | 1031 +++++++++++++ io/jsonwebtoken/lang/Registry.java | 51 + io/jsonwebtoken/lang/RuntimeEnvironment.java | 86 ++ io/jsonwebtoken/lang/Strings.java | 1371 +++++++++++++++++ io/jsonwebtoken/lang/Supplier.java | 37 + .../lang/UnknownClassException.java | 64 + io/jsonwebtoken/security/AeadAlgorithm.java | 91 ++ io/jsonwebtoken/security/AeadRequest.java | 30 + io/jsonwebtoken/security/AeadResult.java | 53 + .../security/AssociatedDataSupplier.java | 39 + io/jsonwebtoken/security/AsymmetricJwk.java | 75 + .../security/AsymmetricJwkBuilder.java | 81 + io/jsonwebtoken/security/Curve.java | 41 + .../security/DecryptAeadRequest.java | 28 + .../security/DecryptionKeyRequest.java | 42 + io/jsonwebtoken/security/DigestAlgorithm.java | 101 ++ io/jsonwebtoken/security/DigestSupplier.java | 35 + .../security/DynamicJwkBuilder.java | 388 +++++ io/jsonwebtoken/security/EcPrivateJwk.java | 43 + .../security/EcPrivateJwkBuilder.java | 27 + io/jsonwebtoken/security/EcPublicJwk.java | 42 + .../security/EcPublicJwkBuilder.java | 27 + io/jsonwebtoken/security/HashAlgorithm.java | 45 + .../security/InvalidKeyException.java | 45 + io/jsonwebtoken/security/IvSupplier.java | 36 + io/jsonwebtoken/security/Jwk.java | 177 +++ io/jsonwebtoken/security/JwkBuilder.java | 138 ++ .../security/JwkParserBuilder.java | 35 + io/jsonwebtoken/security/JwkSet.java | 47 + io/jsonwebtoken/security/JwkSetBuilder.java | 66 + .../security/JwkSetParserBuilder.java | 57 + io/jsonwebtoken/security/JwkThumbprint.java | 54 + io/jsonwebtoken/security/Jwks.java | 482 ++++++ io/jsonwebtoken/security/KeyAlgorithm.java | 84 + io/jsonwebtoken/security/KeyBuilder.java | 34 + .../security/KeyBuilderSupplier.java | 40 + io/jsonwebtoken/security/KeyException.java | 44 + .../security/KeyLengthSupplier.java | 31 + io/jsonwebtoken/security/KeyOperation.java | 55 + .../security/KeyOperationBuilder.java | 73 + .../security/KeyOperationPolicied.java | 51 + .../security/KeyOperationPolicy.java | 43 + .../security/KeyOperationPolicyBuilder.java | 114 ++ io/jsonwebtoken/security/KeyPair.java | 51 + io/jsonwebtoken/security/KeyPairBuilder.java | 31 + .../security/KeyPairBuilderSupplier.java | 38 + io/jsonwebtoken/security/KeyRequest.java | 77 + io/jsonwebtoken/security/KeyResult.java | 34 + io/jsonwebtoken/security/KeySupplier.java | 34 + io/jsonwebtoken/security/Keys.java | 332 ++++ io/jsonwebtoken/security/MacAlgorithm.java | 65 + .../security/MalformedKeyException.java | 44 + .../security/MalformedKeySetException.java | 44 + io/jsonwebtoken/security/Message.java | 36 + io/jsonwebtoken/security/OctetPrivateJwk.java | 68 + .../security/OctetPrivateJwkBuilder.java | 30 + io/jsonwebtoken/security/OctetPublicJwk.java | 63 + .../security/OctetPublicJwkBuilder.java | 31 + io/jsonwebtoken/security/Password.java | 63 + io/jsonwebtoken/security/PrivateJwk.java | 61 + .../security/PrivateJwkBuilder.java | 53 + .../security/PrivateKeyBuilder.java | 38 + io/jsonwebtoken/security/PublicJwk.java | 27 + .../security/PublicJwkBuilder.java | 47 + io/jsonwebtoken/security/Request.java | 57 + io/jsonwebtoken/security/RsaPrivateJwk.java | 43 + .../security/RsaPrivateJwkBuilder.java | 27 + io/jsonwebtoken/security/RsaPublicJwk.java | 42 + .../security/RsaPublicJwkBuilder.java | 28 + io/jsonwebtoken/security/SecretJwk.java | 33 + .../security/SecretJwkBuilder.java | 26 + .../security/SecretKeyAlgorithm.java | 26 + .../security/SecretKeyBuilder.java | 27 + .../security/SecureDigestAlgorithm.java | 55 + io/jsonwebtoken/security/SecureRequest.java | 28 + io/jsonwebtoken/security/SecurityBuilder.java | 52 + .../security/SecurityException.java | 46 + .../security/SignatureAlgorithm.java | 55 + .../security/SignatureException.java | 44 + .../security/UnsupportedKeyException.java | 43 + .../security/VerifyDigestRequest.java | 33 + .../security/VerifySecureDigestRequest.java | 34 + .../security/WeakKeyException.java | 34 + io/jsonwebtoken/security/X509Accessor.java | 138 ++ io/jsonwebtoken/security/X509Builder.java | 56 + io/jsonwebtoken/security/X509Mutator.java | 141 ++ 171 files changed, 19359 insertions(+) create mode 100644 META-INF/MANIFEST.MF create mode 100644 META-INF/maven/io.jsonwebtoken/jjwt-api/pom.properties create mode 100644 META-INF/maven/io.jsonwebtoken/jjwt-api/pom.xml create mode 100644 io/jsonwebtoken/ClaimJwtException.java create mode 100644 io/jsonwebtoken/Claims.java create mode 100644 io/jsonwebtoken/ClaimsBuilder.java create mode 100644 io/jsonwebtoken/ClaimsMutator.java create mode 100644 io/jsonwebtoken/Clock.java create mode 100644 io/jsonwebtoken/CompressionCodec.java create mode 100644 io/jsonwebtoken/CompressionCodecResolver.java create mode 100644 io/jsonwebtoken/CompressionCodecs.java create mode 100644 io/jsonwebtoken/CompressionException.java create mode 100644 io/jsonwebtoken/ExpiredJwtException.java create mode 100644 io/jsonwebtoken/Header.java create mode 100644 io/jsonwebtoken/HeaderMutator.java create mode 100644 io/jsonwebtoken/Identifiable.java create mode 100644 io/jsonwebtoken/IncorrectClaimException.java create mode 100644 io/jsonwebtoken/InvalidClaimException.java create mode 100644 io/jsonwebtoken/Jwe.java create mode 100644 io/jsonwebtoken/JweHeader.java create mode 100644 io/jsonwebtoken/JweHeaderMutator.java create mode 100644 io/jsonwebtoken/Jws.java create mode 100644 io/jsonwebtoken/JwsHeader.java create mode 100644 io/jsonwebtoken/Jwt.java create mode 100644 io/jsonwebtoken/JwtBuilder.java create mode 100644 io/jsonwebtoken/JwtException.java create mode 100644 io/jsonwebtoken/JwtHandler.java create mode 100644 io/jsonwebtoken/JwtHandlerAdapter.java create mode 100644 io/jsonwebtoken/JwtParser.java create mode 100644 io/jsonwebtoken/JwtParserBuilder.java create mode 100644 io/jsonwebtoken/JwtVisitor.java create mode 100644 io/jsonwebtoken/Jwts.java create mode 100644 io/jsonwebtoken/Locator.java create mode 100644 io/jsonwebtoken/LocatorAdapter.java create mode 100644 io/jsonwebtoken/MalformedJwtException.java create mode 100644 io/jsonwebtoken/MissingClaimException.java create mode 100644 io/jsonwebtoken/PrematureJwtException.java create mode 100644 io/jsonwebtoken/ProtectedHeader.java create mode 100644 io/jsonwebtoken/ProtectedHeaderMutator.java create mode 100644 io/jsonwebtoken/ProtectedJwt.java create mode 100644 io/jsonwebtoken/RequiredTypeException.java create mode 100644 io/jsonwebtoken/SignatureAlgorithm.java create mode 100644 io/jsonwebtoken/SignatureException.java create mode 100644 io/jsonwebtoken/SigningKeyResolver.java create mode 100644 io/jsonwebtoken/SigningKeyResolverAdapter.java create mode 100644 io/jsonwebtoken/SupportedJwtVisitor.java create mode 100644 io/jsonwebtoken/UnsupportedJwtException.java create mode 100644 io/jsonwebtoken/io/AbstractDeserializer.java create mode 100644 io/jsonwebtoken/io/AbstractSerializer.java create mode 100644 io/jsonwebtoken/io/Base64.java create mode 100644 io/jsonwebtoken/io/Base64Decoder.java create mode 100644 io/jsonwebtoken/io/Base64Encoder.java create mode 100644 io/jsonwebtoken/io/Base64Support.java create mode 100644 io/jsonwebtoken/io/Base64UrlDecoder.java create mode 100644 io/jsonwebtoken/io/Base64UrlEncoder.java create mode 100644 io/jsonwebtoken/io/CodecException.java create mode 100644 io/jsonwebtoken/io/CompressionAlgorithm.java create mode 100644 io/jsonwebtoken/io/Decoder.java create mode 100644 io/jsonwebtoken/io/Decoders.java create mode 100644 io/jsonwebtoken/io/DecodingException.java create mode 100644 io/jsonwebtoken/io/DeserializationException.java create mode 100644 io/jsonwebtoken/io/Deserializer.java create mode 100644 io/jsonwebtoken/io/Encoder.java create mode 100644 io/jsonwebtoken/io/Encoders.java create mode 100644 io/jsonwebtoken/io/EncodingException.java create mode 100644 io/jsonwebtoken/io/ExceptionPropagatingDecoder.java create mode 100644 io/jsonwebtoken/io/ExceptionPropagatingEncoder.java create mode 100644 io/jsonwebtoken/io/IOException.java create mode 100644 io/jsonwebtoken/io/Parser.java create mode 100644 io/jsonwebtoken/io/ParserBuilder.java create mode 100644 io/jsonwebtoken/io/SerialException.java create mode 100644 io/jsonwebtoken/io/SerializationException.java create mode 100644 io/jsonwebtoken/io/Serializer.java create mode 100644 io/jsonwebtoken/lang/Arrays.java create mode 100644 io/jsonwebtoken/lang/Assert.java create mode 100644 io/jsonwebtoken/lang/Builder.java create mode 100644 io/jsonwebtoken/lang/Classes.java create mode 100644 io/jsonwebtoken/lang/CollectionMutator.java create mode 100644 io/jsonwebtoken/lang/Collections.java create mode 100644 io/jsonwebtoken/lang/Conjunctor.java create mode 100644 io/jsonwebtoken/lang/DateFormats.java create mode 100644 io/jsonwebtoken/lang/InstantiationException.java create mode 100644 io/jsonwebtoken/lang/MapMutator.java create mode 100644 io/jsonwebtoken/lang/Maps.java create mode 100644 io/jsonwebtoken/lang/NestedCollection.java create mode 100644 io/jsonwebtoken/lang/Objects.java create mode 100644 io/jsonwebtoken/lang/Registry.java create mode 100644 io/jsonwebtoken/lang/RuntimeEnvironment.java create mode 100644 io/jsonwebtoken/lang/Strings.java create mode 100644 io/jsonwebtoken/lang/Supplier.java create mode 100644 io/jsonwebtoken/lang/UnknownClassException.java create mode 100644 io/jsonwebtoken/security/AeadAlgorithm.java create mode 100644 io/jsonwebtoken/security/AeadRequest.java create mode 100644 io/jsonwebtoken/security/AeadResult.java create mode 100644 io/jsonwebtoken/security/AssociatedDataSupplier.java create mode 100644 io/jsonwebtoken/security/AsymmetricJwk.java create mode 100644 io/jsonwebtoken/security/AsymmetricJwkBuilder.java create mode 100644 io/jsonwebtoken/security/Curve.java create mode 100644 io/jsonwebtoken/security/DecryptAeadRequest.java create mode 100644 io/jsonwebtoken/security/DecryptionKeyRequest.java create mode 100644 io/jsonwebtoken/security/DigestAlgorithm.java create mode 100644 io/jsonwebtoken/security/DigestSupplier.java create mode 100644 io/jsonwebtoken/security/DynamicJwkBuilder.java create mode 100644 io/jsonwebtoken/security/EcPrivateJwk.java create mode 100644 io/jsonwebtoken/security/EcPrivateJwkBuilder.java create mode 100644 io/jsonwebtoken/security/EcPublicJwk.java create mode 100644 io/jsonwebtoken/security/EcPublicJwkBuilder.java create mode 100644 io/jsonwebtoken/security/HashAlgorithm.java create mode 100644 io/jsonwebtoken/security/InvalidKeyException.java create mode 100644 io/jsonwebtoken/security/IvSupplier.java create mode 100644 io/jsonwebtoken/security/Jwk.java create mode 100644 io/jsonwebtoken/security/JwkBuilder.java create mode 100644 io/jsonwebtoken/security/JwkParserBuilder.java create mode 100644 io/jsonwebtoken/security/JwkSet.java create mode 100644 io/jsonwebtoken/security/JwkSetBuilder.java create mode 100644 io/jsonwebtoken/security/JwkSetParserBuilder.java create mode 100644 io/jsonwebtoken/security/JwkThumbprint.java create mode 100644 io/jsonwebtoken/security/Jwks.java create mode 100644 io/jsonwebtoken/security/KeyAlgorithm.java create mode 100644 io/jsonwebtoken/security/KeyBuilder.java create mode 100644 io/jsonwebtoken/security/KeyBuilderSupplier.java create mode 100644 io/jsonwebtoken/security/KeyException.java create mode 100644 io/jsonwebtoken/security/KeyLengthSupplier.java create mode 100644 io/jsonwebtoken/security/KeyOperation.java create mode 100644 io/jsonwebtoken/security/KeyOperationBuilder.java create mode 100644 io/jsonwebtoken/security/KeyOperationPolicied.java create mode 100644 io/jsonwebtoken/security/KeyOperationPolicy.java create mode 100644 io/jsonwebtoken/security/KeyOperationPolicyBuilder.java create mode 100644 io/jsonwebtoken/security/KeyPair.java create mode 100644 io/jsonwebtoken/security/KeyPairBuilder.java create mode 100644 io/jsonwebtoken/security/KeyPairBuilderSupplier.java create mode 100644 io/jsonwebtoken/security/KeyRequest.java create mode 100644 io/jsonwebtoken/security/KeyResult.java create mode 100644 io/jsonwebtoken/security/KeySupplier.java create mode 100644 io/jsonwebtoken/security/Keys.java create mode 100644 io/jsonwebtoken/security/MacAlgorithm.java create mode 100644 io/jsonwebtoken/security/MalformedKeyException.java create mode 100644 io/jsonwebtoken/security/MalformedKeySetException.java create mode 100644 io/jsonwebtoken/security/Message.java create mode 100644 io/jsonwebtoken/security/OctetPrivateJwk.java create mode 100644 io/jsonwebtoken/security/OctetPrivateJwkBuilder.java create mode 100644 io/jsonwebtoken/security/OctetPublicJwk.java create mode 100644 io/jsonwebtoken/security/OctetPublicJwkBuilder.java create mode 100644 io/jsonwebtoken/security/Password.java create mode 100644 io/jsonwebtoken/security/PrivateJwk.java create mode 100644 io/jsonwebtoken/security/PrivateJwkBuilder.java create mode 100644 io/jsonwebtoken/security/PrivateKeyBuilder.java create mode 100644 io/jsonwebtoken/security/PublicJwk.java create mode 100644 io/jsonwebtoken/security/PublicJwkBuilder.java create mode 100644 io/jsonwebtoken/security/Request.java create mode 100644 io/jsonwebtoken/security/RsaPrivateJwk.java create mode 100644 io/jsonwebtoken/security/RsaPrivateJwkBuilder.java create mode 100644 io/jsonwebtoken/security/RsaPublicJwk.java create mode 100644 io/jsonwebtoken/security/RsaPublicJwkBuilder.java create mode 100644 io/jsonwebtoken/security/SecretJwk.java create mode 100644 io/jsonwebtoken/security/SecretJwkBuilder.java create mode 100644 io/jsonwebtoken/security/SecretKeyAlgorithm.java create mode 100644 io/jsonwebtoken/security/SecretKeyBuilder.java create mode 100644 io/jsonwebtoken/security/SecureDigestAlgorithm.java create mode 100644 io/jsonwebtoken/security/SecureRequest.java create mode 100644 io/jsonwebtoken/security/SecurityBuilder.java create mode 100644 io/jsonwebtoken/security/SecurityException.java create mode 100644 io/jsonwebtoken/security/SignatureAlgorithm.java create mode 100644 io/jsonwebtoken/security/SignatureException.java create mode 100644 io/jsonwebtoken/security/UnsupportedKeyException.java create mode 100644 io/jsonwebtoken/security/VerifyDigestRequest.java create mode 100644 io/jsonwebtoken/security/VerifySecureDigestRequest.java create mode 100644 io/jsonwebtoken/security/WeakKeyException.java create mode 100644 io/jsonwebtoken/security/X509Accessor.java create mode 100644 io/jsonwebtoken/security/X509Builder.java create mode 100644 io/jsonwebtoken/security/X509Mutator.java diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000..18bd855 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Created-By: Maven Source Plugin 3.2.1 + diff --git a/META-INF/maven/io.jsonwebtoken/jjwt-api/pom.properties b/META-INF/maven/io.jsonwebtoken/jjwt-api/pom.properties new file mode 100644 index 0000000..0756d00 --- /dev/null +++ b/META-INF/maven/io.jsonwebtoken/jjwt-api/pom.properties @@ -0,0 +1,3 @@ +artifactId=jjwt-api +groupId=io.jsonwebtoken +version=0.12.6 diff --git a/META-INF/maven/io.jsonwebtoken/jjwt-api/pom.xml b/META-INF/maven/io.jsonwebtoken/jjwt-api/pom.xml new file mode 100644 index 0000000..9b5ea1e --- /dev/null +++ b/META-INF/maven/io.jsonwebtoken/jjwt-api/pom.xml @@ -0,0 +1,53 @@ + + + + + 4.0.0 + + + io.jsonwebtoken + jjwt-root + 0.12.6 + ../pom.xml + + + jjwt-api + JJWT :: API + jar + + + ${basedir}/.. + + + + + + com.github.siom79.japicmp + japicmp-maven-plugin + + + japicmp + + cmp + + + + + + + + \ No newline at end of file diff --git a/io/jsonwebtoken/ClaimJwtException.java b/io/jsonwebtoken/ClaimJwtException.java new file mode 100644 index 0000000..756b367 --- /dev/null +++ b/io/jsonwebtoken/ClaimJwtException.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * ClaimJwtException is a subclass of the {@link JwtException} that is thrown after a validation of an JWT claim failed. + * + * @since 0.5 + */ +public abstract class ClaimJwtException extends JwtException { + + /** + * Deprecated as this is an implementation detail accidentally exposed in the JJWT 0.5 public API. It is no + * longer referenced anywhere in JJWT's implementation and will be removed in a future release. + * + * @deprecated will be removed in a future release. + */ + @Deprecated + public static final String INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was: %s."; + + /** + * Deprecated as this is an implementation detail accidentally exposed in the JJWT 0.5 public API. It is no + * longer referenced anywhere in JJWT's implementation and will be removed in a future release. + * + * @deprecated will be removed in a future release. + */ + @Deprecated + public static final String MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was not present in the JWT claims."; + + /** + * The header associated with the Claims that failed validation. + */ + private final Header header; + + /** + * The Claims that failed validation. + */ + private final Claims claims; + + /** + * Creates a new instance with the specified header, claims and exception message. + * + * @param header the header inspected + * @param claims the claims obtained + * @param message the exception message + */ + protected ClaimJwtException(Header header, Claims claims, String message) { + super(message); + this.header = header; + this.claims = claims; + } + + /** + * Creates a new instance with the specified header, claims and exception message as a result of encountering + * the specified {@code cause}. + * + * @param header the header inspected + * @param claims the claims obtained + * @param message the exception message + * @param cause the exception that caused this ClaimJwtException to be thrown. + */ + protected ClaimJwtException(Header header, Claims claims, String message, Throwable cause) { + super(message, cause); + this.header = header; + this.claims = claims; + } + + /** + * Returns the {@link Claims} that failed validation. + * + * @return the {@link Claims} that failed validation. + */ + public Claims getClaims() { + return claims; + } + + /** + * Returns the header associated with the {@link #getClaims() claims} that failed validation. + * + * @return the header associated with the {@link #getClaims() claims} that failed validation. + */ + public Header getHeader() { + return header; + } +} diff --git a/io/jsonwebtoken/Claims.java b/io/jsonwebtoken/Claims.java new file mode 100644 index 0000000..4f8589f --- /dev/null +++ b/io/jsonwebtoken/Claims.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import java.util.Date; +import java.util.Map; +import java.util.Set; + +/** + * A JWT Claims set. + * + *

This is an immutable JSON map with convenient type-safe getters for JWT standard claim names.

+ * + *

Additionally, this interface also extends Map<String, Object>, so you can use standard + * {@code Map} accessor/iterator methods as desired, for example:

+ * + *
+ * claims.get("someKey");
+ * + *

However, because {@code Claims} instances are immutable, calling any of the map mutation methods + * (such as {@code Map.}{@link Map#put(Object, Object) put}, etc) will result in a runtime exception. The + * {@code Map} interface is implemented specifically for the convenience of working with existing Map-based utilities + * and APIs.

+ * + * @since 0.1 + */ +public interface Claims extends Map, Identifiable { + + /** + * JWT {@code Issuer} claims parameter name: "iss" + */ + String ISSUER = "iss"; + + /** + * JWT {@code Subject} claims parameter name: "sub" + */ + String SUBJECT = "sub"; + + /** + * JWT {@code Audience} claims parameter name: "aud" + */ + String AUDIENCE = "aud"; + + /** + * JWT {@code Expiration} claims parameter name: "exp" + */ + String EXPIRATION = "exp"; + + /** + * JWT {@code Not Before} claims parameter name: "nbf" + */ + String NOT_BEFORE = "nbf"; + + /** + * JWT {@code Issued At} claims parameter name: "iat" + */ + String ISSUED_AT = "iat"; + + /** + * JWT {@code JWT ID} claims parameter name: "jti" + */ + String ID = "jti"; + + /** + * Returns the JWT + * iss (issuer) value or {@code null} if not present. + * + * @return the JWT {@code iss} value or {@code null} if not present. + */ + String getIssuer(); + + /** + * Returns the JWT + * sub (subject) value or {@code null} if not present. + * + * @return the JWT {@code sub} value or {@code null} if not present. + */ + String getSubject(); + + /** + * Returns the JWT + * aud (audience) value or {@code null} if not present. + * + * @return the JWT {@code aud} value or {@code null} if not present. + */ + Set getAudience(); + + /** + * Returns the JWT + * exp (expiration) timestamp or {@code null} if not present. + * + *

A JWT obtained after this timestamp should not be used.

+ * + * @return the JWT {@code exp} value or {@code null} if not present. + */ + Date getExpiration(); + + /** + * Returns the JWT + * nbf (not before) timestamp or {@code null} if not present. + * + *

A JWT obtained before this timestamp should not be used.

+ * + * @return the JWT {@code nbf} value or {@code null} if not present. + */ + Date getNotBefore(); + + /** + * Returns the JWT + * iat (issued at) timestamp or {@code null} if not present. + * + *

If present, this value is the timestamp when the JWT was created.

+ * + * @return the JWT {@code iat} value or {@code null} if not present. + */ + Date getIssuedAt(); + + /** + * Returns the JWTs + * jti (JWT ID) value or {@code null} if not present. + * + *

This value is a CaSe-SenSiTiVe unique identifier for the JWT. If available, this value is expected to be + * assigned in a manner that ensures that there is a negligible probability that the same value will be + * accidentally + * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

+ * + * @return the JWT {@code jti} value or {@code null} if not present. + */ + @Override + // just for JavaDoc specific to the JWT spec + String getId(); + + /** + * Returns the JWTs claim ({@code claimName}) value as a {@code requiredType} instance, or {@code null} if not + * present. + * + *

JJWT only converts simple String, Date, Long, Integer, Short and Byte types automatically. Anything more + * complex is expected to be already converted to your desired type by the JSON parser. You may specify a custom + * JSON processor using the {@code JwtParserBuilder}'s + * {@link JwtParserBuilder#json(io.jsonwebtoken.io.Deserializer) json(Deserializer)} method. See the JJWT + * documentation on custom JSON processors for more + * information. If using Jackson, you can specify custom claim POJO types as described in + * custom claim types. + * + * @param claimName name of claim + * @param requiredType the type of the value expected to be returned + * @param the type of the value expected to be returned + * @return the JWT {@code claimName} value or {@code null} if not present. + * @throws RequiredTypeException throw if the claim value is not null and not of type {@code requiredType} + * @see JJWT JSON Support + */ + T get(String claimName, Class requiredType); +} diff --git a/io/jsonwebtoken/ClaimsBuilder.java b/io/jsonwebtoken/ClaimsBuilder.java new file mode 100644 index 0000000..eabd5b5 --- /dev/null +++ b/io/jsonwebtoken/ClaimsBuilder.java @@ -0,0 +1,29 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.lang.Builder; +import io.jsonwebtoken.lang.MapMutator; + +/** + * {@link Builder} used to create an immutable {@link Claims} instance. + * + * @see JwtBuilder + * @see Claims + * @since 0.12.0 + */ +public interface ClaimsBuilder extends MapMutator, ClaimsMutator, Builder { +} diff --git a/io/jsonwebtoken/ClaimsMutator.java b/io/jsonwebtoken/ClaimsMutator.java new file mode 100644 index 0000000..1fdca1e --- /dev/null +++ b/io/jsonwebtoken/ClaimsMutator.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.lang.NestedCollection; + +import java.util.Collection; +import java.util.Date; + +/** + * Mutation (modifications) to a {@link io.jsonwebtoken.Claims Claims} instance. + * + * @param the type of mutator + * @see io.jsonwebtoken.JwtBuilder + * @see io.jsonwebtoken.Claims + * @since 0.2 + */ +public interface ClaimsMutator> { + + /** + * Sets the JWT + * iss (issuer) claim. A {@code null} value will remove the property from the JSON Claims map. + * + * @param iss the JWT {@code iss} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named + * {@link #issuer(String)}. This method will be removed before the JJWT 1.0 release. + */ + @Deprecated + T setIssuer(String iss); + + /** + * Sets the JWT + * iss (issuer) claim. A {@code null} value will remove the property from the JSON Claims map. + * + * @param iss the JWT {@code iss} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @since 0.12.0 + */ + T issuer(String iss); + + /** + * Sets the JWT + * sub (subject) claim. A {@code null} value will remove the property from the JSON Claims map. + * + * @param sub the JWT {@code sub} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named + * {@link #subject(String)}. This method will be removed before the JJWT 1.0 release. + */ + @Deprecated + T setSubject(String sub); + + /** + * Sets the JWT + * sub (subject) claim. A {@code null} value will remove the property from the JSON Claims map. + * + * @param sub the JWT {@code sub} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @since 0.12.0 + */ + T subject(String sub); + + /** + * Sets the JWT aud (audience) + * claim as a single String, NOT a String array. This method exists only for producing + * JWTs sent to legacy recipients that are unable to interpret the {@code aud} value as a JSON String Array; it is + * strongly recommended to avoid calling this method whenever possible and favor the + * {@link #audience()}.{@link AudienceCollection#add(Object) add(String)} and + * {@link AudienceCollection#add(Collection) add(Collection)} methods instead, as they ensure a single + * deterministic data type for recipients. + * + * @param aud the JWT {@code aud} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @deprecated since 0.12.0 in favor of {@link #audience()}. This method will be removed before + * the JJWT 1.0 release. + */ + @Deprecated + T setAudience(String aud); + + /** + * Configures the JWT + * aud (audience) Claim + * set, quietly ignoring any null, empty, whitespace-only, or existing value already in the set. + * + *

When finished, the {@code audience} collection's {@link AudienceCollection#and() and()} method may be used + * to continue configuration. For example:

+ *
+     *  Jwts.builder() // or Jwts.claims()
+     *
+     *     .audience().add("anAudience").and() // return parent
+     *
+     *  .subject("Joe") // resume configuration...
+     *  // etc...
+     * 
+ * + * @return the {@link AudienceCollection AudienceCollection} to use for {@code aud} configuration. + * @see AudienceCollection AudienceCollection + * @see AudienceCollection#single(String) AudienceCollection.single(String) + * @since 0.12.0 + */ + AudienceCollection audience(); + + /** + * Sets the JWT + * exp (expiration) timestamp claim. A {@code null} value will remove the property from the + * JSON Claims map. + * + *

A JWT obtained after this timestamp should not be used.

+ * + * @param exp the JWT {@code exp} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named + * {@link #expiration(Date)}. This method will be removed before the JJWT 1.0 release. + */ + @Deprecated + T setExpiration(Date exp); + + /** + * Sets the JWT + * exp (expiration) timestamp claim. A {@code null} value will remove the property from the + * JSON Claims map. + * + *

A JWT obtained after this timestamp should not be used.

+ * + * @param exp the JWT {@code exp} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @since 0.12.0 + */ + T expiration(Date exp); + + /** + * Sets the JWT + * nbf (not before) timestamp claim. A {@code null} value will remove the property from the + * JSON Claims map. + * + *

A JWT obtained before this timestamp should not be used.

+ * + * @param nbf the JWT {@code nbf} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named + * {@link #notBefore(Date)}. This method will be removed before the JJWT 1.0 release. + */ + @Deprecated + T setNotBefore(Date nbf); + + /** + * Sets the JWT + * nbf (not before) timestamp claim. A {@code null} value will remove the property from the + * JSON Claims map. + * + *

A JWT obtained before this timestamp should not be used.

+ * + * @param nbf the JWT {@code nbf} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @since 0.12.0 + */ + T notBefore(Date nbf); + + /** + * Sets the JWT + * iat (issued at) timestamp claim. A {@code null} value will remove the property from the + * JSON Claims map. + * + *

The value is the timestamp when the JWT was created.

+ * + * @param iat the JWT {@code iat} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named + * {@link #issuedAt(Date)}. This method will be removed before the JJWT 1.0 release. + */ + @Deprecated + T setIssuedAt(Date iat); + + /** + * Sets the JWT + * iat (issued at) timestamp claim. A {@code null} value will remove the property from the + * JSON Claims map. + * + *

The value is the timestamp when the JWT was created.

+ * + * @param iat the JWT {@code iat} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @since 0.12.0 + */ + T issuedAt(Date iat); + + /** + * Sets the JWT + * jti (JWT ID) claim. A {@code null} value will remove the property from the JSON Claims map. + * + *

This value is a CaSe-SenSiTiVe unique identifier for the JWT. If specified, this value MUST be assigned in a + * manner that ensures that there is a negligible probability that the same value will be accidentally + * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

+ * + * @param jti the JWT {@code jti} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named + * {@link #id(String)}. This method will be removed before the JJWT 1.0 release. + */ + @Deprecated + T setId(String jti); + + /** + * Sets the JWT + * jti (JWT ID) claim. A {@code null} value will remove the property from the JSON Claims map. + * + *

This value is a CaSe-SenSiTiVe unique identifier for the JWT. If specified, this value MUST be assigned in a + * manner that ensures that there is a negligible probability that the same value will be accidentally + * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

+ * + * @param jti the JWT {@code jti} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @since 0.12.0 + */ + T id(String jti); + + /** + * A {@code NestedCollection} for setting {@link #audience()} values that also allows overriding the collection + * to be a {@link #single(String) single string value} for legacy JWT recipients if necessary. + * + *

Because this interface extends {@link NestedCollection}, the {@link #and()} method may be used to continue + * parent configuration. For example:

+ *
+     *  Jwts.builder() // or Jwts.claims()
+     *
+     *     .audience().add("anAudience").and() // return parent
+     *
+     *  .subject("Joe") // resume parent configuration...
+     *  // etc...
+ * + * @param

the type of ClaimsMutator to return for method chaining. + * @see #single(String) + * @since 0.12.0 + */ + interface AudienceCollection

extends NestedCollection { + + /** + * Sets the JWT aud (audience) + * Claim as a single String, NOT a String array. This method exists only for producing + * JWTs sent to legacy recipients that are unable to interpret the {@code aud} value as a JSON String Array; + * it is strongly recommended to avoid calling this method whenever possible and favor the + * {@link #add(Object) add(String)} or {@link #add(Collection)} methods instead, as they ensure a single + * deterministic data type for recipients. + * + * @param aud the value to use as the {@code aud} Claim single-String value (and not an array of Strings), or + * {@code null}, empty or whitespace to remove the property from the JSON map. + * @return the instance for method chaining + * @since 0.12.0 + * @deprecated This is technically not deprecated because the JWT RFC mandates support for single string values, + * but it is marked as deprecated to discourage its use when possible. + */ + // DO NOT REMOVE EVER. This is a required RFC feature, but marked as deprecated to discourage its use + @Deprecated + P single(String aud); + } +} diff --git a/io/jsonwebtoken/Clock.java b/io/jsonwebtoken/Clock.java new file mode 100644 index 0000000..584dd60 --- /dev/null +++ b/io/jsonwebtoken/Clock.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import java.util.Date; + +/** + * A clock represents a time source that can be used when creating and verifying JWTs. + * + * @since 0.7.0 + */ +public interface Clock { + + /** + * Returns the clock's current timestamp at the instant the method is invoked. + * + * @return the clock's current timestamp at the instant the method is invoked. + */ + Date now(); +} diff --git a/io/jsonwebtoken/CompressionCodec.java b/io/jsonwebtoken/CompressionCodec.java new file mode 100644 index 0000000..b3b9228 --- /dev/null +++ b/io/jsonwebtoken/CompressionCodec.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2015 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.io.CompressionAlgorithm; + +/** + * Compresses and decompresses byte arrays according to a compression algorithm. + * + *

"zip" identifier

+ * + *

{@code CompressionCodec} extends {@code Identifiable}; the value returned from + * {@link Identifiable#getId() getId()} will be used as the JWT + * zip header value.

+ * + * @see Jwts.ZIP#DEF + * @see Jwts.ZIP#GZIP + * @since 0.6.0 + * @deprecated since 0.12.0 in favor of {@link io.jsonwebtoken.io.CompressionAlgorithm} to equal the RFC name for this concept. + */ +@Deprecated +public interface CompressionCodec extends CompressionAlgorithm { + + /** + * The algorithm name to use as the JWT + * zip header value. + * + * @return the algorithm name to use as the JWT + * zip header value. + * @deprecated since 0.12.0 in favor of {@link #getId()} to ensure congruence with + * all other identifiable algorithms. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + String getAlgorithmName(); + + /** + * Compresses the specified byte array, returning the compressed byte array result. + * + * @param content bytes to compress + * @return compressed bytes + * @throws CompressionException if the specified byte array cannot be compressed. + */ + @Deprecated + byte[] compress(byte[] content) throws CompressionException; + + /** + * Decompresses the specified compressed byte array, returning the decompressed byte array result. The + * specified byte array must already be in compressed form. + * + * @param compressed compressed bytes + * @return decompressed bytes + * @throws CompressionException if the specified byte array cannot be decompressed. + */ + @Deprecated + byte[] decompress(byte[] compressed) throws CompressionException; +} diff --git a/io/jsonwebtoken/CompressionCodecResolver.java b/io/jsonwebtoken/CompressionCodecResolver.java new file mode 100644 index 0000000..58df740 --- /dev/null +++ b/io/jsonwebtoken/CompressionCodecResolver.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * Looks for a JWT {@code zip} header, and if found, returns the corresponding {@link CompressionCodec} the parser + * can use to decompress the JWT body. + * + *

JJWT's default {@link JwtParser} implementation supports both the + * {@link Jwts.ZIP#DEF DEFLATE} and {@link Jwts.ZIP#GZIP GZIP} algorithms by default - you do not need to + * specify a {@code CompressionCodecResolver} in these cases.

+ * + *

However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you can implement + * your own {@link CompressionCodecResolver} and specify that when + * {@link io.jsonwebtoken.JwtBuilder#compressWith(io.jsonwebtoken.io.CompressionAlgorithm) building} and + * {@link io.jsonwebtoken.JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.

+ * + * @see JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver) + * @see JwtParserBuilder#zip() + * @since 0.6.0 + * @deprecated in favor of {@link JwtParserBuilder#zip()} + */ +@SuppressWarnings("DeprecatedIsStillUsed") +@Deprecated +public interface CompressionCodecResolver { + + /** + * Looks for a JWT {@code zip} header, and if found, returns the corresponding {@link CompressionCodec} the parser + * can use to decompress the JWT body. + * + * @param header of the JWT + * @return CompressionCodec matching the {@code zip} header, or null if there is no {@code zip} header. + * @throws CompressionException if a {@code zip} header value is found and not supported. + */ + CompressionCodec resolveCompressionCodec(Header header) throws CompressionException; + +} diff --git a/io/jsonwebtoken/CompressionCodecs.java b/io/jsonwebtoken/CompressionCodecs.java new file mode 100644 index 0000000..b1797a5 --- /dev/null +++ b/io/jsonwebtoken/CompressionCodecs.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * Provides default implementations of the {@link CompressionCodec} interface. + * + * @see Jwts.ZIP#DEF + * @see Jwts.ZIP#GZIP + * @since 0.7.0 + * @deprecated in favor of {@link Jwts.ZIP}. + */ +@Deprecated //TODO: delete for 1.0 +public final class CompressionCodecs { + + private CompressionCodecs() { + } //prevent external instantiation + + /** + * Codec implementing the JWA standard + * deflate compression algorithm + * + * @deprecated in favor of {@link Jwts.ZIP#DEF}. + */ + @Deprecated + public static final CompressionCodec DEFLATE = (CompressionCodec) Jwts.ZIP.DEF; + + /** + * Codec implementing the gzip compression algorithm. + * + *

Compatibility Warning

+ * + *

This is not a standard JWA compression algorithm. Be sure to use this only when you are confident + * that all parties accessing the token support the gzip algorithm.

+ * + *

If you're concerned about compatibility, the {@link Jwts.ZIP#DEF DEF} code is JWA standards-compliant.

+ * + * @deprecated in favor of {@link Jwts.ZIP#GZIP} + */ + @Deprecated + public static final CompressionCodec GZIP = (CompressionCodec) Jwts.ZIP.GZIP; + +} diff --git a/io/jsonwebtoken/CompressionException.java b/io/jsonwebtoken/CompressionException.java new file mode 100644 index 0000000..fd2c045 --- /dev/null +++ b/io/jsonwebtoken/CompressionException.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.io.IOException; + +/** + * Exception indicating that either compressing or decompressing a JWT body failed. + * + * @since 0.6.0 + */ +public class CompressionException extends IOException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public CompressionException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public CompressionException(String message, Throwable cause) { + super(message, cause); + } + +} \ No newline at end of file diff --git a/io/jsonwebtoken/ExpiredJwtException.java b/io/jsonwebtoken/ExpiredJwtException.java new file mode 100644 index 0000000..815a15c --- /dev/null +++ b/io/jsonwebtoken/ExpiredJwtException.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * Exception indicating that a JWT was accepted after it expired and must be rejected. + * + * @since 0.3 + */ +public class ExpiredJwtException extends ClaimJwtException { + + /** + * Creates a new instance with the specified header, claims, and explanation message. + * + * @param header jwt header + * @param claims jwt claims (body) + * @param message the message explaining why the exception is thrown. + */ + public ExpiredJwtException(Header header, Claims claims, String message) { + super(header, claims, message); + } + + /** + * Creates a new instance with the specified header, claims, explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + * @param header jwt header + * @param claims jwt claims (body) + * @since 0.5 + */ + public ExpiredJwtException(Header header, Claims claims, String message, Throwable cause) { + super(header, claims, message, cause); + } +} diff --git a/io/jsonwebtoken/Header.java b/io/jsonwebtoken/Header.java new file mode 100644 index 0000000..f6dd1f9 --- /dev/null +++ b/io/jsonwebtoken/Header.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import java.util.Map; + +/** + * A JWT JOSE header. + * + *

This is an immutable JSON map with convenient type-safe getters for JWT standard header parameter names.

+ * + *

Because this interface extends Map<String, Object>, you can use standard {@code Map} + * accessor/iterator methods as desired, for example:

+ * + *
+ * header.get("someKey");
+ * + *

However, because {@code Header} instances are immutable, calling any of the map mutation methods + * (such as {@code Map.}{@link Map#put(Object, Object) put}, etc) will result in a runtime exception.

+ * + *

Security

+ * + *

The {@code Header} interface itself makes no implications of integrity protection via either digital signatures or + * encryption. Instead, {@link JwsHeader} and {@link JweHeader} represent this information for respective + * {@link Jws} and {@link Jwe} instances.

+ * + * @see ProtectedHeader + * @see JwsHeader + * @see JweHeader + * @since 0.1 + */ +public interface Header extends Map { + + /** + * JWT {@code Type} (typ) value: "JWT" + * + * @deprecated since 0.12.0 - this constant is never used within the JJWT codebase. + */ + @Deprecated + String JWT_TYPE = "JWT"; + + /** + * JWT {@code Type} header parameter name: "typ" + * @deprecated since 0.12.0 in favor of {@link #getType()}. + */ + @Deprecated + String TYPE = "typ"; + + /** + * JWT {@code Content Type} header parameter name: "cty" + * @deprecated since 0.12.0 in favor of {@link #getContentType()}. + */ + @Deprecated + String CONTENT_TYPE = "cty"; + + /** + * JWT {@code Algorithm} header parameter name: "alg". + * + * @see JWS Algorithm Header + * @see JWE Algorithm Header + * @deprecated since 0.12.0 in favor of {@link #getAlgorithm()}. + */ + @Deprecated + String ALGORITHM = "alg"; + + /** + * JWT {@code Compression Algorithm} header parameter name: "zip" + * @deprecated since 0.12.0 in favor of {@link #getCompressionAlgorithm()} + */ + @Deprecated + String COMPRESSION_ALGORITHM = "zip"; + + /** + * JJWT legacy/deprecated compression algorithm header parameter name: "calg" + * + * @deprecated use {@link #COMPRESSION_ALGORITHM} instead. + */ + @Deprecated + String DEPRECATED_COMPRESSION_ALGORITHM = "calg"; + + /** + * Returns the + * typ (Type) header value or {@code null} if not present. + * + * @return the {@code typ} header value or {@code null} if not present. + */ + String getType(); + + /** + * Returns the + * cty (Content Type) header value or {@code null} if not present. + * + *

The cty (Content Type) Header Parameter is used by applications to declare the + * IANA MediaType of the content + * (the payload). This is intended for use by the application when more than + * one kind of object could be present in the Payload; the application can use this value to disambiguate among + * the different kinds of objects that might be present. It will typically not be used by applications when + * the kind of object is already known. This parameter is ignored by JWT implementations (like JJWT); any + * processing of this parameter is performed by the JWS application. Use of this Header Parameter is OPTIONAL.

+ * + *

To keep messages compact in common situations, it is RECOMMENDED that producers omit an + * application/ prefix of a media type value in a {@code cty} Header Parameter when + * no other '/' appears in the media type value. A recipient using the media type value MUST + * treat it as if application/ were prepended to any {@code cty} value not containing a + * '/'. For instance, a {@code cty} value of example SHOULD be used to + * represent the application/example media type, whereas the media type + * application/example;part="1/2" cannot be shortened to + * example;part="1/2".

+ * + * @return the {@code typ} header parameter value or {@code null} if not present. + */ + String getContentType(); + + /** + * Returns the JWT {@code alg} (Algorithm) header value or {@code null} if not present. + * + *
    + *
  • If the JWT is a Signed JWT (a JWS), the + * alg (Algorithm) header parameter identifies the cryptographic algorithm used to secure the + * JWS. Consider using {@link Jwts.SIG}.{@link io.jsonwebtoken.lang.Registry#get(Object) get(id)} + * to convert this string value to a type-safe {@code SecureDigestAlgorithm} instance.
  • + *
  • If the JWT is an Encrypted JWT (a JWE), the + * alg (Algorithm) header parameter + * identifies the cryptographic key management algorithm used to encrypt or determine the value of the Content + * Encryption Key (CEK). The encrypted content is not usable if the alg value does not represent a + * supported algorithm, or if the recipient does not have a key that can be used with that algorithm. Consider + * using {@link Jwts.KEY}.{@link io.jsonwebtoken.lang.Registry#get(Object) get(id)} to convert this string value + * to a type-safe {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm} instance.
  • + *
+ * + * @return the {@code alg} header value or {@code null} if not present. This will always be + * {@code non-null} on validly constructed JWT instances, but could be {@code null} during construction. + * @since 0.12.0 + */ + String getAlgorithm(); + + /** + * Returns the JWT zip + * (Compression Algorithm) header parameter value or {@code null} if not present. + * + *

Compatibility Note

+ * + *

While the JWT family of specifications only defines the zip header in the JWE + * (JSON Web Encryption) specification, JJWT will also support compression for JWS as well if you choose to use it. + * However, be aware that if you use compression when creating a JWS token, other libraries may not be able to + * parse the JWS. However, compression when creating JWE tokens should be universally accepted for any library + * that supports JWE.

+ * + * @return the {@code zip} header parameter value or {@code null} if not present. + * @since 0.6.0 + */ + String getCompressionAlgorithm(); +} diff --git a/io/jsonwebtoken/HeaderMutator.java b/io/jsonwebtoken/HeaderMutator.java new file mode 100644 index 0000000..acaf687 --- /dev/null +++ b/io/jsonwebtoken/HeaderMutator.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.lang.MapMutator; + +/** + * Mutation (modifications) to a {@link Header Header} instance. + * + * @param the mutator subtype, for method chaining + * @since 0.12.0 + */ +public interface HeaderMutator> extends MapMutator { + + //IMPLEMENTOR NOTE: if this `algorithm` method ever needs to be exposed in the public API, it might be better to + // have it in the Jwts.HeaderBuilder interface and NOT this one: in the context of + // JwtBuilder.Header, there is never a reason for an application developer to call algorithm(id) + // directly because the KeyAlgorithm or SecureDigestAlgorithm instance must always be provided + // via the signWith or encryptWith methods. The JwtBuilder will always set the algorithm + // header based on these two instances, so there is no need for an app dev to do so. + /* + * Sets the JWT {@code alg} (Algorithm) header value. A {@code null} value will remove the property + * from the JSON map. + *
    + *
  • If the JWT is a Signed JWT (a JWS), the + * {@code alg} (Algorithm) header + * parameter identifies the cryptographic algorithm used to secure the JWS.
  • + *
  • If the JWT is an Encrypted JWT (a JWE), the + * alg (Algorithm) header parameter + * identifies the cryptographic key management algorithm used to encrypt or determine the value of the Content + * Encryption Key (CEK). The encrypted content is not usable if the alg value does not represent a + * supported algorithm, or if the recipient does not have a key that can be used with that algorithm.
  • + *
+ * + * @param alg the {@code alg} header value + * @return this header for method chaining + * @since 0.12.0 + * + T algorithm(String alg); + */ + + /** + * Sets the JWT + * typ (Type) header value. A {@code null} value will remove the property from the JSON map. + * + * @param typ the JWT JOSE {@code typ} header value or {@code null} to remove the property from the JSON map. + * @return the instance for method chaining. + */ + T type(String typ); + + /** + * Sets the compact + * cty (Content Type) header parameter value, used by applications to declare the + * IANA MediaType of the JWT + * payload. A {@code null} value will remove the property from the JSON map. + * + *

Compact Media Type Identifier

+ * + *

This method will automatically remove any application/ prefix from the + * {@code cty} string if possible according to the rules defined in the last paragraph of + * RFC 7517, Section 4.1.10:

+ *
+     *     To keep messages compact in common situations, it is RECOMMENDED that
+     *     producers omit an "application/" prefix of a media type value in a
+     *     "cty" Header Parameter when no other '/' appears in the media type
+     *     value.  A recipient using the media type value MUST treat it as if
+     *     "application/" were prepended to any "cty" value not containing a
+     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
+     *     represent the "application/example" media type, whereas the media
+     *     type "application/example;part="1/2"" cannot be shortened to
+     *     "example;part="1/2"".
+ * + *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the + * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as + * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media + * Type identifiers without needing JWT-specific prefix conditional logic in application code. + *

+ * + * @param cty the JWT {@code cty} header value or {@code null} to remove the property from the JSON map. + * @return the instance for method chaining. + */ + T contentType(String cty); + + /** + * Deprecated since of 0.12.0, delegates to {@link #type(String)}. + * + * @param typ the JWT JOSE {@code typ} header value or {@code null} to remove the property from the JSON map. + * @return the instance for method chaining. + * @see #type(String) + * @deprecated since 0.12.0 in favor of the more modern builder-style {@link #type(String)} method. + * This method will be removed before the 1.0 release. + */ + @Deprecated + T setType(String typ); + + /** + * Deprecated as of 0.12.0, delegates to {@link #contentType(String)}. + * + * @param cty the JWT JOSE {@code cty} header value or {@code null} to remove the property from the JSON map. + * @return the instance for method chaining. + * @see #contentType(String) + * @deprecated since 0.12.0 in favor of the more modern builder-style {@link #contentType(String)}. + */ + @Deprecated + T setContentType(String cty); + + /** + * Deprecated as of 0.12.0, there is no need to set this any longer as the {@code JwtBuilder} will + * always set the {@code zip} header as necessary. + * + * @param zip the JWT compression algorithm {@code zip} value or {@code null} to remove the property from the JSON map. + * @return the instance for method chaining. + * @since 0.6.0 + * @deprecated since 0.12.0 and will be removed before the 1.0 release. + */ + @Deprecated + T setCompressionAlgorithm(String zip); +} diff --git a/io/jsonwebtoken/Identifiable.java b/io/jsonwebtoken/Identifiable.java new file mode 100644 index 0000000..8872571 --- /dev/null +++ b/io/jsonwebtoken/Identifiable.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * An object that may be uniquely identified by an {@link #getId() id} relative to other instances of the same type. + * + *

The following table indicates how various JWT or JWK {@link #getId() getId()} values are used.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
JWA Identifiable Concepts
JJWT TypeHow {@link #getId()} is Used
{@link io.jsonwebtoken.Claims Claims}JWT's {@code jti} (JWT ID) + * claim.
{@link io.jsonwebtoken.security.Jwk Jwk}JWK's {@code kid} (Key ID) + * parameter value.
{@link io.jsonwebtoken.security.Curve Curve}JWK's {@code crv} (Curve) + * parameter value.
{@link io.jsonwebtoken.io.CompressionAlgorithm CompressionAlgorithm}JWE protected header's + * {@code zip} (Compression Algorithm) + * parameter value.
{@link io.jsonwebtoken.security.HashAlgorithm HashAlgorithm}Within a {@link io.jsonwebtoken.security.JwkThumbprint JwkThumbprint}'s URI value.
{@link io.jsonwebtoken.security.MacAlgorithm MacAlgorithm}JWS protected header's + * {@code alg} (Algorithm) parameter value.
{@link io.jsonwebtoken.security.SignatureAlgorithm SignatureAlgorithm}JWS protected header's + * {@code alg} (Algorithm) parameter value.
{@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm}JWE protected header's + * {@code alg} (Key Management Algorithm) + * parameter value.
{@link io.jsonwebtoken.security.AeadAlgorithm AeadAlgorithm}JWE protected header's + * {@code enc} (Encryption Algorithm) + * parameter value.
+ * + * @since 0.12.0 + */ +public interface Identifiable { + + /** + * Returns the unique string identifier of the associated object. + * + * @return the unique string identifier of the associated object. + */ + String getId(); +} diff --git a/io/jsonwebtoken/IncorrectClaimException.java b/io/jsonwebtoken/IncorrectClaimException.java new file mode 100644 index 0000000..71b0b9d --- /dev/null +++ b/io/jsonwebtoken/IncorrectClaimException.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * Exception thrown when discovering that a required claim does not equal the required value, indicating the JWT is + * invalid and may not be used. + * + * @since 0.6 + */ +public class IncorrectClaimException extends InvalidClaimException { + + /** + * Creates a new instance with the specified header, claims and explanation message. + * + * @param header the header inspected + * @param claims the claims with the incorrect claim value + * @param claimName the name of the claim that could not be validated + * @param claimValue the value of the claim that could not be validated + * @param message the exception message + */ + public IncorrectClaimException(Header header, Claims claims, String claimName, Object claimValue, String message) { + super(header, claims, claimName, claimValue, message); + } + + /** + * Creates a new instance with the specified header, claims, explanation message and underlying cause. + * + * @param header the header inspected + * @param claims the claims with the incorrect claim value + * @param claimName the name of the claim that could not be validated + * @param claimValue the value of the claim that could not be validated + * @param message the exception message + * @param cause the underlying cause that resulted in this exception being thrown + */ + public IncorrectClaimException(Header header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) { + super(header, claims, claimName, claimValue, message, cause); + } +} diff --git a/io/jsonwebtoken/InvalidClaimException.java b/io/jsonwebtoken/InvalidClaimException.java new file mode 100644 index 0000000..eba777c --- /dev/null +++ b/io/jsonwebtoken/InvalidClaimException.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * Exception indicating a parsed claim is invalid in some way. Subclasses reflect the specific + * reason the claim is invalid. + * + * @see IncorrectClaimException + * @see MissingClaimException + * @since 0.6 + */ +public class InvalidClaimException extends ClaimJwtException { + + /** + * The name of the invalid claim. + */ + private final String claimName; + + /** + * The claim value that could not be validated. + */ + private final Object claimValue; + + /** + * Creates a new instance with the specified header, claims and explanation message. + * + * @param header the header inspected + * @param claims the claims obtained + * @param claimName the name of the claim that could not be validated + * @param claimValue the value of the claim that could not be validated + * @param message the exception message + */ + protected InvalidClaimException(Header header, Claims claims, String claimName, Object claimValue, String message) { + super(header, claims, message); + this.claimName = claimName; + this.claimValue = claimValue; + } + + /** + * Creates a new instance with the specified header, claims, explanation message and underlying cause. + * + * @param header the header inspected + * @param claims the claims obtained + * @param claimName the name of the claim that could not be validated + * @param claimValue the value of the claim that could not be validated + * @param message the exception message + * @param cause the underlying cause that resulted in this exception being thrown + */ + protected InvalidClaimException(Header header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) { + super(header, claims, message, cause); + this.claimName = claimName; + this.claimValue = claimValue; + } + + /** + * Returns the name of the invalid claim. + * + * @return the name of the invalid claim. + */ + public String getClaimName() { + return claimName; + } + + /** + * Returns the claim value that could not be validated. + * + * @return the claim value that could not be validated. + */ + public Object getClaimValue() { + return claimValue; + } +} diff --git a/io/jsonwebtoken/Jwe.java b/io/jsonwebtoken/Jwe.java new file mode 100644 index 0000000..885ddae --- /dev/null +++ b/io/jsonwebtoken/Jwe.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * An encrypted JWT, called a "JWE", per the + * JWE (RFC 7516) Specification. + * + * @param payload type, either {@link Claims} or {@code byte[]} content. + * @since 0.12.0 + */ +public interface Jwe extends ProtectedJwt { + + /** + * Visitor implementation that ensures the visited JWT is a JSON Web Encryption ('JWE') message with an + * authenticated and decrypted {@code byte[]} array payload, and rejects all others with an + * {@link UnsupportedJwtException}. + * + * @see SupportedJwtVisitor#onDecryptedContent(Jwe) + * @since 0.12.0 + */ + @SuppressWarnings("UnnecessaryModifier") + public static final JwtVisitor> CONTENT = new SupportedJwtVisitor>() { + @Override + public Jwe onDecryptedContent(Jwe jwe) { + return jwe; + } + }; + + /** + * Visitor implementation that ensures the visited JWT is a JSON Web Encryption ('JWE') message with an + * authenticated and decrypted {@link Claims} payload, and rejects all others with an + * {@link UnsupportedJwtException}. + * + * @see SupportedJwtVisitor#onDecryptedClaims(Jwe) + * @since 0.12.0 + */ + @SuppressWarnings("UnnecessaryModifier") + public static final JwtVisitor> CLAIMS = new SupportedJwtVisitor>() { + @Override + public Jwe onDecryptedClaims(Jwe jwe) { + return jwe; + } + }; + + /** + * Returns the Initialization Vector used during JWE encryption and decryption. + * + * @return the Initialization Vector used during JWE encryption and decryption. + */ + byte[] getInitializationVector(); +} diff --git a/io/jsonwebtoken/JweHeader.java b/io/jsonwebtoken/JweHeader.java new file mode 100644 index 0000000..8ba6b7f --- /dev/null +++ b/io/jsonwebtoken/JweHeader.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.KeyAlgorithm; +import io.jsonwebtoken.security.PublicJwk; + +import javax.crypto.SecretKey; +import java.security.Key; + +/** + * A JWE header. + * + * @since 0.12.0 + */ +public interface JweHeader extends ProtectedHeader { + + /** + * Returns the JWE {@code enc} (Encryption + * Algorithm) header value or {@code null} if not present. + * + *

The JWE {@code enc} (encryption algorithm) Header Parameter identifies the content encryption algorithm + * used to perform authenticated encryption on the plaintext to produce the ciphertext and the JWE + * {@code Authentication Tag}.

+ * + *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by + * supplying an {@link AeadAlgorithm} to a {@link JwtBuilder} via one of its + * {@link JwtBuilder#encryptWith(SecretKey, AeadAlgorithm) encryptWith(SecretKey, AeadAlgorithm)} or + * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} + * methods. JJWT will then set this {@code enc} header value automatically to the {@code AeadAlgorithm}'s + * {@link AeadAlgorithm#getId() getId()} value during encryption.

+ * + * @return the JWE {@code enc} (Encryption Algorithm) header value or {@code null} if not present. This will + * always be {@code non-null} on validly-constructed JWE instances, but could be {@code null} during construction. + * @see JwtBuilder#encryptWith(SecretKey, AeadAlgorithm) + * @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) + */ + String getEncryptionAlgorithm(); + + /** + * Returns the {@code epk} (Ephemeral + * Public Key) header value created by the JWE originator for use with key agreement algorithms, or + * {@code null} if not present. + * + *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by + * supplying an ECDH-ES {@link KeyAlgorithm} to a {@link JwtBuilder} via its + * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} + * method. The ECDH-ES {@code KeyAlgorithm} implementation will then set this {@code epk} header value + * automatically when producing the encryption key.

+ * + * @return the {@code epk} (Ephemeral + * Public Key) header value created by the JWE originator for use with key agreement algorithms, or + * {@code null} if not present. + * @see Jwts.KEY + * @see Jwts.KEY#ECDH_ES + * @see Jwts.KEY#ECDH_ES_A128KW + * @see Jwts.KEY#ECDH_ES_A192KW + * @see Jwts.KEY#ECDH_ES_A256KW + */ + PublicJwk getEphemeralPublicKey(); + + /** + * Returns any information about the JWE producer for use with key agreement algorithms, or {@code null} if not + * present. + * + * @return any information about the JWE producer for use with key agreement algorithms, or {@code null} if not + * present. + * @see JWE apu (Agreement PartyUInfo) Header Parameter + * @see Jwts.KEY#ECDH_ES + * @see Jwts.KEY#ECDH_ES_A128KW + * @see Jwts.KEY#ECDH_ES_A192KW + * @see Jwts.KEY#ECDH_ES_A256KW + */ + byte[] getAgreementPartyUInfo(); + + /** + * Returns any information about the JWE recipient for use with key agreement algorithms, or {@code null} if not + * present. + * + * @return any information about the JWE recipient for use with key agreement algorithms, or {@code null} if not + * present. + * @see JWE apv (Agreement PartyVInfo) Header Parameter + * @see Jwts.KEY#ECDH_ES + * @see Jwts.KEY#ECDH_ES_A128KW + * @see Jwts.KEY#ECDH_ES_A192KW + * @see Jwts.KEY#ECDH_ES_A256KW + */ + byte[] getAgreementPartyVInfo(); + + /** + * Returns the 96-bit "iv" + * (Initialization Vector) generated during key encryption, or {@code null} if not present. + * Set by AES GCM {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm} implementations. + * + *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by + * supplying an AES GCM Wrap {@link KeyAlgorithm} to a {@link JwtBuilder} via its + * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} + * method. The AES GCM Wrap {@code KeyAlgorithm} implementation will then set this {@code iv} header value + * automatically when producing the encryption key.

+ * + * @return the 96-bit initialization vector generated during key encryption, or {@code null} if not present. + * @see Jwts.KEY#A128GCMKW + * @see Jwts.KEY#A192GCMKW + * @see Jwts.KEY#A256GCMKW + */ + byte[] getInitializationVector(); + + /** + * Returns the 128-bit "tag" + * (Authentication Tag) resulting from key encryption, or {@code null} if not present. + * + *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by + * supplying an AES GCM Wrap {@link KeyAlgorithm} to a {@link JwtBuilder} via its + * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} + * method. The AES GCM Wrap {@code KeyAlgorithm} implementation will then set this {@code tag} header value + * automatically when producing the encryption key.

+ * + * @return the 128-bit authentication tag resulting from key encryption, or {@code null} if not present. + * @see Jwts.KEY#A128GCMKW + * @see Jwts.KEY#A192GCMKW + * @see Jwts.KEY#A256GCMKW + */ + byte[] getAuthenticationTag(); + + /** + * Returns the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, or {@code null} + * if not present. Used with password-based {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm}s. + * + * @return the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, or {@code null} + * if not present. + * @see JWE p2c (PBES2 Count) Header Parameter + * @see Jwts.KEY#PBES2_HS256_A128KW + * @see Jwts.KEY#PBES2_HS384_A192KW + * @see Jwts.KEY#PBES2_HS512_A256KW + */ + Integer getPbes2Count(); + + /** + * Returns the PBKDF2 {@code Salt Input} value necessary to derive the key used during JWE encryption, or + * {@code null} if not present. + * + *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by + * supplying a password-based {@link KeyAlgorithm} to a {@link JwtBuilder} via its + * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} + * method. The password-based {@code KeyAlgorithm} implementation will then set this {@code p2s} header value + * automatically when producing the encryption key.

+ * + * @return the PBKDF2 {@code Salt Input} value necessary to derive the key used during JWE encryption, or + * {@code null} if not present. + * @see JWE p2s (PBES2 Salt Input) Header Parameter + * @see Jwts.KEY#PBES2_HS256_A128KW + * @see Jwts.KEY#PBES2_HS384_A192KW + * @see Jwts.KEY#PBES2_HS512_A256KW + */ + byte[] getPbes2Salt(); +} diff --git a/io/jsonwebtoken/JweHeaderMutator.java b/io/jsonwebtoken/JweHeaderMutator.java new file mode 100644 index 0000000..912136d --- /dev/null +++ b/io/jsonwebtoken/JweHeaderMutator.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.security.KeyAlgorithm; + +/** + * Mutation (modifications) to a {@link JweHeader} instance. + * + * @param the mutator subtype, for method chaining + * @since 0.12.0 + */ +public interface JweHeaderMutator> extends ProtectedHeaderMutator { + + /** + * Sets any information about the JWE producer for use with key agreement algorithms. A {@code null} or empty value + * removes the property from the JSON map. + * + * @param info information about the JWE producer to use with key agreement algorithms. + * @return the header for method chaining. + * @see JWE apu (Agreement PartyUInfo) Header Parameter + * @see Jwts.KEY#ECDH_ES + * @see Jwts.KEY#ECDH_ES_A128KW + * @see Jwts.KEY#ECDH_ES_A192KW + * @see Jwts.KEY#ECDH_ES_A256KW + */ + T agreementPartyUInfo(byte[] info); + + /** + * Sets any information about the JWE producer for use with key agreement algorithms. A {@code null} value removes + * the property from the JSON map. + * + *

If not {@code null}, this is a convenience method that calls the equivalent of the following:

+ *
+     * {@link #agreementPartyUInfo(byte[]) agreementPartyUInfo}(info.getBytes(StandardCharsets.UTF_8))
+ * + * @param info information about the JWE producer to use with key agreement algorithms. + * @return the header for method chaining. + * @see JWE apu (Agreement PartyUInfo) Header Parameter + * @see Jwts.KEY#ECDH_ES + * @see Jwts.KEY#ECDH_ES_A128KW + * @see Jwts.KEY#ECDH_ES_A192KW + * @see Jwts.KEY#ECDH_ES_A256KW + */ + T agreementPartyUInfo(String info); + + /** + * Sets any information about the JWE recipient for use with key agreement algorithms. A {@code null} value removes + * the property from the JSON map. + * + * @param info information about the JWE recipient to use with key agreement algorithms. + * @return the header for method chaining. + * @see JWE apv (Agreement PartyVInfo) Header Parameter + * @see Jwts.KEY#ECDH_ES + * @see Jwts.KEY#ECDH_ES_A128KW + * @see Jwts.KEY#ECDH_ES_A192KW + * @see Jwts.KEY#ECDH_ES_A256KW + */ + T agreementPartyVInfo(byte[] info); + + /** + * Sets any information about the JWE recipient for use with key agreement algorithms. A {@code null} value removes + * the property from the JSON map. + * + *

If not {@code null}, this is a convenience method that calls the equivalent of the following:

+ *
+     * {@link #agreementPartyVInfo(byte[]) setAgreementPartVUInfo}(info.getBytes(StandardCharsets.UTF_8))
+ * + * @param info information about the JWE recipient to use with key agreement algorithms. + * @return the header for method chaining. + * @see JWE apv (Agreement PartyVInfo) Header Parameter + * @see Jwts.KEY#ECDH_ES + * @see Jwts.KEY#ECDH_ES_A128KW + * @see Jwts.KEY#ECDH_ES_A192KW + * @see Jwts.KEY#ECDH_ES_A256KW + */ + T agreementPartyVInfo(String info); + + /** + * Sets the number of PBKDF2 iterations necessary to derive the key used during JWE encryption. If this value + * is not set when a password-based {@link KeyAlgorithm} is used, JJWT will automatically choose a suitable + * number of iterations based on + * OWASP PBKDF2 Iteration Recommendations. + * + *

Minimum Count

+ * + *

{@code IllegalArgumentException} will be thrown during encryption if a specified {@code count} is + * less than 1000 (one thousand), which is the + * minimum number recommended by the + * JWA specification. Anything less is susceptible to security attacks so the default PBKDF2 + * {@code KeyAlgorithm} implementations reject such values.

+ * + * @param count the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, must be + * greater than or equal to 1000 (one thousand). + * @return the header for method chaining + * @see JWE p2c (PBES2 Count) Header Parameter + * @see Jwts.KEY#PBES2_HS256_A128KW + * @see Jwts.KEY#PBES2_HS384_A192KW + * @see Jwts.KEY#PBES2_HS512_A256KW + * @see OWASP PBKDF2 Iteration Recommendations + */ + T pbes2Count(int count); +} diff --git a/io/jsonwebtoken/Jws.java b/io/jsonwebtoken/Jws.java new file mode 100644 index 0000000..8c6010c --- /dev/null +++ b/io/jsonwebtoken/Jws.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * An expanded (not compact/serialized) Signed JSON Web Token. + * + * @param

the type of the JWS payload, either a byte[] or a {@link Claims} instance. + * @since 0.1 + */ +public interface Jws

extends ProtectedJwt { + + /** + * Visitor implementation that ensures the visited JWT is a JSON Web Signature ('JWS') message with a + * cryptographically authenticated/verified {@code byte[]} array payload, and rejects all others with an + * {@link UnsupportedJwtException}. + * + * @see SupportedJwtVisitor#onVerifiedContent(Jws) + * @since 0.12.0 + */ + @SuppressWarnings("UnnecessaryModifier") + public static final JwtVisitor> CONTENT = new SupportedJwtVisitor>() { + @Override + public Jws onVerifiedContent(Jws jws) { + return jws; + } + }; + + /** + * Visitor implementation that ensures the visited JWT is a JSON Web Signature ('JWS') message with a + * cryptographically authenticated/verified {@link Claims} payload, and rejects all others with an + * {@link UnsupportedJwtException}. + * + * @see SupportedJwtVisitor#onVerifiedClaims(Jws) + * @since 0.12.0 + */ + @SuppressWarnings("UnnecessaryModifier") + public static final JwtVisitor> CLAIMS = new SupportedJwtVisitor>() { + @Override + public Jws onVerifiedClaims(Jws jws) { + return jws; + } + }; + + /** + * Returns the verified JWS signature as a Base64Url string. + * + * @return the verified JWS signature as a Base64Url string. + * @deprecated since 0.12.0 in favor of {@link #getDigest() getDigest()}. + */ + @Deprecated + String getSignature(); //TODO for 1.0: return a byte[] +} diff --git a/io/jsonwebtoken/JwsHeader.java b/io/jsonwebtoken/JwsHeader.java new file mode 100644 index 0000000..0afab2a --- /dev/null +++ b/io/jsonwebtoken/JwsHeader.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * A JWS header. + * + * @since 0.1 + */ +public interface JwsHeader extends ProtectedHeader { + + /** + * JWS Algorithm Header name: the string literal alg + * + * @deprecated since 0.12.0 in favor of {@link #getAlgorithm()} + */ + @Deprecated + String ALGORITHM = "alg"; + + /** + * JWS JWK Set URL Header name: the string literal jku + * + * @deprecated since 0.12.0 in favor of {@link #getJwkSetUrl()} + */ + @Deprecated + String JWK_SET_URL = "jku"; + + /** + * JWS JSON Web Key Header name: the string literal jwk + * + * @deprecated since 0.12.0 in favor of {@link #getJwk()} + */ + @Deprecated + String JSON_WEB_KEY = "jwk"; + + /** + * JWS Key ID Header name: the string literal kid + * + * @deprecated since 0.12.0 in favor of {@link #getKeyId()} + */ + @Deprecated + String KEY_ID = "kid"; + + /** + * JWS X.509 URL Header name: the string literal x5u + * + * @deprecated since 0.12.0 in favor of {@link #getX509Url()} + */ + @Deprecated + String X509_URL = "x5u"; + + /** + * JWS X.509 Certificate Chain Header name: the string literal x5c + * + * @deprecated since 0.12.0 in favor of {@link #getX509Chain()} + */ + @Deprecated + String X509_CERT_CHAIN = "x5c"; + + /** + * JWS X.509 Certificate SHA-1 Thumbprint Header name: the string literal x5t + * + * @deprecated since 0.12.0 in favor of {@link #getX509Sha1Thumbprint()} + */ + @Deprecated + String X509_CERT_SHA1_THUMBPRINT = "x5t"; + + /** + * JWS X.509 Certificate SHA-256 Thumbprint Header name: the string literal x5t#S256 + * + * @deprecated since 0.12.0 in favor of {@link #getX509Sha256Thumbprint()} + */ + @Deprecated + String X509_CERT_SHA256_THUMBPRINT = "x5t#S256"; + + /** + * JWS Critical Header name: the string literal crit + * + * @deprecated since 0.12.0 in favor of {@link #getCritical()} + */ + @Deprecated + String CRITICAL = "crit"; + + /** + * Returns {@code true} if the payload is Base64Url-encoded per standard JWS rules, or {@code false} if the + * RFC 7797: JSON Web Signature (JWS) Unencoded Payload + * Option has been specified. + * + * @return {@code true} if the payload is Base64Url-encoded per standard JWS rules, or {@code false} if the + * RFC 7797: JSON Web Signature (JWS) Unencoded Payload + * Option has been specified. + */ + boolean isPayloadEncoded(); +} diff --git a/io/jsonwebtoken/Jwt.java b/io/jsonwebtoken/Jwt.java new file mode 100644 index 0000000..a1bb23a --- /dev/null +++ b/io/jsonwebtoken/Jwt.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * An expanded (not compact/serialized) JSON Web Token. + * + * @param the type of the JWT header + * @param

the type of the JWT payload, either a content byte array or a {@link Claims} instance. + * @since 0.1 + */ +public interface Jwt { + + /** + * Visitor implementation that ensures the visited JWT is an unsecured content JWT (one not cryptographically + * signed or encrypted) and rejects all others with an {@link UnsupportedJwtException}. + * + * @see SupportedJwtVisitor#onUnsecuredContent(Jwt) + * @since 0.12.0 + */ + @SuppressWarnings("UnnecessaryModifier") + public static final JwtVisitor> UNSECURED_CONTENT = new SupportedJwtVisitor>() { + @Override + public Jwt onUnsecuredContent(Jwt jwt) { + return jwt; + } + }; + + /** + * Visitor implementation that ensures the visited JWT is an unsecured {@link Claims} JWT (one not + * cryptographically signed or encrypted) and rejects all others with an {@link UnsupportedJwtException}. + * + * @see SupportedJwtVisitor#onUnsecuredClaims(Jwt) + * @since 0.12.0 + */ + @SuppressWarnings("UnnecessaryModifier") + public static final JwtVisitor> UNSECURED_CLAIMS = new SupportedJwtVisitor>() { + @Override + public Jwt onUnsecuredClaims(Jwt jwt) { + return jwt; + } + }; + + /** + * Returns the JWT {@link Header} or {@code null} if not present. + * + * @return the JWT {@link Header} or {@code null} if not present. + */ + H getHeader(); + + /** + * Returns the JWT payload, either a {@code byte[]} or a {@code Claims} instance. Use + * {@link #getPayload()} instead, as this method will be removed prior to the 1.0 release. + * + * @return the JWT payload, either a {@code byte[]} or a {@code Claims} instance. + * @deprecated since 0.12.0 because it has been renamed to {@link #getPayload()}. 'Payload' (not + * body) is what the JWT specifications call this property, so it has been renamed to reflect the correct JWT + * nomenclature/taxonomy. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + P getBody(); // TODO: remove for 1.0 + + /** + * Returns the JWT payload, either a {@code byte[]} or a {@code Claims} instance. If the payload is a byte + * array, and if the JWT creator set the (optional) {@link Header#getContentType() contentType} header + * value, the application may inspect the {@code contentType} value to determine how to convert the byte array to + * the final content type as desired. + * + * @return the JWT payload, either a {@code byte[]} or a {@code Claims} instance. + * @since 0.12.0 + */ + P getPayload(); + + /** + * Invokes the specified {@code visitor}'s appropriate type-specific {@code visit} method based on this JWT's type. + * + * @param visitor the visitor to invoke. + * @param the value type returned from the {@code visit} method. + * @return the value returned from visitor's {@code visit} method implementation. + */ + T accept(JwtVisitor visitor); +} diff --git a/io/jsonwebtoken/JwtBuilder.java b/io/jsonwebtoken/JwtBuilder.java new file mode 100644 index 0000000..6348008 --- /dev/null +++ b/io/jsonwebtoken/JwtBuilder.java @@ -0,0 +1,1056 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.io.CompressionAlgorithm; +import io.jsonwebtoken.io.Decoder; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.io.Encoder; +import io.jsonwebtoken.io.Serializer; +import io.jsonwebtoken.lang.Conjunctor; +import io.jsonwebtoken.lang.MapMutator; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.KeyAlgorithm; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.Password; +import io.jsonwebtoken.security.SecureDigestAlgorithm; +import io.jsonwebtoken.security.WeakKeyException; +import io.jsonwebtoken.security.X509Builder; + +import javax.crypto.SecretKey; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.Key; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.interfaces.ECKey; +import java.security.interfaces.RSAKey; +import java.util.Date; +import java.util.Map; + +/** + * A builder for constructing Unprotected JWTs, Signed JWTs (aka 'JWS's) and Encrypted JWTs (aka 'JWE's). + * + * @since 0.1 + */ +public interface JwtBuilder extends ClaimsMutator { + + /** + * Sets the JCA Provider to use during cryptographic signing or encryption operations, or {@code null} if the + * JCA subsystem preferred provider should be used. + * + * @param provider the JCA Provider to use during cryptographic signing or encryption operations, or {@code null} if the + * JCA subsystem preferred provider should be used. + * @return the builder for method chaining. + * @since 0.12.0 + */ + JwtBuilder provider(Provider provider); + + /** + * Sets the {@link SecureRandom} to use during cryptographic signing or encryption operations, or {@code null} if + * a default {@link SecureRandom} should be used. + * + * @param secureRandom the {@link SecureRandom} to use during cryptographic signing or encryption operations, or + * {@code null} if a default {@link SecureRandom} should be used. + * @return the builder for method chaining. + * @since 0.12.0 + */ + JwtBuilder random(SecureRandom secureRandom); + + /** + * Returns the {@code Header} to use to modify the constructed JWT's header name/value pairs as desired. + * When finished, callers may return to JWT construction via the {@link BuilderHeader#and() and()} method. + * For example: + * + *

+     * String jwt = Jwts.builder()
+     *
+     *     .header()
+     *         .keyId("keyId")
+     *         .add("aName", aValue)
+     *         .add(myHeaderMap)
+     *         // ... etc ...
+     *         .{@link BuilderHeader#and() and()} //return back to the JwtBuilder
+     *
+     *     .subject("Joe") // resume JwtBuilder calls
+     *     // ... etc ...
+     *     .compact();
+ * + * @return the {@link BuilderHeader} to use for header construction. + * @since 0.12.0 + */ + BuilderHeader header(); + + /** + * Per standard Java idiom 'setter' conventions, this method sets (and fully replaces) any existing header with the + * specified name/value pairs. This is a wrapper method for: + * + *
+     * {@link #header()}.{@link MapMutator#empty() empty()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()}
+ * + *

If you do not want to replace the existing header and only want to append to it, + * call {@link #header()}.{@link io.jsonwebtoken.lang.MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()} instead.

+ * + * @param map the name/value pairs to set as (and potentially replace) the constructed JWT header. + * @return the builder for method chaining. + * @deprecated since 0.12.0 in favor of + * {@link #header()}.{@link MapMutator#empty() empty()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()} + * (to replace all header parameters) or + * {@link #header()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()} + * to only append the {@code map} entries. This method will be removed before the 1.0 release. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + JwtBuilder setHeader(Map map); + + /** + * Adds the specified name/value pairs to the header. Any parameter with an empty or null value will remove the + * entry from the header. This is a wrapper method for: + *
+     * {@link #header()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()}
+ * + * @param params the header name/value pairs to append to the header. + * @return the builder for method chaining. + * @deprecated since 0.12.0 in favor of + * {@link #header()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()}. + * This method will be removed before the 1.0 release. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + JwtBuilder setHeaderParams(Map params); + + /** + * Adds the specified name/value pair to the header. If the value is {@code null} or empty, the parameter will + * be removed from the header entirely. This is a wrapper method for: + *
+     * {@link #header()}.{@link MapMutator#add(Object, Object) add(name, value)}.{@link BuilderHeader#and() and()}
+ * + * @param name the header parameter name + * @param value the header parameter value + * @return the builder for method chaining. + * @deprecated since 0.12.0 in favor of + * {@link #header()}.{@link MapMutator#add(Object, Object) add(name, value)}.{@link BuilderHeader#and() and()}. + * This method will be removed before the 1.0 release. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + JwtBuilder setHeaderParam(String name, Object value); + + /** + * Since JJWT 0.12.0, this is an alias for {@link #content(String)}. This method will be removed + * before the 1.0 release. + * + * @param payload the string used to set UTF-8-encoded bytes as the JWT payload. + * @return the builder for method chaining. + * @see #content(String) + * @deprecated since 0.12.0 in favor of {@link #content(String)} + * because both Claims and Content are technically 'payloads', so this method name is misleading. This method will + * be removed before the 1.0 release. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + JwtBuilder setPayload(String payload); + + /** + * Sets the JWT payload to be the specified string's UTF-8 bytes. This is a convenience method semantically + * equivalent to calling: + * + *
+     * {@link #content(byte[]) content}(payload.getBytes(StandardCharsets.UTF_8))
+ * + *

Content Type Recommendation

+ * + *

Unless you are confident that the JWT recipient will always know to convert the payload bytes + * to a UTF-8 string without additional metadata, it is strongly recommended to use the + * {@link #content(String, String)} method instead of this one. That method ensures that a JWT recipient can + * inspect the {@code cty} header to know how to handle the payload bytes without ambiguity.

+ * + *

Mutually Exclusive Claims and Content

+ * + *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} + * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the + * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

+ * + * @param content the content string to use for the JWT payload + * @return the builder for method chaining. + * @see #content(String, String) + * @see #content(byte[], String) + * @see #content(InputStream, String) + * @since 0.12.0 + */ + JwtBuilder content(String content); + + /** + * Sets the JWT payload to be the specified content byte array. This is a convenience method semantically + * equivalent to calling: + *
+     * {@link #content(InputStream) content}(new ByteArrayInputStream(content))
+ * + *

Content Type Recommendation

+ * + *

Unless you are confident that the JWT recipient will always know how to use the payload bytes + * without additional metadata, it is strongly recommended to also set the + * {@link Header#getContentType() contentType} header. For example:

+ * + *
+     * content(bytes).{@link #header() header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
+ * + *

This ensures a JWT recipient can inspect the {@code cty} header to know how to handle the payload bytes + * without ambiguity.

+ * + *

Mutually Exclusive Claims and Content

+ * + *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} + * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the + * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

+ * + * @param content the content byte array to use as the JWT payload + * @return the builder for method chaining. + * @see #content(byte[], String) + * @since 0.12.0 + */ + JwtBuilder content(byte[] content); + + /** + * Sets the JWT payload to be the bytes in the specified content stream. + * + *

Content Type Recommendation

+ * + *

Unless you are confident that the JWT recipient will always know how to use the payload bytes + * without additional metadata, it is strongly recommended to also set the + * {@link HeaderMutator#contentType(String) contentType} header. For example:

+ * + *
+     * content(in).{@link #header() header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
+ * + *

This ensures a JWT recipient can inspect the {@code cty} header to know how to handle the payload bytes + * without ambiguity.

+ * + *

Mutually Exclusive Claims and Content

+ * + *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} + * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the + * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

+ * + * @param in the input stream containing the bytes to use as the JWT payload + * @return the builder for method chaining. + * @see #content(byte[], String) + * @since 0.12.0 + */ + JwtBuilder content(InputStream in); + + /** + * Sets the JWT payload to be the specified String's UTF-8 bytes, and also sets the + * {@link HeaderMutator#contentType(String) contentType} header value to a compact {@code cty} IANA Media Type + * identifier to indicate the data format of the resulting byte array. The JWT recipient can inspect the + * {@code cty} value to determine how to convert the byte array to the final content type as desired. This is a + * convenience method semantically equivalent to: + * + *
+     * {@link #content(String) content(content)}.{@link #header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
+ * + *

Compact Media Type Identifier

+ * + *

This method will automatically remove any application/ prefix from the + * {@code cty} string if possible according to the rules defined in the last paragraph of + * RFC 7517, Section 4.1.10:

+ * + *
+     *     To keep messages compact in common situations, it is RECOMMENDED that
+     *     producers omit an "application/" prefix of a media type value in a
+     *     "cty" Header Parameter when no other '/' appears in the media type
+     *     value.  A recipient using the media type value MUST treat it as if
+     *     "application/" were prepended to any "cty" value not containing a
+     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
+     *     represent the "application/example" media type, whereas the media
+     *     type "application/example;part="1/2"" cannot be shortened to
+     *     "example;part="1/2"".
+ * + *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the + * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as + * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media + * Type identifiers without needing JWT-specific prefix conditional logic in application code. + *

+ * + *

Mutually Exclusive Claims and Content

+ * + *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} + * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the + * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

+ * + * @param content the content byte array that will be the JWT payload. Cannot be null or empty. + * @param cty the content type (media type) identifier attributed to the byte array. Cannot be null or empty. + * @return the builder for method chaining. + * @throws IllegalArgumentException if either {@code content} or {@code cty} are null or empty. + * @since 0.12.0 + */ + JwtBuilder content(String content, String cty) throws IllegalArgumentException; + + /** + * Sets the JWT payload to be the specified byte array, and also sets the + * {@link HeaderMutator#contentType(String) contentType} header value to a compact {@code cty} IANA Media Type + * identifier to indicate the data format of the byte array. The JWT recipient can inspect the + * {@code cty} value to determine how to convert the byte array to the final content type as desired. This is a + * convenience method semantically equivalent to: + * + *
+     * {@link #content(byte[]) content(content)}.{@link #header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
+ * + *

Compact Media Type Identifier

+ * + *

This method will automatically remove any application/ prefix from the + * {@code cty} string if possible according to the rules defined in the last paragraph of + * RFC 7517, Section 4.1.10:

+ *
+     *     To keep messages compact in common situations, it is RECOMMENDED that
+     *     producers omit an "application/" prefix of a media type value in a
+     *     "cty" Header Parameter when no other '/' appears in the media type
+     *     value.  A recipient using the media type value MUST treat it as if
+     *     "application/" were prepended to any "cty" value not containing a
+     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
+     *     represent the "application/example" media type, whereas the media
+     *     type "application/example;part="1/2"" cannot be shortened to
+     *     "example;part="1/2"".
+ * + *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the + * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as + * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media + * Type identifiers without needing JWT-specific prefix conditional logic in application code. + *

+ * + *

Mutually Exclusive Claims and Content

+ * + *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} + * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the + * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

+ * + * @param content the content byte array that will be the JWT payload. Cannot be null or empty. + * @param cty the content type (media type) identifier attributed to the byte array. Cannot be null or empty. + * @return the builder for method chaining. + * @throws IllegalArgumentException if either {@code content} or {@code cty} are null or empty. + * @since 0.12.0 + */ + JwtBuilder content(byte[] content, String cty) throws IllegalArgumentException; + + /** + * Sets the JWT payload to be the specified content byte stream and also sets the + * {@link BuilderHeader#contentType(String) contentType} header value to a compact {@code cty} IANA Media Type + * identifier to indicate the data format of the byte array. The JWT recipient can inspect the + * {@code cty} value to determine how to convert the byte array to the final content type as desired. This is a + * convenience method semantically equivalent to: + * + *
+     * {@link #content(InputStream) content(content)}.{@link #header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
+ * + *

Compact Media Type Identifier

+ * + *

This method will automatically remove any application/ prefix from the + * {@code cty} string if possible according to the rules defined in the last paragraph of + * RFC 7517, Section 4.1.10:

+ * + *
+     *     To keep messages compact in common situations, it is RECOMMENDED that
+     *     producers omit an "application/" prefix of a media type value in a
+     *     "cty" Header Parameter when no other '/' appears in the media type
+     *     value.  A recipient using the media type value MUST treat it as if
+     *     "application/" were prepended to any "cty" value not containing a
+     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
+     *     represent the "application/example" media type, whereas the media
+     *     type "application/example;part="1/2"" cannot be shortened to
+     *     "example;part="1/2"".
+ * + *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the + * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as + * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media + * Type identifiers without needing JWT-specific prefix conditional logic in application code. + *

+ * + *

Mutually Exclusive Claims and Content

+ * + *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} + * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the + * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

+ * + * @param content the content byte array that will be the JWT payload. Cannot be null. + * @param cty the content type (media type) identifier attributed to the byte array. Cannot be null or empty. + * @return the builder for method chaining. + * @throws IllegalArgumentException if either {@code content} or {@code cty} are null or empty. + * @since 0.12.0 + */ + JwtBuilder content(InputStream content, String cty) throws IllegalArgumentException; + + /** + * Returns the JWT {@code Claims} payload to modify as desired. When finished, callers may + * return to {@code JwtBuilder} configuration via the {@link BuilderClaims#and() and()} method. + * For example: + * + *
+     * String jwt = Jwts.builder()
+     *
+     *     .claims()
+     *         .issuer("me")
+     *         .subject("Joe")
+     *         .audience().add("you").and()
+     *         .add("customClaim", customValue)
+     *         .add(myClaimsMap)
+     *         // ... etc ...
+     *         .{@link BuilderClaims#and() and()} //return back to the JwtBuilder
+     *
+     *     .signWith(key) // resume JwtBuilder calls
+     *     // ... etc ...
+     *     .compact();
+ * + * @return the {@link BuilderClaims} to use for Claims construction. + * @since 0.12.0 + */ + BuilderClaims claims(); + + /** + * Replaces the JWT Claims payload with the specified name/value pairs. This is an alias for: + *
+     * {@link #claims()}.{@link MapMutator#empty() empty()}.{@link MapMutator#add(Map) add(claims)}.{@link BuilderClaims#and() and()}
+ * + *

The {@code content} and {@code claims} properties are mutually exclusive - only one of the two variants + * may be used.

+ * + * @param claims the JWT Claims to be set as the JWT payload. + * @return the builder for method chaining. + * @see #claims() + * @see #content(String) + * @see #content(byte[]) + * @see #content(InputStream) + * @deprecated since 0.12.0 in favor of using the {@link #claims()} builder. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + JwtBuilder setClaims(Map claims); + + /** + * Adds/appends all given name/value pairs to the JSON Claims in the payload. This is an alias for: + * + *
+     * {@link #claims()}.{@link MapMutator#add(Map) add(claims)}.{@link BuilderClaims#and() and()}
+ * + *

The content and claims properties are mutually exclusive - only one of the two may be used.

+ * + * @param claims the JWT Claims to be added to the JWT payload. + * @return the builder for method chaining. + * @since 0.8 + * @deprecated since 0.12.0 in favor of + * {@link #claims()}.{@link BuilderClaims#add(Map) add(Map)}.{@link BuilderClaims#and() and()}. + * This method will be removed before the 1.0 release. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + JwtBuilder addClaims(Map claims); + + /** + * Sets a JWT claim, overwriting any existing claim with the same name. A {@code null} or empty + * value will remove the claim entirely. This is a convenience alias for: + *
+     * {@link #claims()}.{@link MapMutator#add(Object, Object) add(name, value)}.{@link BuilderClaims#and() and()}
+ * + * @param name the JWT Claims property name + * @param value the value to set for the specified Claims property name + * @return the builder instance for method chaining. + * @since 0.2 + */ + JwtBuilder claim(String name, Object value); + + /** + * Adds all given name/value pairs to the JSON Claims in the payload, overwriting any existing claims + * with the same names. If any name has a {@code null} or empty value, that claim will be removed from the + * Claims. This is a convenience alias for: + *
+     * {@link #claims()}.{@link MapMutator#add(Map) add(claims)}.{@link BuilderClaims#and() and()}
+ * + *

The content and claims properties are mutually exclusive - only one of the two may be used.

+ * + * @param claims the JWT Claims to be added to the JWT payload. + * @return the builder instance for method chaining + * @since 0.12.0 + */ + JwtBuilder claims(Map claims); + + /** + * Sets the JWT Claims + * iss (issuer) claim. A {@code null} value will remove the property from the Claims. + * This is a convenience wrapper for: + *
+     * {@link #claims()}.{@link ClaimsMutator#issuer(String) issuer(iss)}.{@link BuilderClaims#and() and()}
+ * + * @param iss the JWT {@code iss} value or {@code null} to remove the property from the Claims map. + * @return the builder instance for method chaining. + */ + @Override + // for better/targeted JavaDoc + JwtBuilder issuer(String iss); + + /** + * Sets the JWT Claims + * sub (subject) claim. A {@code null} value will remove the property from the Claims. + * This is a convenience wrapper for: + *
+     * {@link #claims()}.{@link ClaimsMutator#subject(String) subject(sub)}.{@link BuilderClaims#and() and()}
+ * + * @param sub the JWT {@code sub} value or {@code null} to remove the property from the Claims map. + * @return the builder instance for method chaining. + */ + @Override + // for better/targeted JavaDoc + JwtBuilder subject(String sub); + + /** + * Sets the JWT Claims + * exp (expiration) claim. A {@code null} value will remove the property from the Claims. + * + *

A JWT obtained after this timestamp should not be used.

+ * + *

This is a convenience wrapper for:

+ *
+     * {@link #claims()}.{@link ClaimsMutator#expiration(Date) expiration(exp)}.{@link BuilderClaims#and() and()}
+ * + * @param exp the JWT {@code exp} value or {@code null} to remove the property from the Claims map. + * @return the builder instance for method chaining. + */ + @Override + // for better/targeted JavaDoc + JwtBuilder expiration(Date exp); + + /** + * Sets the JWT Claims + * nbf (not before) claim. A {@code null} value will remove the property from the Claims. + * + *

A JWT obtained before this timestamp should not be used.

+ * + *

This is a convenience wrapper for:

+ *
+     * {@link #claims()}.{@link ClaimsMutator#notBefore(Date) notBefore(nbf)}.{@link BuilderClaims#and() and()}
+ * + * @param nbf the JWT {@code nbf} value or {@code null} to remove the property from the Claims map. + * @return the builder instance for method chaining. + */ + @Override + // for better/targeted JavaDoc + JwtBuilder notBefore(Date nbf); + + /** + * Sets the JWT Claims + * iat (issued at) claim. A {@code null} value will remove the property from the Claims. + * + *

The value is the timestamp when the JWT was created.

+ * + *

This is a convenience wrapper for:

+ *
+     * {@link #claims()}.{@link ClaimsMutator#issuedAt(Date) issuedAt(iat)}.{@link BuilderClaims#and() and()}
+ * + * @param iat the JWT {@code iat} value or {@code null} to remove the property from the Claims map. + * @return the builder instance for method chaining. + */ + @Override + // for better/targeted JavaDoc + JwtBuilder issuedAt(Date iat); + + /** + * Sets the JWT Claims + * jti (JWT ID) claim. A {@code null} value will remove the property from the Claims. + * + *

The value is a CaSe-SenSiTiVe unique identifier for the JWT. If specified, this value MUST be assigned in a + * manner that ensures that there is a negligible probability that the same value will be accidentally + * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

+ * + *

This is a convenience wrapper for:

+ *
+     * {@link #claims()}.{@link ClaimsMutator#id(String) id(jti)}.{@link BuilderClaims#and() and()}
+ * + * @param jti the JWT {@code jti} (id) value or {@code null} to remove the property from the Claims map. + * @return the builder instance for method chaining. + */ + @Override + // for better/targeted JavaDoc + JwtBuilder id(String jti); + + /** + * Signs the constructed JWT with the specified key using the key's recommended signature algorithm + * as defined below, producing a JWS. If the recommended signature algorithm isn't sufficient for your needs, + * consider using {@link #signWith(Key, SecureDigestAlgorithm)} instead. + * + *

If you are looking to invoke this method with a byte array that you are confident may be used for HMAC-SHA + * algorithms, consider using {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to + * convert the byte array into a valid {@code Key}.

+ * + *

Recommended Signature Algorithm

+ * + *

The recommended signature algorithm used with a given key is chosen based on the following:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Key Recommended Signature Algorithm
If the Key is a:And:With a key size of:The SignatureAlgorithm used will be:
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA256")1256 <= size <= 383 2{@link Jwts.SIG#HS256 HS256}
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA384")1384 <= size <= 511{@link Jwts.SIG#HS384 HS384}
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA512")1512 <= size{@link Jwts.SIG#HS512 HS512}
{@link ECKey}instanceof {@link PrivateKey}256 <= size <= 383 3{@link Jwts.SIG#ES256 ES256}
{@link ECKey}instanceof {@link PrivateKey}384 <= size <= 520 4{@link Jwts.SIG#ES384 ES384}
{@link ECKey}instanceof {@link PrivateKey}521 <= size 4{@link Jwts.SIG#ES512 ES512}
{@link RSAKey}instanceof {@link PrivateKey}2048 <= size <= 3071 5,6{@link Jwts.SIG#RS256 RS256}
{@link RSAKey}instanceof {@link PrivateKey}3072 <= size <= 4095 6{@link Jwts.SIG#RS384 RS384}
{@link RSAKey}instanceof {@link PrivateKey}4096 <= size 5{@link Jwts.SIG#RS512 RS512}
EdECKey7instanceof {@link PrivateKey}256 || 456{@link Jwts.SIG#EdDSA EdDSA}
+ *

Notes:

+ *
    + *
  1. {@code SecretKey} instances must have an {@link Key#getAlgorithm() algorithm} name equal + * to {@code HmacSHA256}, {@code HmacSHA384} or {@code HmacSHA512}. If not, the key bytes might not be + * suitable for HMAC signatures will be rejected with a {@link InvalidKeyException}.
  2. + *
  3. The JWT JWA Specification (RFC 7518, + * Section 3.2) mandates that HMAC-SHA-* signing keys MUST be 256 bits or greater. + * {@code SecretKey}s with key lengths less than 256 bits will be rejected with an + * {@link WeakKeyException}.
  4. + *
  5. The JWT JWA Specification (RFC 7518, + * Section 3.4) mandates that ECDSA signing key lengths MUST be 256 bits or greater. + * {@code ECKey}s with key lengths less than 256 bits will be rejected with a + * {@link WeakKeyException}.
  6. + *
  7. The ECDSA {@code P-521} curve does indeed use keys of 521 bits, not 512 as might be expected. ECDSA + * keys of 384 < size <= 520 are suitable for ES384, while ES512 requires keys >= 521 bits. The '512' part of the + * ES512 name reflects the usage of the SHA-512 algorithm, not the ECDSA key length. ES512 with ECDSA keys less + * than 521 bits will be rejected with a {@link WeakKeyException}.
  8. + *
  9. The JWT JWA Specification (RFC 7518, + * Section 3.3) mandates that RSA signing key lengths MUST be 2048 bits or greater. + * {@code RSAKey}s with key lengths less than 2048 bits will be rejected with a + * {@link WeakKeyException}.
  10. + *
  11. Technically any RSA key of length >= 2048 bits may be used with the + * {@link Jwts.SIG#RS256 RS256}, {@link Jwts.SIG#RS384 RS384}, and + * {@link Jwts.SIG#RS512 RS512} algorithms, so we assume an RSA signature algorithm based on the key + * length to parallel similar decisions in the JWT specification for HMAC and ECDSA signature algorithms. + * This is not required - just a convenience.
  12. + *
  13. EdECKeys + * require JDK >= 15 or BouncyCastle in the runtime classpath.
  14. + *
+ * + *

This implementation does not use the {@link Jwts.SIG#PS256 PS256}, + * {@link Jwts.SIG#PS384 PS384}, or {@link Jwts.SIG#PS512 PS512} RSA variants for any + * specified {@link RSAKey} because the the {@link Jwts.SIG#RS256 RS256}, + * {@link Jwts.SIG#RS384 RS384}, and {@link Jwts.SIG#RS512 RS512} algorithms are + * available in the JDK by default while the {@code PS}* variants require either JDK 11 or an additional JCA + * Provider (like BouncyCastle). If you wish to use a {@code PS}* variant with your key, use the + * {@link #signWith(Key, SecureDigestAlgorithm)} method instead.

+ * + *

Finally, this method will throw an {@link InvalidKeyException} for any key that does not match the + * heuristics and requirements documented above, since that inevitably means the Key is either insufficient, + * unsupported, or explicitly disallowed by the JWT specification.

+ * + * @param key the key to use for signing + * @return the builder instance for method chaining. + * @throws InvalidKeyException if the Key is insufficient, unsupported, or explicitly disallowed by the JWT + * specification as described above in recommended signature algorithms. + * @see Jwts.SIG + * @see #signWith(Key, SecureDigestAlgorithm) + * @since 0.10.0 + */ + JwtBuilder signWith(Key key) throws InvalidKeyException; + + /** + * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS. + * + *

Deprecation Notice: Deprecated as of 0.10.0

+ * + *

Use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to + * obtain the {@code Key} and then invoke {@link #signWith(Key)} or + * {@link #signWith(Key, SecureDigestAlgorithm)}.

+ * + *

This method will be removed in the 1.0 release.

+ * + * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. + * @param secretKey the algorithm-specific signing key to use to digitally sign the JWT. + * @return the builder for method chaining. + * @throws InvalidKeyException if the Key is insufficient for the specified algorithm or explicitly disallowed by + * the JWT specification. + * @deprecated as of 0.10.0: use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to + * obtain the {@code Key} and then invoke {@link #signWith(Key)} or + * {@link #signWith(Key, SecureDigestAlgorithm)}. + * This method will be removed in the 1.0 release. + */ + @Deprecated + JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey) throws InvalidKeyException; + + /** + * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS. + * + *

This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting + * byte array is used to invoke {@link #signWith(SignatureAlgorithm, byte[])}.

+ * + *

Deprecation Notice: Deprecated as of 0.10.0, will be removed in the 1.0 release.

+ * + *

This method has been deprecated because the {@code key} argument for this method can be confusing: keys for + * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were + * obtained from the String argument.

+ * + *

This method always expected a String argument that was effectively the same as the result of the following + * (pseudocode):

+ * + *

{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}

+ * + *

However, a non-trivial number of JJWT users were confused by the method signature and attempted to + * use raw password strings as the key argument - for example {@code with(HS256, myPassword)} - which is + * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.

+ * + *

See this + * + * StackOverflow answer explaining why raw (non-base64-encoded) strings are almost always incorrect for + * signature operations.

+ * + *

To perform the correct logic with base64EncodedSecretKey strings with JJWT >= 0.10.0, you may do this:

+ *

+     * byte[] keyBytes = {@link Decoders Decoders}.{@link Decoders#BASE64 BASE64}.{@link Decoder#decode(Object) decode(base64EncodedSecretKey)};
+     * Key key = {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(keyBytes)};
+     * jwtBuilder.with(key); //or {@link #signWith(Key, SignatureAlgorithm)}
+     * 
+ * + *

This method will be removed in the 1.0 release.

+ * + * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. + * @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signing key to use to digitally sign the + * JWT. + * @return the builder for method chaining. + * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as + * described by {@link SignatureAlgorithm#forSigningKey(Key)}. + * @deprecated as of 0.10.0: use {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)} instead. This + * method will be removed in the 1.0 release. + */ + @Deprecated + JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) throws InvalidKeyException; + + /** + * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS. + * + *

It is typically recommended to call the {@link #signWith(Key)} instead for simplicity. + * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if + * you want explicit control over the signature algorithm used with the specified key.

+ * + * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. + * @param key the algorithm-specific signing key to use to digitally sign the JWT. + * @return the builder for method chaining. + * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for + * the specified algorithm. + * @see #signWith(Key) + * @deprecated since 0.10.0. Use {@link #signWith(Key, SecureDigestAlgorithm)} instead. + * This method will be removed before the 1.0 release. + */ + @Deprecated + JwtBuilder signWith(SignatureAlgorithm alg, Key key) throws InvalidKeyException; + + /** + *

Deprecation Notice

+ * + *

This has been deprecated since 0.12.0. Use + * {@link #signWith(Key, SecureDigestAlgorithm)} instead. Standard JWA algorithms + * are represented as instances of this new interface in the {@link Jwts.SIG} + * algorithm registry.

+ * + *

Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS.

+ * + *

It is typically recommended to call the {@link #signWith(Key)} instead for simplicity. + * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if + * you want explicit control over the signature algorithm used with the specified key.

+ * + * @param key the signing key to use to digitally sign the JWT. + * @param alg the JWS algorithm to use with the key to digitally sign the JWT, thereby producing a JWS. + * @return the builder for method chaining. + * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for + * the specified algorithm. + * @see #signWith(Key) + * @since 0.10.0 + * @deprecated since 0.12.0 to use the more flexible {@link #signWith(Key, SecureDigestAlgorithm)}. + */ + @Deprecated + JwtBuilder signWith(Key key, SignatureAlgorithm alg) throws InvalidKeyException; + + /** + * Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS. + * + *

The {@link Jwts.SIG} registry makes available all standard signature + * algorithms defined in the JWA specification.

+ * + *

It is typically recommended to call the {@link #signWith(Key)} instead for simplicity. + * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if + * you want explicit control over the signature algorithm used with the specified key.

+ * + * @param key the signing key to use to digitally sign the JWT. + * @param The type of key accepted by the {@code SignatureAlgorithm}. + * @param alg the JWS algorithm to use with the key to digitally sign the JWT, thereby producing a JWS. + * @return the builder for method chaining. + * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for + * the specified algorithm. + * @see #signWith(Key) + * @see Jwts.SIG + * @since 0.12.0 + */ + JwtBuilder signWith(K key, SecureDigestAlgorithm alg) throws InvalidKeyException; + + /** + * Encrypts the constructed JWT with the specified symmetric {@code key} using the provided {@code enc}ryption + * algorithm, producing a JWE. Because it is a symmetric key, the JWE recipient + * must also have access to the same key to decrypt. + * + *

This method is a convenience method that delegates to + * {@link #encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} + * based on the {@code key} argument:

+ *
    + *
  • If the provided {@code key} is a {@link Password Password} instance, + * the {@code KeyAlgorithm} used will be one of the three JWA-standard password-based key algorithms + * ({@link Jwts.KEY#PBES2_HS256_A128KW PBES2_HS256_A128KW}, + * {@link Jwts.KEY#PBES2_HS384_A192KW PBES2_HS384_A192KW}, or + * {@link Jwts.KEY#PBES2_HS512_A256KW PBES2_HS512_A256KW}) as determined by the {@code enc} algorithm's + * {@link AeadAlgorithm#getKeyBitLength() key length} requirement.
  • + *
  • If the {@code key} is otherwise a standard {@code SecretKey}, the {@code KeyAlgorithm} will be + * {@link Jwts.KEY#DIRECT DIRECT}, indicating that {@code key} should be used directly with the + * {@code enc} algorithm. In this case, the {@code key} argument MUST be of sufficient strength to + * use with the specified {@code enc} algorithm, otherwise an exception will be thrown during encryption. If + * desired, secure-random keys suitable for an {@link AeadAlgorithm} may be generated using the algorithm's + * {@link AeadAlgorithm#key() key()} builder.
  • + *
+ * + * @param key the symmetric encryption key to use with the {@code enc} algorithm. + * @param enc the {@link AeadAlgorithm} algorithm used to encrypt the JWE, usually one of the JWA-standard + * algorithms accessible via {@link Jwts.ENC}. + * @return the JWE builder for method chaining. + * @see Jwts.ENC + */ + JwtBuilder encryptWith(SecretKey key, AeadAlgorithm enc); + + /** + * Encrypts the constructed JWT using the specified {@code enc} algorithm with the symmetric key produced by the + * {@code keyAlg} when invoked with the given {@code key}, producing a JWE. + * + *

This behavior can be illustrated by the following pseudocode, a rough example of what happens during + * {@link #compact() compact}ion:

+ *
+     *     SecretKey encryptionKey = keyAlg.getEncryptionKey(key);           // (1)
+     *     byte[] jweCiphertext = enc.encrypt(payloadBytes, encryptionKey);  // (2)
+ *
    + *
  1. The {@code keyAlg} argument is first invoked with the provided {@code key} argument, resulting in a + * {@link SecretKey}.
  2. + *
  3. This {@code SecretKey} result is used to call the provided {@code enc} encryption algorithm argument, + * resulting in the final JWE ciphertext.
  4. + *
+ * + *

Most application developers will reference one of the JWA + * {@link Jwts.KEY standard key algorithms} and {@link Jwts.ENC standard encryption algorithms} + * when invoking this method, but custom implementations are also supported.

+ * + * @param the type of key that must be used with the specified {@code keyAlg} instance. + * @param key the key used to invoke the provided {@code keyAlg} instance. + * @param keyAlg the key management algorithm that will produce the symmetric {@code SecretKey} to use with the + * {@code enc} algorithm + * @param enc the {@link AeadAlgorithm} algorithm used to encrypt the JWE + * @return the JWE builder for method chaining. + * @see Jwts.ENC + * @see Jwts.KEY + */ + JwtBuilder encryptWith(K key, KeyAlgorithm keyAlg, AeadAlgorithm enc); + + /** + * Compresses the JWT payload using the specified {@link CompressionAlgorithm}. + * + *

If your compact JWTs are large, and you want to reduce their total size during network transmission, this + * can be useful. For example, when embedding JWTs in URLs, some browsers may not support URLs longer than a + * certain length. Using compression can help ensure the compact JWT fits within that length. However, NOTE:

+ * + *

Compatibility Warning

+ * + *

The JWT family of specifications defines compression only for JWE (JSON Web Encryption) + * tokens. Even so, JJWT will also support compression for JWS tokens as well if you choose to use it. + * However, be aware that if you use compression when creating a JWS token, other libraries may not be able to + * parse that JWS token. When using compression for JWS tokens, be sure that all parties accessing the + * JWS token support compression for JWS.

+ * + *

Compression when creating JWE tokens however should be universally accepted for any + * library that supports JWE.

+ * + * @param alg implementation of the {@link CompressionAlgorithm} to be used. + * @return the builder for method chaining. + * @see Jwts.ZIP + * @since 0.12.0 + */ + JwtBuilder compressWith(CompressionAlgorithm alg); + + /** + * Perform Base64Url encoding during {@link #compact() compaction} with the specified Encoder. + * + *

JJWT uses a spec-compliant encoder that works on all supported JDK versions, but you may call this method + * to specify a different encoder if you desire.

+ * + * @param base64UrlEncoder the encoder to use when Base64Url-encoding + * @return the builder for method chaining. + * @see #b64Url(Encoder) + * @since 0.10.0 + * @deprecated since 0.12.0 in favor of {@link #b64Url(Encoder)}. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + JwtBuilder base64UrlEncodeWith(Encoder base64UrlEncoder); + + /** + * Perform Base64Url encoding during {@link #compact() compaction} with the specified {@code OutputStream} Encoder. + * The Encoder's {@link Encoder#encode(Object) encode} method will be given a target {@code OutputStream} to + * wrap, and the resulting (wrapping) {@code OutputStream} will be used for writing, ensuring automatic + * Base64URL-encoding during write operations. + * + *

JJWT uses a spec-compliant encoder that works on all supported JDK versions, but you may call this method + * to specify a different stream encoder if desired.

+ * + * @param encoder the encoder to use when Base64Url-encoding + * @return the builder for method chaining. + * @since 0.12.0 + */ + JwtBuilder b64Url(Encoder encoder); + + /** + * Enables RFC 7797: JSON Web Signature (JWS) + * Unencoded Payload Option if {@code false}, or standard JWT/JWS/JWE payload encoding otherwise. The default + * value is {@code true} per standard RFC behavior rules. + * + *

This value may only be {@code false} for JWSs (signed JWTs). It may not be used for standard + * (unprotected) JWTs or encrypted JWTs (JWEs). The builder will throw an exception during {@link #compact()} if + * {@code false} and a JWS is not being created.

+ * + * @param b64 whether to Base64URL-encode the JWS payload + * @return the builder for method chaining. + */ + JwtBuilder encodePayload(boolean b64); + + /** + * Performs Map-to-JSON serialization with the specified Serializer. This is used by the builder to convert + * JWT/JWS/JWE headers and claims Maps to JSON strings as required by the JWT specification. + * + *

If this method is not called, JJWT will use whatever serializer it can find at runtime, checking for the + * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found + * in the runtime classpath, an exception will be thrown when the {@link #compact()} method is invoked.

+ * + * @param serializer the serializer to use when converting Map objects to JSON strings. + * @return the builder for method chaining. + * @since 0.10.0 + * @deprecated since 0.12.0 in favor of {@link #json(Serializer)} + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + JwtBuilder serializeToJsonWith(Serializer> serializer); + + /** + * Perform Map-to-JSON serialization with the specified Serializer. This is used by the builder to convert + * JWT/JWS/JWE headers and Claims Maps to JSON strings as required by the JWT specification. + * + *

If this method is not called, JJWT will use whatever Serializer it can find at runtime, checking for the + * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found + * in the runtime classpath, an exception will be thrown when the {@link #compact()} method is invoked.

+ * + * @param serializer the Serializer to use when converting Map objects to JSON strings. + * @return the builder for method chaining. + * @since 0.12.0 + */ + JwtBuilder json(Serializer> serializer); + + /** + * Actually builds the JWT and serializes it to a compact, URL-safe string according to the + * JWT Compact Serialization + * rules. + * + * @return A compact URL-safe JWT string. + */ + String compact(); + + /** + * Claims for use with a {@link JwtBuilder} that supports method chaining for standard JWT Claims parameters. + * Once claims are configured, the associated {@link JwtBuilder} may be obtained with the {@link #and() and()} + * method for continued configuration. + * + * @since 0.12.0 + */ + interface BuilderClaims extends MapMutator, ClaimsMutator, + Conjunctor { + } + + /** + * Header for use with a {@link JwtBuilder} that supports method chaining for + * standard JWT, JWS and JWE header parameters. Once header parameters are configured, the associated + * {@link JwtBuilder} may be obtained with the {@link #and() and()} method for continued configuration. + * + * @since 0.12.0 + */ + interface BuilderHeader extends JweHeaderMutator, X509Builder, + Conjunctor { + } +} diff --git a/io/jsonwebtoken/JwtException.java b/io/jsonwebtoken/JwtException.java new file mode 100644 index 0000000..e3990da --- /dev/null +++ b/io/jsonwebtoken/JwtException.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * Base class for JWT-related runtime exceptions. + * + * @since 0.1 + */ +public class JwtException extends RuntimeException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public JwtException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public JwtException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/JwtHandler.java b/io/jsonwebtoken/JwtHandler.java new file mode 100644 index 0000000..faf41a6 --- /dev/null +++ b/io/jsonwebtoken/JwtHandler.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * A JwtHandler is invoked by a {@link io.jsonwebtoken.JwtParser JwtParser} after parsing a JWT to indicate the exact + * type of JWT, JWS or JWE parsed. + * + * @param the type of object to return to the parser caller after handling the parsed JWT. + * @since 0.2 + * @deprecated since 0.12.0 in favor of calling {@link Jwt#accept(JwtVisitor)}. + */ +@SuppressWarnings("DeprecatedIsStillUsed") +@Deprecated +public interface JwtHandler extends JwtVisitor { + + /** + * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is + * an unsecured content JWT. An unsecured content JWT has a byte array payload that is not + * cryptographically signed or encrypted. If the JWT creator set the (optional) + * {@link Header#getContentType() contentType} header value, the application may inspect that value to determine + * how to convert the byte array to the final content type as desired. + * + * @param jwt the parsed unsecured content JWT + * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. + */ + T onContentJwt(Jwt jwt); + + /** + * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is + * a Claims JWT. A Claims JWT has a {@link Claims} payload that is not cryptographically signed or encrypted. + * + * @param jwt the parsed claims JWT + * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. + */ + T onClaimsJwt(Jwt jwt); + + /** + * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is + * a content JWS. A content JWS is a JWT with a byte array payload that has been cryptographically signed. + * If the JWT creator set the (optional) {@link Header#getContentType() contentType} header value, the + * application may inspect that value to determine how to convert the byte array to the final content type + * as desired. + * + *

This method will only be invoked if the cryptographic signature can be successfully verified.

+ * + * @param jws the parsed content JWS + * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. + */ + T onContentJws(Jws jws); + + /** + * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is + * a valid Claims JWS. A Claims JWS is a JWT with a {@link Claims} payload that has been cryptographically signed. + * + *

This method will only be invoked if the cryptographic signature can be successfully verified.

+ * + * @param jws the parsed claims JWS + * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. + */ + T onClaimsJws(Jws jws); + + /** + * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is + * a content JWE. A content JWE is a JWE with a byte array payload that has been encrypted. If the JWT creator set + * the (optional) {@link Header#getContentType() contentType} header value, the application may inspect that + * value to determine how to convert the byte array to the final content type as desired. + * + *

This method will only be invoked if the content JWE can be successfully decrypted.

+ * + * @param jwe the parsed content jwe + * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. + * @since 0.12.0 + */ + T onContentJwe(Jwe jwe); + + /** + * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is + * a valid Claims JWE. A Claims JWE is a JWT with a {@link Claims} payload that has been encrypted. + * + *

This method will only be invoked if the Claims JWE can be successfully decrypted.

+ * + * @param jwe the parsed claims jwe + * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. + * @since 0.12.0 + */ + T onClaimsJwe(Jwe jwe); + +} diff --git a/io/jsonwebtoken/JwtHandlerAdapter.java b/io/jsonwebtoken/JwtHandlerAdapter.java new file mode 100644 index 0000000..6d07a8f --- /dev/null +++ b/io/jsonwebtoken/JwtHandlerAdapter.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * An Adapter implementation of the + * {@link JwtHandler} interface that allows for anonymous subclasses to process only the JWT results that are + * known/expected for a particular use case. + * + *

All of the methods in this implementation throw exceptions: overridden methods represent + * scenarios expected by calling code in known situations. It would be unexpected to receive a JWT that did + * not match parsing expectations, so all non-overridden methods throw exceptions to indicate that the JWT + * input was unexpected.

+ * + * @param the type of object to return to the parser caller after handling the parsed JWT. + * @since 0.2 + */ +public abstract class JwtHandlerAdapter extends SupportedJwtVisitor implements JwtHandler { + + /** + * Default constructor, does not initialize any internal state. + */ + public JwtHandlerAdapter() { + } + + @Override + public T onUnsecuredContent(Jwt jwt) { + return onContentJwt(jwt); // bridge for existing implementations + } + + @Override + public T onUnsecuredClaims(Jwt jwt) { + return onClaimsJwt(jwt); + } + + @Override + public T onVerifiedContent(Jws jws) { + return onContentJws(jws); + } + + @Override + public T onVerifiedClaims(Jws jws) { + return onClaimsJws(jws); + } + + @Override + public T onDecryptedContent(Jwe jwe) { + return onContentJwe(jwe); + } + + @Override + public T onDecryptedClaims(Jwe jwe) { + return onClaimsJwe(jwe); + } + + @Override + public T onContentJwt(Jwt jwt) { + return super.onUnsecuredContent(jwt); + } + + @Override + public T onClaimsJwt(Jwt jwt) { + return super.onUnsecuredClaims(jwt); + } + + @Override + public T onContentJws(Jws jws) { + return super.onVerifiedContent(jws); + } + + @Override + public T onClaimsJws(Jws jws) { + return super.onVerifiedClaims(jws); + } + + @Override + public T onContentJwe(Jwe jwe) { + return super.onDecryptedContent(jwe); + } + + @Override + public T onClaimsJwe(Jwe jwe) { + return super.onDecryptedClaims(jwe); + } +} diff --git a/io/jsonwebtoken/JwtParser.java b/io/jsonwebtoken/JwtParser.java new file mode 100644 index 0000000..df7e173 --- /dev/null +++ b/io/jsonwebtoken/JwtParser.java @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.io.Parser; +import io.jsonwebtoken.security.SecurityException; +import io.jsonwebtoken.security.SignatureException; + +import java.io.InputStream; + +/** + * A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT. + * A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT. + * + * @since 0.1 + */ +public interface JwtParser extends Parser> { + + /** + * Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false} + * otherwise. + * + *

Note that if you are reasonably sure that the token is signed, it is more efficient to attempt to + * parse the token (and catching exceptions if necessary) instead of calling this method first before parsing.

+ * + * @param compact the compact serialized JWT to check + * @return {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false} + * otherwise. + */ + boolean isSigned(CharSequence compact); + + /** + * Parses the specified compact serialized JWT string based on the builder's current configuration state and + * returns the resulting JWT, JWS, or JWE instance. + * + *

Because it is often cumbersome to determine if the result is a JWT, JWS or JWE, or if the payload is a Claims + * or {@code byte[]} array with {@code instanceof} checks, it may be useful to call the result's + * {@link Jwt#accept(JwtVisitor) accept(JwtVisitor)} method for a type-safe callback approach instead of using if-then-else + * {@code instanceof} conditionals. For example, instead of:

+ * + *
+     * // NOT RECOMMENDED:
+     * Jwt<?,?> jwt = parser.parse(input);
+     * if (jwt instanceof Jwe<?>) {
+     *     Jwe<?> jwe = (Jwe<?>)jwt;
+     *     if (jwe.getPayload() instanceof Claims) {
+     *         Jwe<Claims> claimsJwe = (Jwe<Claims>)jwe;
+     *         // do something with claimsJwe
+     *     }
+     * }
+ * + *

the following alternative is usually preferred:

+ * + *
+     * Jwe<Claims> jwe = parser.parse(input).accept({@link Jwe#CLAIMS});
+ * + * @param jwt the compact serialized JWT to parse + * @return the parsed JWT instance + * @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid). + * Invalid JWTs should not be trusted and should be discarded. + * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail + * signature validation should not be trusted and should be discarded. + * @throws SecurityException if the specified JWT string is a JWE and decryption fails + * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time + * before the time this method is invoked. + * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace. + * @see Jwt#accept(JwtVisitor) + */ + Jwt parse(CharSequence jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException, + SecurityException, IllegalArgumentException; + + /** + * Deprecated since 0.12.0 in favor of calling any {@code parse*} method immediately + * followed by invoking the parsed JWT's {@link Jwt#accept(JwtVisitor) accept} method with your preferred visitor. For + * example: + * + *
+     * {@link #parse(CharSequence) parse}(jwt).{@link Jwt#accept(JwtVisitor) accept}({@link JwtVisitor visitor});
+ * + *

This method will be removed before the 1.0 release.

+ * + * @param jwt the compact serialized JWT to parse + * @param handler the handler to invoke when encountering a specific type of JWT + * @param the type of object returned from the {@code handler} + * @return the result returned by the {@code JwtHandler} + * @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid). + * Invalid JWTs should not be trusted and should be discarded. + * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail + * signature validation should not be trusted and should be discarded. + * @throws SecurityException if the specified JWT string is a JWE and decryption fails + * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time + * before the time this method is invoked. + * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace, or if the + * {@code handler} is {@code null}. + * @see Jwt#accept(JwtVisitor) + * @since 0.2 + * @deprecated since 0.12.0 in favor of + * {@link #parse(CharSequence)}.{@link Jwt#accept(JwtVisitor) accept}({@link JwtVisitor visitor}); + */ + @Deprecated + T parse(CharSequence jwt, JwtHandler handler) throws ExpiredJwtException, UnsupportedJwtException, + MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException; + + /** + * Deprecated since 0.12.0 in favor of {@link #parseUnsecuredContent(CharSequence)}. + * + *

This method will be removed before the 1.0 release.

+ * + * @param jwt a compact serialized unsecured content JWT string. + * @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string. + * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured content JWT + * @throws MalformedJwtException if the {@code jwt} string is not a valid JWT + * @throws SignatureException if the {@code jwt} string is actually a JWS and signature validation fails + * @throws SecurityException if the {@code jwt} string is actually a JWE and decryption fails + * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace + * @see #parseUnsecuredContent(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.2 + * @deprecated since 0.12.0 in favor of {@link #parseUnsecuredContent(CharSequence)}. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + Jwt parseContentJwt(CharSequence jwt) throws UnsupportedJwtException, MalformedJwtException, + SignatureException, SecurityException, IllegalArgumentException; + + /** + * Deprecated since 0.12.0 in favor of {@link #parseUnsecuredClaims(CharSequence)}. + * + *

This method will be removed before the 1.0 release.

+ * + * @param jwt a compact serialized unsecured Claims JWT string. + * @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string. + * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured Claims JWT + * @throws MalformedJwtException if the {@code jwt} string is not a valid JWT + * @throws SignatureException if the {@code jwt} string is actually a JWS and signature validation fails + * @throws SecurityException if the {@code jwt} string is actually a JWE and decryption fails + * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace + * @see #parseUnsecuredClaims(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.2 + * @deprecated since 0.12.0 in favor of {@link #parseUnsecuredClaims(CharSequence)}. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + Jwt parseClaimsJwt(CharSequence jwt) throws ExpiredJwtException, UnsupportedJwtException, + MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException; + + /** + * Deprecated since 0.12.0 in favor of {@link #parseSignedContent(CharSequence)}. + * + *

This method will be removed before the 1.0 release.

+ * + * @param jws a compact content JWS string + * @return the parsed and validated content JWS + * @throws UnsupportedJwtException if the {@code jws} argument does not represent a content JWS + * @throws MalformedJwtException if the {@code jws} string is not a valid JWS + * @throws SignatureException if the {@code jws} JWS signature validation fails + * @throws SecurityException if the {@code jws} string is actually a JWE and decryption fails + * @throws IllegalArgumentException if the {@code jws} string is {@code null} or empty or only whitespace + * @see #parseSignedContent(CharSequence) + * @see #parseEncryptedContent(CharSequence) + * @see #parse(CharSequence) + * @since 0.2 + * @deprecated since 0.12.0 in favor of {@link #parseSignedContent(CharSequence)}. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + Jws parseContentJws(CharSequence jws) throws UnsupportedJwtException, MalformedJwtException, SignatureException, + SecurityException, IllegalArgumentException; + + /** + * Deprecated since 0.12.0 in favor of {@link #parseSignedClaims(CharSequence)}. + * + * @param jws a compact Claims JWS string. + * @return the parsed and validated Claims JWS + * @throws UnsupportedJwtException if the {@code claimsJws} argument does not represent an Claims JWS + * @throws MalformedJwtException if the {@code claimsJws} string is not a valid JWS + * @throws SignatureException if the {@code claimsJws} JWS signature validation fails + * @throws SecurityException if the {@code jws} string is actually a JWE and decryption fails + * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time + * before the time this method is invoked. + * @throws IllegalArgumentException if the {@code claimsJws} string is {@code null} or empty or only whitespace + * @see #parseSignedClaims(CharSequence) + * @see #parseEncryptedClaims(CharSequence) + * @see #parse(CharSequence) + * @since 0.2 + * @deprecated since 0.12.0 in favor of {@link #parseSignedClaims(CharSequence)}. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + Jws parseClaimsJws(CharSequence jws) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, + SignatureException, SecurityException, IllegalArgumentException; + + /** + * Parses the {@code jwt} argument, expected to be an unsecured content JWT. If the JWT creator set + * the (optional) {@link Header#getContentType() contentType} header value, the application may inspect that + * value to determine how to convert the byte array to the final content type as desired. + * + *

This is a convenience method logically equivalent to the following:

+ * + *
+     * {@link #parse(CharSequence) parse}(jwt).{@link Jwt#accept(JwtVisitor) accept}({@link
+     * Jwt#UNSECURED_CONTENT});
+ * + * @param jwt a compact unsecured content JWT. + * @return the parsed unsecured content JWT. + * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured content JWT + * @throws JwtException if the {@code jwt} string cannot be parsed or validated as required. + * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace + * @see #parse(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.12.0 + */ + Jwt parseUnsecuredContent(CharSequence jwt) throws JwtException, IllegalArgumentException; + + /** + * Parses the {@code jwt} argument, expected to be an unsecured {@code Claims} JWT. This is a + * convenience method logically equivalent to the following: + * + *
+     * {@link #parse(CharSequence) parse}(jwt).{@link Jwt#accept(JwtVisitor) accept}({@link
+     * Jwt#UNSECURED_CLAIMS});
+ * + * @param jwt a compact unsecured Claims JWT. + * @return the parsed unsecured Claims JWT. + * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured Claims JWT + * @throws JwtException if the {@code jwt} string cannot be parsed or validated as required. + * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace + * @see #parse(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.12.0 + */ + Jwt parseUnsecuredClaims(CharSequence jwt) throws JwtException, IllegalArgumentException; + + /** + * Parses the {@code jws} argument, expected to be a cryptographically-signed content JWS. If the JWS + * creator set the (optional) {@link Header#getContentType() contentType} header value, the application may + * inspect that value to determine how to convert the byte array to the final content type as desired. + * + *

This is a convenience method logically equivalent to the following:

+ * + *
+     * {@link #parse(CharSequence) parse}(jws).{@link Jwt#accept(JwtVisitor) accept}({@link
+     * Jws#CONTENT});
+ * + * @param jws a compact cryptographically-signed content JWS. + * @return the parsed cryptographically-verified content JWS. + * @throws UnsupportedJwtException if the {@code jws} argument does not represent a signed content JWS + * @throws JwtException if the {@code jws} string cannot be parsed or validated as required. + * @throws IllegalArgumentException if the {@code jws} string is {@code null} or empty or only whitespace + * @see #parse(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.12.0 + */ + Jws parseSignedContent(CharSequence jws) throws JwtException, IllegalArgumentException; + + /** + * Parses a JWS known to use the + * RFC 7797: JSON Web Signature (JWS) Unencoded Payload + * Option, using the specified {@code unencodedPayload} for signature verification. + * + *

Unencoded Non-Detached Payload

+ * + *

Note that if the JWS contains a valid unencoded Payload string (what RFC 7797 calls an + * "unencoded non-detached + * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes + * the payload content necessary for signature verification.

+ * + * @param jws the Unencoded Payload JWS to parse. + * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. + * @return the parsed Unencoded Payload. + * @since 0.12.0 + */ + Jws parseSignedContent(CharSequence jws, byte[] unencodedPayload); + + /** + * Parses a JWS known to use the + * RFC 7797: JSON Web Signature (JWS) Unencoded Payload + * Option, using the bytes from the specified {@code unencodedPayload} stream for signature verification. + * + *

Because it is not possible to know how large the {@code unencodedPayload} stream will be, the stream bytes + * will not be buffered in memory, ensuring the resulting {@link Jws} return value's {@link Jws#getPayload()} + * is always empty. This is generally not a concern since the caller already has access to the stream bytes and + * may obtain them independently before or after calling this method if they are needed otherwise.

+ * + *

Unencoded Non-Detached Payload

+ * + *

Note that if the JWS contains a valid unencoded payload String (what RFC 7797 calls an + * "unencoded non-detached + * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes + * the payload content necessary for signature verification. In this case the resulting {@link Jws} return + * value's {@link Jws#getPayload()} will contain the embedded payload String's UTF-8 bytes.

+ * + * @param jws the Unencoded Payload JWS to parse. + * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. + * @return the parsed Unencoded Payload. + * @since 0.12.0 + */ + Jws parseSignedContent(CharSequence jws, InputStream unencodedPayload); + + /** + * Parses the {@code jws} argument, expected to be a cryptographically-signed {@code Claims} JWS. This is a + * convenience method logically equivalent to the following: + * + *
+     * {@link #parse(CharSequence) parse}(jws).{@link Jwt#accept(JwtVisitor) accept}({@link
+     * Jws#CLAIMS});
+ * + * @param jws a compact cryptographically-signed Claims JWS. + * @return the parsed cryptographically-verified Claims JWS. + * @throws UnsupportedJwtException if the {@code jwt} argument does not represent a signed Claims JWT + * @throws JwtException if the {@code jwt} string cannot be parsed or validated as required. + * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace + * @see #parse(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.12.0 + */ + Jws parseSignedClaims(CharSequence jws) throws JwtException, IllegalArgumentException; + + /** + * Parses a JWS known to use the + * RFC 7797: JSON Web Signature (JWS) Unencoded Payload + * Option, using the specified {@code unencodedPayload} for signature verification. + * + *

Unencoded Non-Detached Payload

+ * + *

Note that if the JWS contains a valid unencoded payload String (what RFC 7797 calls an + * "unencoded non-detached + * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes + * the payload content necessary for signature verification and claims creation.

+ * + * @param jws the Unencoded Payload JWS to parse. + * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. + * @return the parsed and validated Claims JWS. + * @throws JwtException if parsing, signature verification, or JWT validation fails. + * @throws IllegalArgumentException if either the {@code jws} or {@code unencodedPayload} are null or empty. + * @since 0.12.0 + */ + Jws parseSignedClaims(CharSequence jws, byte[] unencodedPayload) throws JwtException, IllegalArgumentException; + + /** + * Parses a JWS known to use the + * RFC 7797: JSON Web Signature (JWS) Unencoded Payload + * Option, using the bytes from the specified {@code unencodedPayload} stream for signature verification and + * {@link Claims} creation. + * + *

NOTE: however, because calling this method indicates a completed + * {@link Claims} instance is desired, the specified {@code unencodedPayload} JSON stream will be fully + * read into a Claims instance. If this will be problematic for your application (perhaps if you expect extremely + * large Claims), it is recommended to use the {@link #parseSignedContent(CharSequence, InputStream)} method + * instead.

+ * + *

Unencoded Non-Detached Payload

+ * + *

Note that if the JWS contains a valid unencoded Payload string (what RFC 7797 calls an + * "unencoded non-detached + * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes + * the payload content necessary for signature verification and Claims creation.

+ * + * @param jws the Unencoded Payload JWS to parse. + * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. + * @return the parsed and validated Claims JWS. + * @throws JwtException if parsing, signature verification, or JWT validation fails. + * @throws IllegalArgumentException if either the {@code jws} or {@code unencodedPayload} are null or empty. + * @since 0.12.0 + */ + Jws parseSignedClaims(CharSequence jws, InputStream unencodedPayload) throws JwtException, IllegalArgumentException; + + /** + * Parses the {@code jwe} argument, expected to be an encrypted content JWE. If the JWE + * creator set the (optional) {@link Header#getContentType() contentType} header value, the application may + * inspect that value to determine how to convert the byte array to the final content type as desired. + * + *

This is a convenience method logically equivalent to the following:

+ * + *
+     * {@link #parse(CharSequence) parse}(jwe).{@link Jwt#accept(JwtVisitor) accept}({@link
+     * Jwe#CONTENT});
+ * + * @param jwe a compact encrypted content JWE. + * @return the parsed decrypted content JWE. + * @throws UnsupportedJwtException if the {@code jwe} argument does not represent an encrypted content JWE + * @throws JwtException if the {@code jwe} string cannot be parsed or validated as required. + * @throws IllegalArgumentException if the {@code jwe} string is {@code null} or empty or only whitespace + * @see #parse(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.12.0 + */ + Jwe parseEncryptedContent(CharSequence jwe) throws JwtException, IllegalArgumentException; + + /** + * Parses the {@code jwe} argument, expected to be an encrypted {@code Claims} JWE. This is a + * convenience method logically equivalent to the following: + * + *
+     * {@link #parse(CharSequence) parse}(jwe).{@link Jwt#accept(JwtVisitor) accept}({@link
+     * Jwe#CLAIMS});
+ * + * @param jwe a compact encrypted Claims JWE. + * @return the parsed decrypted Claims JWE. + * @throws UnsupportedJwtException if the {@code jwe} argument does not represent an encrypted Claims JWE. + * @throws JwtException if the {@code jwe} string cannot be parsed or validated as required. + * @throws IllegalArgumentException if the {@code jwe} string is {@code null} or empty or only whitespace + * @see #parse(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.12.0 + */ + Jwe parseEncryptedClaims(CharSequence jwe) throws JwtException, IllegalArgumentException; +} diff --git a/io/jsonwebtoken/JwtParserBuilder.java b/io/jsonwebtoken/JwtParserBuilder.java new file mode 100644 index 0000000..7993669 --- /dev/null +++ b/io/jsonwebtoken/JwtParserBuilder.java @@ -0,0 +1,826 @@ +/* + * Copyright (C) 2019 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.io.CompressionAlgorithm; +import io.jsonwebtoken.io.Decoder; +import io.jsonwebtoken.io.Deserializer; +import io.jsonwebtoken.lang.Builder; +import io.jsonwebtoken.lang.Conjunctor; +import io.jsonwebtoken.lang.NestedCollection; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.KeyAlgorithm; +import io.jsonwebtoken.security.SecureDigestAlgorithm; + +import javax.crypto.SecretKey; +import java.io.InputStream; +import java.security.Key; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.util.Date; +import java.util.Map; + +/** + * A builder to construct a {@link JwtParser}. Example usage: + *
{@code
+ *     Jwts.parser()
+ *         .requireIssuer("https://issuer.example.com")
+ *         .verifyWith(...)
+ *         .build()
+ *         .parse(jwtString)
+ * }
+ * + * @since 0.11.0 + */ +@SuppressWarnings("JavadocLinkAsPlainText") +public interface JwtParserBuilder extends Builder { + + /** + * Enables parsing of Unsecured JWTs (JWTs with an 'alg' (Algorithm) header value of + * 'none' or missing the 'alg' header entirely). Be careful when calling this method - one should fully understand + * Unsecured JWS Security Considerations + * before enabling this feature. + *

If this method is not called, Unsecured JWTs are disabled by default as mandated by + * RFC 7518, Section + * 3.6.

+ * + * @return the builder for method chaining. + * @see Unsecured JWS Security Considerations + * @see Using the Algorithm "none" + * @see Jwts.SIG#NONE + * @see #unsecuredDecompression() + * @since 0.12.0 + */ + JwtParserBuilder unsecured(); + + /** + * If the parser is {@link #unsecured()}, calling this method additionally enables + * payload decompression of Unsecured JWTs (JWTs with an 'alg' (Algorithm) header value of 'none') that also have + * a 'zip' (Compression) header. This behavior is disabled by default because using compression + * algorithms with data from unverified (unauthenticated) parties can be susceptible to Denial of Service attacks + * and other data integrity problems as described in + * In the + * Compression Hornet’s Nest: A Security Study of Data Compression in Network Services. + * + *

Because this behavior is only relevant if the parser is unsecured, + * calling this method without also calling {@link #unsecured()} will result in a build exception, as the + * incongruent state could reflect a misunderstanding of both behaviors which should be remedied by the + * application developer.

+ * + * As is the case for {@link #unsecured()}, be careful when calling this method - one should fully + * understand + * Unsecured JWS Security Considerations + * before enabling this feature. + * + * @return the builder for method chaining. + * @see Unsecured JWS Security Considerations + * @see In the + * Compression Hornet’s Nest: A Security Study of Data Compression in Network Services + * @see Jwts.SIG#NONE + * @see #unsecured() + * @since 0.12.0 + */ + JwtParserBuilder unsecuredDecompression(); + + /** + * Configures the {@link ProtectedHeader} parameter names used in JWT extensions supported by the application. If + * the parser encounters a Protected JWT that {@link ProtectedHeader#getCritical() requires} extensions, and + * those extensions' header names are not specified via this method, the parser will reject that JWT. + * + *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser + * configuration, for example:

+ *
+     * parserBuilder.critical().add("headerName").{@link Conjunctor#and() and()} // etc...
+ * + *

Extension Behavior

+ * + *

The {@code critical} collection only identifies header parameter names that are used in extensions supported + * by the application. Application developers, not JJWT, MUST perform the associated extension behavior + * using the parsed JWT.

+ * + *

Continued Parser Configuration

+ *

When finished, use the collection's + * {@link Conjunctor#and() and()} method to continue parser configuration, for example: + *

+     * Jwts.parser()
+     *     .critical().add("headerName").{@link Conjunctor#and() and()} // return parent
+     * // resume parser configuration...
+ * + * @return the {@link NestedCollection} to use for {@code crit} configuration. + * @see ProtectedHeader#getCritical() + * @since 0.12.0 + */ + NestedCollection critical(); + + /** + * Sets the JCA Provider to use during cryptographic signature and key decryption operations, or {@code null} if the + * JCA subsystem preferred provider should be used. + * + * @param provider the JCA Provider to use during cryptographic signature and decryption operations, or {@code null} + * if the JCA subsystem preferred provider should be used. + * @return the builder for method chaining. + * @since 0.12.0 + */ + JwtParserBuilder provider(Provider provider); + + /** + * Ensures that the specified {@code jti} exists in the parsed JWT. If missing or if the parsed + * value does not equal the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param id the required value of the {@code jti} header parameter. + * @return the parser builder for method chaining. + * @see MissingClaimException + * @see IncorrectClaimException + */ + JwtParserBuilder requireId(String id); + + /** + * Ensures that the specified {@code sub} exists in the parsed JWT. If missing or if the parsed + * value does not equal the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param subject the required value of the {@code sub} header parameter. + * @return the parser builder for method chaining. + * @see MissingClaimException + * @see IncorrectClaimException + */ + JwtParserBuilder requireSubject(String subject); + + /** + * Ensures that the specified {@code aud} exists in the parsed JWT. If missing or if the parsed + * value does not contain the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param audience the required value of the {@code aud} header parameter. + * @return the parser builder for method chaining. + * @see MissingClaimException + * @see IncorrectClaimException + */ + JwtParserBuilder requireAudience(String audience); + + /** + * Ensures that the specified {@code iss} exists in the parsed JWT. If missing or if the parsed + * value does not equal the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param issuer the required value of the {@code iss} header parameter. + * @return the parser builder for method chaining. + * @see MissingClaimException + * @see IncorrectClaimException + */ + JwtParserBuilder requireIssuer(String issuer); + + /** + * Ensures that the specified {@code iat} exists in the parsed JWT. If missing or if the parsed + * value does not equal the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param issuedAt the required value of the {@code iat} header parameter. + * @return the parser builder for method chaining. + * @see MissingClaimException + * @see IncorrectClaimException + */ + JwtParserBuilder requireIssuedAt(Date issuedAt); + + /** + * Ensures that the specified {@code exp} exists in the parsed JWT. If missing or if the parsed + * value does not equal the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param expiration the required value of the {@code exp} header parameter. + * @return the parser builder for method chaining. + * @see MissingClaimException + * @see IncorrectClaimException + */ + JwtParserBuilder requireExpiration(Date expiration); + + /** + * Ensures that the specified {@code nbf} exists in the parsed JWT. If missing or if the parsed + * value does not equal the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param notBefore the required value of the {@code npf} header parameter. + * @return the parser builder for method chaining + * @see MissingClaimException + * @see IncorrectClaimException + */ + JwtParserBuilder requireNotBefore(Date notBefore); + + /** + * Ensures that the specified {@code claimName} exists in the parsed JWT. If missing or if the parsed + * value does not equal the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param claimName the name of a claim that must exist + * @param value the required value of the specified {@code claimName} + * @return the parser builder for method chaining. + * @see MissingClaimException + * @see IncorrectClaimException + */ + JwtParserBuilder require(String claimName, Object value); + + /** + * Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT. + * The parser uses a default Clock implementation that simply returns {@code new Date()} when called. + * + * @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT. + * @return the parser builder for method chaining. + * @deprecated since 0.12.0 for the more modern builder-style named {@link #clock(Clock)} method. + * This method will be removed before the JJWT 1.0 release. + */ + @Deprecated + JwtParserBuilder setClock(Clock clock); + + /** + * Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT. + * The parser uses a default Clock implementation that simply returns {@code new Date()} when called. + * + * @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT. + * @return the parser builder for method chaining. + */ + JwtParserBuilder clock(Clock clock); + + /** + * Sets the amount of clock skew in seconds to tolerate when verifying the local time against the {@code exp} + * and {@code nbf} claims. + * + * @param seconds the number of seconds to tolerate for clock skew when verifying {@code exp} or {@code nbf} claims. + * @return the parser builder for method chaining. + * @throws IllegalArgumentException if {@code seconds} is a value greater than {@code Long.MAX_VALUE / 1000} as + * any such value would cause numeric overflow when multiplying by 1000 to obtain + * a millisecond value. + * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named + * {@link #clockSkewSeconds(long)}. This method will be removed before the JJWT 1.0 release. + */ + @Deprecated + JwtParserBuilder setAllowedClockSkewSeconds(long seconds) throws IllegalArgumentException; + + /** + * Sets the amount of clock skew in seconds to tolerate when verifying the local time against the {@code exp} + * and {@code nbf} claims. + * + * @param seconds the number of seconds to tolerate for clock skew when verifying {@code exp} or {@code nbf} claims. + * @return the parser builder for method chaining. + * @throws IllegalArgumentException if {@code seconds} is a value greater than {@code Long.MAX_VALUE / 1000} as + * any such value would cause numeric overflow when multiplying by 1000 to obtain + * a millisecond value. + */ + JwtParserBuilder clockSkewSeconds(long seconds) throws IllegalArgumentException; + + /** + *

Deprecation Notice

+ * + *

This method has been deprecated since 0.12.0 and will be removed before 1.0. It was not + * readily obvious to many JJWT users that this method was for bytes that pertained only to HMAC + * {@code SecretKey}s, and could be confused with keys of other types. It is better to obtain a type-safe + * {@link SecretKey} instance and call {@link #verifyWith(SecretKey)} instead.

+ * + *

Previous Documentation

+ * + *

Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not + * a JWS (no signature), this key is not used.

+ * + *

Note that this key MUST be a valid key for the signature algorithm found in the JWT header + * (as the {@code alg} header parameter).

+ * + *

This method overwrites any previously set key.

+ * + * @param key the algorithm-specific signature verification key used to validate any discovered JWS digital + * signature. + * @return the parser builder for method chaining. + * @deprecated since 0.12.0 in favor of {@link #verifyWith(SecretKey)} for type safety and name + * congruence with the {@link #decryptWith(SecretKey)} method. + */ + @Deprecated + JwtParserBuilder setSigningKey(byte[] key); + + /** + *

Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0

+ * + *

This method has been deprecated because the {@code key} argument for this method can be confusing: keys for + * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were + * obtained from the String argument.

+ * + *

This method always expected a String argument that was effectively the same as the result of the following + * (pseudocode):

+ * + *

{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}

+ * + *

However, a non-trivial number of JJWT users were confused by the method signature and attempted to + * use raw password strings as the key argument - for example {@code setSigningKey(myPassword)} - which is + * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.

+ * + *

See this + * + * StackOverflow answer explaining why raw (non-base64-encoded) strings are almost always incorrect for + * signature operations.

+ * + *

Finally, please use the {@link #verifyWith(SecretKey)} method instead, as this method (and likely + * {@link #setSigningKey(byte[])}) will be removed before the 1.0.0 release.

+ * + *

Previous JavaDoc

+ * + *

This is a convenience method that equates to the following:

+ * + *
+     * byte[] bytes = Decoders.{@link io.jsonwebtoken.io.Decoders#BASE64 BASE64}.decode(base64EncodedSecretKey);
+     * Key key = Keys.{@link io.jsonwebtoken.security.Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor}(bytes);
+     * return {@link #verifyWith(SecretKey) verifyWith}(key);
+ * + * @param base64EncodedSecretKey BASE64-encoded HMAC-SHA key bytes used to create a Key which will be used to + * verify all encountered JWS digital signatures. + * @return the parser builder for method chaining. + * @deprecated in favor of {@link #verifyWith(SecretKey)} as explained in the above Deprecation Notice, + * and will be removed in 1.0.0. + */ + @Deprecated + JwtParserBuilder setSigningKey(String base64EncodedSecretKey); + + /** + *

Deprecation Notice

+ * + *

This method is being renamed to accurately reflect its purpose - the key is not technically a signing key, + * it is a signature verification key, and the two concepts can be different, especially with asymmetric key + * cryptography. The method has been deprecated since 0.12.0 in favor of + * {@link #verifyWith(SecretKey)} for type safety, to reflect accurate naming of the concept, and for name + * congruence with the {@link #decryptWith(SecretKey)} method.

+ * + *

This method merely delegates directly to {@link #verifyWith(SecretKey)} or {@link #verifyWith(PublicKey)}}.

+ * + * @param key the algorithm-specific signature verification key to use to verify all encountered JWS digital + * signatures. + * @return the parser builder for method chaining. + * @deprecated since 0.12.0 in favor of {@link #verifyWith(SecretKey)} for naming congruence with the + * {@link #decryptWith(SecretKey)} method. + */ + @Deprecated + JwtParserBuilder setSigningKey(Key key); + + /** + * Sets the signature verification SecretKey used to verify all encountered JWS signatures. If the encountered JWT + * string is not a JWS (e.g. unsigned or a JWE), this key is not used. + * + *

This is a convenience method to use in a specific scenario: when the parser will only ever encounter + * JWSs with signatures that can always be verified by a single SecretKey. This also implies that this key + * MUST be a valid key for the signature algorithm ({@code alg} header) used for the JWS.

+ * + *

If there is any chance that the parser will also encounter JWEs, or JWSs that need different signature + * verification keys based on the JWS being parsed, it is strongly recommended to configure your own + * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

+ * + *

Calling this method overrides any previously set signature verification key.

+ * + * @param key the signature verification key to use to verify all encountered JWS digital signatures. + * @return the parser builder for method chaining. + * @see #verifyWith(PublicKey) + * @since 0.12.0 + */ + JwtParserBuilder verifyWith(SecretKey key); + + /** + * Sets the signature verification PublicKey used to verify all encountered JWS signatures. If the encountered JWT + * string is not a JWS (e.g. unsigned or a JWE), this key is not used. + * + *

This is a convenience method to use in a specific scenario: when the parser will only ever encounter + * JWSs with signatures that can always be verified by a single PublicKey. This also implies that this key + * MUST be a valid key for the signature algorithm ({@code alg} header) used for the JWS.

+ * + *

If there is any chance that the parser will also encounter JWEs, or JWSs that need different signature + * verification keys based on the JWS being parsed, it is strongly recommended to configure your own + * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

+ * + *

Calling this method overrides any previously set signature verification key.

+ * + * @param key the signature verification key to use to verify all encountered JWS digital signatures. + * @return the parser builder for method chaining. + * @see #verifyWith(SecretKey) + * @since 0.12.0 + */ + JwtParserBuilder verifyWith(PublicKey key); + + /** + * Sets the decryption SecretKey used to decrypt all encountered JWEs. If the encountered JWT string is not a + * JWE (e.g. a JWS), this key is not used. + * + *

This is a convenience method to use in specific circumstances: when the parser will only ever encounter + * JWEs that can always be decrypted by a single SecretKey. This also implies that this key MUST be a valid + * key for both the key management algorithm ({@code alg} header) and the content encryption algorithm + * ({@code enc} header) used for the JWE.

+ * + *

If there is any chance that the parser will also encounter JWSs, or JWEs that need different decryption + * keys based on the JWE being parsed, it is strongly recommended to configure your own + * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

+ * + *

Calling this method overrides any previously set decryption key.

+ * + * @param key the algorithm-specific decryption key to use to decrypt all encountered JWEs. + * @return the parser builder for method chaining. + * @see #decryptWith(PrivateKey) + * @since 0.12.0 + */ + JwtParserBuilder decryptWith(SecretKey key); + + /** + * Sets the decryption PrivateKey used to decrypt all encountered JWEs. If the encountered JWT string is not a + * JWE (e.g. a JWS), this key is not used. + * + *

This is a convenience method to use in specific circumstances: when the parser will only ever encounter JWEs + * that can always be decrypted by a single PrivateKey. This also implies that this key MUST be a valid + * key for the JWE's key management algorithm ({@code alg} header).

+ * + *

If there is any chance that the parser will also encounter JWSs, or JWEs that need different decryption + * keys based on the JWE being parsed, it is strongly recommended to configure your own + * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

+ * + *

Calling this method overrides any previously set decryption key.

+ * + * @param key the algorithm-specific decryption key to use to decrypt all encountered JWEs. + * @return the parser builder for method chaining. + * @see #decryptWith(SecretKey) + * @since 0.12.0 + */ + JwtParserBuilder decryptWith(PrivateKey key); + + /** + * Sets the {@link Locator} used to acquire any signature verification or decryption key needed during parsing. + *
    + *
  • If the parsed String is a JWS, the {@code Locator} will be called to find the appropriate key + * necessary to verify the JWS signature.
  • + *
  • If the parsed String is a JWE, it will be called to find the appropriate decryption key.
  • + *
+ * + *

A key {@code Locator} is necessary when the signature verification or decryption key is not + * already known before parsing the JWT and the JWT header must be inspected first to determine how to + * look up the verification or decryption key. Once returned by the locator, the JwtParser will then either + * verify the JWS signature or decrypt the JWE payload with the returned key. For example:

+ * + *
+     * Jws<Claims> jws = Jwts.parser().keyLocator(new Locator<Key>() {
+     *         @Override
+     *         public Key locate(Header<?> header) {
+     *             if (header instanceof JwsHeader) {
+     *                 return getSignatureVerificationKey((JwsHeader)header); // implement me
+     *             } else {
+     *                 return getDecryptionKey((JweHeader)header); // implement me
+     *             }
+     *         }})
+     *     .build()
+     *     .parseSignedClaims(compact);
+     * 
+ * + *

A Key {@code Locator} is invoked once during parsing before performing decryption or signature verification.

+ * + *

Provider-constrained Keys

+ * + *

If any verification or decryption key returned from a Key {@code Locator} must be used with a specific + * security {@link Provider} (such as for PKCS11 or Hardware Security Module (HSM) keys), you must make that + * Provider available for JWT parsing in one of 3 ways, listed in order of recommendation and simplicity:

+ * + *
    + *
  1. + * Configure the Provider in the JVM, either by modifying the {@code java.security} file or by + * registering the Provider dynamically via + * {@link java.security.Security#addProvider(Provider) Security.addProvider(Provider)}. This is the + * recommended approach so you do not need to modify code anywhere that may need to parse JWTs.
  2. + *
  3. Specify the {@code Provider} as the {@code JwtParser} default via {@link #provider(Provider)}. This will + * ensure the provider is used by default with all located keys unless overridden by a + * key-specific Provider. This is only recommended when you are confident that all JWTs encountered by the + * parser instance will use keys attributed to the same {@code Provider}, unless overridden by a specific + * key.
  4. + *
  5. Associate the {@code Provider} with a specific key so it is used for that key only. This option + * is useful if some located keys require a specific provider, while other located keys can assume a + * default provider.
  6. + *
+ * + *

If you need to use option #3, you associate a key for the {@code JwtParser}'s needs by using a + * key builder before returning the key as the {@code Locator} return value. For example:

+ *
+     *     public Key locate(Header<?> header) {
+     *         PrivateKey key = findKey(header); // or SecretKey
+     *         Provider keySpecificProvider = getKeyProvider(key); // implement me
+     *         // associate the key with its required provider:
+     *         return Keys.builder(key).provider(keySpecificProvider).build();
+     *     }
+ * + * @param keyLocator the locator used to retrieve decryption or signature verification keys. + * @return the parser builder for method chaining. + * @since 0.12.0 + */ + JwtParserBuilder keyLocator(Locator keyLocator); + + /** + *

Deprecation Notice

+ * + *

This method has been deprecated as of JJWT version 0.12.0 because it only supports key location + * for JWSs (signed JWTs) instead of both signed (JWS) and encrypted (JWE) scenarios. Use the + * {@link #keyLocator(Locator) keyLocator} method instead to ensure a locator that can work for both JWS and + * JWE inputs. This method will be removed for the 1.0 release.

+ * + *

Previous Documentation

+ * + *

Sets the {@link SigningKeyResolver} used to acquire the signing key that should be used to verify + * a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used.

+ * + *

Specifying a {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing + * the JWT and the JWT header or payload (content byte array or Claims) must be inspected first to determine how to + * look up the signing key. Once returned by the resolver, the JwtParser will then verify the JWS signature with the + * returned key. For example:

+ * + *
+     * Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
+     *         @Override
+     *         public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
+     *             //inspect the header or claims, lookup and return the signing key
+     *             return getSigningKey(header, claims); //implement me
+     *         }})
+     *     .build().parseSignedClaims(compact);
+     * 
+ * + *

A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.

+ * + * @param signingKeyResolver the signing key resolver used to retrieve the signing key. + * @return the parser builder for method chaining. + * @deprecated since 0.12.0 in favor of {@link #keyLocator(Locator)} + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + JwtParserBuilder setSigningKeyResolver(SigningKeyResolver signingKeyResolver); + + /** + * Configures the parser's supported {@link AeadAlgorithm}s used to decrypt JWE payloads. If the parser + * encounters a JWE {@link JweHeader#getEncryptionAlgorithm() enc} header value that equals an + * AEAD algorithm's {@link Identifiable#getId() id}, that algorithm will be used to decrypt the JWT + * payload. + * + *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser + * configuration, for example:

+ *
+     * parserBuilder.enc().add(anAeadAlgorithm).{@link Conjunctor#and() and()} // etc...
+ * + *

Standard Algorithms and Overrides

+ * + *

All JWA-standard AEAD encryption algorithms in the {@link Jwts.ENC} registry are supported by default and + * do not need to be added. The collection may be useful however for removing some algorithms (for example, + * any algorithms not used by the application, or those not compatible with application security requirements), + * or for adding custom implementations.

+ * + *

Custom Implementations

+ * + *

There may be only one registered {@code AeadAlgorithm} per algorithm {@code id}, and any algorithm + * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a + * duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. But beware: + * + *

+ * Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will + * replace (override) the JJWT standard algorithm implementation.
+ * + *

This is to allow application developers to favor their + * own implementations over JJWT's default implementations if necessary (for example, to support legacy or + * custom behavior).

+ * + * @return the {@link NestedCollection} to use to configure the AEAD encryption algorithms available when parsing. + * @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) + * @see Jwts.ENC + * @see "enc" (Encryption Algorithm) Header Parameter + * @see Encryption Algorithm Name (id) requirements + * @since 0.12.0 + */ + NestedCollection enc(); + + /** + * Configures the parser's supported {@link KeyAlgorithm}s used to obtain a JWE's decryption key. If the + * parser encounters a JWE {@link JweHeader#getAlgorithm()} alg} header value that equals a {@code KeyAlgorithm}'s + * {@link Identifiable#getId() id}, that key algorithm will be used to obtain the JWE's decryption key. + * + *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser + * configuration, for example:

+ *
+     * parserBuilder.key().add(aKeyAlgorithm).{@link Conjunctor#and() and()} // etc...
+ * + *

Standard Algorithms and Overrides

+ * + *

All JWA-standard key encryption algorithms in the {@link Jwts.KEY} registry are supported by default and + * do not need to be added. The collection may be useful however for removing some algorithms (for example, + * any algorithms not used by the application, or those not compatible with application security requirements), + * or for adding custom implementations.

+ * + *

Custom Implementations

+ * + *

There may be only one registered {@code KeyAlgorithm} per algorithm {@code id}, and any algorithm + * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a + * duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. But beware: + * + *

+ * Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will + * replace (override) the JJWT standard algorithm implementation.
+ * + *

This is to allow application developers to favor their + * own implementations over JJWT's default implementations if necessary (for example, to support legacy or + * custom behavior).

+ * + * @return the {@link NestedCollection} to use to configure the key algorithms available when parsing. + * @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) + * @see Jwts.KEY + * @see JWE "alg" (Algorithm) Header Parameter + * @see Key Algorithm Name (id) requirements + * @since 0.12.0 + */ + NestedCollection, JwtParserBuilder> key(); + + /** + * Configures the parser's supported + * {@link io.jsonwebtoken.security.SignatureAlgorithm SignatureAlgorithm} and + * {@link io.jsonwebtoken.security.MacAlgorithm MacAlgorithm}s used to verify JWS signatures. If the parser + * encounters a JWS {@link ProtectedHeader#getAlgorithm() alg} header value that equals a signature or MAC + * algorithm's {@link Identifiable#getId() id}, that algorithm will be used to verify the JWS signature. + * + *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser + * configuration, for example:

+ *
+     * parserBuilder.sig().add(aSignatureAlgorithm).{@link Conjunctor#and() and()} // etc...
+ * + *

Standard Algorithms and Overrides

+ * + *

All JWA-standard signature and MAC algorithms in the {@link Jwts.SIG} registry are supported by default and + * do not need to be added. The collection may be useful however for removing some algorithms (for example, + * any algorithms not used by the application, or those not compatible with application security requirements), or + * for adding custom implementations.

+ * + *

Custom Implementations

+ * + *

There may be only one registered {@code SecureDigestAlgorithm} per algorithm {@code id}, and any algorithm + * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a + * duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. But beware: + * + *

+ * Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will + * replace (override) the JJWT standard algorithm implementation.
+ * + *

This is to allow application developers to favor their + * own implementations over JJWT's default implementations if necessary (for example, to support legacy or + * custom behavior).

+ * + * @return the {@link NestedCollection} to use to configure the signature and MAC algorithms available when parsing. + * @see JwtBuilder#signWith(Key, SecureDigestAlgorithm) + * @see Jwts.SIG + * @see JWS "alg" (Algorithm) Header Parameter + * @see Algorithm Name (id) requirements + * @since 0.12.0 + */ + NestedCollection, JwtParserBuilder> sig(); + + /** + * Configures the parser's supported {@link CompressionAlgorithm}s used to decompress JWT payloads. If the parser + * encounters a JWT {@link ProtectedHeader#getCompressionAlgorithm() zip} header value that equals a + * compression algorithm's {@link Identifiable#getId() id}, that algorithm will be used to decompress the JWT + * payload. + * + *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser + * configuration, for example:

+ *
+     * parserBuilder.zip().add(aCompressionAlgorithm).{@link Conjunctor#and() and()} // etc...
+ * + *

Standard Algorithms and Overrides

+ * + *

All JWA-standard compression algorithms in the {@link Jwts.ZIP} registry are supported by default and + * do not need to be added. The collection may be useful however for removing some algorithms (for example, + * any algorithms not used by the application), or for adding custom implementations.

+ * + *

Custom Implementations

+ * + *

There may be only one registered {@code CompressionAlgorithm} per algorithm {@code id}, and any algorithm + * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a + * duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. But beware: + * + *

+ * Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will + * replace (override) the JJWT standard algorithm implementation.
+ * + *

This is to allow application developers to favor their + * own implementations over JJWT's default implementations if necessary (for example, to support legacy or + * custom behavior).

+ * + * @return the {@link NestedCollection} to use to configure the compression algorithms available when parsing. + * @see JwtBuilder#compressWith(CompressionAlgorithm) + * @see Jwts.ZIP + * @see "zip" (Compression Algorithm) Header Parameter + * @see Compression Algorithm Name (id) requirements + * @since 0.12.0 + */ + NestedCollection zip(); + + /** + *

Deprecated as of JJWT 0.12.0. This method will be removed before the 1.0 release.

+ * + *

This method has been deprecated as of JJWT version 0.12.0 because it imposed unnecessary + * implementation requirements on application developers when simply adding to a compression algorithm collection + * would suffice. Use the {@link #zip()} method instead to add + * any custom algorithm implementations without needing to also implement a Locator implementation.

+ * + *

Previous Documentation

+ *

+ * Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to + * decompress the JWT body. If the parsed JWT is not compressed, this resolver is not used. + * + *

WARNING: Compression is not defined by the JWS Specification - only the JWE Specification - and it is + * not expected that other libraries (including JJWT versions < 0.6.0) are able to consume a compressed JWS + * body correctly.

+ * + *

Default Support

+ * + *

JJWT's default {@link JwtParser} implementation supports both the {@link Jwts.ZIP#DEF DEF} + * and {@link Jwts.ZIP#GZIP GZIP} algorithms by default - you do not need to + * specify a {@code CompressionCodecResolver} in these cases.

+ * + * @param compressionCodecResolver the compression codec resolver used to decompress the JWT body. + * @return the parser builder for method chaining. + * @deprecated since 0.12.0 in favor of {@link #zip()}. This method will be removed before the + * 1.0 release. + */ + @Deprecated + JwtParserBuilder setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver); + + /** + * Perform Base64Url decoding with the specified Decoder + * + *

JJWT uses a spec-compliant decoder that works on all supported JDK versions, but you may call this method + * to specify a different decoder if you desire.

+ * + * @param base64UrlDecoder the decoder to use when Base64Url-decoding + * @return the parser builder for method chaining. + * @deprecated since 0.12.0 in favor of {@link #b64Url(Decoder)}. This method will be removed + * before the JJWT 1.0 release. + */ + @Deprecated + JwtParserBuilder base64UrlDecodeWith(Decoder base64UrlDecoder); + + /** + * Perform Base64Url decoding during parsing with the specified {@code InputStream} Decoder. + * The Decoder's {@link Decoder#decode(Object) decode} method will be given a source {@code InputStream} to + * wrap, and the resulting (wrapping) {@code InputStream} will be used for reading , ensuring automatic + * Base64URL-decoding during read operations. + * + *

JJWT uses a spec-compliant decoder that works on all supported JDK versions, but you may call this method + * to specify a different stream decoder if desired.

+ * + * @param base64UrlDecoder the stream decoder to use when Base64Url-decoding + * @return the parser builder for method chaining. + */ + JwtParserBuilder b64Url(Decoder base64UrlDecoder); + + /** + * Uses the specified deserializer to convert JSON Strings (UTF-8 byte arrays) into Java Map objects. This is + * used by the parser after Base64Url-decoding to convert JWT/JWS/JWT JSON headers and claims into Java Map + * objects. + * + *

If this method is not called, JJWT will use whatever deserializer it can find at runtime, checking for the + * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found + * in the runtime classpath, an exception will be thrown when one of the various {@code parse}* methods is + * invoked.

+ * + * @param deserializer the deserializer to use when converting JSON Strings (UTF-8 byte arrays) into Map objects. + * @return the builder for method chaining. + * @deprecated since 0.12.0 in favor of {@link #json(Deserializer)}. + * This method will be removed before the JJWT 1.0 release. + */ + @Deprecated + JwtParserBuilder deserializeJsonWith(Deserializer> deserializer); + + /** + * Uses the specified JSON {@link Deserializer} to deserialize JSON (UTF-8 byte streams) into Java Map objects. + * This is used by the parser after Base64Url-decoding to convert JWT/JWS/JWT headers and Claims into Java Map + * instances. + * + *

If this method is not called, JJWT will use whatever Deserializer it can find at runtime, checking for the + * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found + * in the runtime classpath, an exception will be thrown when one of the various {@code parse}* methods is + * invoked.

+ * + * @param deserializer the deserializer to use to deserialize JSON (UTF-8 byte streams) into Map instances. + * @return the builder for method chaining. + * @since 0.12.0 + */ + JwtParserBuilder json(Deserializer> deserializer); + + /** + * Returns an immutable/thread-safe {@link JwtParser} created from the configuration from this JwtParserBuilder. + * + * @return an immutable/thread-safe JwtParser created from the configuration from this JwtParserBuilder. + */ + JwtParser build(); +} diff --git a/io/jsonwebtoken/JwtVisitor.java b/io/jsonwebtoken/JwtVisitor.java new file mode 100644 index 0000000..3b66738 --- /dev/null +++ b/io/jsonwebtoken/JwtVisitor.java @@ -0,0 +1,68 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * A JwtVisitor supports the Visitor design pattern for + * {@link Jwt} instances. Visitor implementations define logic for a specific JWT subtype or payload subtype + * avoiding type-checking if-then-else conditionals in favor of type-safe method dispatch when encountering a JWT. + * + * @param the type of object to return after invoking the {@link Jwt#accept(JwtVisitor)} method. + * @since 0.12.0 + */ +public interface JwtVisitor { + + /** + * Handles an encountered Unsecured JWT that has not been cryptographically secured at all. Implementations can + * check the {@link Jwt#getPayload()} to determine if it is a {@link Claims} instance or a {@code byte[]} array. + * + *

If the payload is a {@code byte[]} array, and the JWT creator has set the (optional) + * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert + * the byte array to the final type as desired.

+ * + * @param jwt the parsed Unsecured JWT. + * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. + */ + T visit(Jwt jwt); + + /** + * Handles an encountered JSON Web Signature (aka 'JWS') message that has been cryptographically + * verified/authenticated. Implementations can check the {@link Jwt#getPayload()} determine if it is a + * {@link Claims} instance or a {@code byte[]} array. + * + *

If the payload is a {@code byte[]} array, and the JWS creator has set the (optional) + * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert + * the byte array to the final type as desired.

+ * + * @param jws the parsed verified/authenticated JWS. + * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. + */ + T visit(Jws jws); + + /** + * Handles an encountered JSON Web Encryption (aka 'JWE') message that has been authenticated and decrypted. + * Implementations can check the (decrypted) {@link Jwt#getPayload()} to determine if it is a {@link Claims} + * instance or a {@code byte[]} array. + * + *

If the payload is a {@code byte[]} array, and the JWE creator has set the (optional) + * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert + * the byte array to the final type as desired.

+ * + * @param jwe the parsed authenticated and decrypted JWE. + * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. + */ + T visit(Jwe jwe); +} diff --git a/io/jsonwebtoken/Jwts.java b/io/jsonwebtoken/Jwts.java new file mode 100644 index 0000000..8efac29 --- /dev/null +++ b/io/jsonwebtoken/Jwts.java @@ -0,0 +1,1077 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.io.CompressionAlgorithm; +import io.jsonwebtoken.lang.Builder; +import io.jsonwebtoken.lang.Classes; +import io.jsonwebtoken.lang.Registry; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.KeyAlgorithm; +import io.jsonwebtoken.security.KeyPairBuilderSupplier; +import io.jsonwebtoken.security.MacAlgorithm; +import io.jsonwebtoken.security.Password; +import io.jsonwebtoken.security.SecretKeyAlgorithm; +import io.jsonwebtoken.security.SecureDigestAlgorithm; +import io.jsonwebtoken.security.SignatureAlgorithm; +import io.jsonwebtoken.security.X509Builder; + +import javax.crypto.SecretKey; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Map; + +/** + * Factory class useful for creating instances of JWT interfaces. Using this factory class can be a good + * alternative to tightly coupling your code to implementation classes. + * + *

Standard Algorithm References

+ *

Standard JSON Web Token algorithms used during JWS or JWE building or parsing are available organized by + * algorithm type. Each organized collection of algorithms is available via a constant to allow + * for easy code-completion in IDEs, showing available algorithm instances. For example, when typing:

+ *
+ * Jwts.// press code-completion hotkeys to suggest available algorithm registry fields
+ * Jwts.{@link SIG SIG}.// press hotkeys to suggest individual Digital Signature or MAC algorithms or utility methods
+ * Jwts.{@link ENC ENC}.// press hotkeys to suggest individual encryption algorithms or utility methods
+ * Jwts.{@link KEY KEY}.// press hotkeys to suggest individual key algorithms or utility methods
+ * + * @since 0.1 + */ +public final class Jwts { + + + // do not change this visibility. Raw type method signature not be publicly exposed: + @SuppressWarnings("unchecked") + private static T get(Registry registry, String id) { + return (T) registry.forKey(id); + } + + /** + * Constants for all standard JWA + * Cryptographic Algorithms for Content + * Encryption defined in the JSON + * Web Signature and Encryption Algorithms Registry. Each standard algorithm is available as a + * ({@code public static final}) constant for direct type-safe reference in application code. For example: + *
+     * Jwts.builder()
+     *    // ... etc ...
+     *    .encryptWith(aKey, Jwts.ENC.A256GCM) // or A128GCM, A192GCM, etc...
+     *    .build();
+ *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

+ * + * @see #get() + * @since 0.12.0 + */ + public static final class ENC { + + private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardEncryptionAlgorithms"; + private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); + + /** + * Returns all standard JWA Cryptographic + * Algorithms for Content Encryption defined in the + * JSON Web Signature and Encryption + * Algorithms Registry. + * + * @return all standard JWA content encryption algorithms. + */ + public static Registry get() { + return REGISTRY; + } + + // prevent instantiation + private ENC() { + } + + /** + * {@code AES_128_CBC_HMAC_SHA_256} authenticated encryption algorithm as defined by + * RFC 7518, Section 5.2.3. This algorithm + * requires a 256-bit (32 byte) key. + */ + public static final AeadAlgorithm A128CBC_HS256 = get().forKey("A128CBC-HS256"); + + /** + * {@code AES_192_CBC_HMAC_SHA_384} authenticated encryption algorithm, as defined by + * RFC 7518, Section 5.2.4. This algorithm + * requires a 384-bit (48 byte) key. + */ + public static final AeadAlgorithm A192CBC_HS384 = get().forKey("A192CBC-HS384"); + + /** + * {@code AES_256_CBC_HMAC_SHA_512} authenticated encryption algorithm, as defined by + * RFC 7518, Section 5.2.5. This algorithm + * requires a 512-bit (64 byte) key. + */ + public static final AeadAlgorithm A256CBC_HS512 = get().forKey("A256CBC-HS512"); + + /** + * "AES GCM using 128-bit key" as defined by + * RFC 7518, Section 5.31. This + * algorithm requires a 128-bit (16 byte) key. + * + *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ */ + public static final AeadAlgorithm A128GCM = get().forKey("A128GCM"); + + /** + * "AES GCM using 192-bit key" as defined by + * RFC 7518, Section 5.31. This + * algorithm requires a 192-bit (24 byte) key. + * + *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ */ + public static final AeadAlgorithm A192GCM = get().forKey("A192GCM"); + + /** + * "AES GCM using 256-bit key" as defined by + * RFC 7518, Section 5.31. This + * algorithm requires a 256-bit (32 byte) key. + * + *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ */ + public static final AeadAlgorithm A256GCM = get().forKey("A256GCM"); + } + + /** + * Constants for all JWA (RFC 7518) standard + * Cryptographic Algorithms for Digital Signatures and MACs defined in the + * JSON Web Signature and Encryption Algorithms + * Registry. Each standard algorithm is available as a ({@code public static final}) constant for + * direct type-safe reference in application code. For example: + *
+     * Jwts.builder()
+     *    // ... etc ...
+     *    .signWith(aKey, Jwts.SIG.HS512) // or RS512, PS256, EdDSA, etc...
+     *    .build();
+ *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

+ * + * @see #get() + * @since 0.12.0 + */ + public static final class SIG { + + private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms"; + private static final Registry> REGISTRY = Classes.newInstance(IMPL_CLASSNAME); + + //prevent instantiation + private SIG() { + } + + /** + * Returns all standard JWA Cryptographic + * Algorithms for Digital Signatures and MACs defined in the + * JSON Web Signature and Encryption + * Algorithms Registry. + * + * @return all standard JWA digital signature and MAC algorithms. + */ + public static Registry> get() { + return REGISTRY; + } + + /** + * The "none" signature algorithm as defined by + * RFC 7518, Section 3.6. This algorithm + * is used only when creating unsecured (not integrity protected) JWSs and is not usable in any other scenario. + * Any attempt to call its methods will result in an exception being thrown. + */ + public static final SecureDigestAlgorithm NONE = Jwts.get(REGISTRY, "none"); + + /** + * {@code HMAC using SHA-256} message authentication algorithm as defined by + * RFC 7518, Section 3.2. This algorithm + * requires a 256-bit (32 byte) key. + */ + public static final MacAlgorithm HS256 = Jwts.get(REGISTRY, "HS256"); + + /** + * {@code HMAC using SHA-384} message authentication algorithm as defined by + * RFC 7518, Section 3.2. This algorithm + * requires a 384-bit (48 byte) key. + */ + public static final MacAlgorithm HS384 = Jwts.get(REGISTRY, "HS384"); + + /** + * {@code HMAC using SHA-512} message authentication algorithm as defined by + * RFC 7518, Section 3.2. This algorithm + * requires a 512-bit (64 byte) key. + */ + public static final MacAlgorithm HS512 = Jwts.get(REGISTRY, "HS512"); + + /** + * {@code RSASSA-PKCS1-v1_5 using SHA-256} signature algorithm as defined by + * RFC 7518, Section 3.3. This algorithm + * requires a 2048-bit key. + */ + public static final SignatureAlgorithm RS256 = Jwts.get(REGISTRY, "RS256"); + + /** + * {@code RSASSA-PKCS1-v1_5 using SHA-384} signature algorithm as defined by + * RFC 7518, Section 3.3. This algorithm + * requires a 2048-bit key, but the JJWT team recommends a 3072-bit key. + */ + public static final SignatureAlgorithm RS384 = Jwts.get(REGISTRY, "RS384"); + + /** + * {@code RSASSA-PKCS1-v1_5 using SHA-512} signature algorithm as defined by + * RFC 7518, Section 3.3. This algorithm + * requires a 2048-bit key, but the JJWT team recommends a 4096-bit key. + */ + public static final SignatureAlgorithm RS512 = Jwts.get(REGISTRY, "RS512"); + + /** + * {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256} signature algorithm as defined by + * RFC 7518, Section 3.51. + * This algorithm requires a 2048-bit key. + * + *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ */ + public static final SignatureAlgorithm PS256 = Jwts.get(REGISTRY, "PS256"); + + /** + * {@code RSASSA-PSS using SHA-384 and MGF1 with SHA-384} signature algorithm as defined by + * RFC 7518, Section 3.51. + * This algorithm requires a 2048-bit key, but the JJWT team recommends a 3072-bit key. + * + *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ */ + public static final SignatureAlgorithm PS384 = Jwts.get(REGISTRY, "PS384"); + + /** + * {@code RSASSA-PSS using SHA-512 and MGF1 with SHA-512} signature algorithm as defined by + * RFC 7518, Section 3.51. + * This algorithm requires a 2048-bit key, but the JJWT team recommends a 4096-bit key. + * + *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ */ + public static final SignatureAlgorithm PS512 = Jwts.get(REGISTRY, "PS512"); + + /** + * {@code ECDSA using P-256 and SHA-256} signature algorithm as defined by + * RFC 7518, Section 3.4. This algorithm + * requires a 256-bit key. + */ + public static final SignatureAlgorithm ES256 = Jwts.get(REGISTRY, "ES256"); + + /** + * {@code ECDSA using P-384 and SHA-384} signature algorithm as defined by + * RFC 7518, Section 3.4. This algorithm + * requires a 384-bit key. + */ + public static final SignatureAlgorithm ES384 = Jwts.get(REGISTRY, "ES384"); + + /** + * {@code ECDSA using P-521 and SHA-512} signature algorithm as defined by + * RFC 7518, Section 3.4. This algorithm + * requires a 521-bit key. + */ + public static final SignatureAlgorithm ES512 = Jwts.get(REGISTRY, "ES512"); + + /** + * {@code EdDSA} signature algorithm defined by + * RFC 8037, Section 3.1 that requires + * either {@code Ed25519} or {@code Ed448} Edwards Elliptic Curve1 keys. + * + *

KeyPair Generation

+ * + *

This instance's {@link KeyPairBuilderSupplier#keyPair() keyPair()} builder creates {@code Ed448} keys, + * and is essentially an alias for + * {@link io.jsonwebtoken.security.Jwks.CRV Jwks.CRV}.{@link io.jsonwebtoken.security.Jwks.CRV#Ed448 Ed448}.{@link KeyPairBuilderSupplier#keyPair() keyPair()}.

+ * + *

If you would like to generate an {@code Ed25519} {@code KeyPair} for use with the {@code EdDSA} algorithm, + * you may use the + * {@link io.jsonwebtoken.security.Jwks.CRV Jwks.CRV}.{@link io.jsonwebtoken.security.Jwks.CRV#Ed25519 Ed25519}.{@link KeyPairBuilderSupplier#keyPair() keyPair()} + * builder instead.

+ * + *

1This algorithm requires at least JDK 15 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath.

+ */ + public static final SignatureAlgorithm EdDSA = Jwts.get(REGISTRY, "EdDSA"); + } + + /** + * Constants for all standard JWA (RFC 7518) + * Cryptographic Algorithms for Key Management. Each standard algorithm is available as a + * ({@code public static final}) constant for direct type-safe reference in application code. For example: + *
+     * Jwts.builder()
+     *    // ... etc ...
+     *    .encryptWith(aKey, Jwts.KEY.ECDH_ES_A256KW, Jwts.ENC.A256GCM)
+     *    .build();
+ *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

+ * + * @see #get() + * @since 0.12.0 + */ + public static final class KEY { + + private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardKeyAlgorithms"; + private static final Registry> REGISTRY = Classes.newInstance(IMPL_CLASSNAME); + + /** + * Returns all standard JWA standard Cryptographic + * Algorithms for Key Management.. + * + * @return all standard JWA Key Management algorithms. + */ + public static Registry> get() { + return REGISTRY; + } + + /** + * Key algorithm reflecting direct use of a shared symmetric key as the JWE AEAD encryption key, as defined + * by RFC 7518 (JWA), Section 4.5. This + * algorithm does not produce encrypted key ciphertext. + */ + public static final KeyAlgorithm DIRECT = Jwts.get(REGISTRY, "dir"); + + /** + * AES Key Wrap algorithm with default initial value using a 128-bit key, as defined by + * RFC 7518 (JWA), Section 4.4. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Encrypts this newly-generated {@code SecretKey} with a 128-bit shared symmetric key using the + * AES Key Wrap algorithm, producing encrypted key ciphertext.
  4. + *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Decrypts the encrypted key ciphertext with the 128-bit shared symmetric key, + * using the AES Key Unwrap algorithm, producing the decryption key plaintext.
  4. + *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. + *
+ */ + public static final SecretKeyAlgorithm A128KW = Jwts.get(REGISTRY, "A128KW"); + + /** + * AES Key Wrap algorithm with default initial value using a 192-bit key, as defined by + * RFC 7518 (JWA), Section 4.4. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Encrypts this newly-generated {@code SecretKey} with a 192-bit shared symmetric key using the + * AES Key Wrap algorithm, producing encrypted key ciphertext.
  4. + *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Decrypts the encrypted key ciphertext with the 192-bit shared symmetric key, + * using the AES Key Unwrap algorithm, producing the decryption key plaintext.
  4. + *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. + *
+ */ + public static final SecretKeyAlgorithm A192KW = Jwts.get(REGISTRY, "A192KW"); + + /** + * AES Key Wrap algorithm with default initial value using a 256-bit key, as defined by + * RFC 7518 (JWA), Section 4.4. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Encrypts this newly-generated {@code SecretKey} with a 256-bit shared symmetric key using the + * AES Key Wrap algorithm, producing encrypted key ciphertext.
  4. + *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Decrypts the encrypted key ciphertext with the 256-bit shared symmetric key, + * using the AES Key Unwrap algorithm, producing the decryption key plaintext.
  4. + *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. + *
+ */ + public static final SecretKeyAlgorithm A256KW = Jwts.get(REGISTRY, "A256KW"); + + /** + * Key wrap algorithm with AES GCM using a 128-bit key, as defined by + * RFC 7518 (JWA), Section 4.7. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Generates a new secure-random 96-bit Initialization Vector to use during key wrap/encryption.
  4. + *
  5. Encrypts this newly-generated {@code SecretKey} with a 128-bit shared symmetric key using the + * AES GCM Key Wrap algorithm with the generated Initialization Vector, producing encrypted key ciphertext + * and GCM authentication tag.
  6. + *
  7. Sets the generated initialization vector as the required + * "iv" + * (Initialization Vector) Header Parameter
  8. + *
  9. Sets the resulting GCM authentication tag as the required + * "tag" + * (Authentication Tag) Header Parameter
  10. + *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  12. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Obtains the required initialization vector from the + * "iv" + * (Initialization Vector) Header Parameter
  4. + *
  5. Obtains the required GCM authentication tag from the + * "tag" + * (Authentication Tag) Header Parameter
  6. + *
  7. Decrypts the encrypted key ciphertext with the 128-bit shared symmetric key, the initialization vector + * and GCM authentication tag using the AES GCM Key Unwrap algorithm, producing the decryption key + * plaintext.
  8. + *
  9. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. + *
+ */ + public static final SecretKeyAlgorithm A128GCMKW = Jwts.get(REGISTRY, "A128GCMKW"); + + /** + * Key wrap algorithm with AES GCM using a 192-bit key, as defined by + * RFC 7518 (JWA), Section 4.7. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Generates a new secure-random 96-bit Initialization Vector to use during key wrap/encryption.
  4. + *
  5. Encrypts this newly-generated {@code SecretKey} with a 192-bit shared symmetric key using the + * AES GCM Key Wrap algorithm with the generated Initialization Vector, producing encrypted key ciphertext + * and GCM authentication tag.
  6. + *
  7. Sets the generated initialization vector as the required + * "iv" + * (Initialization Vector) Header Parameter
  8. + *
  9. Sets the resulting GCM authentication tag as the required + * "tag" + * (Authentication Tag) Header Parameter
  10. + *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  12. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Obtains the required initialization vector from the + * "iv" + * (Initialization Vector) Header Parameter
  4. + *
  5. Obtains the required GCM authentication tag from the + * "tag" + * (Authentication Tag) Header Parameter
  6. + *
  7. Decrypts the encrypted key ciphertext with the 192-bit shared symmetric key, the initialization vector + * and GCM authentication tag using the AES GCM Key Unwrap algorithm, producing the decryption key \ + * plaintext.
  8. + *
  9. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. + *
+ */ + public static final SecretKeyAlgorithm A192GCMKW = Jwts.get(REGISTRY, "A192GCMKW"); + + /** + * Key wrap algorithm with AES GCM using a 256-bit key, as defined by + * RFC 7518 (JWA), Section 4.7. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Generates a new secure-random 96-bit Initialization Vector to use during key wrap/encryption.
  4. + *
  5. Encrypts this newly-generated {@code SecretKey} with a 256-bit shared symmetric key using the + * AES GCM Key Wrap algorithm with the generated Initialization Vector, producing encrypted key ciphertext + * and GCM authentication tag.
  6. + *
  7. Sets the generated initialization vector as the required + * "iv" + * (Initialization Vector) Header Parameter
  8. + *
  9. Sets the resulting GCM authentication tag as the required + * "tag" + * (Authentication Tag) Header Parameter
  10. + *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  12. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Obtains the required initialization vector from the + * "iv" + * (Initialization Vector) Header Parameter
  4. + *
  5. Obtains the required GCM authentication tag from the + * "tag" + * (Authentication Tag) Header Parameter
  6. + *
  7. Decrypts the encrypted key ciphertext with the 256-bit shared symmetric key, the initialization vector + * and GCM authentication tag using the AES GCM Key Unwrap algorithm, producing the decryption key \ + * plaintext.
  8. + *
  9. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. + *
+ */ + public static final SecretKeyAlgorithm A256GCMKW = Jwts.get(REGISTRY, "A256GCMKW"); + + /** + * Key encryption algorithm using PBES2 with HMAC SHA-256 and "A128KW" wrapping + * as defined by + * RFC 7518 (JWA), Section 4.8. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Determines the number of PBDKF2 iterations via the JWE header's + * {@link JweHeader#getPbes2Count() pbes2Count} value. If that value is not set, a suitable number of + * iterations will be chosen based on + * OWASP + * PBKDF2 recommendations and then that value is set as the JWE header {@code pbes2Count} value.
  2. + *
  3. Generates a new secure-random salt input and sets it as the JWE header + * {@link JweHeader#getPbes2Salt() pbes2Salt} value.
  4. + *
  5. Derives a 128-bit Key Encryption Key with the PBES2-HS256 password-based key derivation algorithm, + * using the provided password, iteration count, and input salt as arguments.
  6. + *
  7. Generates a new secure-random Content Encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  8. + *
  9. Encrypts this newly-generated Content Encryption {@code SecretKey} with the {@code A128KW} key wrap + * algorithm using the 128-bit derived password-based Key Encryption Key from step {@code #3}, + * producing encrypted key ciphertext.
  10. + *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * Content Encryption {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated + * {@link AeadAlgorithm}.
  12. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the required PBKDF2 input salt from the + * "p2s" + * (PBES2 Salt Input) Header Parameter
  2. + *
  3. Obtains the required PBKDF2 iteration count from the + * "p2c" + * (PBES2 Count) Header Parameter
  4. + *
  5. Derives the 128-bit Key Encryption Key with the PBES2-HS256 password-based key derivation algorithm, + * using the provided password, obtained salt input, and obtained iteration count as arguments.
  6. + *
  7. Obtains the encrypted key ciphertext embedded in the received JWE.
  8. + *
  9. Decrypts the encrypted key ciphertext with with the {@code A128KW} key unwrap + * algorithm using the 128-bit derived password-based Key Encryption Key from step {@code #3}, + * producing the decryption key plaintext.
  10. + *
  11. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  12. + *
+ */ + public static final KeyAlgorithm PBES2_HS256_A128KW = Jwts.get(REGISTRY, "PBES2-HS256+A128KW"); + + /** + * Key encryption algorithm using PBES2 with HMAC SHA-384 and "A192KW" wrapping + * as defined by + * RFC 7518 (JWA), Section 4.8. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Determines the number of PBDKF2 iterations via the JWE header's + * {@link JweHeader#getPbes2Count() pbes2Count} value. If that value is not set, a suitable number of + * iterations will be chosen based on + * OWASP + * PBKDF2 recommendations and then that value is set as the JWE header {@code pbes2Count} value.
  2. + *
  3. Generates a new secure-random salt input and sets it as the JWE header + * {@link JweHeader#getPbes2Salt() pbes2Salt} value.
  4. + *
  5. Derives a 192-bit Key Encryption Key with the PBES2-HS384 password-based key derivation algorithm, + * using the provided password, iteration count, and input salt as arguments.
  6. + *
  7. Generates a new secure-random Content Encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  8. + *
  9. Encrypts this newly-generated Content Encryption {@code SecretKey} with the {@code A192KW} key wrap + * algorithm using the 192-bit derived password-based Key Encryption Key from step {@code #3}, + * producing encrypted key ciphertext.
  10. + *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * Content Encryption {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated + * {@link AeadAlgorithm}.
  12. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the required PBKDF2 input salt from the + * "p2s" + * (PBES2 Salt Input) Header Parameter
  2. + *
  3. Obtains the required PBKDF2 iteration count from the + * "p2c" + * (PBES2 Count) Header Parameter
  4. + *
  5. Derives the 192-bit Key Encryption Key with the PBES2-HS384 password-based key derivation algorithm, + * using the provided password, obtained salt input, and obtained iteration count as arguments.
  6. + *
  7. Obtains the encrypted key ciphertext embedded in the received JWE.
  8. + *
  9. Decrypts the encrypted key ciphertext with with the {@code A192KW} key unwrap + * algorithm using the 192-bit derived password-based Key Encryption Key from step {@code #3}, + * producing the decryption key plaintext.
  10. + *
  11. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  12. + *
+ */ + public static final KeyAlgorithm PBES2_HS384_A192KW = Jwts.get(REGISTRY, "PBES2-HS384+A192KW"); + + /** + * Key encryption algorithm using PBES2 with HMAC SHA-512 and "A256KW" wrapping + * as defined by + * RFC 7518 (JWA), Section 4.8. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Determines the number of PBDKF2 iterations via the JWE header's + * {@link JweHeader#getPbes2Count() pbes2Count} value. If that value is not set, a suitable number of + * iterations will be chosen based on + * OWASP + * PBKDF2 recommendations and then that value is set as the JWE header {@code pbes2Count} value.
  2. + *
  3. Generates a new secure-random salt input and sets it as the JWE header + * {@link JweHeader#getPbes2Salt() pbes2Salt} value.
  4. + *
  5. Derives a 256-bit Key Encryption Key with the PBES2-HS512 password-based key derivation algorithm, + * using the provided password, iteration count, and input salt as arguments.
  6. + *
  7. Generates a new secure-random Content Encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  8. + *
  9. Encrypts this newly-generated Content Encryption {@code SecretKey} with the {@code A256KW} key wrap + * algorithm using the 256-bit derived password-based Key Encryption Key from step {@code #3}, + * producing encrypted key ciphertext.
  10. + *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * Content Encryption {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated + * {@link AeadAlgorithm}.
  12. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the required PBKDF2 input salt from the + * "p2s" + * (PBES2 Salt Input) Header Parameter
  2. + *
  3. Obtains the required PBKDF2 iteration count from the + * "p2c" + * (PBES2 Count) Header Parameter
  4. + *
  5. Derives the 256-bit Key Encryption Key with the PBES2-HS512 password-based key derivation algorithm, + * using the provided password, obtained salt input, and obtained iteration count as arguments.
  6. + *
  7. Obtains the encrypted key ciphertext embedded in the received JWE.
  8. + *
  9. Decrypts the encrypted key ciphertext with with the {@code A256KW} key unwrap + * algorithm using the 256-bit derived password-based Key Encryption Key from step {@code #3}, + * producing the decryption key plaintext.
  10. + *
  11. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  12. + *
+ */ + public static final KeyAlgorithm PBES2_HS512_A256KW = Jwts.get(REGISTRY, "PBES2-HS512+A256KW"); + + /** + * Key Encryption with {@code RSAES-PKCS1-v1_5}, as defined by + * RFC 7518 (JWA), Section 4.2. + * This algorithm requires a key size of 2048 bits or larger. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Encrypts this newly-generated {@code SecretKey} with the RSA key wrap algorithm, using the JWE + * recipient's RSA Public Key, producing encrypted key ciphertext.
  4. + *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Receives the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Decrypts the encrypted key ciphertext with the RSA key unwrap algorithm, using the JWE recipient's + * RSA Private Key, producing the decryption key plaintext.
  4. + *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. + *
+ */ + public static final KeyAlgorithm RSA1_5 = Jwts.get(REGISTRY, "RSA1_5"); + + /** + * Key Encryption with {@code RSAES OAEP using default parameters}, as defined by + * RFC 7518 (JWA), Section 4.3. + * This algorithm requires a key size of 2048 bits or larger. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Encrypts this newly-generated {@code SecretKey} with the RSA OAEP with SHA-1 and MGF1 key wrap algorithm, + * using the JWE recipient's RSA Public Key, producing encrypted key ciphertext.
  4. + *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Receives the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Decrypts the encrypted key ciphertext with the RSA OAEP with SHA-1 and MGF1 key unwrap algorithm, + * using the JWE recipient's RSA Private Key, producing the decryption key plaintext.
  4. + *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. + *
+ */ + public static final KeyAlgorithm RSA_OAEP = Jwts.get(REGISTRY, "RSA-OAEP"); + + /** + * Key Encryption with {@code RSAES OAEP using SHA-256 and MGF1 with SHA-256}, as defined by + * RFC 7518 (JWA), Section 4.3. + * This algorithm requires a key size of 2048 bits or larger. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Encrypts this newly-generated {@code SecretKey} with the RSA OAEP with SHA-256 and MGF1 key wrap + * algorithm, using the JWE recipient's RSA Public Key, producing encrypted key ciphertext.
  4. + *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Receives the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Decrypts the encrypted key ciphertext with the RSA OAEP with SHA-256 and MGF1 key unwrap algorithm, + * using the JWE recipient's RSA Private Key, producing the decryption key plaintext.
  4. + *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. + *
+ */ + public static final KeyAlgorithm RSA_OAEP_256 = Jwts.get(REGISTRY, "RSA-OAEP-256"); + + /** + * Key Agreement with {@code ECDH-ES using Concat KDF} as defined by + * RFC 7518 (JWA), Section 4.6. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the + * JWE recipient's EC Public Key.
  2. + *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key + * and the JWE recipient's EC Public Key.
  4. + *
  5. Derives a symmetric Content + * Encryption {@code SecretKey} with the Concat KDF algorithm using the + * generated shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. + *
  7. Sets the generated EC key pair's Public Key as the required + * "epk" + * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. + *
  9. Returns the derived symmetric {@code SecretKey} for JJWT to use to encrypt the entire JWE with the + * associated {@link AeadAlgorithm}. Encrypted key ciphertext is not produced with this algorithm, so + * the resulting JWE will not contain any embedded key ciphertext.
  10. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the + * "epk" + * (Ephemeral Public Key) Header Parameter.
  2. + *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. + *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key + * and the JWE recipient's EC Private Key.
  6. + *
  7. Derives the symmetric Content + * Encryption {@code SecretKey} with the Concat KDF algorithm using the + * obtained shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. + *
  9. Returns the derived symmetric {@code SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. + *
+ */ + public static final KeyAlgorithm ECDH_ES = Jwts.get(REGISTRY, "ECDH-ES"); + + /** + * Key Agreement with Key Wrapping via + * ECDH-ES using Concat KDF and CEK wrapped with "A128KW" as defined by + * RFC 7518 (JWA), Section 4.6. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the + * JWE recipient's EC Public Key.
  2. + *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key + * and the JWE recipient's EC Public Key.
  4. + *
  5. Derives a 128-bit symmetric Key + * Encryption {@code SecretKey} with the Concat KDF algorithm using the + * generated shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. + *
  7. Sets the generated EC key pair's Public Key as the required + * "epk" + * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. + *
  9. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  10. + *
  11. Encrypts this newly-generated {@code SecretKey} with the {@code A128KW} key wrap + * algorithm using the derived symmetric Key Encryption Key from step {@code #3}, producing encrypted key ciphertext.
  12. + *
  13. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  14. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the + * "epk" + * (Ephemeral Public Key) Header Parameter.
  2. + *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. + *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key + * and the JWE recipient's EC Private Key.
  6. + *
  7. Derives the symmetric Key + * Encryption {@code SecretKey} with the Concat KDF algorithm using the + * obtained shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. + *
  9. Obtains the encrypted key ciphertext embedded in the received JWE.
  10. + *
  11. Decrypts the encrypted key ciphertext with the AES Key Unwrap algorithm using the + * 128-bit derived symmetric key from step {@code #4}, producing the decryption key plaintext.
  12. + *
  13. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  14. + *
+ */ + public static final KeyAlgorithm ECDH_ES_A128KW = Jwts.get(REGISTRY, "ECDH-ES+A128KW"); + + /** + * Key Agreement with Key Wrapping via + * ECDH-ES using Concat KDF and CEK wrapped with "A192KW" as defined by + * RFC 7518 (JWA), Section 4.6. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the + * JWE recipient's EC Public Key.
  2. + *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key + * and the JWE recipient's EC Public Key.
  4. + *
  5. Derives a 192-bit symmetric Key + * Encryption {@code SecretKey} with the Concat KDF algorithm using the + * generated shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. + *
  7. Sets the generated EC key pair's Public Key as the required + * "epk" + * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. + *
  9. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  10. + *
  11. Encrypts this newly-generated {@code SecretKey} with the {@code A192KW} key wrap + * algorithm using the derived symmetric Key Encryption Key from step {@code #3}, producing encrypted key + * ciphertext.
  12. + *
  13. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  14. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the + * "epk" + * (Ephemeral Public Key) Header Parameter.
  2. + *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. + *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key + * and the JWE recipient's EC Private Key.
  6. + *
  7. Derives the 192-bit symmetric + * Key Encryption {@code SecretKey} with the Concat KDF algorithm using the + * obtained shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. + *
  9. Obtains the encrypted key ciphertext embedded in the received JWE.
  10. + *
  11. Decrypts the encrypted key ciphertext with the AES Key Unwrap algorithm using the + * 192-bit derived symmetric key from step {@code #4}, producing the decryption key plaintext.
  12. + *
  13. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  14. + *
+ */ + public static final KeyAlgorithm ECDH_ES_A192KW = Jwts.get(REGISTRY, "ECDH-ES+A192KW"); + + /** + * Key Agreement with Key Wrapping via + * ECDH-ES using Concat KDF and CEK wrapped with "A256KW" as defined by + * RFC 7518 (JWA), Section 4.6. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the + * JWE recipient's EC Public Key.
  2. + *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key + * and the JWE recipient's EC Public Key.
  4. + *
  5. Derives a 256-bit symmetric Key + * Encryption {@code SecretKey} with the Concat KDF algorithm using the + * generated shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. + *
  7. Sets the generated EC key pair's Public Key as the required + * "epk" + * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. + *
  9. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  10. + *
  11. Encrypts this newly-generated {@code SecretKey} with the {@code A256KW} key wrap + * algorithm using the derived symmetric Key Encryption Key from step {@code #3}, producing encrypted key + * ciphertext.
  12. + *
  13. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  14. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the + * "epk" + * (Ephemeral Public Key) Header Parameter.
  2. + *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. + *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key + * and the JWE recipient's EC Private Key.
  6. + *
  7. Derives the 256-bit symmetric + * Key Encryption {@code SecretKey} with the Concat KDF algorithm using the + * obtained shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. + *
  9. Obtains the encrypted key ciphertext embedded in the received JWE.
  10. + *
  11. Decrypts the encrypted key ciphertext with the AES Key Unwrap algorithm using the + * 256-bit derived symmetric key from step {@code #4}, producing the decryption key plaintext.
  12. + *
  13. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  14. + *
+ */ + public static final KeyAlgorithm ECDH_ES_A256KW = Jwts.get(REGISTRY, "ECDH-ES+A256KW"); + + //prevent instantiation + private KEY() { + } + } + + /** + * Constants for JWA (RFC 7518) compression algorithms referenced in the {@code zip} header defined in the + * JSON Web Encryption Compression Algorithms + * Registry. Each algorithm is available as a ({@code public static final}) constant for + * direct type-safe reference in application code. For example: + *
+     * Jwts.builder()
+     *    // ... etc ...
+     *    .compressWith(Jwts.ZIP.DEF)
+     *    .build();
+ *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

+ * + * @see #get() + * @since 0.12.0 + */ + public static final class ZIP { + + private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.io.StandardCompressionAlgorithms"; + private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); + + /** + * Returns various useful + * Compression Algorithms. + * + * @return various standard and non-standard useful compression algorithms. + */ + public static Registry get() { + return REGISTRY; + } + + /** + * The JWE-standard DEFLATE + * compression algorithm with a {@code zip} header value of {@code "DEF"}. + * + * @see JWE RFC 7516, Section 4.1.3 + */ + public static final CompressionAlgorithm DEF = get().forKey("DEF"); + + /** + * A commonly used, but NOT JWA-STANDARD + * gzip compression algorithm with a {@code zip} header value + * of {@code "GZIP"}. + * + *

Compatibility Warning

+ * + *

This is not a standard JWE compression algorithm. Be sure to use this only when you are confident + * that all parties accessing the token support the "GZIP" identifier and associated algorithm.

+ * + *

If you're concerned about compatibility, {@link #DEF DEF} is the only JWA standards-compliant algorithm.

+ * + * @see #DEF + */ + public static final CompressionAlgorithm GZIP = get().forKey("GZIP"); + + //prevent instantiation + private ZIP() { + } + } + + /** + * A {@link Builder} that dynamically determines the type of {@link Header} to create based on builder state. + * + * @since 0.12.0 + */ + public interface HeaderBuilder extends JweHeaderMutator, X509Builder, Builder
{ + } + + /** + * Returns a new {@link HeaderBuilder} that can build any type of {@link Header} instance depending on + * which builder properties are set. + * + * @return a new {@link HeaderBuilder} that can build any type of {@link Header} instance depending on + * which builder properties are set. + * @since 0.12.0 + */ + public static HeaderBuilder header() { + return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtHeaderBuilder"); + } + + /** + * Returns a new {@link Claims} builder instance to be used to populate JWT claims, which in aggregate will be + * the JWT payload. + * + * @return a new {@link Claims} builder instance to be used to populate JWT claims, which in aggregate will be + * the JWT payload. + */ + public static ClaimsBuilder claims() { + return Classes.newInstance("io.jsonwebtoken.impl.DefaultClaimsBuilder"); + } + + /** + *

Deprecated since 0.12.0 in favor of + * {@code Jwts.}{@link #claims()}{@code .add(map).build()}. + * This method will be removed before 1.0.

+ * + *

Returns a new {@link Claims} instance populated with the specified name/value pairs.

+ * + * @param claims the name/value pairs to populate the new Claims instance. + * @return a new {@link Claims} instance populated with the specified name/value pairs. + * @deprecated since 0.12.0 in favor of {@code Jwts.}{@link #claims()}{@code .putAll(map).build()}. + * This method will be removed before 1.0. + */ + @Deprecated + public static Claims claims(Map claims) { + return claims().add(claims).build(); + } + + /** + * Returns a new {@link JwtBuilder} instance that can be configured and then used to create JWT compact serialized + * strings. + * + * @return a new {@link JwtBuilder} instance that can be configured and then used to create JWT compact serialized + * strings. + */ + public static JwtBuilder builder() { + return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtBuilder"); + } + + /** + * Returns a new {@link JwtParserBuilder} instance that can be configured to create an immutable/thread-safe {@link JwtParser}. + * + * @return a new {@link JwtParser} instance that can be configured create an immutable/thread-safe {@link JwtParser}. + */ + public static JwtParserBuilder parser() { + return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtParserBuilder"); + } + + /** + * Private constructor, prevent instantiation. + */ + private Jwts() { + } +} diff --git a/io/jsonwebtoken/Locator.java b/io/jsonwebtoken/Locator.java new file mode 100644 index 0000000..1d22258 --- /dev/null +++ b/io/jsonwebtoken/Locator.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import java.security.Key; + +/** + * A {@link Locator} can return an object referenced in a JWT {@link Header} that is necessary to process + * the associated JWT. + * + *

For example, a {@code Locator} implementation can inspect a header's {@code kid} (Key ID) parameter, and use the + * discovered {@code kid} value to lookup and return the associated {@link Key} instance. JJWT could then use this + * {@code key} to decrypt a JWE or verify a JWS signature.

+ * + * @param the type of object that may be returned from the {@link #locate(Header)} method + * @since 0.12.0 + */ +public interface Locator { + + /** + * Returns an object referenced in the specified {@code header}, or {@code null} if the object couldn't be found. + * + * @param header the JWT header to inspect; may be an instance of {@link Header}, {@link JwsHeader} or + * {@link JweHeader} depending on if the respective JWT is an unprotected JWT, JWS or JWE. + * @return an object referenced in the specified {@code header}, or {@code null} if the object couldn't be found. + */ + T locate(Header header); +} diff --git a/io/jsonwebtoken/LocatorAdapter.java b/io/jsonwebtoken/LocatorAdapter.java new file mode 100644 index 0000000..43f12dc --- /dev/null +++ b/io/jsonwebtoken/LocatorAdapter.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.lang.Assert; + +/** + * Adapter pattern implementation for the {@link Locator} interface. Subclasses can override any of the + * {@link #doLocate(Header)}, {@link #locate(ProtectedHeader)}, {@link #locate(JwsHeader)}, or + * {@link #locate(JweHeader)} methods for type-specific logic if desired when the encountered header is an + * unprotected JWT, or an integrity-protected JWT (either a JWS or JWE). + * + * @param the type of object to locate + * @since 0.12.0 + */ +public abstract class LocatorAdapter implements Locator { + + /** + * Constructs a new instance, where all default method implementations return {@code null}. + */ + public LocatorAdapter() { + } + + /** + * Inspects the specified header, and delegates to the {@link #locate(ProtectedHeader)} method if the header + * is protected (either a {@link JwsHeader} or {@link JweHeader}), or the {@link #doLocate(Header)} method + * if the header is not integrity protected. + * + * @param header the JWT header to inspect; may be an instance of {@link Header}, {@link JwsHeader}, or + * {@link JweHeader} depending on if the respective JWT is an unprotected JWT, JWS or JWE. + * @return an object referenced in the specified header, or {@code null} if the referenced object cannot be found + * or does not exist. + */ + @Override + public final T locate(Header header) { + Assert.notNull(header, "Header cannot be null."); + if (header instanceof ProtectedHeader) { + ProtectedHeader protectedHeader = (ProtectedHeader) header; + return locate(protectedHeader); + } + return doLocate(header); + } + + /** + * Returns an object referenced in the specified {@link ProtectedHeader}, or {@code null} if the referenced + * object cannot be found or does not exist. This is a convenience method that delegates to + * {@link #locate(JwsHeader)} if the {@code header} is a {@link JwsHeader} or {@link #locate(JweHeader)} if the + * {@code header} is a {@link JweHeader}. + * + * @param header the protected header of an encountered JWS or JWE. + * @return an object referenced in the specified {@link ProtectedHeader}, or {@code null} if the referenced + * object cannot be found or does not exist. + */ + protected T locate(ProtectedHeader header) { + if (header instanceof JwsHeader) { + return locate((JwsHeader) header); + } else { + Assert.isInstanceOf(JweHeader.class, header, "Unrecognized ProtectedHeader type."); + return locate((JweHeader) header); + } + } + + /** + * Returns an object referenced in the specified JWE header, or {@code null} if the referenced + * object cannot be found or does not exist. Default implementation simply returns {@code null}. + * + * @param header the header of an encountered JWE. + * @return an object referenced in the specified JWE header, or {@code null} if the referenced + * object cannot be found or does not exist. + */ + protected T locate(JweHeader header) { + return null; + } + + /** + * Returns an object referenced in the specified JWS header, or {@code null} if the referenced + * object cannot be found or does not exist. Default implementation simply returns {@code null}. + * + * @param header the header of an encountered JWS. + * @return an object referenced in the specified JWS header, or {@code null} if the referenced + * object cannot be found or does not exist. + */ + protected T locate(JwsHeader header) { + return null; + } + + /** + * Returns an object referenced in the specified unprotected JWT header, or {@code null} if the referenced + * object cannot be found or does not exist. Default implementation simply returns {@code null}. + * + * @param header the header of an encountered JWT. + * @return an object referenced in the specified unprotected JWT header, or {@code null} if the referenced + * object cannot be found or does not exist. + */ + @SuppressWarnings("unused") + protected T doLocate(Header header) { + return null; + } +} diff --git a/io/jsonwebtoken/MalformedJwtException.java b/io/jsonwebtoken/MalformedJwtException.java new file mode 100644 index 0000000..5729388 --- /dev/null +++ b/io/jsonwebtoken/MalformedJwtException.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * Exception indicating that a JWT was not correctly constructed and should be rejected. + * + * @since 0.2 + */ +public class MalformedJwtException extends JwtException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public MalformedJwtException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public MalformedJwtException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/MissingClaimException.java b/io/jsonwebtoken/MissingClaimException.java new file mode 100644 index 0000000..246748d --- /dev/null +++ b/io/jsonwebtoken/MissingClaimException.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * Exception thrown when discovering that a required claim is not present, indicating the JWT is + * invalid and may not be used. + * + * @since 0.6 + */ +public class MissingClaimException extends InvalidClaimException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param header the header associated with the claims that did not contain the required claim + * @param claims the claims that did not contain the required claim + * @param claimName the name of the claim that could not be validated + * @param claimValue the value of the claim that could not be validated + * @param message the message explaining why the exception is thrown. + */ + public MissingClaimException(Header header, Claims claims, String claimName, Object claimValue, String message) { + super(header, claims, claimName, claimValue, message); + } + + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param header the header associated with the claims that did not contain the required claim + * @param claims the claims that did not contain the required claim + * @param claimName the name of the claim that could not be validated + * @param claimValue the value of the claim that could not be validated + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + * @deprecated since 0.12.0 since it is not used in JJWT's codebase + */ + @Deprecated + public MissingClaimException(Header header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) { + super(header, claims, claimName, claimValue, message, cause); + } +} diff --git a/io/jsonwebtoken/PrematureJwtException.java b/io/jsonwebtoken/PrematureJwtException.java new file mode 100644 index 0000000..4bdb2ee --- /dev/null +++ b/io/jsonwebtoken/PrematureJwtException.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * Exception indicating that a JWT was accepted before it is allowed to be accessed and must be rejected. + * + * @since 0.3 + */ +public class PrematureJwtException extends ClaimJwtException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param header jwt header + * @param claims jwt claims (body) + * @param message the message explaining why the exception is thrown. + */ + public PrematureJwtException(Header header, Claims claims, String message) { + super(header, claims, message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param header jwt header + * @param claims jwt claims (body) + * @param message exception message + * @param cause cause + * @since 0.5 + * @deprecated since 0.12.0 since it is not used in JJWT's codebase + */ + @Deprecated + public PrematureJwtException(Header header, Claims claims, String message, Throwable cause) { + super(header, claims, message, cause); + } +} diff --git a/io/jsonwebtoken/ProtectedHeader.java b/io/jsonwebtoken/ProtectedHeader.java new file mode 100644 index 0000000..4c13c28 --- /dev/null +++ b/io/jsonwebtoken/ProtectedHeader.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.security.PublicJwk; +import io.jsonwebtoken.security.X509Accessor; + +import java.net.URI; +import java.util.Set; + +/** + * A JWT header that is integrity protected, either by JWS digital signature or JWE AEAD encryption. + * + * @see JwsHeader + * @see JweHeader + * @since 0.12.0 + */ +public interface ProtectedHeader extends Header, X509Accessor { + + /** + * Returns the {@code jku} (JWK Set URL) value that refers to a + * JWK Set + * resource containing JSON-encoded Public Keys, or {@code null} if not present. When present in a + * {@link JwsHeader}, the first public key in the JWK Set must be the public key complement of the private + * key used to sign the JWS. When present in a {@link JweHeader}, the first public key in the JWK Set must + * be the public key used during encryption. + * + * @return a URI that refers to a JWK Set + * resource for a set of JSON-encoded Public Keys, or {@code null} if not present. + * @see JWS JWK Set URL + * @see JWE JWK Set URL + */ + URI getJwkSetUrl(); + + /** + * Returns the {@code jwk} (JSON Web Key) associated with the JWT. When present in a {@link JwsHeader}, the + * {@code jwk} is the public key complement of the private key used to digitally sign the JWS. When present in a + * {@link JweHeader}, the {@code jwk} is the public key to which the JWE was encrypted, and may be used to + * determine the private key needed to decrypt the JWE. + * + * @return the {@code jwk} (JSON Web Key) associated with the header. + * @see JWS {@code jwk} (JSON Web Key) Header Parameter + * @see JWE {@code jwk} (JSON Web Key) Header Parameter + */ + PublicJwk getJwk(); + + /** + * Returns the JWT case-sensitive {@code kid} (Key ID) header value or {@code null} if not present. + * + *

The keyId header parameter is a hint indicating which key was used to secure a JWS or JWE. This + * parameter allows originators to explicitly signal a change of key to recipients. The structure of the keyId + * value is unspecified. Its value is a CaSe-SeNsItIvE string.

+ * + *

When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.

+ * + * @return the case-sensitive {@code kid} header value or {@code null} if not present. + * @see JWS Key ID + * @see JWE Key ID + */ + String getKeyId(); + + /** + * Returns the header parameter names that use extensions to the JWT or JWA specification(s) that MUST + * be understood and supported by the JWT recipient, or {@code null} if not present. + * + * @return the header parameter names that use extensions to the JWT or JWA specification(s) that MUST + * be understood and supported by the JWT recipient, or {@code null} if not present. + * @see JWS {@code crit} (Critical) Header Parameter + * @see JWS {@code crit} (Critical) Header Parameter + */ + Set getCritical(); +} diff --git a/io/jsonwebtoken/ProtectedHeaderMutator.java b/io/jsonwebtoken/ProtectedHeaderMutator.java new file mode 100644 index 0000000..0022506 --- /dev/null +++ b/io/jsonwebtoken/ProtectedHeaderMutator.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.lang.Conjunctor; +import io.jsonwebtoken.lang.NestedCollection; +import io.jsonwebtoken.security.PublicJwk; +import io.jsonwebtoken.security.X509Mutator; + +import java.net.URI; + +/** + * Mutation (modifications) to a {@link ProtectedHeader Header} instance. + * + * @param the mutator subtype, for method chaining + * @since 0.12.0 + */ +public interface ProtectedHeaderMutator> extends HeaderMutator, X509Mutator { + + /** + * Configures names of header parameters used by JWT or JWA specification extensions that MUST be + * understood and supported by the JWT recipient. When finished, use the collection's + * {@link Conjunctor#and() and()} method to continue header configuration, for example: + *
+     * headerBuilder
+     *     .critical().add("headerName").{@link Conjunctor#and() and()} // return parent
+     * // resume header configuration...
+ * + * @return the {@link NestedCollection} to use for {@code crit} configuration. + * @see JWS crit (Critical) Header Parameter + * @see JWS crit (Critical) Header Parameter + */ + NestedCollection critical(); + + /** + * Sets the {@code jwk} (JSON Web Key) associated with the JWT. When set for a {@link JwsHeader}, the + * {@code jwk} is the public key complement of the private key used to digitally sign the JWS. When set for a + * {@link JweHeader}, the {@code jwk} is the public key to which the JWE was encrypted, and may be used to + * determine the private key needed to decrypt the JWE. + * + * @param jwk the {@code jwk} (JSON Web Key) associated with the header. + * @return the header for method chaining + * @see JWS jwk (JSON Web Key) Header Parameter + * @see JWE jwk (JSON Web Key) Header Parameter + */ + T jwk(PublicJwk jwk); + + /** + * Sets the {@code jku} (JWK Set URL) value that refers to a + * JWK Set + * resource containing JSON-encoded Public Keys, or {@code null} if not present. When set for a + * {@link JwsHeader}, the first public key in the JWK Set must be the public key complement of the + * private key used to sign the JWS. When set for a {@link JweHeader}, the first public key in the JWK Set + * must be the public key used during encryption. + * + * @param uri a URI that refers to a JWK Set + * resource containing JSON-encoded Public Keys + * @return the header for method chaining + * @see JWS JWK Set URL + * @see JWE JWK Set URL + */ + T jwkSetUrl(URI uri); + + /** + * Sets the JWT case-sensitive {@code kid} (Key ID) header value. A {@code null} value will remove the property + * from the JSON map. + * + *

The keyId header parameter is a hint indicating which key was used to secure a JWS or JWE. This parameter + * allows originators to explicitly signal a change of key to recipients. The structure of the keyId value is + * unspecified. Its value MUST be a case-sensitive string.

+ * + *

When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.

+ * + * @param kid the case-sensitive JWS {@code kid} header value or {@code null} to remove the property from the JSON map. + * @return the header instance for method chaining. + * @see JWS Key ID + * @see JWE Key ID + */ + T keyId(String kid); + + /** + * Deprecated since 0.12.0, delegates to {@link #keyId(String)}. + * + * @param kid the case-sensitive JWS {@code kid} header value or {@code null} to remove the property from the JSON map. + * @return the instance for method chaining. + * @see JWS Key ID + * @see JWE Key ID + * @deprecated since 0.12.0 in favor of the more modern builder-style {@link #keyId(String)} method. + */ + @Deprecated + T setKeyId(String kid); + + /** + * Deprecated as of 0.12.0, there is no need to set this any longer as the {@code JwtBuilder} will + * always set the {@code alg} header as necessary. + * + * @param alg the JWS or JWE algorithm {@code alg} value or {@code null} to remove the property from the JSON map. + * @return the instance for method chaining. + * @since 0.1 + * @deprecated since 0.12.0 and will be removed before the 1.0 release. + */ + @Deprecated + T setAlgorithm(String alg); +} diff --git a/io/jsonwebtoken/ProtectedJwt.java b/io/jsonwebtoken/ProtectedJwt.java new file mode 100644 index 0000000..1531a13 --- /dev/null +++ b/io/jsonwebtoken/ProtectedJwt.java @@ -0,0 +1,37 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.security.DigestSupplier; + +/** + * A {@code ProtectedJwt} is a {@link Jwt} that is integrity protected via a cryptographic algorithm that produces + * a cryptographic digest, such as a MAC, Digital Signature or Authentication Tag. + * + *

Cryptographic Digest

+ *

This interface extends DigestSupplier to make available the {@code ProtectedJwt}'s associated cryptographic + * digest:

+ *
    + *
  • If the JWT is a {@link Jws}, {@link #getDigest() getDigest() } returns the JWS signature.
  • + *
  • If the JWT is a {@link Jwe}, {@link #getDigest() getDigest() } returns the AAD Authentication Tag.
  • + *
+ * + * @param the type of the JWT protected header + * @param

the type of the JWT payload, either a content byte array or a {@link Claims} instance. + * @since 0.12.0 + */ +public interface ProtectedJwt extends Jwt, DigestSupplier { +} diff --git a/io/jsonwebtoken/RequiredTypeException.java b/io/jsonwebtoken/RequiredTypeException.java new file mode 100644 index 0000000..77a0035 --- /dev/null +++ b/io/jsonwebtoken/RequiredTypeException.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * Exception thrown when attempting to obtain a value from a JWT or JWK and the existing value does not match the + * expected type. + * + * @since 0.6 + */ +public class RequiredTypeException extends JwtException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public RequiredTypeException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public RequiredTypeException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/SignatureAlgorithm.java b/io/jsonwebtoken/SignatureAlgorithm.java new file mode 100644 index 0000000..ee25883 --- /dev/null +++ b/io/jsonwebtoken/SignatureAlgorithm.java @@ -0,0 +1,656 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SignatureException; +import io.jsonwebtoken.security.WeakKeyException; + +import javax.crypto.SecretKey; +import java.security.Key; +import java.security.PrivateKey; +import java.security.interfaces.ECKey; +import java.security.interfaces.RSAKey; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Type-safe representation of standard JWT signature algorithm names as defined in the + * JSON Web Algorithms specification. + * + * @since 0.1 + * @deprecated since 0.12.0; use {@link Jwts.SIG} instead. + */ +@Deprecated +public enum SignatureAlgorithm { + + /** + * JWA name for {@code No digital signature or MAC performed} + */ + NONE("none", "No digital signature or MAC performed", "None", null, false, 0, 0), + + /** + * JWA algorithm name for {@code HMAC using SHA-256} + */ + HS256("HS256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true, 256, 256, "1.2.840.113549.2.9"), + + /** + * JWA algorithm name for {@code HMAC using SHA-384} + */ + HS384("HS384", "HMAC using SHA-384", "HMAC", "HmacSHA384", true, 384, 384, "1.2.840.113549.2.10"), + + /** + * JWA algorithm name for {@code HMAC using SHA-512} + */ + HS512("HS512", "HMAC using SHA-512", "HMAC", "HmacSHA512", true, 512, 512, "1.2.840.113549.2.11"), + + /** + * JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-256} + */ + RS256("RS256", "RSASSA-PKCS-v1_5 using SHA-256", "RSA", "SHA256withRSA", true, 256, 2048), + + /** + * JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-384} + */ + RS384("RS384", "RSASSA-PKCS-v1_5 using SHA-384", "RSA", "SHA384withRSA", true, 384, 2048), + + /** + * JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-512} + */ + RS512("RS512", "RSASSA-PKCS-v1_5 using SHA-512", "RSA", "SHA512withRSA", true, 512, 2048), + + /** + * JWA algorithm name for {@code ECDSA using P-256 and SHA-256} + */ + ES256("ES256", "ECDSA using P-256 and SHA-256", "ECDSA", "SHA256withECDSA", true, 256, 256), + + /** + * JWA algorithm name for {@code ECDSA using P-384 and SHA-384} + */ + ES384("ES384", "ECDSA using P-384 and SHA-384", "ECDSA", "SHA384withECDSA", true, 384, 384), + + /** + * JWA algorithm name for {@code ECDSA using P-521 and SHA-512} + */ + ES512("ES512", "ECDSA using P-521 and SHA-512", "ECDSA", "SHA512withECDSA", true, 512, 521), + + /** + * JWA algorithm name for {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256}. This algorithm requires + * Java 11 or later or a JCA provider like BouncyCastle to be in the runtime classpath. If on Java 10 or + * earlier, BouncyCastle will be used automatically if found in the runtime classpath. + */ + PS256("PS256", "RSASSA-PSS using SHA-256 and MGF1 with SHA-256", "RSA", "RSASSA-PSS", false, 256, 2048), + + /** + * JWA algorithm name for {@code RSASSA-PSS using SHA-384 and MGF1 with SHA-384}. This algorithm requires + * Java 11 or later or a JCA provider like BouncyCastle to be in the runtime classpath. If on Java 10 or + * earlier, BouncyCastle will be used automatically if found in the runtime classpath. + */ + PS384("PS384", "RSASSA-PSS using SHA-384 and MGF1 with SHA-384", "RSA", "RSASSA-PSS", false, 384, 2048), + + /** + * JWA algorithm name for {@code RSASSA-PSS using SHA-512 and MGF1 with SHA-512}. This algorithm requires + * Java 11 or later or a JCA provider like BouncyCastle to be in the runtime classpath. If on Java 10 or + * earlier, BouncyCastle will be used automatically if found in the runtime classpath. + */ + PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "RSA", "RSASSA-PSS", false, 512, 2048); + + //purposefully ordered higher to lower: + private static final List PREFERRED_HMAC_ALGS = Collections.unmodifiableList(Arrays.asList( + SignatureAlgorithm.HS512, SignatureAlgorithm.HS384, SignatureAlgorithm.HS256)); + //purposefully ordered higher to lower: + private static final List PREFERRED_EC_ALGS = Collections.unmodifiableList(Arrays.asList( + SignatureAlgorithm.ES512, SignatureAlgorithm.ES384, SignatureAlgorithm.ES256)); + + private final String value; + private final String description; + private final String familyName; + private final String jcaName; + private final boolean jdkStandard; + private final int digestLength; + private final int minKeyLength; + /** + * Algorithm name as given by {@link Key#getAlgorithm()} if the key was loaded from a pkcs12 Keystore. + * + * @deprecated This is just a workaround for https://bugs.openjdk.java.net/browse/JDK-8243551 + */ + @Deprecated + private final String pkcs12Name; + + SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard, + int digestLength, int minKeyLength) { + this(value, description, familyName, jcaName, jdkStandard, digestLength, minKeyLength, jcaName); + } + + SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard, + int digestLength, int minKeyLength, String pkcs12Name) { + this.value = value; + this.description = description; + this.familyName = familyName; + this.jcaName = jcaName; + this.jdkStandard = jdkStandard; + this.digestLength = digestLength; + this.minKeyLength = minKeyLength; + this.pkcs12Name = pkcs12Name; + } + + /** + * Returns the JWA algorithm name constant. + * + * @return the JWA algorithm name constant. + */ + public String getValue() { + return value; + } + + /** + * Returns the JWA algorithm description. + * + * @return the JWA algorithm description. + */ + public String getDescription() { + return description; + } + + + /** + * Returns the cryptographic family name of the signature algorithm. The value returned is according to the + * following table: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Crypto Family
SignatureAlgorithmFamily Name
HS256HMAC
HS384HMAC
HS512HMAC
RS256RSA
RS384RSA
RS512RSA
PS256RSA
PS384RSA
PS512RSA
ES256ECDSA
ES384ECDSA
ES512ECDSA
+ * + * @return Returns the cryptographic family name of the signature algorithm. + * @since 0.5 + */ + public String getFamilyName() { + return familyName; + } + + /** + * Returns the name of the JCA algorithm used to compute the signature. + * + * @return the name of the JCA algorithm used to compute the signature. + */ + public String getJcaName() { + return jcaName; + } + + /** + * Returns {@code true} if the algorithm is supported by standard JDK distributions or {@code false} if the + * algorithm implementation is not in the JDK and must be provided by a separate runtime JCA Provider (like + * BouncyCastle for example). + * + * @return {@code true} if the algorithm is supported by standard JDK distributions or {@code false} if the + * algorithm implementation is not in the JDK and must be provided by a separate runtime JCA Provider (like + * BouncyCastle for example). + */ + public boolean isJdkStandard() { + return jdkStandard; + } + + /** + * Returns {@code true} if the enum instance represents an HMAC signature algorithm, {@code false} otherwise. + * + * @return {@code true} if the enum instance represents an HMAC signature algorithm, {@code false} otherwise. + */ + public boolean isHmac() { + return familyName.equals("HMAC"); + } + + /** + * Returns {@code true} if the enum instance represents an RSA public/private key pair signature algorithm, + * {@code false} otherwise. + * + * @return {@code true} if the enum instance represents an RSA public/private key pair signature algorithm, + * {@code false} otherwise. + */ + public boolean isRsa() { + return familyName.equals("RSA"); + } + + /** + * Returns {@code true} if the enum instance represents an Elliptic Curve ECDSA signature algorithm, {@code false} + * otherwise. + * + * @return {@code true} if the enum instance represents an Elliptic Curve ECDSA signature algorithm, {@code false} + * otherwise. + */ + public boolean isEllipticCurve() { + return familyName.equals("ECDSA"); + } + + /** + * Returns the minimum key length in bits (not bytes) that may be used with this algorithm according to the + * JWT JWA Specification (RFC 7518). + * + * @return the minimum key length in bits (not bytes) that may be used with this algorithm according to the + * JWT JWA Specification (RFC 7518). + * @since 0.10.0 + */ + public int getMinKeyLength() { + return this.minKeyLength; + } + + /** + * Returns quietly if the specified key is allowed to create signatures using this algorithm + * according to the JWT JWA Specification (RFC 7518) or throws an + * {@link InvalidKeyException} if the key is not allowed or not secure enough for this algorithm. + * + * @param key the key to check for validity. + * @throws InvalidKeyException if the key is not allowed or not secure enough for this algorithm. + * @since 0.10.0 + */ + public void assertValidSigningKey(Key key) throws InvalidKeyException { + assertValid(key, true); + } + + /** + * Returns quietly if the specified key is allowed to verify signatures using this algorithm + * according to the JWT JWA Specification (RFC 7518) or throws an + * {@link InvalidKeyException} if the key is not allowed or not secure enough for this algorithm. + * + * @param key the key to check for validity. + * @throws InvalidKeyException if the key is not allowed or not secure enough for this algorithm. + * @since 0.10.0 + */ + public void assertValidVerificationKey(Key key) throws InvalidKeyException { + assertValid(key, false); + } + + /** + * @since 0.10.0 to support assertValid(Key, boolean) + */ + private static String keyType(boolean signing) { + return signing ? "signing" : "verification"; + } + + /** + * @since 0.10.0 + */ + private void assertValid(Key key, boolean signing) throws InvalidKeyException { + + if (this == NONE) { + + String msg = "The 'NONE' signature algorithm does not support cryptographic keys."; + throw new InvalidKeyException(msg); + + } else if (isHmac()) { + + if (!(key instanceof SecretKey)) { + String msg = this.familyName + " " + keyType(signing) + " keys must be SecretKey instances."; + throw new InvalidKeyException(msg); + } + SecretKey secretKey = (SecretKey) key; + + byte[] encoded = secretKey.getEncoded(); + if (encoded == null) { + throw new InvalidKeyException("The " + keyType(signing) + " key's encoded bytes cannot be null."); + } + + String alg = secretKey.getAlgorithm(); + if (alg == null) { + throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm cannot be null."); + } + + // These next checks use equalsIgnoreCase per https://github.com/jwtk/jjwt/issues/381#issuecomment-412912272 + if (!HS256.jcaName.equalsIgnoreCase(alg) && + !HS384.jcaName.equalsIgnoreCase(alg) && + !HS512.jcaName.equalsIgnoreCase(alg) && + !HS256.pkcs12Name.equals(alg) && + !HS384.pkcs12Name.equals(alg) && + !HS512.pkcs12Name.equals(alg)) { + throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm '" + alg + + "' does not equal a valid HmacSHA* algorithm name and cannot be used with " + name() + "."); + } + + int size = encoded.length * 8; //size in bits + if (size < this.minKeyLength) { + String msg = "The " + keyType(signing) + " key's size is " + size + " bits which " + + "is not secure enough for the " + name() + " algorithm. The JWT " + + "JWA Specification (RFC 7518, Section 3.2) states that keys used with " + name() + " MUST have a " + + "size >= " + minKeyLength + " bits (the key size must be greater than or equal to the hash " + + "output size). Consider using the " + Keys.class.getName() + " class's " + + "'secretKeyFor(SignatureAlgorithm." + name() + ")' method to create a key guaranteed to be " + + "secure enough for " + name() + ". See " + + "https://tools.ietf.org/html/rfc7518#section-3.2 for more information."; + throw new WeakKeyException(msg); + } + + } else { //EC or RSA + + if (signing) { + if (!(key instanceof PrivateKey)) { + String msg = familyName + " signing keys must be PrivateKey instances."; + throw new InvalidKeyException(msg); + } + } + + if (isEllipticCurve()) { + + if (!(key instanceof ECKey)) { + String msg = familyName + " " + keyType(signing) + " keys must be ECKey instances."; + throw new InvalidKeyException(msg); + } + + ECKey ecKey = (ECKey) key; + int size = ecKey.getParams().getOrder().bitLength(); + if (size < this.minKeyLength) { + String msg = "The " + keyType(signing) + " key's size (ECParameterSpec order) is " + size + + " bits which is not secure enough for the " + name() + " algorithm. The JWT " + + "JWA Specification (RFC 7518, Section 3.4) states that keys used with " + + name() + " MUST have a size >= " + this.minKeyLength + + " bits. Consider using the " + Keys.class.getName() + " class's " + + "'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " + + "to be secure enough for " + name() + ". See " + + "https://tools.ietf.org/html/rfc7518#section-3.4 for more information."; + throw new WeakKeyException(msg); + } + + } else { //RSA + + if (!(key instanceof RSAKey)) { + String msg = familyName + " " + keyType(signing) + " keys must be RSAKey instances."; + throw new InvalidKeyException(msg); + } + + RSAKey rsaKey = (RSAKey) key; + int size = rsaKey.getModulus().bitLength(); + if (size < this.minKeyLength) { + + String section = name().startsWith("P") ? "3.5" : "3.3"; + + String msg = "The " + keyType(signing) + " key's size is " + size + " bits which is not secure " + + "enough for the " + name() + " algorithm. The JWT JWA Specification (RFC 7518, Section " + + section + ") states that keys used with " + name() + " MUST have a size >= " + + this.minKeyLength + " bits. Consider using the " + Keys.class.getName() + " class's " + + "'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " + + "to be secure enough for " + name() + ". See " + + "https://tools.ietf.org/html/rfc7518#section-" + section + " for more information."; + throw new WeakKeyException(msg); + } + } + } + } + + /** + * Returns the recommended signature algorithm to be used with the specified key according to the following + * heuristics: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Key Signature Algorithm
If the Key is a:And:With a key size of:The returned SignatureAlgorithm will be:
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA256")1256 <= size <= 383 2{@link SignatureAlgorithm#HS256 HS256}
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA384")1384 <= size <= 511{@link SignatureAlgorithm#HS384 HS384}
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA512")1512 <= size{@link SignatureAlgorithm#HS512 HS512}
{@link ECKey}instanceof {@link PrivateKey}256 <= size <= 383 3{@link SignatureAlgorithm#ES256 ES256}
{@link ECKey}instanceof {@link PrivateKey}384 <= size <= 511{@link SignatureAlgorithm#ES384 ES384}
{@link ECKey}instanceof {@link PrivateKey}4096 <= size{@link SignatureAlgorithm#ES512 ES512}
{@link RSAKey}instanceof {@link PrivateKey}2048 <= size <= 3071 4,5{@link SignatureAlgorithm#RS256 RS256}
{@link RSAKey}instanceof {@link PrivateKey}3072 <= size <= 4095 5{@link SignatureAlgorithm#RS384 RS384}
{@link RSAKey}instanceof {@link PrivateKey}4096 <= size 5{@link SignatureAlgorithm#RS512 RS512}
+ *

Notes:

+ *
    + *
  1. {@code SecretKey} instances must have an {@link Key#getAlgorithm() algorithm} name equal + * to {@code HmacSHA256}, {@code HmacSHA384} or {@code HmacSHA512}. If not, the key bytes might not be + * suitable for HMAC signatures will be rejected with a {@link InvalidKeyException}.
  2. + *
  3. The JWT JWA Specification (RFC 7518, + * Section 3.2) mandates that HMAC-SHA-* signing keys MUST be 256 bits or greater. + * {@code SecretKey}s with key lengths less than 256 bits will be rejected with an + * {@link WeakKeyException}.
  4. + *
  5. The JWT JWA Specification (RFC 7518, + * Section 3.4) mandates that ECDSA signing key lengths MUST be 256 bits or greater. + * {@code ECKey}s with key lengths less than 256 bits will be rejected with a + * {@link WeakKeyException}.
  6. + *
  7. The JWT JWA Specification (RFC 7518, + * Section 3.3) mandates that RSA signing key lengths MUST be 2048 bits or greater. + * {@code RSAKey}s with key lengths less than 2048 bits will be rejected with a + * {@link WeakKeyException}.
  8. + *
  9. Technically any RSA key of length >= 2048 bits may be used with the {@link #RS256}, {@link #RS384}, and + * {@link #RS512} algorithms, so we assume an RSA signature algorithm based on the key length to + * parallel similar decisions in the JWT specification for HMAC and ECDSA signature algorithms. + * This is not required - just a convenience.
  10. + *
+ *

This implementation does not return the {@link #PS256}, {@link #PS256}, {@link #PS256} RSA variant for any + * specified {@link RSAKey} because: + *

    + *
  • The JWT JWA Specification (RFC 7518, + * Section 3.1) indicates that {@link #RS256}, {@link #RS384}, and {@link #RS512} are + * recommended algorithms while the {@code PS}* variants are simply marked as optional.
  • + *
  • The {@link #RS256}, {@link #RS384}, and {@link #RS512} algorithms are available in the JDK by default + * while the {@code PS}* variants require an additional JCA Provider (like BouncyCastle).
  • + *
+ * + *

Finally, this method will throw an {@link InvalidKeyException} for any key that does not match the + * heuristics and requirements documented above, since that inevitably means the Key is either insufficient or + * explicitly disallowed by the JWT specification.

+ * + * @param key the key to inspect + * @return the recommended signature algorithm to be used with the specified key + * @throws InvalidKeyException for any key that does not match the heuristics and requirements documented above, + * since that inevitably means the Key is either insufficient or explicitly disallowed by the JWT specification. + * @since 0.10.0 + */ + public static SignatureAlgorithm forSigningKey(Key key) throws InvalidKeyException { + + if (key == null) { + throw new InvalidKeyException("Key argument cannot be null."); + } + + if (!(key instanceof SecretKey || + (key instanceof PrivateKey && (key instanceof ECKey || key instanceof RSAKey)))) { + String msg = "JWT standard signing algorithms require either 1) a SecretKey for HMAC-SHA algorithms or " + + "2) a private RSAKey for RSA algorithms or 3) a private ECKey for Elliptic Curve algorithms. " + + "The specified key is of type " + key.getClass().getName(); + throw new InvalidKeyException(msg); + } + + if (key instanceof SecretKey) { + + SecretKey secretKey = (SecretKey) key; + int bitLength = io.jsonwebtoken.lang.Arrays.length(secretKey.getEncoded()) * Byte.SIZE; + + for (SignatureAlgorithm alg : PREFERRED_HMAC_ALGS) { + // ensure compatibility check is based on key length. See https://github.com/jwtk/jjwt/issues/381 + if (bitLength >= alg.minKeyLength) { + return alg; + } + } + + String msg = "The specified SecretKey is not strong enough to be used with JWT HMAC signature " + + "algorithms. The JWT specification requires HMAC keys to be >= 256 bits long. The specified " + + "key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.2 for more " + + "information."; + throw new WeakKeyException(msg); + } + + if (key instanceof RSAKey) { + + RSAKey rsaKey = (RSAKey) key; + int bitLength = rsaKey.getModulus().bitLength(); + + if (bitLength >= 4096) { + RS512.assertValidSigningKey(key); + return RS512; + } else if (bitLength >= 3072) { + RS384.assertValidSigningKey(key); + return RS384; + } else if (bitLength >= RS256.minKeyLength) { + RS256.assertValidSigningKey(key); + return RS256; + } + + String msg = "The specified RSA signing key is not strong enough to be used with JWT RSA signature " + + "algorithms. The JWT specification requires RSA keys to be >= 2048 bits long. The specified RSA " + + "key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.3 for more " + + "information."; + throw new WeakKeyException(msg); + } + + // if we've made it this far in the method, the key is an ECKey due to the instanceof assertions at the + // top of the method + + ECKey ecKey = (ECKey) key; + int bitLength = ecKey.getParams().getOrder().bitLength(); + + for (SignatureAlgorithm alg : PREFERRED_EC_ALGS) { + if (bitLength >= alg.minKeyLength) { + alg.assertValidSigningKey(key); + return alg; + } + } + + String msg = "The specified Elliptic Curve signing key is not strong enough to be used with JWT ECDSA " + + "signature algorithms. The JWT specification requires ECDSA keys to be >= 256 bits long. " + + "The specified ECDSA key is " + bitLength + " bits. See " + + "https://tools.ietf.org/html/rfc7518#section-3.4 for more information."; + throw new WeakKeyException(msg); + } + + /** + * Looks up and returns the corresponding {@code SignatureAlgorithm} enum instance based on a + * case-insensitive name comparison. + * + * @param value The case-insensitive name of the {@code SignatureAlgorithm} instance to return + * @return the corresponding {@code SignatureAlgorithm} enum instance based on a + * case-insensitive name comparison. + * @throws SignatureException if the specified value does not match any {@code SignatureAlgorithm} + * name. + */ + public static SignatureAlgorithm forName(String value) throws SignatureException { + for (SignatureAlgorithm alg : values()) { + if (alg.getValue().equalsIgnoreCase(value)) { + return alg; + } + } + + throw new SignatureException("Unsupported signature algorithm '" + value + "'"); + } +} diff --git a/io/jsonwebtoken/SignatureException.java b/io/jsonwebtoken/SignatureException.java new file mode 100644 index 0000000..7a54cda --- /dev/null +++ b/io/jsonwebtoken/SignatureException.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.security.SecurityException; + +/** + * Exception indicating that either calculating a signature or verifying an existing signature of a JWT failed. + * + * @since 0.1 + * @deprecated in favor of {@link io.jsonwebtoken.security.SignatureException}; this class will be removed before 1.0 + */ +@Deprecated +public class SignatureException extends SecurityException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public SignatureException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public SignatureException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/SigningKeyResolver.java b/io/jsonwebtoken/SigningKeyResolver.java new file mode 100644 index 0000000..82b9edc --- /dev/null +++ b/io/jsonwebtoken/SigningKeyResolver.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import java.security.Key; + +/** + * A {@code SigningKeyResolver} can be used by a {@link io.jsonwebtoken.JwtParser JwtParser} to find a signing key that + * should be used to verify a JWS signature. + * + *

A {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing the JWT and the + * JWT header or payload (byte array or Claims) must be inspected first to determine how to look up the signing key. + * Once returned by the resolver, the JwtParser will then verify the JWS signature with the returned key. For + * example:

+ * + *
+ * Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
+ *         @Override
+ *         public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
+ *             //inspect the header or claims, lookup and return the signing key
+ *             return getSigningKeyBytes(header, claims); //implement me
+ *         }})
+ *     .build().parseSignedClaims(compact);
+ * 
+ * + *

A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.

+ * + *

Using an Adapter

+ * + *

If you only need to resolve a signing key for a particular JWS (either a content or Claims JWS), consider using + * the {@link io.jsonwebtoken.SigningKeyResolverAdapter} and overriding only the method you need to support instead of + * implementing this interface directly.

+ * + * @see io.jsonwebtoken.JwtParserBuilder#keyLocator(Locator) + * @since 0.4 + * @deprecated since 0.12.0. Implement {@link Locator} instead. + */ +@Deprecated +public interface SigningKeyResolver { + + /** + * Returns the signing key that should be used to validate a digital signature for the Claims JWS with the specified + * header and claims. + * + * @param header the header of the JWS to validate + * @param claims the Claims payload of the JWS to validate + * @return the signing key that should be used to validate a digital signature for the Claims JWS with the specified + * header and claims. + */ + Key resolveSigningKey(JwsHeader header, Claims claims); + + /** + * Returns the signing key that should be used to validate a digital signature for the content JWS with the + * specified header and byte array payload. + * + * @param header the header of the JWS to validate + * @param content the byte array payload of the JWS to validate + * @return the signing key that should be used to validate a digital signature for the content JWS with the + * specified header and byte array payload. + */ + Key resolveSigningKey(JwsHeader header, byte[] content); +} diff --git a/io/jsonwebtoken/SigningKeyResolverAdapter.java b/io/jsonwebtoken/SigningKeyResolverAdapter.java new file mode 100644 index 0000000..6e90ca1 --- /dev/null +++ b/io/jsonwebtoken/SigningKeyResolverAdapter.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.lang.Assert; + +import javax.crypto.spec.SecretKeySpec; +import java.security.Key; + +/** + *

Deprecation Notice

+ * + *

As of JJWT 0.12.0, various Resolver concepts (including the {@code SigningKeyResolver}) have been + * unified into a single {@link Locator} interface. For key location, (for both signing and encryption keys), + * use the {@link JwtParserBuilder#keyLocator(Locator)} to configure a parser with your desired Key locator instead + * of using a {@code SigningKeyResolver}. Also see {@link LocatorAdapter} for the Adapter pattern parallel of this + * class. This {@code SigningKeyResolverAdapter} class will be removed before the 1.0 release.

+ * + *

Previous Documentation

+ * + *

An Adapter implementation of the + * {@link SigningKeyResolver} interface that allows subclasses to process only the type of JWS body that + * is known/expected for a particular case.

+ * + *

The {@link #resolveSigningKey(JwsHeader, Claims)} and {@link #resolveSigningKey(JwsHeader, byte[])} method + * implementations delegate to the + * {@link #resolveSigningKeyBytes(JwsHeader, Claims)} and {@link #resolveSigningKeyBytes(JwsHeader, byte[])} methods + * respectively. The latter two methods simply throw exceptions: they represent scenarios expected by + * calling code in known situations, and it is expected that you override the implementation in those known situations; + * non-overridden *KeyBytes methods indicates that the JWS input was unexpected.

+ * + *

If either {@link #resolveSigningKey(JwsHeader, byte[])} or {@link #resolveSigningKey(JwsHeader, Claims)} + * are not overridden, one (or both) of the *KeyBytes variants must be overridden depending on your expected + * use case. You do not have to override any method that does not represent an expected condition.

+ * + * @see io.jsonwebtoken.JwtParserBuilder#keyLocator(Locator) + * @see LocatorAdapter + * @since 0.4 + * @deprecated since 0.12.0. Use {@link LocatorAdapter LocatorAdapter} with + * {@link JwtParserBuilder#keyLocator(Locator)} + */ +@SuppressWarnings("DeprecatedIsStillUsed") +@Deprecated +public class SigningKeyResolverAdapter implements SigningKeyResolver { + + /** + * Default constructor. + */ + public SigningKeyResolverAdapter() { + + } + + @Override + public Key resolveSigningKey(JwsHeader header, Claims claims) { + SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm()); + Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, Claims) implementation cannot " + + "be used for asymmetric key algorithms (RSA, Elliptic Curve). " + + "Override the resolveSigningKey(JwsHeader, Claims) method instead and return a " + + "Key instance appropriate for the " + alg.name() + " algorithm."); + byte[] keyBytes = resolveSigningKeyBytes(header, claims); + return new SecretKeySpec(keyBytes, alg.getJcaName()); + } + + @Override + public Key resolveSigningKey(JwsHeader header, byte[] content) { + SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm()); + Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, byte[]) implementation cannot " + + "be used for asymmetric key algorithms (RSA, Elliptic Curve). " + + "Override the resolveSigningKey(JwsHeader, byte[]) method instead and return a " + + "Key instance appropriate for the " + alg.name() + " algorithm."); + byte[] keyBytes = resolveSigningKeyBytes(header, content); + return new SecretKeySpec(keyBytes, alg.getJcaName()); + } + + /** + * Convenience method invoked by {@link #resolveSigningKey(JwsHeader, Claims)} that obtains the necessary signing + * key bytes. This implementation simply throws an exception: if the JWS parsed is a Claims JWS, you must + * override this method or the {@link #resolveSigningKey(JwsHeader, Claims)} method instead. + * + *

NOTE: You cannot override this method when validating RSA signatures. If you expect RSA signatures, + * you must override the {@link #resolveSigningKey(JwsHeader, Claims)} method instead.

+ * + * @param header the parsed {@link JwsHeader} + * @param claims the parsed {@link Claims} + * @return the signing key bytes to use to verify the JWS signature. + */ + public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { + throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " + + "Claims JWS signing key resolution. Consider overriding either the " + + "resolveSigningKey(JwsHeader, Claims) method or, for HMAC algorithms, the " + + "resolveSigningKeyBytes(JwsHeader, Claims) method."); + } + + /** + * Convenience method invoked by {@link #resolveSigningKey(JwsHeader, byte[])} that obtains the necessary signing + * key bytes. This implementation simply throws an exception: if the JWS parsed is a content JWS, you must + * override this method or the {@link #resolveSigningKey(JwsHeader, byte[])} method instead. + * + * @param header the parsed {@link JwsHeader} + * @param content the byte array payload + * @return the signing key bytes to use to verify the JWS signature. + */ + @SuppressWarnings("unused") + public byte[] resolveSigningKeyBytes(JwsHeader header, byte[] content) { + throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " + + "content JWS signing key resolution. Consider overriding either the " + + "resolveSigningKey(JwsHeader, byte[]) method or, for HMAC algorithms, the " + + "resolveSigningKeyBytes(JwsHeader, byte[]) method."); + } +} diff --git a/io/jsonwebtoken/SupportedJwtVisitor.java b/io/jsonwebtoken/SupportedJwtVisitor.java new file mode 100644 index 0000000..61cdd78 --- /dev/null +++ b/io/jsonwebtoken/SupportedJwtVisitor.java @@ -0,0 +1,200 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +import io.jsonwebtoken.lang.Assert; + +/** + * A {@code JwtVisitor} that guarantees only supported JWT instances are handled, rejecting + * all other (unsupported) JWTs with {@link UnsupportedJwtException}s. A JWT is considered supported + * only if the type-specific handler method is overridden by a subclass. + * + * @param the type of value returned from the subclass handler method implementation. + * @since 0.12.0 + */ +public class SupportedJwtVisitor implements JwtVisitor { + + /** + * Default constructor, does not initialize any internal state. + */ + public SupportedJwtVisitor() { + } + + /** + * Handles an encountered unsecured JWT by delegating to either {@link #onUnsecuredContent(Jwt)} or + * {@link #onUnsecuredClaims(Jwt)} depending on the payload type. + * + * @param jwt the parsed unsecured JWT + * @return the value returned by either {@link #onUnsecuredContent(Jwt)} or {@link #onUnsecuredClaims(Jwt)} + * depending on the payload type. + * @throws UnsupportedJwtException if the payload is neither a {@code byte[]} nor {@code Claims}, or either + * delegate method throws the same. + */ + @SuppressWarnings("unchecked") + @Override + public T visit(Jwt jwt) { + Assert.notNull(jwt, "JWT cannot be null."); + Object payload = jwt.getPayload(); + if (payload instanceof byte[]) { + return onUnsecuredContent((Jwt) jwt); + } else { + // only other type we support: + Assert.stateIsInstance(Claims.class, payload, "Unexpected payload data type: "); + return onUnsecuredClaims((Jwt) jwt); + } + } + + /** + * Handles an encountered unsecured content JWT - one that is not cryptographically signed nor + * encrypted, and has a byte[] array payload. If the JWT creator has set the (optional) + * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert + * the byte array to the final type as desired. + * + *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that + * subclasses will override this method if the application needs to support this type of JWT.

+ * + * @param jwt the parsed unsecured content JWT + * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. + * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. + */ + public T onUnsecuredContent(Jwt jwt) throws UnsupportedJwtException { + throw new UnsupportedJwtException("Unexpected unsecured content JWT."); + } + + /** + * Handles an encountered unsecured Claims JWT - one that is not cryptographically signed nor + * encrypted, and has a {@link Claims} payload. + * + *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that + * subclasses will override this method if the application needs to support this type of JWT.

+ * + * @param jwt the parsed unsecured content JWT + * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. + * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. + */ + public T onUnsecuredClaims(Jwt jwt) { + throw new UnsupportedJwtException("Unexpected unsecured Claims JWT."); + } + + /** + * Handles an encountered JSON Web Token (aka 'JWS') message that has been cryptographically verified/authenticated + * by delegating to either {@link #onVerifiedContent(Jws)} or {@link #onVerifiedClaims(Jws)} depending on the payload + * type. + * + * @param jws the parsed verified/authenticated JWS. + * @return the value returned by either {@link #onVerifiedContent(Jws)} or {@link #onVerifiedClaims(Jws)} + * depending on the payload type. + * @throws UnsupportedJwtException if the payload is neither a {@code byte[]} nor {@code Claims}, or either + * delegate method throws the same. + */ + @SuppressWarnings("unchecked") + @Override + public T visit(Jws jws) { + Assert.notNull(jws, "JWS cannot be null."); + Object payload = jws.getPayload(); + if (payload instanceof byte[]) { + return onVerifiedContent((Jws) jws); + } else { + Assert.stateIsInstance(Claims.class, payload, "Unexpected payload data type: "); + return onVerifiedClaims((Jws) jws); + } + } + + /** + * Handles an encountered JWS message that has been cryptographically verified/authenticated and has + * a byte[] array payload. If the JWT creator has set the (optional) {@link Header#getContentType()} value, the + * application may inspect that value to determine how to convert the byte array to the final type as desired. + * + *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that + * subclasses will override this method if the application needs to support this type of JWT.

+ * + * @param jws the parsed verified/authenticated JWS. + * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. + * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. + */ + public T onVerifiedContent(Jws jws) { + throw new UnsupportedJwtException("Unexpected content JWS."); + } + + /** + * Handles an encountered JWS message that has been cryptographically verified/authenticated and has a + * {@link Claims} payload. + * + *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that + * subclasses will override this method if the application needs to support this type of JWT.

+ * + * @param jws the parsed signed (and verified) Claims JWS + * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. + * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. + */ + public T onVerifiedClaims(Jws jws) { + throw new UnsupportedJwtException("Unexpected Claims JWS."); + } + + /** + * Handles an encountered JSON Web Encryption (aka 'JWE') message that has been authenticated and decrypted by + * delegating to either {@link #onDecryptedContent(Jwe)} or {@link #onDecryptedClaims(Jwe)} depending on the + * payload type. + * + * @param jwe the parsed authenticated and decrypted JWE. + * @return the value returned by either {@link #onDecryptedContent(Jwe)} or {@link #onDecryptedClaims(Jwe)} + * depending on the payload type. + * @throws UnsupportedJwtException if the payload is neither a {@code byte[]} nor {@code Claims}, or either + * delegate method throws the same. + */ + @SuppressWarnings("unchecked") + @Override + public T visit(Jwe jwe) { + Assert.notNull(jwe, "JWE cannot be null."); + Object payload = jwe.getPayload(); + if (payload instanceof byte[]) { + return onDecryptedContent((Jwe) jwe); + } else { + Assert.stateIsInstance(Claims.class, payload, "Unexpected payload data type: "); + return onDecryptedClaims((Jwe) jwe); + } + } + + /** + * Handles an encountered JWE message that has been authenticated and decrypted, and has byte[] array payload. If + * the JWT creator has set the (optional) {@link Header#getContentType()} value, the application may inspect that + * value to determine how to convert the byte array to the final type as desired. + * + *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that + * subclasses will override this method if the application needs to support this type of JWT.

+ * + * @param jwe the parsed authenticated and decrypted content JWE. + * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. + * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. + */ + public T onDecryptedContent(Jwe jwe) { + throw new UnsupportedJwtException("Unexpected content JWE."); + } + + /** + * Handles an encountered JWE message that has been authenticated and decrypted, and has a {@link Claims} payload. + * + *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that + * subclasses will override this method if the application needs to support this type of JWT.

+ * + * @param jwe the parsed authenticated and decrypted content JWE. + * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. + * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. + */ + public T onDecryptedClaims(Jwe jwe) { + throw new UnsupportedJwtException("Unexpected Claims JWE."); + } +} diff --git a/io/jsonwebtoken/UnsupportedJwtException.java b/io/jsonwebtoken/UnsupportedJwtException.java new file mode 100644 index 0000000..a1ec968 --- /dev/null +++ b/io/jsonwebtoken/UnsupportedJwtException.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * Exception thrown when receiving a JWT in a particular format/configuration that does not match the format expected + * by the application. + * + *

For example, this exception would be thrown if parsing an unprotected content JWT when the application + * requires a cryptographically signed Claims JWS instead.

+ * + * @since 0.2 + */ +public class UnsupportedJwtException extends JwtException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public UnsupportedJwtException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public UnsupportedJwtException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/io/AbstractDeserializer.java b/io/jsonwebtoken/io/AbstractDeserializer.java new file mode 100644 index 0000000..0d29faf --- /dev/null +++ b/io/jsonwebtoken/io/AbstractDeserializer.java @@ -0,0 +1,84 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +import io.jsonwebtoken.lang.Assert; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; + +/** + * Convenient base class to use to implement {@link Deserializer}s, with subclasses only needing to implement + * {@link #doDeserialize(Reader)}. + * + * @param the type of object returned after deserialization + * @since 0.12.0 + */ +public abstract class AbstractDeserializer implements Deserializer { + + /** + * EOF (End of File) marker, equal to {@code -1}. + */ + protected static final int EOF = -1; + + private static final byte[] EMPTY_BYTES = new byte[0]; + + /** + * Default constructor, does not initialize any internal state. + */ + protected AbstractDeserializer() { + } + + /** + * {@inheritDoc} + */ + @Override + public final T deserialize(byte[] bytes) throws DeserializationException { + bytes = bytes == null ? EMPTY_BYTES : bytes; // null safe + InputStream in = new ByteArrayInputStream(bytes); + Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8); + return deserialize(reader); + } + + /** + * {@inheritDoc} + */ + @Override + public final T deserialize(Reader reader) throws DeserializationException { + Assert.notNull(reader, "Reader argument cannot be null."); + try { + return doDeserialize(reader); + } catch (Throwable t) { + if (t instanceof DeserializationException) { + throw (DeserializationException) t; + } + String msg = "Unable to deserialize: " + t.getMessage(); + throw new DeserializationException(msg, t); + } + } + + /** + * Reads the specified character stream and returns the corresponding Java object. + * + * @param reader the reader to use to read the character stream + * @return the deserialized Java object + * @throws Exception if there is a problem reading the stream or creating the expected Java object + */ + protected abstract T doDeserialize(Reader reader) throws Exception; +} diff --git a/io/jsonwebtoken/io/AbstractSerializer.java b/io/jsonwebtoken/io/AbstractSerializer.java new file mode 100644 index 0000000..5c74b50 --- /dev/null +++ b/io/jsonwebtoken/io/AbstractSerializer.java @@ -0,0 +1,75 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +import io.jsonwebtoken.lang.Objects; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; + +/** + * Convenient base class to use to implement {@link Serializer}s, with subclasses only needing to implement + * * {@link #doSerialize(Object, OutputStream)}. + * + * @param the type of object to serialize + * @since 0.12.0 + */ +public abstract class AbstractSerializer implements Serializer { + + /** + * Default constructor, does not initialize any internal state. + */ + protected AbstractSerializer() { + } + + /** + * {@inheritDoc} + */ + @Override + public final byte[] serialize(T t) throws SerializationException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + serialize(t, out); + return out.toByteArray(); + } + + /** + * {@inheritDoc} + */ + @Override + public final void serialize(T t, OutputStream out) throws SerializationException { + try { + doSerialize(t, out); + } catch (Throwable e) { + if (e instanceof SerializationException) { + throw (SerializationException) e; + } + String msg = "Unable to serialize object of type " + Objects.nullSafeClassName(t) + ": " + e.getMessage(); + throw new SerializationException(msg, e); + } + } + + /** + * Converts the specified Java object into a formatted data byte stream, writing the bytes to the specified + * {@code out}put stream. + * + * @param t the object to convert to a byte stream + * @param out the stream to write to + * @throws Exception if there is a problem converting the object to a byte stream or writing the + * bytes to the {@code out}put stream. + * @since 0.12.0 + */ + protected abstract void doSerialize(T t, OutputStream out) throws Exception; +} diff --git a/io/jsonwebtoken/io/Base64.java b/io/jsonwebtoken/io/Base64.java new file mode 100644 index 0000000..81e3817 --- /dev/null +++ b/io/jsonwebtoken/io/Base64.java @@ -0,0 +1,681 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +import java.util.Arrays; + +/** + * A very fast and memory efficient class to encode and decode to and from BASE64 or BASE64URL in full accordance + * with RFC 4648. + * + *

Based initially on MigBase64 with continued modifications for Base64 URL support and JDK-standard code formatting.

+ * + *

This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only + * allocates the resulting array. This produces less garbage and it is possible to handle arrays twice + * as large as algorithms that create a temporary array.

+ * + *

There is also a "fast" version of all decode methods that works the same way as the normal ones, but + * has a few demands on the decoded input. Normally though, these fast versions should be used if the source if + * the input is known and it hasn't bee tampered with.

+ * + * @author Mikael Grev + * @author Les Hazlewood + * @since 0.10.0 + */ +@SuppressWarnings("Duplicates") +final class Base64 { //final and package-protected on purpose + + private static final char[] BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); + private static final char[] BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".toCharArray(); + private static final int[] BASE64_IALPHABET = new int[256]; + private static final int[] BASE64URL_IALPHABET = new int[256]; + private static final int IALPHABET_MAX_INDEX = BASE64_IALPHABET.length - 1; + + static { + Arrays.fill(BASE64_IALPHABET, -1); + System.arraycopy(BASE64_IALPHABET, 0, BASE64URL_IALPHABET, 0, BASE64_IALPHABET.length); + for (int i = 0, iS = BASE64_ALPHABET.length; i < iS; i++) { + BASE64_IALPHABET[BASE64_ALPHABET[i]] = i; + BASE64URL_IALPHABET[BASE64URL_ALPHABET[i]] = i; + } + BASE64_IALPHABET['='] = 0; + BASE64URL_IALPHABET['='] = 0; + } + + static final Base64 DEFAULT = new Base64(false); + static final Base64 URL_SAFE = new Base64(true); + + private final boolean urlsafe; + private final char[] ALPHABET; + private final int[] IALPHABET; + + private Base64(boolean urlsafe) { + this.urlsafe = urlsafe; + this.ALPHABET = urlsafe ? BASE64URL_ALPHABET : BASE64_ALPHABET; + this.IALPHABET = urlsafe ? BASE64URL_IALPHABET : BASE64_IALPHABET; + } + + // **************************************************************************************** + // * char[] version + // **************************************************************************************** + + private String getName() { + return urlsafe ? "base64url" : "base64"; // RFC 4648 codec names are all lowercase + } + + /** + * Encodes a raw byte array into a BASE64 char[] representation in accordance with RFC 2045. + * + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + private char[] encodeToChar(byte[] sArr, boolean lineSep) { + + // Check special case + int sLen = sArr != null ? sArr.length : 0; + if (sLen == 0) { + return new char[0]; + } + + int eLen = (sLen / 3) * 3; // # of bytes that can encode evenly into 24-bit chunks + int left = sLen - eLen; // # of bytes that remain after 24-bit chunking. Always 0, 1 or 2 + + int cCnt = (((sLen - 1) / 3 + 1) << 2); // # of base64-encoded characters including padding + int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned char array with padding and any line separators + + int padCount = 0; + if (left == 2) { + padCount = 1; + } else if (left == 1) { + padCount = 2; + } + + char[] dArr = new char[urlsafe ? (dLen - padCount) : dLen]; + + // Encode even 24-bits + for (int s = 0, d = 0, cc = 0; s < eLen; ) { + + // Copy next three bytes into lower 24 bits of int, paying attention to sign. + int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); + + // Encode the int into four chars + dArr[d++] = ALPHABET[(i >>> 18) & 0x3f]; + dArr[d++] = ALPHABET[(i >>> 12) & 0x3f]; + dArr[d++] = ALPHABET[(i >>> 6) & 0x3f]; + dArr[d++] = ALPHABET[i & 0x3f]; + + // Add optional line separator + if (lineSep && ++cc == 19 && d < dLen - 2) { + dArr[d++] = '\r'; + dArr[d++] = '\n'; + cc = 0; + } + } + + // Pad and encode last bits if source isn't even 24 bits. + if (left > 0) { + // Prepare the int + int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0); + + // Set last four chars + dArr[dLen - 4] = ALPHABET[i >> 12]; + dArr[dLen - 3] = ALPHABET[(i >>> 6) & 0x3f]; + //dArr[dLen - 2] = left == 2 ? ALPHABET[i & 0x3f] : '='; + //dArr[dLen - 1] = '='; + if (left == 2) { + dArr[dLen - 2] = ALPHABET[i & 0x3f]; + } else if (!urlsafe) { // if not urlsafe, we need to include the padding characters + dArr[dLen - 2] = '='; + } + if (!urlsafe) { // include padding + dArr[dLen - 1] = '='; + } + } + return dArr; + } + + /* + * Decodes a BASE64 encoded char array. All illegal characters will be ignored and can handle both arrays with + * and without line separators. + * + * @param sArr The source array. null or length 0 will return an empty array. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + * + public final byte[] decode(char[] sArr) { + // Check special case + int sLen = sArr != null ? sArr.length : 0; + if (sLen == 0) { + return new byte[0]; + } + + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. + if (IALPHABET[sArr[i]] < 0) { + sepCnt++; + } + } + + // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) { + return null; + } + + int pad = 0; + for (int i = sLen; i > 1 && IALPHABET[sArr[--i]] <= 0; ) { + if (sArr[i] == '=') { + pad++; + } + } + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len; ) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IALPHABET[sArr[s++]]; + if (c >= 0) { + i |= c << (18 - j * 6); + } else { + j--; + } + } + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++] = (byte) (i >> 8); + if (d < len) { + dArr[d++] = (byte) i; + } + } + } + return dArr; + } + */ + + private int ctoi(char c) { + int i = c > IALPHABET_MAX_INDEX ? -1 : IALPHABET[c]; + if (i < 0) { + String msg = "Illegal " + getName() + " character: '" + c + "'"; + throw new DecodingException(msg); + } + return i; + } + + /** + * Decodes a BASE64-encoded {@code CharSequence} that is known to be reasonably well formatted. The preconditions + * are:
+ * + The sequence must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The sequence must not contain illegal characters within the encoded string
+ * + The sequence CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * + * @param seq The source sequence. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + * @throws DecodingException on illegal input + */ + byte[] decodeFast(CharSequence seq) throws DecodingException { + + // Check special case + int sLen = seq != null ? seq.length() : 0; + if (sLen == 0) { + return new byte[0]; + } + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IALPHABET[seq.charAt(sIx)] < 0) { + sIx++; + } + + // Trim illegal chars from end + while (eIx > 0 && IALPHABET[seq.charAt(eIx)] < 0) { + eIx--; + } + + // get the padding count (=) (0, 1 or 2) + int pad = seq.charAt(eIx) == '=' ? (seq.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (seq.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) { + + // Assemble three bytes into an int from four "valid" characters. + int i = ctoi(seq.charAt(sIx++)) << 18 | ctoi(seq.charAt(sIx++)) << 12 | ctoi(seq.charAt(sIx++)) << 6 | ctoi(seq.charAt(sIx++)); + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) { + i |= ctoi(seq.charAt(sIx++)) << (18 - j * 6); + } + + for (int r = 16; d < len; r -= 8) { + dArr[d++] = (byte) (i >> r); + } + } + + return dArr; + } + + // **************************************************************************************** + // * byte[] version + // **************************************************************************************** + + /* + * Encodes a raw byte array into a BASE64 byte[] representation i accordance with RFC 2045. + * + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + * + public final byte[] encodeToByte(byte[] sArr, boolean lineSep) { + return encodeToByte(sArr, 0, sArr != null ? sArr.length : 0, lineSep); + } + + /** + * Encodes a raw byte array into a BASE64 byte[] representation i accordance with RFC 2045. + * + * @param sArr The bytes to convert. If null an empty array will be returned. + * @param sOff The starting position in the bytes to convert. + * @param sLen The number of bytes to convert. If 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + * + public final byte[] encodeToByte(byte[] sArr, int sOff, int sLen, boolean lineSep) { + + // Check special case + if (sArr == null || sLen == 0) { + return new byte[0]; + } + + int eLen = (sLen / 3) * 3; // Length of even 24-bits. + int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count + int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array + byte[] dArr = new byte[dLen]; + + // Encode even 24-bits + for (int s = sOff, d = 0, cc = 0; s < sOff + eLen; ) { + + // Copy next three bytes into lower 24 bits of int, paying attention to sign. + int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); + + // Encode the int into four chars + dArr[d++] = (byte) ALPHABET[(i >>> 18) & 0x3f]; + dArr[d++] = (byte) ALPHABET[(i >>> 12) & 0x3f]; + dArr[d++] = (byte) ALPHABET[(i >>> 6) & 0x3f]; + dArr[d++] = (byte) ALPHABET[i & 0x3f]; + + // Add optional line separator + if (lineSep && ++cc == 19 && d < dLen - 2) { + dArr[d++] = '\r'; + dArr[d++] = '\n'; + cc = 0; + } + } + + // Pad and encode last bits if source isn't an even 24 bits. + int left = sLen - eLen; // 0 - 2. + if (left > 0) { + // Prepare the int + int i = ((sArr[sOff + eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sOff + sLen - 1] & 0xff) << 2) : 0); + + // Set last four chars + dArr[dLen - 4] = (byte) ALPHABET[i >> 12]; + dArr[dLen - 3] = (byte) ALPHABET[(i >>> 6) & 0x3f]; + dArr[dLen - 2] = left == 2 ? (byte) ALPHABET[i & 0x3f] : (byte) '='; + dArr[dLen - 1] = '='; + } + return dArr; + } + + /** + * Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with + * and without line separators. + * + * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + * + public final byte[] decode(byte[] sArr) { + return decode(sArr, 0, sArr.length); + } + + /** + * Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with + * and without line separators. + * + * @param sArr The source array. null will throw an exception. + * @param sOff The starting position in the source array. + * @param sLen The number of bytes to decode from the source array. Length 0 will return an empty array. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + * + public final byte[] decode(byte[] sArr, int sOff, int sLen) { + + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. + if (IALPHABET[sArr[sOff + i] & 0xff] < 0) { + sepCnt++; + } + } + + // Check so that legal chars (including '=') are evenly divisible by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) { + return null; + } + + int pad = 0; + for (int i = sLen; i > 1 && IALPHABET[sArr[sOff + --i] & 0xff] <= 0; ) { + if (sArr[sOff + i] == '=') { + pad++; + } + } + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len; ) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IALPHABET[sArr[sOff + s++] & 0xff]; + if (c >= 0) { + i |= c << (18 - j * 6); + } else { + j--; + } + } + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++] = (byte) (i >> 8); + if (d < len) { + dArr[d++] = (byte) i; + } + } + } + + return dArr; + } + + + /* + * Decodes a BASE64 encoded byte array that is known to be reasonably well formatted. The method is about twice as + * fast as {@link #decode(byte[])}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * + * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + * + public final byte[] decodeFast(byte[] sArr) { + + // Check special case + int sLen = sArr.length; + if (sLen == 0) { + return new byte[0]; + } + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IALPHABET[sArr[sIx] & 0xff] < 0) { + sIx++; + } + + // Trim illegal chars from end + while (eIx > 0 && IALPHABET[sArr[eIx] & 0xff] < 0) { + eIx--; + } + + // get the padding count (=) (0, 1 or 2) + int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) { + + // Assemble three bytes into an int from four "valid" characters. + int i = IALPHABET[sArr[sIx++]] << 18 | IALPHABET[sArr[sIx++]] << 12 | IALPHABET[sArr[sIx++]] << 6 | IALPHABET[sArr[sIx++]]; + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) { + i |= IALPHABET[sArr[sIx++]] << (18 - j * 6); + } + + for (int r = 16; d < len; r -= 8) { + dArr[d++] = (byte) (i >> r); + } + } + + return dArr; + } + */ + + // **************************************************************************************** + // * String version + // **************************************************************************************** + + /** + * Encodes a raw byte array into a BASE64 String representation i accordance with RFC 2045. + * + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + String encodeToString(byte[] sArr, boolean lineSep) { + // Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower. + return new String(encodeToChar(sArr, lineSep)); + } + + /* + * Decodes a BASE64 encoded String. All illegal characters will be ignored and can handle both strings with + * and without line separators.
+ * Note! It can be up to about 2x the speed to call decode(str.toCharArray()) instead. That + * will create a temporary array though. This version will use str.charAt(i) to iterate the string. + * + * @param str The source string. null or length 0 will return an empty array. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + * + public final byte[] decode(String str) { + + // Check special case + int sLen = str != null ? str.length() : 0; + if (sLen == 0) { + return new byte[0]; + } + + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. + if (IALPHABET[str.charAt(i)] < 0) { + sepCnt++; + } + } + + // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) { + return null; + } + + // Count '=' at end + int pad = 0; + for (int i = sLen; i > 1 && IALPHABET[str.charAt(--i)] <= 0; ) { + if (str.charAt(i) == '=') { + pad++; + } + } + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len; ) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IALPHABET[str.charAt(s++)]; + if (c >= 0) { + i |= c << (18 - j * 6); + } else { + j--; + } + } + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++] = (byte) (i >> 8); + if (d < len) { + dArr[d++] = (byte) i; + } + } + } + return dArr; + } + + /** + * Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as + * fast as {@link #decode(String)}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * + * @param s The source string. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + * + public final byte[] decodeFast(String s) { + + // Check special case + int sLen = s.length(); + if (sLen == 0) { + return new byte[0]; + } + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IALPHABET[s.charAt(sIx) & 0xff] < 0) { + sIx++; + } + + // Trim illegal chars from end + while (eIx > 0 && IALPHABET[s.charAt(eIx) & 0xff] < 0) { + eIx--; + } + + // get the padding count (=) (0, 1 or 2) + int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) { + // Assemble three bytes into an int from four "valid" characters. + int i = IALPHABET[s.charAt(sIx++)] << 18 | IALPHABET[s.charAt(sIx++)] << 12 | IALPHABET[s.charAt(sIx++)] << 6 | IALPHABET[s.charAt(sIx++)]; + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) { + i |= IALPHABET[s.charAt(sIx++)] << (18 - j * 6); + } + + for (int r = 16; d < len; r -= 8) { + dArr[d++] = (byte) (i >> r); + } + } + + return dArr; + } + */ +} diff --git a/io/jsonwebtoken/io/Base64Decoder.java b/io/jsonwebtoken/io/Base64Decoder.java new file mode 100644 index 0000000..e0cb963 --- /dev/null +++ b/io/jsonwebtoken/io/Base64Decoder.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +import io.jsonwebtoken.lang.Assert; + +/** + * Very fast Base64 decoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + * + * @since 0.10.0 + */ +class Base64Decoder extends Base64Support implements Decoder { + + Base64Decoder() { + super(Base64.DEFAULT); + } + + Base64Decoder(Base64 base64) { + super(base64); + } + + @Override + public byte[] decode(CharSequence s) throws DecodingException { + Assert.notNull(s, "String argument cannot be null"); + return this.base64.decodeFast(s); + } +} \ No newline at end of file diff --git a/io/jsonwebtoken/io/Base64Encoder.java b/io/jsonwebtoken/io/Base64Encoder.java new file mode 100644 index 0000000..6e0a6b0 --- /dev/null +++ b/io/jsonwebtoken/io/Base64Encoder.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +import io.jsonwebtoken.lang.Assert; + +/** + * Very fast Base64 encoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + * + * @since 0.10.0 + */ +class Base64Encoder extends Base64Support implements Encoder { + + Base64Encoder() { + this(Base64.DEFAULT); + } + + Base64Encoder(Base64 base64) { + super(base64); + } + + @Override + public String encode(byte[] bytes) throws EncodingException { + Assert.notNull(bytes, "byte array argument cannot be null"); + return this.base64.encodeToString(bytes, false); + } +} diff --git a/io/jsonwebtoken/io/Base64Support.java b/io/jsonwebtoken/io/Base64Support.java new file mode 100644 index 0000000..8f8a4c1 --- /dev/null +++ b/io/jsonwebtoken/io/Base64Support.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +import io.jsonwebtoken.lang.Assert; + +/** + * Parent class for Base64 encoders and decoders. + * + * @since 0.10.0 + */ +class Base64Support { + + protected final Base64 base64; + + Base64Support(Base64 base64) { + Assert.notNull(base64, "Base64 argument cannot be null"); + this.base64 = base64; + } +} diff --git a/io/jsonwebtoken/io/Base64UrlDecoder.java b/io/jsonwebtoken/io/Base64UrlDecoder.java new file mode 100644 index 0000000..fcca4cb --- /dev/null +++ b/io/jsonwebtoken/io/Base64UrlDecoder.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +/** + * Very fast Base64Url decoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + * + * @since 0.10.0 + */ +class Base64UrlDecoder extends Base64Decoder { + + Base64UrlDecoder() { + super(Base64.URL_SAFE); + } +} diff --git a/io/jsonwebtoken/io/Base64UrlEncoder.java b/io/jsonwebtoken/io/Base64UrlEncoder.java new file mode 100644 index 0000000..1377d31 --- /dev/null +++ b/io/jsonwebtoken/io/Base64UrlEncoder.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +/** + * Very fast Base64Url encoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + * + * @since 0.10.0 + */ +class Base64UrlEncoder extends Base64Encoder { + + Base64UrlEncoder() { + super(Base64.URL_SAFE); + } +} diff --git a/io/jsonwebtoken/io/CodecException.java b/io/jsonwebtoken/io/CodecException.java new file mode 100644 index 0000000..f25d8ca --- /dev/null +++ b/io/jsonwebtoken/io/CodecException.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +/** + * An exception thrown when encountering a problem during encoding or decoding. + * + * @since 0.10.0 + */ +public class CodecException extends IOException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public CodecException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public CodecException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/io/CompressionAlgorithm.java b/io/jsonwebtoken/io/CompressionAlgorithm.java new file mode 100644 index 0000000..5ad0164 --- /dev/null +++ b/io/jsonwebtoken/io/CompressionAlgorithm.java @@ -0,0 +1,65 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.JwtParserBuilder; +import io.jsonwebtoken.Jwts; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Compresses and decompresses byte streams. + * + *

"zip" identifier

+ * + *

{@code CompressionAlgorithm} extends {@code Identifiable}; the value returned from + * {@link Identifiable#getId() getId()} will be used as the JWT + * zip header value.

+ * + *

Custom Implementations

+ * + *

A custom implementation of this interface may be used when creating a JWT by calling the + * {@link JwtBuilder#compressWith(CompressionAlgorithm)} method.

+ * + *

To ensure that parsing is possible, the parser must be aware of the implementation by adding it to the + * {@link JwtParserBuilder#zip()} collection during parser construction.

+ * + * @see Jwts.ZIP#DEF + * @see Jwts.ZIP#GZIP + * @see JSON Web Encryption Compression Algorithms Registry + * @since 0.12.0 + */ +public interface CompressionAlgorithm extends Identifiable { + + /** + * Wraps the specified {@code OutputStream} to ensure any stream bytes are compressed as they are written. + * + * @param out the stream to wrap for compression + * @return the stream to use for writing + */ + OutputStream compress(OutputStream out); + + /** + * Wraps the specified {@code InputStream} to ensure any stream bytes are decompressed as they are read. + * + * @param in the stream to wrap for decompression + * @return the stream to use for reading + */ + InputStream decompress(InputStream in); +} diff --git a/io/jsonwebtoken/io/Decoder.java b/io/jsonwebtoken/io/Decoder.java new file mode 100644 index 0000000..2cf4fe8 --- /dev/null +++ b/io/jsonwebtoken/io/Decoder.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +/** + * A decoder converts an already-encoded data value to a desired data type. + * + * @param decoding input type + * @param decoding output type + * @since 0.10.0 + */ +public interface Decoder { + + /** + * Convert the specified encoded data value into the desired data type. + * + * @param t the encoded data + * @return the resulting expected data + * @throws DecodingException if there is a problem during decoding. + */ + R decode(T t) throws DecodingException; +} diff --git a/io/jsonwebtoken/io/Decoders.java b/io/jsonwebtoken/io/Decoders.java new file mode 100644 index 0000000..6b7c7e6 --- /dev/null +++ b/io/jsonwebtoken/io/Decoders.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +/** + * Constant definitions for various decoding algorithms. + * + * @see #BASE64 + * @see #BASE64URL + * @since 0.10.0 + */ +public final class Decoders { + + /** + * Very fast Base64 decoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + */ + public static final Decoder BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder()); + + /** + * Very fast Base64Url decoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + */ + public static final Decoder BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder()); + + private Decoders() { //prevent instantiation + } +} diff --git a/io/jsonwebtoken/io/DecodingException.java b/io/jsonwebtoken/io/DecodingException.java new file mode 100644 index 0000000..ab3df92 --- /dev/null +++ b/io/jsonwebtoken/io/DecodingException.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +/** + * An exception thrown when encountering a problem during decoding. + * + * @since 0.10.0 + */ +public class DecodingException extends CodecException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public DecodingException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public DecodingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/io/DeserializationException.java b/io/jsonwebtoken/io/DeserializationException.java new file mode 100644 index 0000000..76c647c --- /dev/null +++ b/io/jsonwebtoken/io/DeserializationException.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +/** + * Exception thrown when reconstituting a serialized byte array into a Java object. + * + * @since 0.10.0 + */ +public class DeserializationException extends SerialException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param msg the message explaining why the exception is thrown. + */ + public DeserializationException(String msg) { + super(msg); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public DeserializationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/io/Deserializer.java b/io/jsonwebtoken/io/Deserializer.java new file mode 100644 index 0000000..a61a28a --- /dev/null +++ b/io/jsonwebtoken/io/Deserializer.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +import java.io.Reader; + +/** + * A {@code Deserializer} is able to convert serialized byte streams into Java objects. + * + * @param the type of object to be returned as a result of deserialization. + * @since 0.10.0 + */ +public interface Deserializer { + + /** + * Convert the specified formatted data byte array into a Java object. + * + * @param bytes the formatted data byte array to convert + * @return the reconstituted Java object + * @throws DeserializationException if there is a problem converting the byte array to an object. + * @deprecated since 0.12.0 in favor of {@link #deserialize(Reader)} + */ + @Deprecated + T deserialize(byte[] bytes) throws DeserializationException; + + /** + * Reads the specified character stream and returns the corresponding Java object. + * + * @param reader the reader to use to read the character stream + * @return the deserialized Java object + * @throws DeserializationException if there is a problem reading the stream or creating the expected Java object + * @since 0.12.0 + */ + T deserialize(Reader reader) throws DeserializationException; +} diff --git a/io/jsonwebtoken/io/Encoder.java b/io/jsonwebtoken/io/Encoder.java new file mode 100644 index 0000000..f334ee8 --- /dev/null +++ b/io/jsonwebtoken/io/Encoder.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +/** + * An encoder converts data of one type into another formatted data value. + * + * @param the type of data to convert + * @param the type of the resulting formatted data + * @since 0.10.0 + */ +public interface Encoder { + + /** + * Convert the specified data into another formatted data value. + * + * @param t the data to convert + * @return the resulting formatted data value + * @throws EncodingException if there is a problem during encoding + */ + R encode(T t) throws EncodingException; +} diff --git a/io/jsonwebtoken/io/Encoders.java b/io/jsonwebtoken/io/Encoders.java new file mode 100644 index 0000000..17f03f2 --- /dev/null +++ b/io/jsonwebtoken/io/Encoders.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +/** + * Constant definitions for various encoding algorithms. + * + * @see #BASE64 + * @see #BASE64URL + * @since 0.10.0 + */ +public final class Encoders { + + /** + * Very fast Base64 encoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + */ + public static final Encoder BASE64 = new ExceptionPropagatingEncoder<>(new Base64Encoder()); + + /** + * Very fast Base64Url encoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + */ + public static final Encoder BASE64URL = new ExceptionPropagatingEncoder<>(new Base64UrlEncoder()); + + private Encoders() { //prevent instantiation + } +} diff --git a/io/jsonwebtoken/io/EncodingException.java b/io/jsonwebtoken/io/EncodingException.java new file mode 100644 index 0000000..c5ee9f9 --- /dev/null +++ b/io/jsonwebtoken/io/EncodingException.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +/** + * An exception thrown when encountering a problem during encoding. + * + * @since 0.10.0 + */ +public class EncodingException extends CodecException { + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public EncodingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/io/ExceptionPropagatingDecoder.java b/io/jsonwebtoken/io/ExceptionPropagatingDecoder.java new file mode 100644 index 0000000..9e5bc78 --- /dev/null +++ b/io/jsonwebtoken/io/ExceptionPropagatingDecoder.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +import io.jsonwebtoken.lang.Assert; + +/** + * Decoder that ensures any exceptions thrown that are not {@link DecodingException}s are wrapped + * and re-thrown as a {@code DecodingException}. + * + * @since 0.10.0 + */ +class ExceptionPropagatingDecoder implements Decoder { + + private final Decoder decoder; + + /** + * Creates a new instance, wrapping the specified {@code decoder} to invoke during {@link #decode(Object)}. + * + * @param decoder the decoder to wrap and call during {@link #decode(Object)} + */ + ExceptionPropagatingDecoder(Decoder decoder) { + Assert.notNull(decoder, "Decoder cannot be null."); + this.decoder = decoder; + } + + /** + * Decode the specified encoded data, delegating to the wrapped Decoder, wrapping any + * non-{@link DecodingException} as a {@code DecodingException}. + * + * @param t the encoded data + * @return the decoded data + * @throws DecodingException if there is an unexpected problem during decoding. + */ + @Override + public R decode(T t) throws DecodingException { + Assert.notNull(t, "Decode argument cannot be null."); + try { + return decoder.decode(t); + } catch (DecodingException e) { + throw e; //propagate + } catch (Exception e) { + String msg = "Unable to decode input: " + e.getMessage(); + throw new DecodingException(msg, e); + } + } +} diff --git a/io/jsonwebtoken/io/ExceptionPropagatingEncoder.java b/io/jsonwebtoken/io/ExceptionPropagatingEncoder.java new file mode 100644 index 0000000..8efca95 --- /dev/null +++ b/io/jsonwebtoken/io/ExceptionPropagatingEncoder.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +import io.jsonwebtoken.lang.Assert; + +/** + * Encoder that ensures any exceptions thrown that are not {@link EncodingException}s are wrapped + * and re-thrown as a {@code EncodingException}. + * + * @since 0.10.0 + */ +class ExceptionPropagatingEncoder implements Encoder { + + private final Encoder encoder; + + /** + * Creates a new instance, wrapping the specified {@code encoder} to invoke during {@link #encode(Object)}. + * + * @param encoder the encoder to wrap and call during {@link #encode(Object)} + */ + ExceptionPropagatingEncoder(Encoder encoder) { + Assert.notNull(encoder, "Encoder cannot be null."); + this.encoder = encoder; + } + + /** + * Encoded the specified data, delegating to the wrapped Encoder, wrapping any + * non-{@link EncodingException} as an {@code EncodingException}. + * + * @param t the data to encode + * @return the encoded data + * @throws EncodingException if there is an unexpected problem during encoding. + */ + @Override + public R encode(T t) throws EncodingException { + Assert.notNull(t, "Encode argument cannot be null."); + try { + return this.encoder.encode(t); + } catch (EncodingException e) { + throw e; //propagate + } catch (Exception e) { + String msg = "Unable to encode input: " + e.getMessage(); + throw new EncodingException(msg, e); + } + } +} diff --git a/io/jsonwebtoken/io/IOException.java b/io/jsonwebtoken/io/IOException.java new file mode 100644 index 0000000..0ccd165 --- /dev/null +++ b/io/jsonwebtoken/io/IOException.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +import io.jsonwebtoken.JwtException; + +/** + * JJWT's base exception for problems during data input or output operations, such as serialization, + * deserialization, marshalling, unmarshalling, etc. + * + * @since 0.10.0 + */ +public class IOException extends JwtException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param msg the message explaining why the exception is thrown. + */ + public IOException(String msg) { + super(msg); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public IOException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/io/Parser.java b/io/jsonwebtoken/io/Parser.java new file mode 100644 index 0000000..b8cac46 --- /dev/null +++ b/io/jsonwebtoken/io/Parser.java @@ -0,0 +1,68 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +import java.io.InputStream; +import java.io.Reader; + +/** + * A Parser converts a character stream into a Java object. + * + * @param the instance type created after parsing + * @since 0.12.0 + */ +public interface Parser { + + /** + * Parse the specified character sequence into a Java object. + * + * @param input the character sequence to parse into a Java object. + * @return the Java object represented by the specified {@code input} stream. + */ + T parse(CharSequence input); + + /** + * Parse the specified character sequence with the specified bounds into a Java object. + * + * @param input The character sequence, may be {@code null} + * @param start The start index in the character sequence, inclusive + * @param end The end index in the character sequence, exclusive + * @return the Java object represented by the specified sequence bounds + * @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index + */ + T parse(CharSequence input, int start, int end); + + /** + * Parse the specified character sequence into a Java object. + * + * @param reader the reader to use to parse a Java object. + * @return the Java object represented by the specified {@code input} stream. + */ + T parse(Reader reader); + + /** + * Parses the specified {@link InputStream} assuming {@link java.nio.charset.StandardCharsets#UTF_8 UTF_8} encoding. + * This is a convenience alias for: + * + *
{@link #parse(Reader) parse}(new {@link java.io.InputStreamReader
+     * InputStreamReader}(in, {@link java.nio.charset.StandardCharsets#UTF_8
+     * StandardCharsets.UTF_8});
+ * + * @param in the UTF-8 InputStream. + * @return the Java object represented by the specified {@link InputStream}. + */ + T parse(InputStream in); +} diff --git a/io/jsonwebtoken/io/ParserBuilder.java b/io/jsonwebtoken/io/ParserBuilder.java new file mode 100644 index 0000000..9cb0068 --- /dev/null +++ b/io/jsonwebtoken/io/ParserBuilder.java @@ -0,0 +1,54 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +import io.jsonwebtoken.lang.Builder; + +import java.security.Provider; +import java.util.Map; + +/** + * A {@code ParserBuilder} configures and creates new {@link Parser} instances. + * + * @param The resulting parser's {@link Parser#parse parse} output type + * @param builder type used for method chaining + * @since 0.12.0 + */ +public interface ParserBuilder> extends Builder> { + + /** + * Sets the JCA Provider to use during cryptographic operations, or {@code null} if the + * JCA subsystem preferred provider should be used. + * + * @param provider the JCA Provider to use during cryptographic key factory operations, or {@code null} + * if the JCA subsystem preferred provider should be used. + * @return the builder for method chaining. + */ + B provider(Provider provider); + + /** + * Uses the specified {@code Deserializer} to convert JSON Strings (UTF-8 byte streams) into Java Map objects. The + * resulting Maps are then used to construct respective JWT objects (JWTs, JWKs, etc). + * + *

If this method is not called, JJWT will use whatever Deserializer it can find at runtime, checking for the + * presence of well-known implementations such as Jackson, Gson, and org.json. If one of these is not found + * in the runtime classpath, an exception will be thrown when the {@link #build()} method is called. + * + * @param deserializer the Deserializer to use when converting JSON Strings (UTF-8 byte streams) into Map objects. + * @return the builder for method chaining. + */ + B json(Deserializer> deserializer); +} diff --git a/io/jsonwebtoken/io/SerialException.java b/io/jsonwebtoken/io/SerialException.java new file mode 100644 index 0000000..0269c96 --- /dev/null +++ b/io/jsonwebtoken/io/SerialException.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +/** + * An exception thrown during serialization or deserialization. + * + * @since 0.10.0 + */ +public class SerialException extends IOException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param msg the message explaining why the exception is thrown. + */ + public SerialException(String msg) { + super(msg); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public SerialException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/io/SerializationException.java b/io/jsonwebtoken/io/SerializationException.java new file mode 100644 index 0000000..d978921 --- /dev/null +++ b/io/jsonwebtoken/io/SerializationException.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +/** + * Exception thrown when converting a Java object to a formatted byte array. + * + * @since 0.10.0 + */ +public class SerializationException extends SerialException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param msg the message explaining why the exception is thrown. + */ + public SerializationException(String msg) { + super(msg); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public SerializationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/io/Serializer.java b/io/jsonwebtoken/io/Serializer.java new file mode 100644 index 0000000..6bc59ce --- /dev/null +++ b/io/jsonwebtoken/io/Serializer.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.io; + +import java.io.OutputStream; + +/** + * A {@code Serializer} is able to convert a Java object into a formatted byte stream. It is expected this byte stream + * can be reconstituted back into a Java object with a matching {@link Deserializer}. + * + * @param The type of object to serialize. + * @since 0.10.0 + */ +public interface Serializer { + + /** + * Converts the specified Java object into a formatted data byte array. + * + * @param t the object to serialize + * @return the serialized byte array representing the specified object. + * @throws SerializationException if there is a problem converting the object to a byte array. + * @deprecated since 0.12.0 in favor of {@link #serialize(Object, OutputStream)} + */ + @Deprecated + byte[] serialize(T t) throws SerializationException; + + /** + * Converts the specified Java object into a formatted data byte stream, writing the bytes to the specified + * {@code out}put stream. + * + * @param t the object to convert to a byte stream + * @param out the stream to write to + * @throws SerializationException if there is a problem converting the object to a byte stream or writing the + * bytes to the {@code out}put stream. + * @since 0.12.0 + */ + void serialize(T t, OutputStream out) throws SerializationException; +} diff --git a/io/jsonwebtoken/lang/Arrays.java b/io/jsonwebtoken/lang/Arrays.java new file mode 100644 index 0000000..6c5b4e2 --- /dev/null +++ b/io/jsonwebtoken/lang/Arrays.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +import java.lang.reflect.Array; +import java.util.List; + +/** + * Utility methods to work with array instances. + * + * @since 0.6 + */ +public final class Arrays { + + private Arrays() { + } //prevent instantiation + + /** + * Returns the length of the array, or {@code 0} if the array is {@code null}. + * + * @param a the possibly-null array + * @param the type of elements in the array + * @return the length of the array, or zero if the array is null. + */ + public static int length(T[] a) { + return a == null ? 0 : a.length; + } + + /** + * Converts the specified array to a {@link List}. If the array is empty, an empty list will be returned. + * + * @param a the array to represent as a list + * @param the type of elements in the array + * @return the array as a list, or an empty list if the array is empty. + */ + public static List asList(T[] a) { + return Objects.isEmpty(a) ? Collections.emptyList() : java.util.Arrays.asList(a); + } + + /** + * Returns the length of the specified byte array, or {@code 0} if the byte array is {@code null}. + * + * @param bytes the array to check + * @return the length of the specified byte array, or {@code 0} if the byte array is {@code null}. + */ + public static int length(byte[] bytes) { + return bytes != null ? bytes.length : 0; + } + + /** + * Returns the byte array unaltered if it is non-null and has a positive length, otherwise {@code null}. + * + * @param bytes the byte array to check. + * @return the byte array unaltered if it is non-null and has a positive length, otherwise {@code null}. + */ + public static byte[] clean(byte[] bytes) { + return length(bytes) > 0 ? bytes : null; + } + + /** + * Creates a shallow copy of the specified object or array. + * + * @param obj the object to copy + * @return a shallow copy of the specified object or array. + */ + public static Object copy(Object obj) { + if (obj == null) { + return null; + } + Assert.isTrue(Objects.isArray(obj), "Argument must be an array."); + if (obj instanceof Object[]) { + return ((Object[]) obj).clone(); + } + if (obj instanceof boolean[]) { + return ((boolean[]) obj).clone(); + } + if (obj instanceof byte[]) { + return ((byte[]) obj).clone(); + } + if (obj instanceof char[]) { + return ((char[]) obj).clone(); + } + if (obj instanceof double[]) { + return ((double[]) obj).clone(); + } + if (obj instanceof float[]) { + return ((float[]) obj).clone(); + } + if (obj instanceof int[]) { + return ((int[]) obj).clone(); + } + if (obj instanceof long[]) { + return ((long[]) obj).clone(); + } + if (obj instanceof short[]) { + return ((short[]) obj).clone(); + } + Class componentType = obj.getClass().getComponentType(); + int length = Array.getLength(obj); + Object[] copy = (Object[]) Array.newInstance(componentType, length); + for (int i = 0; i < length; i++) { + copy[i] = Array.get(obj, i); + } + return copy; + } +} diff --git a/io/jsonwebtoken/lang/Assert.java b/io/jsonwebtoken/lang/Assert.java new file mode 100644 index 0000000..022d58f --- /dev/null +++ b/io/jsonwebtoken/lang/Assert.java @@ -0,0 +1,558 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +import java.util.Collection; +import java.util.Map; + +/** + * Utility methods for providing argument and state assertions to reduce repeating these patterns and otherwise + * increasing cyclomatic complexity. + */ +public final class Assert { + + private Assert() { + } //prevent instantiation + + /** + * Assert a boolean expression, throwing IllegalArgumentException + * if the test result is false. + *

Assert.isTrue(i > 0, "The value must be greater than zero");
+ * + * @param expression a boolean expression + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if expression is false + */ + public static void isTrue(boolean expression, String message) { + if (!expression) { + throw new IllegalArgumentException(message); + } + } + + /** + * Assert a boolean expression, throwing IllegalArgumentException + * if the test result is false. + *
Assert.isTrue(i > 0);
+ * + * @param expression a boolean expression + * @throws IllegalArgumentException if expression is false + */ + public static void isTrue(boolean expression) { + isTrue(expression, "[Assertion failed] - this expression must be true"); + } + + /** + * Assert that an object is null . + *
Assert.isNull(value, "The value must be null");
+ * + * @param object the object to check + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if the object is not null + */ + public static void isNull(Object object, String message) { + if (object != null) { + throw new IllegalArgumentException(message); + } + } + + /** + * Assert that an object is null . + *
Assert.isNull(value);
+ * + * @param object the object to check + * @throws IllegalArgumentException if the object is not null + */ + public static void isNull(Object object) { + isNull(object, "[Assertion failed] - the object argument must be null"); + } + + /** + * Assert that an object is not null . + *
Assert.notNull(clazz, "The class must not be null");
+ * + * @param object the object to check + * @param the type of object + * @param message the exception message to use if the assertion fails + * @return the non-null object + * @throws IllegalArgumentException if the object is null + */ + public static T notNull(T object, String message) { + if (object == null) { + throw new IllegalArgumentException(message); + } + return object; + } + + /** + * Assert that an object is not null . + *
Assert.notNull(clazz);
+ * + * @param object the object to check + * @throws IllegalArgumentException if the object is null + */ + public static void notNull(Object object) { + notNull(object, "[Assertion failed] - this argument is required; it must not be null"); + } + + /** + * Assert that the given String is not empty; that is, + * it must not be null and not the empty String. + *
Assert.hasLength(name, "Name must not be empty");
+ * + * @param text the String to check + * @param message the exception message to use if the assertion fails + * @see Strings#hasLength + */ + public static void hasLength(String text, String message) { + if (!Strings.hasLength(text)) { + throw new IllegalArgumentException(message); + } + } + + /** + * Assert that the given String is not empty; that is, + * it must not be null and not the empty String. + *
Assert.hasLength(name);
+ * + * @param text the String to check + * @see Strings#hasLength + */ + public static void hasLength(String text) { + hasLength(text, + "[Assertion failed] - this String argument must have length; it must not be null or empty"); + } + + /** + * Assert that the given String has valid text content; that is, it must not + * be null and must contain at least one non-whitespace character. + *
Assert.hasText(name, "'name' must not be empty");
+ * + * @param the type of CharSequence + * @param text the CharSequence to check + * @param message the exception message to use if the assertion fails + * @return the CharSequence if it has text + * @see Strings#hasText + */ + public static T hasText(T text, String message) { + if (!Strings.hasText(text)) { + throw new IllegalArgumentException(message); + } + return text; + } + + /** + * Assert that the given String has valid text content; that is, it must not + * be null and must contain at least one non-whitespace character. + *
Assert.hasText(name, "'name' must not be empty");
+ * + * @param text the String to check + * @see Strings#hasText + */ + public static void hasText(String text) { + hasText(text, + "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank"); + } + + /** + * Assert that the given text does not contain the given substring. + *
Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");
+ * + * @param textToSearch the text to search + * @param substring the substring to find within the text + * @param message the exception message to use if the assertion fails + */ + public static void doesNotContain(String textToSearch, String substring, String message) { + if (Strings.hasLength(textToSearch) && Strings.hasLength(substring) && + textToSearch.indexOf(substring) != -1) { + throw new IllegalArgumentException(message); + } + } + + /** + * Assert that the given text does not contain the given substring. + *
Assert.doesNotContain(name, "rod");
+ * + * @param textToSearch the text to search + * @param substring the substring to find within the text + */ + public static void doesNotContain(String textToSearch, String substring) { + doesNotContain(textToSearch, substring, + "[Assertion failed] - this String argument must not contain the substring [" + substring + "]"); + } + + + /** + * Assert that an array has elements; that is, it must not be + * null and must have at least one element. + *
Assert.notEmpty(array, "The array must have elements");
+ * + * @param array the array to check + * @param message the exception message to use if the assertion fails + * @return the non-empty array for immediate use + * @throws IllegalArgumentException if the object array is null or has no elements + */ + public static Object[] notEmpty(Object[] array, String message) { + if (Objects.isEmpty(array)) { + throw new IllegalArgumentException(message); + } + return array; + } + + /** + * Assert that an array has elements; that is, it must not be + * null and must have at least one element. + *
Assert.notEmpty(array);
+ * + * @param array the array to check + * @throws IllegalArgumentException if the object array is null or has no elements + */ + public static void notEmpty(Object[] array) { + notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element"); + } + + /** + * Assert that the specified byte array is not null and has at least one byte element. + * + * @param array the byte array to check + * @param msg the exception message to use if the assertion fails + * @return the byte array if the assertion passes + * @throws IllegalArgumentException if the byte array is null or empty + * @since 0.12.0 + */ + public static byte[] notEmpty(byte[] array, String msg) { + if (Objects.isEmpty(array)) { + throw new IllegalArgumentException(msg); + } + return array; + } + + /** + * Assert that the specified character array is not null and has at least one byte element. + * + * @param chars the character array to check + * @param msg the exception message to use if the assertion fails + * @return the character array if the assertion passes + * @throws IllegalArgumentException if the character array is null or empty + * @since 0.12.0 + */ + public static char[] notEmpty(char[] chars, String msg) { + if (Objects.isEmpty(chars)) { + throw new IllegalArgumentException(msg); + } + return chars; + } + + /** + * Assert that an array has no null elements. + * Note: Does not complain if the array is empty! + *
Assert.noNullElements(array, "The array must have non-null elements");
+ * + * @param array the array to check + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if the object array contains a null element + */ + public static void noNullElements(Object[] array, String message) { + if (array != null) { + for (int i = 0; i < array.length; i++) { + if (array[i] == null) { + throw new IllegalArgumentException(message); + } + } + } + } + + /** + * Assert that an array has no null elements. + * Note: Does not complain if the array is empty! + *
Assert.noNullElements(array);
+ * + * @param array the array to check + * @throws IllegalArgumentException if the object array contains a null element + */ + public static void noNullElements(Object[] array) { + noNullElements(array, "[Assertion failed] - this array must not contain any null elements"); + } + + /** + * Assert that a collection has elements; that is, it must not be + * null and must have at least one element. + *
Assert.notEmpty(collection, "Collection must have elements");
+ * + * @param collection the collection to check + * @param the type of collection + * @param message the exception message to use if the assertion fails + * @return the non-null, non-empty collection + * @throws IllegalArgumentException if the collection is null or has no elements + */ + public static > T notEmpty(T collection, String message) { + if (Collections.isEmpty(collection)) { + throw new IllegalArgumentException(message); + } + return collection; + } + + /** + * Assert that a collection has elements; that is, it must not be + * null and must have at least one element. + *
Assert.notEmpty(collection, "Collection must have elements");
+ * + * @param collection the collection to check + * @throws IllegalArgumentException if the collection is null or has no elements + */ + public static void notEmpty(Collection collection) { + notEmpty(collection, + "[Assertion failed] - this collection must not be empty: it must contain at least 1 element"); + } + + /** + * Assert that a Map has entries; that is, it must not be null + * and must have at least one entry. + *
Assert.notEmpty(map, "Map must have entries");
+ * + * @param map the map to check + * @param the type of Map to check + * @param message the exception message to use if the assertion fails + * @return the non-null, non-empty map + * @throws IllegalArgumentException if the map is null or has no entries + */ + public static > T notEmpty(T map, String message) { + if (Collections.isEmpty(map)) { + throw new IllegalArgumentException(message); + } + return map; + } + + /** + * Assert that a Map has entries; that is, it must not be null + * and must have at least one entry. + *
Assert.notEmpty(map);
+ * + * @param map the map to check + * @throws IllegalArgumentException if the map is null or has no entries + */ + public static void notEmpty(Map map) { + notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry"); + } + + + /** + * Assert that the provided object is an instance of the provided class. + *
Assert.instanceOf(Foo.class, foo);
+ * + * @param the type of instance expected + * @param clazz the required class + * @param obj the object to check + * @return the expected instance of type {@code T} + * @throws IllegalArgumentException if the object is not an instance of clazz + * @see Class#isInstance + */ + public static T isInstanceOf(Class clazz, Object obj) { + return isInstanceOf(clazz, obj, ""); + } + + /** + * Assert that the provided object is an instance of the provided class. + *
Assert.instanceOf(Foo.class, foo);
+ * + * @param type the type to check against + * @param the object's expected type + * @param obj the object to check + * @param message a message which will be prepended to the message produced by + * the function itself, and which may be used to provide context. It should + * normally end in a ": " or ". " so that the function generate message looks + * ok when prepended to it. + * @return the non-null object IFF it is an instance of the specified {@code type}. + * @throws IllegalArgumentException if the object is not an instance of clazz + * @see Class#isInstance + */ + public static T isInstanceOf(Class type, Object obj, String message) { + notNull(type, "Type to check against must not be null"); + if (!type.isInstance(obj)) { + throw new IllegalArgumentException(message + + "Object of class [" + (obj != null ? obj.getClass().getName() : "null") + + "] must be an instance of " + type); + } + return type.cast(obj); + } + + /** + * Asserts that the provided object is an instance of the provided class, throwing an + * {@link IllegalStateException} otherwise. + *
Assert.stateIsInstance(Foo.class, foo);
+ * + * @param type the type to check against + * @param the object's expected type + * @param obj the object to check + * @param message a message which will be prepended to the message produced by + * the function itself, and which may be used to provide context. It should + * normally end in a ": " or ". " so that the function generate message looks + * ok when prepended to it. + * @return the non-null object IFF it is an instance of the specified {@code type}. + * @throws IllegalStateException if the object is not an instance of clazz + * @see Class#isInstance + */ + public static T stateIsInstance(Class type, Object obj, String message) { + notNull(type, "Type to check cannot be null."); + if (!type.isInstance(obj)) { + String msg = message + "Object of class [" + Objects.nullSafeClassName(obj) + + "] must be an instance of " + type; + throw new IllegalStateException(msg); + } + return type.cast(obj); + } + + /** + * Assert that superType.isAssignableFrom(subType) is true. + *
Assert.isAssignable(Number.class, myClass);
+ * + * @param superType the super type to check + * @param subType the sub type to check + * @throws IllegalArgumentException if the classes are not assignable + */ + public static void isAssignable(Class superType, Class subType) { + isAssignable(superType, subType, ""); + } + + /** + * Assert that superType.isAssignableFrom(subType) is true. + *
Assert.isAssignable(Number.class, myClass);
+ * + * @param superType the super type to check against + * @param subType the sub type to check + * @param message a message which will be prepended to the message produced by + * the function itself, and which may be used to provide context. It should + * normally end in a ": " or ". " so that the function generate message looks + * ok when prepended to it. + * @throws IllegalArgumentException if the classes are not assignable + */ + public static void isAssignable(Class superType, Class subType, String message) { + notNull(superType, "Type to check against must not be null"); + if (subType == null || !superType.isAssignableFrom(subType)) { + throw new IllegalArgumentException(message + subType + " is not assignable to " + superType); + } + } + + /** + * Asserts that a specified {@code value} is equal to the given {@code requirement}, throwing + * an {@link IllegalArgumentException} with the given message if not. + * + * @param the type of argument + * @param value the value to check + * @param requirement the requirement that {@code value} must be greater than + * @param msg the message to use for the {@code IllegalArgumentException} if thrown. + * @return {@code value} if greater than the specified {@code requirement}. + * @since 0.12.0 + */ + public static > T eq(T value, T requirement, String msg) { + if (compareTo(value, requirement) != 0) { + throw new IllegalArgumentException(msg); + } + return value; + } + + private static > int compareTo(T value, T requirement) { + notNull(value, "value cannot be null."); + notNull(requirement, "requirement cannot be null."); + return value.compareTo(requirement); + } + + /** + * Asserts that a specified {@code value} is greater than the given {@code requirement}, throwing + * an {@link IllegalArgumentException} with the given message if not. + * + * @param the type of value to check and return if the requirement is met + * @param value the value to check + * @param requirement the requirement that {@code value} must be greater than + * @param msg the message to use for the {@code IllegalArgumentException} if thrown. + * @return {@code value} if greater than the specified {@code requirement}. + * @since 0.12.0 + */ + public static > T gt(T value, T requirement, String msg) { + if (!(compareTo(value, requirement) > 0)) { + throw new IllegalArgumentException(msg); + } + return value; + } + + /** + * Asserts that a specified {@code value} is less than or equal to the given {@code requirement}, throwing + * an {@link IllegalArgumentException} with the given message if not. + * + * @param the type of value to check and return if the requirement is met + * @param value the value to check + * @param requirement the requirement that {@code value} must be greater than + * @param msg the message to use for the {@code IllegalArgumentException} if thrown. + * @return {@code value} if greater than the specified {@code requirement}. + * @since 0.12.0 + */ + public static > T lte(T value, T requirement, String msg) { + if (compareTo(value, requirement) > 0) { + throw new IllegalArgumentException(msg); + } + return value; + } + + + /** + * Assert a boolean expression, throwing IllegalStateException + * if the test result is false. Call isTrue if you wish to + * throw IllegalArgumentException on an assertion failure. + *
Assert.state(id == null, "The id property must not already be initialized");
+ * + * @param expression a boolean expression + * @param message the exception message to use if the assertion fails + * @throws IllegalStateException if expression is false + */ + public static void state(boolean expression, String message) { + if (!expression) { + throw new IllegalStateException(message); + } + } + + /** + * Assert a boolean expression, throwing {@link IllegalStateException} + * if the test result is false. + *

Call {@link #isTrue(boolean)} if you wish to + * throw {@link IllegalArgumentException} on an assertion failure. + *

Assert.state(id == null);
+ * + * @param expression a boolean expression + * @throws IllegalStateException if the supplied expression is false + */ + public static void state(boolean expression) { + state(expression, "[Assertion failed] - this state invariant must be true"); + } + + /** + * Asserts that the specified {@code value} is not null, otherwise throws an + * {@link IllegalStateException} with the specified {@code msg}. Intended to be used with + * code invariants (as opposed to method arguments, like {@link #notNull(Object)}). + * + * @param value value to assert is not null + * @param msg exception message to use if {@code value} is null + * @param value type + * @return the non-null value + * @throws IllegalStateException with the specified {@code msg} if {@code value} is null. + * @since 0.12.0 + */ + public static T stateNotNull(T value, String msg) throws IllegalStateException { + if (value == null) { + throw new IllegalStateException(msg); + } + return value; + } + +} diff --git a/io/jsonwebtoken/lang/Builder.java b/io/jsonwebtoken/lang/Builder.java new file mode 100644 index 0000000..506c802 --- /dev/null +++ b/io/jsonwebtoken/lang/Builder.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +/** + * Type-safe interface that reflects the Builder pattern. + * + * @param The type of object that will be created when {@link #build()} is invoked. + * @since 0.12.0 + */ +public interface Builder { + + /** + * Creates and returns a new instance of type {@code T}. + * + * @return a new instance of type {@code T}. + */ + T build(); +} diff --git a/io/jsonwebtoken/lang/Classes.java b/io/jsonwebtoken/lang/Classes.java new file mode 100644 index 0000000..37e0752 --- /dev/null +++ b/io/jsonwebtoken/lang/Classes.java @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; + +/** + * Utility methods for working with {@link Class}es. + * + * @since 0.1 + */ +public final class Classes { + + private Classes() { + } //prevent instantiation + + private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() { + @Override + protected ClassLoader doGetClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + }; + + private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() { + @Override + protected ClassLoader doGetClassLoader() { + return Classes.class.getClassLoader(); + } + }; + + private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() { + @Override + protected ClassLoader doGetClassLoader() { + return ClassLoader.getSystemClassLoader(); + } + }; + + /** + * Attempts to load the specified class name from the current thread's + * {@link Thread#getContextClassLoader() context class loader}, then the + * current ClassLoader (Classes.class.getClassLoader()), then the system/application + * ClassLoader (ClassLoader.getSystemClassLoader(), in that order. If any of them cannot locate + * the specified class, an UnknownClassException is thrown (our RuntimeException equivalent of + * the JRE's ClassNotFoundException. + * + * @param fqcn the fully qualified class name to load + * @param The type of Class returned + * @return the located class + * @throws UnknownClassException if the class cannot be found. + */ + @SuppressWarnings("unchecked") + public static Class forName(String fqcn) throws UnknownClassException { + + Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn); + + if (clazz == null) { + clazz = CLASS_CL_ACCESSOR.loadClass(fqcn); + } + + if (clazz == null) { + clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn); + } + + if (clazz == null) { + String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " + + "system/application ClassLoaders. All heuristics have been exhausted. Class could not be found."; + + if (fqcn != null && fqcn.startsWith("io.jsonwebtoken.impl")) { + msg += " Have you remembered to include the jjwt-impl.jar in your runtime classpath?"; + } + + throw new UnknownClassException(msg); + } + + return (Class) clazz; + } + + /** + * Returns the specified resource by checking the current thread's + * {@link Thread#getContextClassLoader() context class loader}, then the + * current ClassLoader (Classes.class.getClassLoader()), then the system/application + * ClassLoader (ClassLoader.getSystemClassLoader(), in that order, using + * {@link ClassLoader#getResourceAsStream(String) getResourceAsStream(name)}. + * + * @param name the name of the resource to acquire from the classloader(s). + * @return the InputStream of the resource found, or null if the resource cannot be found from any + * of the three mentioned ClassLoaders. + * @since 0.8 + */ + public static InputStream getResourceAsStream(String name) { + + InputStream is = THREAD_CL_ACCESSOR.getResourceStream(name); + + if (is == null) { + is = CLASS_CL_ACCESSOR.getResourceStream(name); + } + + if (is == null) { + is = SYSTEM_CL_ACCESSOR.getResourceStream(name); + } + + return is; + } + + /** + * Returns the specified resource URL by checking the current thread's + * {@link Thread#getContextClassLoader() context class loader}, then the + * current ClassLoader (Classes.class.getClassLoader()), then the system/application + * ClassLoader (ClassLoader.getSystemClassLoader(), in that order, using + * {@link ClassLoader#getResource(String) getResource(name)}. + * + * @param name the name of the resource to acquire from the classloader(s). + * @return the URL of the resource found, or null if the resource cannot be found from any + * of the three mentioned ClassLoaders. + * @since 0.12.0 + */ + private static URL getResource(String name) { + URL url = THREAD_CL_ACCESSOR.getResource(name); + if (url == null) { + url = CLASS_CL_ACCESSOR.getResource(name); + } + if (url == null) { + return SYSTEM_CL_ACCESSOR.getResource(name); + } + return url; + } + + /** + * Returns {@code true} if the specified {@code fullyQualifiedClassName} can be found in any of the thread + * context, class, or system classloaders, or {@code false} otherwise. + * + * @param fullyQualifiedClassName the fully qualified class name to check + * @return {@code true} if the specified {@code fullyQualifiedClassName} can be found in any of the thread + * context, class, or system classloaders, or {@code false} otherwise. + */ + public static boolean isAvailable(String fullyQualifiedClassName) { + try { + forName(fullyQualifiedClassName); + return true; + } catch (UnknownClassException e) { + return false; + } + } + + /** + * Creates and returns a new instance of the class with the specified fully qualified class name using the + * classes default no-argument constructor. + * + * @param fqcn the fully qualified class name + * @param the type of object created + * @return a new instance of the specified class name + */ + @SuppressWarnings("unchecked") + public static T newInstance(String fqcn) { + return (T) newInstance(forName(fqcn)); + } + + /** + * Creates and returns a new instance of the specified fully qualified class name using the + * specified {@code args} arguments provided to the constructor with {@code ctorArgTypes} + * + * @param fqcn the fully qualified class name + * @param ctorArgTypes the argument types of the constructor to invoke + * @param args the arguments to supply when invoking the constructor + * @param the type of object created + * @return the newly created object + */ + public static T newInstance(String fqcn, Class[] ctorArgTypes, Object... args) { + Class clazz = forName(fqcn); + Constructor ctor = getConstructor(clazz, ctorArgTypes); + return instantiate(ctor, args); + } + + /** + * Creates and returns a new instance of the specified fully qualified class name using a constructor that matches + * the specified {@code args} arguments. + * + * @param fqcn fully qualified class name + * @param args the arguments to supply to the constructor + * @param the type of the object created + * @return the newly created object + */ + @SuppressWarnings("unchecked") + public static T newInstance(String fqcn, Object... args) { + return (T) newInstance(forName(fqcn), args); + } + + /** + * Creates a new instance of the specified {@code clazz} via {@code clazz.newInstance()}. + * + * @param clazz the class to invoke + * @param the type of the object created + * @return the newly created object + */ + public static T newInstance(Class clazz) { + if (clazz == null) { + String msg = "Class method parameter cannot be null."; + throw new IllegalArgumentException(msg); + } + try { + return clazz.newInstance(); + } catch (Exception e) { + throw new InstantiationException("Unable to instantiate class [" + clazz.getName() + "]", e); + } + } + + /** + * Returns a new instance of the specified {@code clazz}, invoking the associated constructor with the specified + * {@code args} arguments. + * + * @param clazz the class to invoke + * @param args the arguments matching an associated class constructor + * @param the type of the created object + * @return the newly created object + */ + public static T newInstance(Class clazz, Object... args) { + Class[] argTypes = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + argTypes[i] = args[i].getClass(); + } + Constructor ctor = getConstructor(clazz, argTypes); + return instantiate(ctor, args); + } + + /** + * Returns the {@link Constructor} for the specified {@code Class} with arguments matching the specified + * {@code argTypes}. + * + * @param clazz the class to inspect + * @param argTypes the argument types for the desired constructor + * @param the type of object to create + * @return the constructor matching the specified argument types + * @throws IllegalStateException if the constructor for the specified {@code argTypes} does not exist. + */ + public static Constructor getConstructor(Class clazz, Class... argTypes) throws IllegalStateException { + try { + return clazz.getConstructor(argTypes); + } catch (NoSuchMethodException e) { + throw new IllegalStateException(e); + } + + } + + /** + * Creates a new object using the specified {@link Constructor}, invoking it with the specified constructor + * {@code args} arguments. + * + * @param ctor the constructor to invoke + * @param args the arguments to supply to the constructor + * @param the type of object to create + * @return the new object instance + * @throws InstantiationException if the constructor cannot be invoked successfully + */ + public static T instantiate(Constructor ctor, Object... args) { + try { + return ctor.newInstance(args); + } catch (Exception e) { + String msg = "Unable to instantiate instance with constructor [" + ctor + "]"; + throw new InstantiationException(msg, e); + } + } + + /** + * Invokes the fully qualified class name's method named {@code methodName} with parameters of type {@code argTypes} + * using the {@code args} as the method arguments. + * + * @param fqcn fully qualified class name to locate + * @param methodName name of the method to invoke on the class + * @param argTypes the method argument types supported by the {@code methodName} method + * @param args the runtime arguments to use when invoking the located class method + * @param the expected type of the object returned from the invoked method. + * @return the result returned by the invoked method + * @since 0.10.0 + */ + public static T invokeStatic(String fqcn, String methodName, Class[] argTypes, Object... args) { + try { + Class clazz = Classes.forName(fqcn); + return invokeStatic(clazz, methodName, argTypes, args); + } catch (Exception e) { + String msg = "Unable to invoke class method " + fqcn + "#" + methodName + ". Ensure the necessary " + + "implementation is in the runtime classpath."; + throw new IllegalStateException(msg, e); + } + } + + /** + * Invokes the {@code clazz}'s matching static method (named {@code methodName} with exact argument types + * of {@code argTypes}) with the given {@code args} arguments, and returns the method return value. + * + * @param clazz the class to invoke + * @param methodName the name of the static method on {@code clazz} to invoke + * @param argTypes the types of the arguments accepted by the method + * @param args the actual runtime arguments to use when invoking the method + * @param the type of object expected to be returned from the method + * @return the result returned by the invoked method. + * @since 0.12.0 + */ + @SuppressWarnings("unchecked") + public static T invokeStatic(Class clazz, String methodName, Class[] argTypes, Object... args) { + try { + Method method = clazz.getDeclaredMethod(methodName, argTypes); + method.setAccessible(true); + return (T) method.invoke(null, args); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw ((RuntimeException) cause); //propagate + } + String msg = "Unable to invoke class method " + clazz.getName() + "#" + methodName + + ". Ensure the necessary implementation is in the runtime classpath."; + throw new IllegalStateException(msg, e); + } + } + + /** + * Returns the {@code instance}'s named (declared) field value. + * + * @param instance the instance with the internal field + * @param fieldName the name of the field to inspect + * @param fieldType the type of field to inspect + * @param field instance value type + * @return the field value + */ + public static T getFieldValue(Object instance, String fieldName, Class fieldType) { + if (instance == null) return null; + try { + Field field = instance.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + Object o = field.get(instance); + return fieldType.cast(o); + } catch (Throwable t) { + String msg = "Unable to read field " + instance.getClass().getName() + + "#" + fieldName + ": " + t.getMessage(); + throw new IllegalStateException(msg, t); + } + } + + /** + * @since 1.0 + */ + private interface ClassLoaderAccessor { + Class loadClass(String fqcn); + + URL getResource(String name); + + InputStream getResourceStream(String name); + } + + /** + * @since 1.0 + */ + private static abstract class ExceptionIgnoringAccessor implements ClassLoaderAccessor { + + public Class loadClass(String fqcn) { + Class clazz = null; + ClassLoader cl = getClassLoader(); + if (cl != null) { + try { + clazz = cl.loadClass(fqcn); + } catch (ClassNotFoundException e) { + //Class couldn't be found by loader + } + } + return clazz; + } + + @Override + public URL getResource(String name) { + URL url = null; + ClassLoader cl = getClassLoader(); + if (cl != null) { + url = cl.getResource(name); + } + return url; + } + + public InputStream getResourceStream(String name) { + InputStream is = null; + ClassLoader cl = getClassLoader(); + if (cl != null) { + is = cl.getResourceAsStream(name); + } + return is; + } + + protected final ClassLoader getClassLoader() { + try { + return doGetClassLoader(); + } catch (Throwable t) { + //Unable to get ClassLoader + } + return null; + } + + protected abstract ClassLoader doGetClassLoader() throws Throwable; + } +} + diff --git a/io/jsonwebtoken/lang/CollectionMutator.java b/io/jsonwebtoken/lang/CollectionMutator.java new file mode 100644 index 0000000..6978ac8 --- /dev/null +++ b/io/jsonwebtoken/lang/CollectionMutator.java @@ -0,0 +1,61 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +import java.util.Collection; + +/** + * Mutation (modifications) to a {@link java.util.Collection} instance while also supporting method chaining. The + * {@link Collection#add(Object)}, {@link Collection#addAll(Collection)}, {@link Collection#remove(Object)}, and + * {@link Collection#clear()} methods do not support method chaining, so this interface enables that behavior. + * + * @param the type of elements in the collection + * @param the mutator subtype, for method chaining + * @since 0.12.0 + */ +public interface CollectionMutator> { + + /** + * Adds the specified element to the collection. + * + * @param e the element to add. + * @return the mutator/builder for method chaining. + */ + M add(E e); + + /** + * Adds the elements to the collection in iteration order. + * + * @param c the collection to add + * @return the mutator/builder for method chaining. + */ + M add(Collection c); + + /** + * Removes all elements in the collection. + * + * @return the mutator/builder for method chaining. + */ + M clear(); + + /** + * Removes the specified element from the collection. + * + * @param e the element to remove. + * @return the mutator/builder for method chaining. + */ + M remove(E e); +} diff --git a/io/jsonwebtoken/lang/Collections.java b/io/jsonwebtoken/lang/Collections.java new file mode 100644 index 0000000..01768fc --- /dev/null +++ b/io/jsonwebtoken/lang/Collections.java @@ -0,0 +1,576 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +/** + * Utility methods for working with {@link Collection}s, {@link List}s, {@link Set}s, and {@link Maps}. + */ +@SuppressWarnings({"unused", "rawtypes"}) +public final class Collections { + + private Collections() { + } //prevent instantiation + + /** + * Returns a type-safe immutable empty {@code List}. + * + * @param list element type + * @return a type-safe immutable empty {@code List}. + */ + public static List emptyList() { + return java.util.Collections.emptyList(); + } + + /** + * Returns a type-safe immutable empty {@code Set}. + * + * @param set element type + * @return a type-safe immutable empty {@code Set}. + */ + @SuppressWarnings("unused") + public static Set emptySet() { + return java.util.Collections.emptySet(); + } + + /** + * Returns a type-safe immutable empty {@code Map}. + * + * @param map key type + * @param map value type + * @return a type-safe immutable empty {@code Map}. + */ + @SuppressWarnings("unused") + public static Map emptyMap() { + return java.util.Collections.emptyMap(); + } + + /** + * Returns a type-safe immutable {@code List} containing the specified array elements. + * + * @param elements array elements to include in the list + * @param list element type + * @return a type-safe immutable {@code List} containing the specified array elements. + */ + @SafeVarargs + public static List of(T... elements) { + if (elements == null || elements.length == 0) { + return java.util.Collections.emptyList(); + } + return java.util.Collections.unmodifiableList(Arrays.asList(elements)); + } + + /** + * Returns the specified collection as a {@link Set} instance. + * + * @param c the collection to represent as a set + * @param collection element type + * @return a type-safe immutable {@code Set} containing the specified collection elements. + * @since 0.12.0 + */ + public static Set asSet(Collection c) { + if (c instanceof Set) { + return (Set) c; + } + if (isEmpty(c)) { + return java.util.Collections.emptySet(); + } + return java.util.Collections.unmodifiableSet(new LinkedHashSet<>(c)); + } + + /** + * Returns a type-safe immutable {@code Set} containing the specified array elements. + * + * @param elements array elements to include in the set + * @param set element type + * @return a type-safe immutable {@code Set} containing the specified array elements. + */ + @SafeVarargs + public static Set setOf(T... elements) { + if (elements == null || elements.length == 0) { + return java.util.Collections.emptySet(); + } + Set set = new LinkedHashSet<>(Arrays.asList(elements)); + return immutable(set); + } + + /** + * Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableList(List)} so both classes + * don't need to be imported. + * + * @param m map to wrap in an immutable/unmodifiable collection + * @param map key type + * @param map value type + * @return an immutable wrapper for {@code m}. + * @since 0.12.0 + */ + public static Map immutable(Map m) { + return m != null ? java.util.Collections.unmodifiableMap(m) : null; + } + + /** + * Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableSet(Set)} so both classes don't + * need to be imported. + * + * @param set set to wrap in an immutable Set + * @param set element type + * @return an immutable wrapper for {@code set} + */ + public static Set immutable(Set set) { + return set != null ? java.util.Collections.unmodifiableSet(set) : null; + } + + /** + * Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableList(List)} so both classes + * don't need to be imported. + * + * @param list list to wrap in an immutable List + * @param list element type + * @return an immutable wrapper for {@code list} + */ + public static List immutable(List list) { + return list != null ? java.util.Collections.unmodifiableList(list) : null; + } + + /** + * Null-safe factory method that returns an immutable/unmodifiable view of the specified collection instance. + * Works for {@link List}, {@link Set} and {@link Collection} arguments. + * + * @param c collection to wrap in an immutable/unmodifiable collection + * @param type of collection + * @param type of elements in the collection + * @return an immutable wrapper for {@code l}. + * @since 0.12.0 + */ + @SuppressWarnings("unchecked") + public static > C immutable(C c) { + if (c == null) { + return null; + } else if (c instanceof Set) { + return (C) java.util.Collections.unmodifiableSet((Set) c); + } else if (c instanceof List) { + return (C) java.util.Collections.unmodifiableList((List) c); + } else { + return (C) java.util.Collections.unmodifiableCollection(c); + } + } + + /** + * Returns a non-null set, either {@code s} if it is not null, or {@link #emptySet()} otherwise. + * + * @param s the set to check for null + * @param type of elements in the set + * @return a non-null set, either {@code s} if it is not null, or {@link #emptySet()} otherwise. + * @since 0.12.0 + */ + public static Set nullSafe(Set s) { + return s == null ? Collections.emptySet() : s; + } + + /** + * Returns a non-null collection, either {@code c} if it is not null, or {@link #emptyList()} otherwise. + * + * @param c the collection to check for null + * @param type of elements in the collection + * @return a non-null collection, either {@code c} if it is not null, or {@link #emptyList()} otherwise. + * @since 0.12.0 + */ + public static Collection nullSafe(Collection c) { + return c == null ? Collections.emptyList() : c; + } + + /** + * Return true if the supplied Collection is null + * or empty. Otherwise, return false. + * + * @param collection the Collection to check + * @return whether the given Collection is empty + */ + public static boolean isEmpty(Collection collection) { + return size(collection) == 0; + } + + /** + * Returns the collection's size or {@code 0} if the collection is {@code null}. + * + * @param collection the collection to check. + * @return the collection's size or {@code 0} if the collection is {@code null}. + * @since 0.9.2 + */ + public static int size(Collection collection) { + return collection == null ? 0 : collection.size(); + } + + /** + * Returns the map's size or {@code 0} if the map is {@code null}. + * + * @param map the map to check + * @return the map's size or {@code 0} if the map is {@code null}. + * @since 0.9.2 + */ + public static int size(Map map) { + return map == null ? 0 : map.size(); + } + + /** + * Return true if the supplied Map is null + * or empty. Otherwise, return false. + * + * @param map the Map to check + * @return whether the given Map is empty + */ + public static boolean isEmpty(Map map) { + return size(map) == 0; + } + + /** + * Convert the supplied array into a List. A primitive array gets + * converted into a List of the appropriate wrapper type. + *

A null source value will be converted to an + * empty List. + * + * @param source the (potentially primitive) array + * @return the converted List result + * @see Objects#toObjectArray(Object) + */ + public static List arrayToList(Object source) { + return Arrays.asList(Objects.toObjectArray(source)); + } + + /** + * Concatenate the specified set with the specified array elements, resulting in a new {@link LinkedHashSet} with + * the array elements appended to the end of the existing Set. + * + * @param c the set to append to + * @param elements the array elements to append to the end of the set + * @param set element type + * @return a new {@link LinkedHashSet} with the array elements appended to the end of the original set. + */ + @SafeVarargs + public static Set concat(Set c, T... elements) { + int size = Math.max(1, Collections.size(c) + io.jsonwebtoken.lang.Arrays.length(elements)); + Set set = new LinkedHashSet<>(size); + set.addAll(c); + java.util.Collections.addAll(set, elements); + return immutable(set); + } + + /** + * Merge the given array into the given Collection. + * + * @param array the array to merge (may be null) + * @param collection the target Collection to merge the array into + */ + @SuppressWarnings("unchecked") + public static void mergeArrayIntoCollection(Object array, Collection collection) { + if (collection == null) { + throw new IllegalArgumentException("Collection must not be null"); + } + Object[] arr = Objects.toObjectArray(array); + java.util.Collections.addAll(collection, arr); + } + + /** + * Merge the given Properties instance into the given Map, + * copying all properties (key-value pairs) over. + *

Uses Properties.propertyNames() to even catch + * default properties linked into the original Properties instance. + * + * @param props the Properties instance to merge (may be null) + * @param map the target Map to merge the properties into + */ + @SuppressWarnings("unchecked") + public static void mergePropertiesIntoMap(Properties props, Map map) { + if (map == null) { + throw new IllegalArgumentException("Map must not be null"); + } + if (props != null) { + for (Enumeration en = props.propertyNames(); en.hasMoreElements(); ) { + String key = (String) en.nextElement(); + Object value = props.getProperty(key); + if (value == null) { + // Potentially a non-String value... + value = props.get(key); + } + map.put(key, value); + } + } + } + + + /** + * Check whether the given Iterator contains the given element. + * + * @param iterator the Iterator to check + * @param element the element to look for + * @return true if found, false else + */ + public static boolean contains(Iterator iterator, Object element) { + if (iterator != null) { + while (iterator.hasNext()) { + Object candidate = iterator.next(); + if (Objects.nullSafeEquals(candidate, element)) { + return true; + } + } + } + return false; + } + + /** + * Check whether the given Enumeration contains the given element. + * + * @param enumeration the Enumeration to check + * @param element the element to look for + * @return true if found, false else + */ + public static boolean contains(Enumeration enumeration, Object element) { + if (enumeration != null) { + while (enumeration.hasMoreElements()) { + Object candidate = enumeration.nextElement(); + if (Objects.nullSafeEquals(candidate, element)) { + return true; + } + } + } + return false; + } + + /** + * Check whether the given Collection contains the given element instance. + *

Enforces the given instance to be present, rather than returning + * true for an equal element as well. + * + * @param collection the Collection to check + * @param element the element to look for + * @return true if found, false else + */ + public static boolean containsInstance(Collection collection, Object element) { + if (collection != null) { + for (Object candidate : collection) { + if (candidate == element) { + return true; + } + } + } + return false; + } + + /** + * Return true if any element in 'candidates' is + * contained in 'source'; otherwise returns false. + * + * @param source the source Collection + * @param candidates the candidates to search for + * @return whether any of the candidates has been found + */ + public static boolean containsAny(Collection source, Collection candidates) { + if (isEmpty(source) || isEmpty(candidates)) { + return false; + } + for (Object candidate : candidates) { + if (source.contains(candidate)) { + return true; + } + } + return false; + } + + /** + * Return the first element in 'candidates' that is contained in + * 'source'. If no element in 'candidates' is present in + * 'source' returns null. Iteration order is + * {@link Collection} implementation specific. + * + * @param source the source Collection + * @param candidates the candidates to search for + * @return the first present object, or null if not found + */ + public static Object findFirstMatch(Collection source, Collection candidates) { + if (isEmpty(source) || isEmpty(candidates)) { + return null; + } + for (Object candidate : candidates) { + if (source.contains(candidate)) { + return candidate; + } + } + return null; + } + + /** + * Find a single value of the given type in the given Collection. + * + * @param collection the Collection to search + * @param type the type to look for + * @param the generic type parameter for {@code type} + * @return a value of the given type found if there is a clear match, + * or null if none or more than one such value found + */ + @SuppressWarnings("unchecked") + public static T findValueOfType(Collection collection, Class type) { + if (isEmpty(collection)) { + return null; + } + T value = null; + for (Object element : collection) { + if (type == null || type.isInstance(element)) { + if (value != null) { + // More than one value found... no clear single value. + return null; + } + value = (T) element; + } + } + return value; + } + + /** + * Find a single value of one of the given types in the given Collection: + * searching the Collection for a value of the first type, then + * searching for a value of the second type, etc. + * + * @param collection the collection to search + * @param types the types to look for, in prioritized order + * @return a value of one of the given types found if there is a clear match, + * or null if none or more than one such value found + */ + public static Object findValueOfType(Collection collection, Class[] types) { + if (isEmpty(collection) || Objects.isEmpty(types)) { + return null; + } + for (Class type : types) { + Object value = findValueOfType(collection, type); + if (value != null) { + return value; + } + } + return null; + } + + /** + * Determine whether the given Collection only contains a single unique object. + * + * @param collection the Collection to check + * @return true if the collection contains a single reference or + * multiple references to the same instance, false else + */ + public static boolean hasUniqueObject(Collection collection) { + if (isEmpty(collection)) { + return false; + } + boolean hasCandidate = false; + Object candidate = null; + for (Object elem : collection) { + if (!hasCandidate) { + hasCandidate = true; + candidate = elem; + } else if (candidate != elem) { + return false; + } + } + return true; + } + + /** + * Find the common element type of the given Collection, if any. + * + * @param collection the Collection to check + * @return the common element type, or null if no clear + * common type has been found (or the collection was empty) + */ + public static Class findCommonElementType(Collection collection) { + if (isEmpty(collection)) { + return null; + } + Class candidate = null; + for (Object val : collection) { + if (val != null) { + if (candidate == null) { + candidate = val.getClass(); + } else if (candidate != val.getClass()) { + return null; + } + } + } + return candidate; + } + + /** + * Marshal the elements from the given enumeration into an array of the given type. + * Enumeration elements must be assignable to the type of the given array. The array + * returned will be a different instance than the array given. + * + * @param enumeration the collection to convert to an array + * @param array an array instance that matches the type of array to return + * @param the element type of the array that will be created + * @param the element type contained within the enumeration. + * @return a new array of type {@code A} that contains the elements in the specified {@code enumeration}. + */ + public static A[] toArray(Enumeration enumeration, A[] array) { + ArrayList elements = new ArrayList<>(); + while (enumeration.hasMoreElements()) { + elements.add(enumeration.nextElement()); + } + return elements.toArray(array); + } + + /** + * Adapt an enumeration to an iterator. + * + * @param enumeration the enumeration + * @param the type of elements in the enumeration + * @return the iterator + */ + public static Iterator toIterator(Enumeration enumeration) { + return new EnumerationIterator<>(enumeration); + } + + /** + * Iterator wrapping an Enumeration. + */ + private static class EnumerationIterator implements Iterator { + + private final Enumeration enumeration; + + public EnumerationIterator(Enumeration enumeration) { + this.enumeration = enumeration; + } + + public boolean hasNext() { + return this.enumeration.hasMoreElements(); + } + + public E next() { + return this.enumeration.nextElement(); + } + + public void remove() throws UnsupportedOperationException { + throw new UnsupportedOperationException("Not supported"); + } + } +} + diff --git a/io/jsonwebtoken/lang/Conjunctor.java b/io/jsonwebtoken/lang/Conjunctor.java new file mode 100644 index 0000000..c604752 --- /dev/null +++ b/io/jsonwebtoken/lang/Conjunctor.java @@ -0,0 +1,33 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +/** + * A {@code Conjunctor} supplies a joined object. It is typically used for nested builders to return + * to the source/original builder. + * + * @param the type of joined object to return. + * @since 0.12.0 + */ +public interface Conjunctor { + + /** + * Returns the joined object. + * + * @return the joined object. + */ + T and(); +} diff --git a/io/jsonwebtoken/lang/DateFormats.java b/io/jsonwebtoken/lang/DateFormats.java new file mode 100644 index 0000000..6a3b501 --- /dev/null +++ b/io/jsonwebtoken/lang/DateFormats.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +/** + * Utility methods to format and parse date strings. + * + * @since 0.10.0 + */ +public final class DateFormats { + + private DateFormats() { + } // prevent instantiation + + private static final String ISO_8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + + private static final String ISO_8601_MILLIS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + + private static final ThreadLocal ISO_8601 = new ThreadLocal() { + @Override + protected DateFormat initialValue() { + SimpleDateFormat format = new SimpleDateFormat(ISO_8601_PATTERN); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format; + } + }; + + private static final ThreadLocal ISO_8601_MILLIS = new ThreadLocal() { + @Override + protected DateFormat initialValue() { + SimpleDateFormat format = new SimpleDateFormat(ISO_8601_MILLIS_PATTERN); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format; + } + }; + + /** + * Return an ISO-8601-formatted string with millisecond precision representing the + * specified {@code date}. + * + * @param date the date for which to create an ISO-8601-formatted string + * @return the date represented as an ISO-8601-formatted string with millisecond precision. + */ + public static String formatIso8601(Date date) { + return formatIso8601(date, true); + } + + /** + * Returns an ISO-8601-formatted string with optional millisecond precision for the specified + * {@code date}. + * + * @param date the date for which to create an ISO-8601-formatted string + * @param includeMillis whether to include millisecond notation within the string. + * @return the date represented as an ISO-8601-formatted string with optional millisecond precision. + */ + public static String formatIso8601(Date date, boolean includeMillis) { + if (includeMillis) { + return ISO_8601_MILLIS.get().format(date); + } + return ISO_8601.get().format(date); + } + + /** + * Parse the specified ISO-8601-formatted date string and return the corresponding {@link Date} instance. The + * date string may optionally contain millisecond notation, and those milliseconds will be represented accordingly. + * + * @param s the ISO-8601-formatted string to parse + * @return the string's corresponding {@link Date} instance. + * @throws ParseException if the specified date string is not a validly-formatted ISO-8601 string. + */ + public static Date parseIso8601Date(String s) throws ParseException { + Assert.notNull(s, "String argument cannot be null."); + if (s.lastIndexOf('.') > -1) { //assume ISO-8601 with milliseconds + return ISO_8601_MILLIS.get().parse(s); + } else { //assume ISO-8601 without millis: + return ISO_8601.get().parse(s); + } + } +} diff --git a/io/jsonwebtoken/lang/InstantiationException.java b/io/jsonwebtoken/lang/InstantiationException.java new file mode 100644 index 0000000..d6b414d --- /dev/null +++ b/io/jsonwebtoken/lang/InstantiationException.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +/** + * {@link RuntimeException} equivalent of {@link java.lang.InstantiationException}. + * + * @since 0.1 + */ +public class InstantiationException extends RuntimeException { + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public InstantiationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/lang/MapMutator.java b/io/jsonwebtoken/lang/MapMutator.java new file mode 100644 index 0000000..5133b30 --- /dev/null +++ b/io/jsonwebtoken/lang/MapMutator.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +import java.util.Map; + +/** + * Mutation (modifications) to a {@link Map} instance while also supporting method chaining. The Map interface's + * {@link Map#put(Object, Object)}, {@link Map#remove(Object)}, {@link Map#putAll(Map)}, and {@link Map#clear()} + * mutation methods do not support method chaining, so this interface enables that behavior. + * + * @param map key type + * @param map value type + * @param the mutator subtype, for method chaining + * @since 0.12.0 + */ +public interface MapMutator> { + + /** + * Removes the map entry with the specified key. + *

This method is the same as {@link Map#remove Map.remove}, but instead returns the mutator instance for + * method chaining.

+ * + * @param key the key for the map entry to remove. + * @return the mutator/builder for method chaining. + */ + T delete(K key); + + /** + * Removes all entries from the map. The map will be empty after this call returns. + *

This method is the same as {@link Map#clear Map.clear}, but instead returns the mutator instance for + * method chaining.

+ * + * @return the mutator/builder for method chaining. + */ + T empty(); + + /** + * Sets the specified key/value pair in the map, overwriting any existing entry with the same key. + * A {@code null} or empty value will remove the entry from the map entirely. + * + *

This method is the same as {@link Map#put Map.put}, but instead returns the mutator instance for + * method chaining.

+ * + * @param key the map key + * @param value the value to set for the specified header parameter name + * @return the mutator/builder for method chaining. + */ + T add(K key, V value); + + /** + * Sets the specified key/value pairs in the map, overwriting any existing entries with the same keys. + * If any pair has a {@code null} or empty value, that pair will be removed from the map entirely. + * + *

This method is the same as {@link Map#putAll Map.putAll}, but instead returns the mutator instance for + * method chaining.

+ * + * @param m the map to add + * @return the mutator/builder for method chaining. + */ + T add(Map m); +} diff --git a/io/jsonwebtoken/lang/Maps.java b/io/jsonwebtoken/lang/Maps.java new file mode 100644 index 0000000..aef613a --- /dev/null +++ b/io/jsonwebtoken/lang/Maps.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2019 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Utility class to help with the manipulation of working with Maps. + * + * @since 0.11.0 + */ +public final class Maps { + + private Maps() { + } //prevent instantiation + + /** + * Creates a new map builder with a single entry. + *

Typical usage:

{@code
+     * Map result = Maps.of("key1", value1)
+     *     .and("key2", value2)
+     *     // ...
+     *     .build();
+     * }
+ * + * @param key the key of an map entry to be added + * @param value the value of map entry to be added + * @param the maps key type + * @param the maps value type + * @return a new map builder with a single entry. + */ + public static MapBuilder of(K key, V value) { + return new HashMapBuilder().and(key, value); + } + + /** + * Utility Builder class for fluently building maps: + *

Typical usage:

{@code
+     * Map result = Maps.of("key1", value1)
+     *     .and("key2", value2)
+     *     // ...
+     *     .build();
+     * }
+ * + * @param the maps key type + * @param the maps value type + */ + public interface MapBuilder extends Builder> { + /** + * Add a new entry to this map builder + * + * @param key the key of an map entry to be added + * @param value the value of map entry to be added + * @return the current MapBuilder to allow for method chaining. + */ + MapBuilder and(K key, V value); + + /** + * Returns the resulting Map object from this MapBuilder. + * + * @return the resulting Map object from this MapBuilder. + */ + Map build(); + } + + private static class HashMapBuilder implements MapBuilder { + + private final Map data = new HashMap<>(); + + public MapBuilder and(K key, V value) { + data.put(key, value); + return this; + } + + public Map build() { + return Collections.unmodifiableMap(data); + } + } +} diff --git a/io/jsonwebtoken/lang/NestedCollection.java b/io/jsonwebtoken/lang/NestedCollection.java new file mode 100644 index 0000000..2fac66c --- /dev/null +++ b/io/jsonwebtoken/lang/NestedCollection.java @@ -0,0 +1,32 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +/** + * A {@link CollectionMutator} that can return access to its parent via the {@link Conjunctor#and() and()} method for + * continued configuration. For example: + *
+ * builder
+ *     .aNestedCollection()// etc...
+ *     .and() // return parent
+ * // resume parent configuration...
+ * + * @param the type of elements in the collection + * @param

the parent to return + * @since 0.12.0 + */ +public interface NestedCollection extends CollectionMutator>, Conjunctor

{ +} diff --git a/io/jsonwebtoken/lang/Objects.java b/io/jsonwebtoken/lang/Objects.java new file mode 100644 index 0000000..4284713 --- /dev/null +++ b/io/jsonwebtoken/lang/Objects.java @@ -0,0 +1,1031 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +/** + * Utility methods for working with object instances to reduce pattern repetition and otherwise + * increased cyclomatic complexity. + */ +public final class Objects { + + private Objects() { + } //prevent instantiation + + private static final int INITIAL_HASH = 7; + private static final int MULTIPLIER = 31; + + private static final String EMPTY_STRING = ""; + private static final String NULL_STRING = "null"; + private static final String ARRAY_START = "{"; + private static final String ARRAY_END = "}"; + private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END; + private static final String ARRAY_ELEMENT_SEPARATOR = ", "; + + /** + * Return whether the given throwable is a checked exception: + * that is, neither a RuntimeException nor an Error. + * + * @param ex the throwable to check + * @return whether the throwable is a checked exception + * @see java.lang.Exception + * @see java.lang.RuntimeException + * @see java.lang.Error + */ + public static boolean isCheckedException(Throwable ex) { + return !(ex instanceof RuntimeException || ex instanceof Error); + } + + /** + * Check whether the given exception is compatible with the exceptions + * declared in a throws clause. + * + * @param ex the exception to checked + * @param declaredExceptions the exceptions declared in the throws clause + * @return whether the given exception is compatible + */ + public static boolean isCompatibleWithThrowsClause(Throwable ex, Class[] declaredExceptions) { + if (!isCheckedException(ex)) { + return true; + } + if (declaredExceptions != null) { + int i = 0; + while (i < declaredExceptions.length) { + if (declaredExceptions[i].isAssignableFrom(ex.getClass())) { + return true; + } + i++; + } + } + return false; + } + + /** + * Returns {@code true} if the specified argument is an Object or primitive array, {@code false} otherwise. + * + * @param obj the object instance to check + * @return {@code true} if the specified argument is an Object or primitive array, {@code false} otherwise. + */ + public static boolean isArray(Object obj) { + return (obj != null && obj.getClass().isArray()); + } + + /** + * Returns {@code true} if the specified argument: + *

    + *
  1. is {@code null}, or
  2. + *
  3. is a CharSequence and {@link Strings#hasText(CharSequence)} is {@code false}, or
  4. + *
  5. is a Collection or Map with zero size, or
  6. + *
  7. is an empty array
  8. + *
+ *

or {@code false} otherwise.

+ * + * @param v object to check + * @return {@code true} if the specified argument is empty, {@code false} otherwise. + * @since 0.12.0 + */ + public static boolean isEmpty(Object v) { + return v == null || + (v instanceof CharSequence && !Strings.hasText((CharSequence) v)) || + (v instanceof Collection && Collections.isEmpty((Collection) v)) || + (v instanceof Map && Collections.isEmpty((Map) v)) || + (v.getClass().isArray() && Array.getLength(v) == 0); + } + + /** + * {@code true} if the specified array is null or zero length, {@code false} if populated. + * + * @param array the array to check + * @return {@code true} if the specified array is null or zero length, {@code false} if populated. + */ + public static boolean isEmpty(Object[] array) { + return (array == null || array.length == 0); + } + + /** + * Returns {@code true} if the specified byte array is null or of zero length, {@code false} if populated. + * + * @param array the byte array to check + * @return {@code true} if the specified byte array is null or of zero length, {@code false} if populated. + */ + public static boolean isEmpty(byte[] array) { + return array == null || array.length == 0; + } + + /** + * Returns {@code true} if the specified character array is null or of zero length, {@code false} otherwise. + * + * @param chars the character array to check + * @return {@code true} if the specified character array is null or of zero length, {@code false} otherwise. + */ + public static boolean isEmpty(char[] chars) { + return chars == null || chars.length == 0; + } + + /** + * Check whether the given array contains the given element. + * + * @param array the array to check (may be null, + * in which case the return value will always be false) + * @param element the element to check for + * @return whether the element has been found in the given array + */ + public static boolean containsElement(Object[] array, Object element) { + if (array == null) { + return false; + } + for (Object arrayEle : array) { + if (nullSafeEquals(arrayEle, element)) { + return true; + } + } + return false; + } + + /** + * Check whether the given array of enum constants contains a constant with the given name, + * ignoring case when determining a match. + * + * @param enumValues the enum values to check, typically the product of a call to MyEnum.values() + * @param constant the constant name to find (must not be null or empty string) + * @return whether the constant has been found in the given array + */ + public static boolean containsConstant(Enum[] enumValues, String constant) { + return containsConstant(enumValues, constant, false); + } + + /** + * Check whether the given array of enum constants contains a constant with the given name. + * + * @param enumValues the enum values to check, typically the product of a call to MyEnum.values() + * @param constant the constant name to find (must not be null or empty string) + * @param caseSensitive whether case is significant in determining a match + * @return whether the constant has been found in the given array + */ + public static boolean containsConstant(Enum[] enumValues, String constant, boolean caseSensitive) { + for (Enum candidate : enumValues) { + if (caseSensitive ? + candidate.toString().equals(constant) : + candidate.toString().equalsIgnoreCase(constant)) { + return true; + } + } + return false; + } + + /** + * Case insensitive alternative to {@link Enum#valueOf(Class, String)}. + * + * @param the concrete Enum type + * @param enumValues the array of all Enum constants in question, usually per Enum.values() + * @param constant the constant to get the enum value of + * @return the enum constant of the specified enum type with the specified case-insensitive name + * @throws IllegalArgumentException if the given constant is not found in the given array + * of enum values. Use {@link #containsConstant(Enum[], String)} as a guard to + * avoid this exception. + */ + public static > E caseInsensitiveValueOf(E[] enumValues, String constant) { + for (E candidate : enumValues) { + if (candidate.toString().equalsIgnoreCase(constant)) { + return candidate; + } + } + throw new IllegalArgumentException( + String.format("constant [%s] does not exist in enum type %s", + constant, enumValues.getClass().getComponentType().getName())); + } + + /** + * Append the given object to the given array, returning a new array + * consisting of the input array contents plus the given object. + * + * @param array the array to append to (can be null) + * @param
the type of each element in the specified {@code array} + * @param obj the object to append + * @param the type of the specified object, which must be equal to or extend the <A> type. + * @return the new array (of the same component type; never null) + */ + public static A[] addObjectToArray(A[] array, O obj) { + Class compType = Object.class; + if (array != null) { + compType = array.getClass().getComponentType(); + } else if (obj != null) { + compType = obj.getClass(); + } + int newArrLength = (array != null ? array.length + 1 : 1); + @SuppressWarnings("unchecked") + A[] newArr = (A[]) Array.newInstance(compType, newArrLength); + if (array != null) { + System.arraycopy(array, 0, newArr, 0, array.length); + } + newArr[newArr.length - 1] = obj; + return newArr; + } + + /** + * Convert the given array (which may be a primitive array) to an + * object array (if necessary of primitive wrapper objects). + *

A null source value will be converted to an + * empty Object array. + * + * @param source the (potentially primitive) array + * @return the corresponding object array (never null) + * @throws IllegalArgumentException if the parameter is not an array + */ + public static Object[] toObjectArray(Object source) { + if (source instanceof Object[]) { + return (Object[]) source; + } + if (source == null) { + return new Object[0]; + } + if (!source.getClass().isArray()) { + throw new IllegalArgumentException("Source is not an array: " + source); + } + int length = Array.getLength(source); + if (length == 0) { + return new Object[0]; + } + Class wrapperType = Array.get(source, 0).getClass(); + Object[] newArray = (Object[]) Array.newInstance(wrapperType, length); + for (int i = 0; i < length; i++) { + newArray[i] = Array.get(source, i); + } + return newArray; + } + + + //--------------------------------------------------------------------- + // Convenience methods for content-based equality/hash-code handling + //--------------------------------------------------------------------- + + /** + * Determine if the given objects are equal, returning true + * if both are null or false if only one is + * null. + *

Compares arrays with Arrays.equals, performing an equality + * check based on the array elements rather than the array reference. + * + * @param o1 first Object to compare + * @param o2 second Object to compare + * @return whether the given objects are equal + * @see java.util.Arrays#equals + */ + public static boolean nullSafeEquals(Object o1, Object o2) { + if (o1 == o2) { + return true; + } + if (o1 == null || o2 == null) { + return false; + } + if (o1.equals(o2)) { + return true; + } + if (o1.getClass().isArray() && o2.getClass().isArray()) { + if (o1 instanceof Object[] && o2 instanceof Object[]) { + return Arrays.equals((Object[]) o1, (Object[]) o2); + } + if (o1 instanceof boolean[] && o2 instanceof boolean[]) { + return Arrays.equals((boolean[]) o1, (boolean[]) o2); + } + if (o1 instanceof byte[] && o2 instanceof byte[]) { + return Arrays.equals((byte[]) o1, (byte[]) o2); + } + if (o1 instanceof char[] && o2 instanceof char[]) { + return Arrays.equals((char[]) o1, (char[]) o2); + } + if (o1 instanceof double[] && o2 instanceof double[]) { + return Arrays.equals((double[]) o1, (double[]) o2); + } + if (o1 instanceof float[] && o2 instanceof float[]) { + return Arrays.equals((float[]) o1, (float[]) o2); + } + if (o1 instanceof int[] && o2 instanceof int[]) { + return Arrays.equals((int[]) o1, (int[]) o2); + } + if (o1 instanceof long[] && o2 instanceof long[]) { + return Arrays.equals((long[]) o1, (long[]) o2); + } + if (o1 instanceof short[] && o2 instanceof short[]) { + return Arrays.equals((short[]) o1, (short[]) o2); + } + } + return false; + } + + /** + * Return as hash code for the given object; typically the value of + * {@link Object#hashCode()}. If the object is an array, + * this method will delegate to any of the nullSafeHashCode + * methods for arrays in this class. If the object is null, + * this method returns 0. + * + * @param obj the object to use for obtaining a hashcode + * @return the object's hashcode, which could be 0 if the object is null. + * @see #nullSafeHashCode(Object[]) + * @see #nullSafeHashCode(boolean[]) + * @see #nullSafeHashCode(byte[]) + * @see #nullSafeHashCode(char[]) + * @see #nullSafeHashCode(double[]) + * @see #nullSafeHashCode(float[]) + * @see #nullSafeHashCode(int[]) + * @see #nullSafeHashCode(long[]) + * @see #nullSafeHashCode(short[]) + */ + public static int nullSafeHashCode(Object obj) { + if (obj == null) { + return 0; + } + if (obj.getClass().isArray()) { + if (obj instanceof Object[]) { + return nullSafeHashCode((Object[]) obj); + } + if (obj instanceof boolean[]) { + return nullSafeHashCode((boolean[]) obj); + } + if (obj instanceof byte[]) { + return nullSafeHashCode((byte[]) obj); + } + if (obj instanceof char[]) { + return nullSafeHashCode((char[]) obj); + } + if (obj instanceof double[]) { + return nullSafeHashCode((double[]) obj); + } + if (obj instanceof float[]) { + return nullSafeHashCode((float[]) obj); + } + if (obj instanceof int[]) { + return nullSafeHashCode((int[]) obj); + } + if (obj instanceof long[]) { + return nullSafeHashCode((long[]) obj); + } + if (obj instanceof short[]) { + return nullSafeHashCode((short[]) obj); + } + } + return obj.hashCode(); + } + + /** + * Return a hash code based on the contents of the specified array. + * If array is null, this method returns 0. + * + * @param array the array to obtain a hashcode + * @return the array's hashcode, which could be 0 if the array is null. + */ + public static int nullSafeHashCode(Object... array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + int arraySize = array.length; + for (int i = 0; i < arraySize; i++) { + hash = MULTIPLIER * hash + nullSafeHashCode(array[i]); + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If array is null, this method returns 0. + * + * @param array the boolean array to obtain a hashcode + * @return the boolean array's hashcode, which could be 0 if the array is null. + */ + public static int nullSafeHashCode(boolean[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + int arraySize = array.length; + for (int i = 0; i < arraySize; i++) { + hash = MULTIPLIER * hash + hashCode(array[i]); + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If array is null, this method returns 0. + * + * @param array the byte array to obtain a hashcode + * @return the byte array's hashcode, which could be 0 if the array is null. + */ + public static int nullSafeHashCode(byte[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + int arraySize = array.length; + for (int i = 0; i < arraySize; i++) { + hash = MULTIPLIER * hash + array[i]; + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If array is null, this method returns 0. + * + * @param array the char array to obtain a hashcode + * @return the char array's hashcode, which could be 0 if the array is null. + */ + public static int nullSafeHashCode(char[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + int arraySize = array.length; + for (int i = 0; i < arraySize; i++) { + hash = MULTIPLIER * hash + array[i]; + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If array is null, this method returns 0. + * + * @param array the double array to obtain a hashcode + * @return the double array's hashcode, which could be 0 if the array is null. + */ + public static int nullSafeHashCode(double[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + int arraySize = array.length; + for (int i = 0; i < arraySize; i++) { + hash = MULTIPLIER * hash + hashCode(array[i]); + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If array is null, this method returns 0. + * + * @param array the float array to obtain a hashcode + * @return the float array's hashcode, which could be 0 if the array is null. + */ + public static int nullSafeHashCode(float[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + int arraySize = array.length; + for (int i = 0; i < arraySize; i++) { + hash = MULTIPLIER * hash + hashCode(array[i]); + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If array is null, this method returns 0. + * + * @param array the int array to obtain a hashcode + * @return the int array's hashcode, which could be 0 if the array is null. + */ + public static int nullSafeHashCode(int[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + int arraySize = array.length; + for (int i = 0; i < arraySize; i++) { + hash = MULTIPLIER * hash + array[i]; + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If array is null, this method returns 0. + * + * @param array the long array to obtain a hashcode + * @return the long array's hashcode, which could be 0 if the array is null. + */ + public static int nullSafeHashCode(long[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + int arraySize = array.length; + for (int i = 0; i < arraySize; i++) { + hash = MULTIPLIER * hash + hashCode(array[i]); + } + return hash; + } + + /** + * Return a hash code based on the contents of the specified array. + * If array is null, this method returns 0. + * + * @param array the short array to obtain a hashcode + * @return the short array's hashcode, which could be 0 if the array is null. + */ + public static int nullSafeHashCode(short[] array) { + if (array == null) { + return 0; + } + int hash = INITIAL_HASH; + int arraySize = array.length; + for (int i = 0; i < arraySize; i++) { + hash = MULTIPLIER * hash + array[i]; + } + return hash; + } + + /** + * Return the same value as {@link Boolean#hashCode()}. + * + * @param bool the boolean to get a hashcode + * @return the same value as {@link Boolean#hashCode()}. + * @see Boolean#hashCode() + */ + public static int hashCode(boolean bool) { + return bool ? 1231 : 1237; + } + + /** + * Return the same value as {@link Double#hashCode()}. + * + * @param dbl the double to get a hashcode + * @return the same value as {@link Double#hashCode()}. + * @see Double#hashCode() + */ + public static int hashCode(double dbl) { + long bits = Double.doubleToLongBits(dbl); + return hashCode(bits); + } + + /** + * Return the same value as {@link Float#hashCode()}. + * + * @param flt the float to get a hashcode + * @return the same value as {@link Float#hashCode()}. + * @see Float#hashCode() + */ + public static int hashCode(float flt) { + return Float.floatToIntBits(flt); + } + + /** + * Return the same value as {@link Long#hashCode()}. + * + * @param lng the long to get a hashcode + * @return the same value as {@link Long#hashCode()}. + * @see Long#hashCode() + */ + public static int hashCode(long lng) { + return (int) (lng ^ (lng >>> 32)); + } + + + //--------------------------------------------------------------------- + // Convenience methods for toString output + //--------------------------------------------------------------------- + + /** + * Return a String representation of an object's overall identity. + * + * @param obj the object (which may be null). + * @return the object's identity as String representation, or an empty String if the object was null. + */ + public static String identityToString(Object obj) { + if (obj == null) { + return EMPTY_STRING; + } + return obj.getClass().getName() + "@" + getIdentityHexString(obj); + } + + /** + * Return a hex String form of an object's identity hash code. + * + * @param obj the object + * @return the object's identity code in hex notation + */ + public static String getIdentityHexString(Object obj) { + return Integer.toHexString(System.identityHashCode(obj)); + } + + /** + * Return a content-based String representation if obj is + * not null; otherwise returns an empty String. + *

Differs from {@link #nullSafeToString(Object)} in that it returns + * an empty String rather than "null" for a null value. + * + * @param obj the object to build a display String for + * @return a display String representation of obj + * @see #nullSafeToString(Object) + */ + public static String getDisplayString(Object obj) { + if (obj == null) { + return EMPTY_STRING; + } + return nullSafeToString(obj); + } + + /** + * Determine the class name for the given object. + *

Returns "null" if obj is null. + * + * @param obj the object to introspect (may be null) + * @return the corresponding class name + */ + public static String nullSafeClassName(Object obj) { + return (obj != null ? obj.getClass().getName() : NULL_STRING); + } + + /** + * Return a String representation of the specified Object. + *

Builds a String representation of the contents in case of an array. + * Returns "null" if obj is null. + * + * @param obj the object to build a String representation for + * @return a String representation of obj + */ + public static String nullSafeToString(Object obj) { + if (obj == null) { + return NULL_STRING; + } + if (obj instanceof String) { + return (String) obj; + } + if (obj instanceof Object[]) { + return nullSafeToString((Object[]) obj); + } + if (obj instanceof boolean[]) { + return nullSafeToString((boolean[]) obj); + } + if (obj instanceof byte[]) { + return nullSafeToString((byte[]) obj); + } + if (obj instanceof char[]) { + return nullSafeToString((char[]) obj); + } + if (obj instanceof double[]) { + return nullSafeToString((double[]) obj); + } + if (obj instanceof float[]) { + return nullSafeToString((float[]) obj); + } + if (obj instanceof int[]) { + return nullSafeToString((int[]) obj); + } + if (obj instanceof long[]) { + return nullSafeToString((long[]) obj); + } + if (obj instanceof short[]) { + return nullSafeToString((short[]) obj); + } + String str = obj.toString(); + return (str != null ? str : EMPTY_STRING); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ("{}"). Adjacent elements are separated + * by the characters ", " (a comma followed by a space). Returns + * "null" if array is null. + * + * @param array the array to build a String representation for + * @return a String representation of array + */ + public static String nullSafeToString(Object[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + if (i == 0) { + sb.append(ARRAY_START); + } else { + sb.append(ARRAY_ELEMENT_SEPARATOR); + } + sb.append(String.valueOf(array[i])); + } + sb.append(ARRAY_END); + return sb.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ("{}"). Adjacent elements are separated + * by the characters ", " (a comma followed by a space). Returns + * "null" if array is null. + * + * @param array the array to build a String representation for + * @return a String representation of array + */ + public static String nullSafeToString(boolean[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + if (i == 0) { + sb.append(ARRAY_START); + } else { + sb.append(ARRAY_ELEMENT_SEPARATOR); + } + + sb.append(array[i]); + } + sb.append(ARRAY_END); + return sb.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ("{}"). Adjacent elements are separated + * by the characters ", " (a comma followed by a space). Returns + * "null" if array is null. + * + * @param array the array to build a String representation for + * @return a String representation of array + */ + public static String nullSafeToString(byte[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + if (i == 0) { + sb.append(ARRAY_START); + } else { + sb.append(ARRAY_ELEMENT_SEPARATOR); + } + sb.append(array[i]); + } + sb.append(ARRAY_END); + return sb.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ("{}"). Adjacent elements are separated + * by the characters ", " (a comma followed by a space). Returns + * "null" if array is null. + * + * @param array the array to build a String representation for + * @return a String representation of array + */ + public static String nullSafeToString(char[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + if (i == 0) { + sb.append(ARRAY_START); + } else { + sb.append(ARRAY_ELEMENT_SEPARATOR); + } + sb.append("'").append(array[i]).append("'"); + } + sb.append(ARRAY_END); + return sb.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ("{}"). Adjacent elements are separated + * by the characters ", " (a comma followed by a space). Returns + * "null" if array is null. + * + * @param array the array to build a String representation for + * @return a String representation of array + */ + public static String nullSafeToString(double[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + if (i == 0) { + sb.append(ARRAY_START); + } else { + sb.append(ARRAY_ELEMENT_SEPARATOR); + } + + sb.append(array[i]); + } + sb.append(ARRAY_END); + return sb.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ("{}"). Adjacent elements are separated + * by the characters ", " (a comma followed by a space). Returns + * "null" if array is null. + * + * @param array the array to build a String representation for + * @return a String representation of array + */ + public static String nullSafeToString(float[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + if (i == 0) { + sb.append(ARRAY_START); + } else { + sb.append(ARRAY_ELEMENT_SEPARATOR); + } + + sb.append(array[i]); + } + sb.append(ARRAY_END); + return sb.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ("{}"). Adjacent elements are separated + * by the characters ", " (a comma followed by a space). Returns + * "null" if array is null. + * + * @param array the array to build a String representation for + * @return a String representation of array + */ + public static String nullSafeToString(int[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + if (i == 0) { + sb.append(ARRAY_START); + } else { + sb.append(ARRAY_ELEMENT_SEPARATOR); + } + sb.append(array[i]); + } + sb.append(ARRAY_END); + return sb.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ("{}"). Adjacent elements are separated + * by the characters ", " (a comma followed by a space). Returns + * "null" if array is null. + * + * @param array the array to build a String representation for + * @return a String representation of array + */ + public static String nullSafeToString(long[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + if (i == 0) { + sb.append(ARRAY_START); + } else { + sb.append(ARRAY_ELEMENT_SEPARATOR); + } + sb.append(array[i]); + } + sb.append(ARRAY_END); + return sb.toString(); + } + + /** + * Return a String representation of the contents of the specified array. + *

The String representation consists of a list of the array's elements, + * enclosed in curly braces ("{}"). Adjacent elements are separated + * by the characters ", " (a comma followed by a space). Returns + * "null" if array is null. + * + * @param array the array to build a String representation for + * @return a String representation of array + */ + public static String nullSafeToString(short[] array) { + if (array == null) { + return NULL_STRING; + } + int length = array.length; + if (length == 0) { + return EMPTY_ARRAY; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + if (i == 0) { + sb.append(ARRAY_START); + } else { + sb.append(ARRAY_ELEMENT_SEPARATOR); + } + sb.append(array[i]); + } + sb.append(ARRAY_END); + return sb.toString(); + } + + /** + * Iterate over the specified {@link Closeable} instances, invoking + * {@link Closeable#close()} on each one, ignoring any potential {@link IOException}s. + * + * @param closeables the closeables to close. + */ + public static void nullSafeClose(Closeable... closeables) { + if (closeables == null) { + return; + } + + for (Closeable closeable : closeables) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException e) { + //Ignore the exception during close. + } + } + } + } + + /** + * Iterate over the specified {@link Flushable} instances, invoking + * {@link Flushable#flush()} on each one, ignoring any potential {@link IOException}s. + * + * @param flushables the flushables to flush. + * @since 0.12.0 + */ + public static void nullSafeFlush(Flushable... flushables) { + if (flushables == null) return; + for (Flushable flushable : flushables) { + if (flushable != null) { + try { + flushable.flush(); + } catch (IOException ignored) { + } + } + } + } +} diff --git a/io/jsonwebtoken/lang/Registry.java b/io/jsonwebtoken/lang/Registry.java new file mode 100644 index 0000000..de5849e --- /dev/null +++ b/io/jsonwebtoken/lang/Registry.java @@ -0,0 +1,51 @@ +/* + * Copyright © 2020 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +import java.util.Map; + +/** + * An immutable (read-only) repository of key-value pairs. In addition to {@link Map} read methods, this interface also + * provides guaranteed/expected lookup via the {@link #forKey(Object)} method. + * + *

Immutability

+ * + *

Registries are immutable and cannot be changed. {@code Registry} extends the + * {@link Map} interface purely out of convenience: to allow easy key/value + * pair access and iteration, and other conveniences provided by the Map interface, as well as for seamless use with + * existing Map-based APIs. Attempting to call any of + * the {@link Map} interface's mutation methods however (such as {@link Map#put(Object, Object) put}, + * {@link Map#remove(Object) remove}, {@link Map#clear() clear}, etc) will throw an + * {@link UnsupportedOperationException}.

+ * + * @param key type + * @param value type + * @since 0.12.0 + */ +public interface Registry extends Map { + + /** + * Returns the value assigned the specified key or throws an {@code IllegalArgumentException} if there is no + * associated value. If a value is not required, consider using the {@link #get(Object)} method instead. + * + * @param key the registry key assigned to the required value + * @return the value assigned the specified key + * @throws IllegalArgumentException if there is no value assigned the specified key + * @see #get(Object) + */ + V forKey(K key) throws IllegalArgumentException; + +} diff --git a/io/jsonwebtoken/lang/RuntimeEnvironment.java b/io/jsonwebtoken/lang/RuntimeEnvironment.java new file mode 100644 index 0000000..885cef8 --- /dev/null +++ b/io/jsonwebtoken/lang/RuntimeEnvironment.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +import java.security.Provider; +import java.security.Security; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * No longer used by JJWT. Will be removed before the 1.0 final release. + * + * @deprecated since 0.12.0. will be removed before the 1.0 final release. + */ +@Deprecated +public final class RuntimeEnvironment { + + private RuntimeEnvironment() { + } //prevent instantiation + + private static final String BC_PROVIDER_CLASS_NAME = "org.bouncycastle.jce.provider.BouncyCastleProvider"; + + private static final AtomicBoolean bcLoaded = new AtomicBoolean(false); + + /** + * {@code true} if BouncyCastle is in the runtime classpath, {@code false} otherwise. + * + * @deprecated since 0.12.0. will be removed before the 1.0 final release. + */ + @Deprecated + public static final boolean BOUNCY_CASTLE_AVAILABLE = Classes.isAvailable(BC_PROVIDER_CLASS_NAME); + + /** + * Register BouncyCastle as a JCA provider in the system's {@link Security#getProviders() Security Providers} list + * if BouncyCastle is in the runtime classpath. + * + * @deprecated since 0.12.0. will be removed before the 1.0 final release. + */ + @Deprecated + public static void enableBouncyCastleIfPossible() { + + if (!BOUNCY_CASTLE_AVAILABLE || bcLoaded.get()) { + return; + } + + try { + Class clazz = Classes.forName(BC_PROVIDER_CLASS_NAME); + + //check to see if the user has already registered the BC provider: + + Provider[] providers = Security.getProviders(); + + for (Provider provider : providers) { + if (clazz.isInstance(provider)) { + bcLoaded.set(true); + return; + } + } + + //bc provider not enabled - add it: + Provider provider = Classes.newInstance(clazz); + Security.addProvider(provider); + bcLoaded.set(true); + + } catch (UnknownClassException e) { + //not available + } + } + + static { + enableBouncyCastleIfPossible(); + } + +} diff --git a/io/jsonwebtoken/lang/Strings.java b/io/jsonwebtoken/lang/Strings.java new file mode 100644 index 0000000..ac66ef1 --- /dev/null +++ b/io/jsonwebtoken/lang/Strings.java @@ -0,0 +1,1371 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.TreeSet; + +/** + * Utility methods for working with Strings to reduce pattern repetition and otherwise + * increased cyclomatic complexity. + */ +public final class Strings { + + /** + * Empty String, equal to "". + */ + public static final String EMPTY = ""; + + private static final CharBuffer EMPTY_BUF = CharBuffer.wrap(EMPTY); + + private static final String FOLDER_SEPARATOR = "/"; + + private static final String WINDOWS_FOLDER_SEPARATOR = "\\"; + + private static final String TOP_PATH = ".."; + + private static final String CURRENT_PATH = "."; + + private static final char EXTENSION_SEPARATOR = '.'; + + /** + * Convenience alias for {@link StandardCharsets#UTF_8}. + */ + public static final Charset UTF_8 = StandardCharsets.UTF_8; + + private Strings() { + } //prevent instantiation + + //--------------------------------------------------------------------- + // General convenience methods for working with Strings + //--------------------------------------------------------------------- + + /** + * Check that the given CharSequence is neither null nor of length 0. + * Note: Will return true for a CharSequence that purely consists of whitespace. + *
+     * Strings.hasLength(null) = false
+     * Strings.hasLength("") = false
+     * Strings.hasLength(" ") = true
+     * Strings.hasLength("Hello") = true
+     * 
+ * + * @param str the CharSequence to check (may be null) + * @return true if the CharSequence is not null and has length + * @see #hasText(String) + */ + public static boolean hasLength(CharSequence str) { + return (str != null && str.length() > 0); + } + + /** + * Check that the given String is neither null nor of length 0. + * Note: Will return true for a String that purely consists of whitespace. + * + * @param str the String to check (may be null) + * @return true if the String is not null and has length + * @see #hasLength(CharSequence) + */ + public static boolean hasLength(String str) { + return hasLength((CharSequence) str); + } + + /** + * Check whether the given CharSequence has actual text. + * More specifically, returns true if the string not null, + * its length is greater than 0, and it contains at least one non-whitespace character. + *
+     * Strings.hasText(null) = false
+     * Strings.hasText("") = false
+     * Strings.hasText(" ") = false
+     * Strings.hasText("12345") = true
+     * Strings.hasText(" 12345 ") = true
+     * 
+ * + * @param str the CharSequence to check (may be null) + * @return true if the CharSequence is not null, + * its length is greater than 0, and it does not contain whitespace only + * @see java.lang.Character#isWhitespace + */ + public static boolean hasText(CharSequence str) { + if (!hasLength(str)) { + return false; + } + int strLen = str.length(); + for (int i = 0; i < strLen; i++) { + if (!Character.isWhitespace(str.charAt(i))) { + return true; + } + } + return false; + } + + /** + * Check whether the given String has actual text. + * More specifically, returns true if the string not null, + * its length is greater than 0, and it contains at least one non-whitespace character. + * + * @param str the String to check (may be null) + * @return true if the String is not null, its length is + * greater than 0, and it does not contain whitespace only + * @see #hasText(CharSequence) + */ + public static boolean hasText(String str) { + return hasText((CharSequence) str); + } + + /** + * Check whether the given CharSequence contains any whitespace characters. + * + * @param str the CharSequence to check (may be null) + * @return true if the CharSequence is not empty and + * contains at least 1 whitespace character + * @see java.lang.Character#isWhitespace + */ + public static boolean containsWhitespace(CharSequence str) { + if (!hasLength(str)) { + return false; + } + int strLen = str.length(); + for (int i = 0; i < strLen; i++) { + if (Character.isWhitespace(str.charAt(i))) { + return true; + } + } + return false; + } + + /** + * Check whether the given String contains any whitespace characters. + * + * @param str the String to check (may be null) + * @return true if the String is not empty and + * contains at least 1 whitespace character + * @see #containsWhitespace(CharSequence) + */ + public static boolean containsWhitespace(String str) { + return containsWhitespace((CharSequence) str); + } + + /** + * Trim leading and trailing whitespace from the given String. + * + * @param str the String to check + * @return the trimmed String + * @see java.lang.Character#isWhitespace + */ + public static String trimWhitespace(String str) { + return (String) trimWhitespace((CharSequence) str); + } + + + private static CharSequence trimWhitespace(CharSequence str) { + if (!hasLength(str)) { + return str; + } + final int length = str.length(); + + int start = 0; + while (start < length && Character.isWhitespace(str.charAt(start))) { + start++; + } + + int end = length; + while (start < length && Character.isWhitespace(str.charAt(end - 1))) { + end--; + } + + return ((start > 0) || (end < length)) ? str.subSequence(start, end) : str; + } + + /** + * Returns the specified string without leading or trailing whitespace, or {@code null} if there are no remaining + * characters. + * + * @param str the string to clean + * @return the specified string without leading or trailing whitespace, or {@code null} if there are no remaining + * characters. + */ + public static String clean(String str) { + CharSequence result = clean((CharSequence) str); + + return result != null ? result.toString() : null; + } + + /** + * Returns the specified {@code CharSequence} without leading or trailing whitespace, or {@code null} if there are + * no remaining characters. + * + * @param str the {@code CharSequence} to clean + * @return the specified string without leading or trailing whitespace, or {@code null} if there are no remaining + * characters. + */ + public static CharSequence clean(CharSequence str) { + str = trimWhitespace(str); + if (!hasLength(str)) { + return null; + } + return str; + } + + /** + * Returns the specified string's UTF-8 bytes, or {@code null} if the string is {@code null}. + * + * @param s the string to obtain UTF-8 bytes + * @return the specified string's UTF-8 bytes, or {@code null} if the string is {@code null}. + * @since 0.12.0 + */ + public static byte[] utf8(CharSequence s) { + if (s == null) return null; + CharBuffer cb = s instanceof CharBuffer ? (CharBuffer) s : CharBuffer.wrap(s); + cb.mark(); + ByteBuffer buf = UTF_8.encode(cb); + byte[] bytes = new byte[buf.remaining()]; + buf.get(bytes); + cb.reset(); + return bytes; + } + + /** + * Returns {@code new String(utf8Bytes, StandardCharsets.UTF_8)}. + * + * @param utf8Bytes UTF-8 bytes to use with the {@code String} constructor. + * @return {@code new String(utf8Bytes, StandardCharsets.UTF_8)}. + * @since 0.12.0 + */ + public static String utf8(byte[] utf8Bytes) { + return new String(utf8Bytes, UTF_8); + } + + /** + * Returns {@code new String(asciiBytes, StandardCharsets.US_ASCII)}. + * + * @param asciiBytes US_ASCII bytes to use with the {@code String} constructor. + * @return {@code new String(asciiBytes, StandardCharsets.US_ASCII)}. + * @since 0.12.0 + */ + public static String ascii(byte[] asciiBytes) { + return new String(asciiBytes, StandardCharsets.US_ASCII); + } + + /** + * Returns the {@link StandardCharsets#US_ASCII US_ASCII}-encoded bytes of the specified {@code CharSequence}. + * + * @param s the {@code CharSequence} to encode to {@code US_ASCII}. + * @return the {@link StandardCharsets#US_ASCII US_ASCII}-encoded bytes of the specified {@code CharSequence}. + */ + public static byte[] ascii(CharSequence s) { + byte[] bytes = null; + if (s != null) { + CharBuffer cb = s instanceof CharBuffer ? (CharBuffer) s : CharBuffer.wrap(s); + ByteBuffer buf = StandardCharsets.US_ASCII.encode(cb); + bytes = new byte[buf.remaining()]; + buf.get(bytes); + } + return bytes; + } + + /** + * Returns a {@code CharBuffer} that wraps {@code seq}, or an empty buffer if {@code seq} is null. If + * {@code seq} is already a {@code CharBuffer}, it is returned unmodified. + * + * @param seq the {@code CharSequence} to wrap. + * @return a {@code CharBuffer} that wraps {@code seq}, or an empty buffer if {@code seq} is null. + */ + public static CharBuffer wrap(CharSequence seq) { + if (!hasLength(seq)) return EMPTY_BUF; + if (seq instanceof CharBuffer) return (CharBuffer) seq; + return CharBuffer.wrap(seq); + } + + /** + * Returns a String representation (1s and 0s) of the specified byte. + * + * @param b the byte to represent as 1s and 0s. + * @return a String representation (1s and 0s) of the specified byte. + */ + public static String toBinary(byte b) { + String bString = Integer.toBinaryString(b & 0xFF); + return String.format("%8s", bString).replace((char) Character.SPACE_SEPARATOR, '0'); + } + + /** + * Returns a String representation (1s and 0s) of the specified byte array. + * + * @param bytes the bytes to represent as 1s and 0s. + * @return a String representation (1s and 0s) of the specified byte array. + */ + public static String toBinary(byte[] bytes) { + StringBuilder sb = new StringBuilder(19); //16 characters + 3 space characters + for (byte b : bytes) { + if (sb.length() > 0) { + sb.append((char) Character.SPACE_SEPARATOR); + } + String val = toBinary(b); + sb.append(val); + } + return sb.toString(); + } + + /** + * Returns a hexadecimal String representation of the specified byte array. + * + * @param bytes the bytes to represent as a hexidecimal string. + * @return a hexadecimal String representation of the specified byte array. + */ + public static String toHex(byte[] bytes) { + StringBuilder result = new StringBuilder(); + for (byte temp : bytes) { + if (result.length() > 0) { + result.append((char) Character.SPACE_SEPARATOR); + } + result.append(String.format("%02x", temp)); + } + return result.toString(); + } + + /** + * Trim all whitespace from the given String: + * leading, trailing, and intermediate characters. + * + * @param str the String to check + * @return the trimmed String + * @see java.lang.Character#isWhitespace + */ + public static String trimAllWhitespace(String str) { + if (!hasLength(str)) { + return str; + } + StringBuilder sb = new StringBuilder(str); + int index = 0; + while (sb.length() > index) { + if (Character.isWhitespace(sb.charAt(index))) { + sb.deleteCharAt(index); + } else { + index++; + } + } + return sb.toString(); + } + + /** + * Trim leading whitespace from the given String. + * + * @param str the String to check + * @return the trimmed String + * @see java.lang.Character#isWhitespace + */ + public static String trimLeadingWhitespace(String str) { + if (!hasLength(str)) { + return str; + } + StringBuilder sb = new StringBuilder(str); + while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) { + sb.deleteCharAt(0); + } + return sb.toString(); + } + + /** + * Trim trailing whitespace from the given String. + * + * @param str the String to check + * @return the trimmed String + * @see java.lang.Character#isWhitespace + */ + public static String trimTrailingWhitespace(String str) { + if (!hasLength(str)) { + return str; + } + StringBuilder sb = new StringBuilder(str); + while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) { + sb.deleteCharAt(sb.length() - 1); + } + return sb.toString(); + } + + /** + * Trim all occurrences of the supplied leading character from the given String. + * + * @param str the String to check + * @param leadingCharacter the leading character to be trimmed + * @return the trimmed String + */ + public static String trimLeadingCharacter(String str, char leadingCharacter) { + if (!hasLength(str)) { + return str; + } + StringBuilder sb = new StringBuilder(str); + while (sb.length() > 0 && sb.charAt(0) == leadingCharacter) { + sb.deleteCharAt(0); + } + return sb.toString(); + } + + /** + * Trim all occurrences of the supplied trailing character from the given String. + * + * @param str the String to check + * @param trailingCharacter the trailing character to be trimmed + * @return the trimmed String + */ + public static String trimTrailingCharacter(String str, char trailingCharacter) { + if (!hasLength(str)) { + return str; + } + StringBuilder sb = new StringBuilder(str); + while (sb.length() > 0 && sb.charAt(sb.length() - 1) == trailingCharacter) { + sb.deleteCharAt(sb.length() - 1); + } + return sb.toString(); + } + + + /** + * Returns {@code true} if the given string starts with the specified case-insensitive prefix, {@code false} otherwise. + * + * @param str the String to check + * @param prefix the prefix to look for + * @return {@code true} if the given string starts with the specified case-insensitive prefix, {@code false} otherwise. + * @see java.lang.String#startsWith + */ + public static boolean startsWithIgnoreCase(String str, String prefix) { + if (str == null || prefix == null) { + return false; + } + if (str.length() < prefix.length()) { + return false; + } + if (str.startsWith(prefix)) { + return true; + } + String lcStr = str.substring(0, prefix.length()).toLowerCase(); + String lcPrefix = prefix.toLowerCase(); + return lcStr.equals(lcPrefix); + } + + /** + * Returns {@code true} if the given string ends with the specified case-insensitive suffix, {@code false} otherwise. + * + * @param str the String to check + * @param suffix the suffix to look for + * @return {@code true} if the given string ends with the specified case-insensitive suffix, {@code false} otherwise. + * @see java.lang.String#endsWith + */ + public static boolean endsWithIgnoreCase(String str, String suffix) { + if (str == null || suffix == null) { + return false; + } + if (str.endsWith(suffix)) { + return true; + } + if (str.length() < suffix.length()) { + return false; + } + + String lcStr = str.substring(str.length() - suffix.length()).toLowerCase(); + String lcSuffix = suffix.toLowerCase(); + return lcStr.equals(lcSuffix); + } + + /** + * Returns {@code true} if the given string matches the given substring at the given index, {@code false} otherwise. + * + * @param str the original string (or StringBuilder) + * @param index the index in the original string to start matching against + * @param substring the substring to match at the given index + * @return {@code true} if the given string matches the given substring at the given index, {@code false} otherwise. + */ + public static boolean substringMatch(CharSequence str, int index, CharSequence substring) { + for (int j = 0; j < substring.length(); j++) { + int i = index + j; + if (i >= str.length() || str.charAt(i) != substring.charAt(j)) { + return false; + } + } + return true; + } + + /** + * Returns the number of occurrences the substring {@code sub} appears in string {@code str}. + * + * @param str string to search in. Return 0 if this is null. + * @param sub string to search for. Return 0 if this is null. + * @return the number of occurrences the substring {@code sub} appears in string {@code str}. + */ + public static int countOccurrencesOf(String str, String sub) { + if (str == null || sub == null || str.length() == 0 || sub.length() == 0) { + return 0; + } + int count = 0; + int pos = 0; + int idx; + while ((idx = str.indexOf(sub, pos)) != -1) { + ++count; + pos = idx + sub.length(); + } + return count; + } + + /** + * Replace all occurrences of a substring within a string with + * another string. + * + * @param inString String to examine + * @param oldPattern String to replace + * @param newPattern String to insert + * @return a String with the replacements + */ + public static String replace(String inString, String oldPattern, String newPattern) { + if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) { + return inString; + } + StringBuilder sb = new StringBuilder(); + int pos = 0; // our position in the old string + int index = inString.indexOf(oldPattern); + // the index of an occurrence we've found, or -1 + int patLen = oldPattern.length(); + while (index >= 0) { + sb.append(inString.substring(pos, index)); + sb.append(newPattern); + pos = index + patLen; + index = inString.indexOf(oldPattern, pos); + } + sb.append(inString.substring(pos)); + // remember to append any characters to the right of a match + return sb.toString(); + } + + /** + * Delete all occurrences of the given substring. + * + * @param inString the original String + * @param pattern the pattern to delete all occurrences of + * @return the resulting String + */ + public static String delete(String inString, String pattern) { + return replace(inString, pattern, ""); + } + + /** + * Delete any character in a given String. + * + * @param inString the original String + * @param charsToDelete a set of characters to delete. + * E.g. "az\n" will delete 'a's, 'z's and new lines. + * @return the resulting String + */ + public static String deleteAny(String inString, String charsToDelete) { + if (!hasLength(inString) || !hasLength(charsToDelete)) { + return inString; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < inString.length(); i++) { + char c = inString.charAt(i); + if (charsToDelete.indexOf(c) == -1) { + sb.append(c); + } + } + return sb.toString(); + } + + + //--------------------------------------------------------------------- + // Convenience methods for working with formatted Strings + //--------------------------------------------------------------------- + + /** + * Quote the given String with single quotes. + * + * @param str the input String (e.g. "myString") + * @return the quoted String (e.g. "'myString'"), + * or null if the input was null + */ + public static String quote(String str) { + return (str != null ? "'" + str + "'" : null); + } + + /** + * Turn the given Object into a String with single quotes + * if it is a String; keeping the Object as-is else. + * + * @param obj the input Object (e.g. "myString") + * @return the quoted String (e.g. "'myString'"), + * or the input object as-is if not a String + */ + public static Object quoteIfString(Object obj) { + return (obj instanceof String ? quote((String) obj) : obj); + } + + /** + * Unqualify a string qualified by a '.' dot character. For example, + * "this.name.is.qualified", returns "qualified". + * + * @param qualifiedName the qualified name + * @return an unqualified string by stripping all previous text before (and including) the last period character. + */ + public static String unqualify(String qualifiedName) { + return unqualify(qualifiedName, '.'); + } + + /** + * Unqualify a string qualified by a separator character. For example, + * "this:name:is:qualified" returns "qualified" if using a ':' separator. + * + * @param qualifiedName the qualified name + * @param separator the separator + * @return an unqualified string by stripping all previous text before and including the last {@code separator} character. + */ + public static String unqualify(String qualifiedName, char separator) { + return qualifiedName.substring(qualifiedName.lastIndexOf(separator) + 1); + } + + /** + * Capitalize a String, changing the first letter to + * upper case as per {@link Character#toUpperCase(char)}. + * No other letters are changed. + * + * @param str the String to capitalize, may be null + * @return the capitalized String, null if null + */ + public static String capitalize(String str) { + return changeFirstCharacterCase(str, true); + } + + /** + * Uncapitalize a String, changing the first letter to + * lower case as per {@link Character#toLowerCase(char)}. + * No other letters are changed. + * + * @param str the String to uncapitalize, may be null + * @return the uncapitalized String, null if null + */ + public static String uncapitalize(String str) { + return changeFirstCharacterCase(str, false); + } + + private static String changeFirstCharacterCase(String str, boolean capitalize) { + if (str == null || str.length() == 0) { + return str; + } + StringBuilder sb = new StringBuilder(str.length()); + if (capitalize) { + sb.append(Character.toUpperCase(str.charAt(0))); + } else { + sb.append(Character.toLowerCase(str.charAt(0))); + } + sb.append(str.substring(1)); + return sb.toString(); + } + + /** + * Extract the filename from the given path, + * e.g. "mypath/myfile.txt" -> "myfile.txt". + * + * @param path the file path (may be null) + * @return the extracted filename, or null if none + */ + public static String getFilename(String path) { + if (path == null) { + return null; + } + int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); + return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path); + } + + /** + * Extract the filename extension from the given path, + * e.g. "mypath/myfile.txt" -> "txt". + * + * @param path the file path (may be null) + * @return the extracted filename extension, or null if none + */ + public static String getFilenameExtension(String path) { + if (path == null) { + return null; + } + int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR); + if (extIndex == -1) { + return null; + } + int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR); + if (folderIndex > extIndex) { + return null; + } + return path.substring(extIndex + 1); + } + + /** + * Strip the filename extension from the given path, + * e.g. "mypath/myfile.txt" -> "mypath/myfile". + * + * @param path the file path (may be null) + * @return the path with stripped filename extension, + * or null if none + */ + public static String stripFilenameExtension(String path) { + if (path == null) { + return null; + } + int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR); + if (extIndex == -1) { + return path; + } + int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR); + if (folderIndex > extIndex) { + return path; + } + return path.substring(0, extIndex); + } + + /** + * Apply the given relative path to the given path, + * assuming standard Java folder separation (i.e. "/" separators). + * + * @param path the path to start from (usually a full file path) + * @param relativePath the relative path to apply + * (relative to the full file path above) + * @return the full file path that results from applying the relative path + */ + public static String applyRelativePath(String path, String relativePath) { + int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); + if (separatorIndex != -1) { + String newPath = path.substring(0, separatorIndex); + if (!relativePath.startsWith(FOLDER_SEPARATOR)) { + newPath += FOLDER_SEPARATOR; + } + return newPath + relativePath; + } else { + return relativePath; + } + } + + /** + * Normalize the path by suppressing sequences like "path/.." and + * inner simple dots. + *

The result is convenient for path comparison. For other uses, + * notice that Windows separators ("\") are replaced by simple slashes. + * + * @param path the original path + * @return the normalized path + */ + public static String cleanPath(String path) { + if (path == null) { + return null; + } + String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR); + + // Strip prefix from path to analyze, to not treat it as part of the + // first path element. This is necessary to correctly parse paths like + // "file:core/../core/io/Resource.class", where the ".." should just + // strip the first "core" directory while keeping the "file:" prefix. + int prefixIndex = pathToUse.indexOf(":"); + String prefix = ""; + if (prefixIndex != -1) { + prefix = pathToUse.substring(0, prefixIndex + 1); + pathToUse = pathToUse.substring(prefixIndex + 1); + } + if (pathToUse.startsWith(FOLDER_SEPARATOR)) { + prefix = prefix + FOLDER_SEPARATOR; + pathToUse = pathToUse.substring(1); + } + + String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR); + List pathElements = new LinkedList(); + int tops = 0; + + for (int i = pathArray.length - 1; i >= 0; i--) { + String element = pathArray[i]; + if (CURRENT_PATH.equals(element)) { + // Points to current directory - drop it. + } else if (TOP_PATH.equals(element)) { + // Registering top path found. + tops++; + } else { + if (tops > 0) { + // Merging path element with element corresponding to top path. + tops--; + } else { + // Normal path element found. + pathElements.add(0, element); + } + } + } + + // Remaining top paths need to be retained. + for (int i = 0; i < tops; i++) { + pathElements.add(0, TOP_PATH); + } + + return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR); + } + + /** + * Compare two paths after normalization of them. + * + * @param path1 first path for comparison + * @param path2 second path for comparison + * @return whether the two paths are equivalent after normalization + */ + public static boolean pathEquals(String path1, String path2) { + return cleanPath(path1).equals(cleanPath(path2)); + } + + /** + * Parse the given localeString value into a {@link java.util.Locale}. + *

This is the inverse operation of {@link java.util.Locale#toString Locale's toString}. + * + * @param localeString the locale string, following Locale's + * toString() format ("en", "en_UK", etc); + * also accepts spaces as separators, as an alternative to underscores + * @return a corresponding Locale instance + */ + public static Locale parseLocaleString(String localeString) { + String[] parts = tokenizeToStringArray(localeString, "_ ", false, false); + String language = (parts.length > 0 ? parts[0] : ""); + String country = (parts.length > 1 ? parts[1] : ""); + validateLocalePart(language); + validateLocalePart(country); + String variant = ""; + if (parts.length >= 2) { + // There is definitely a variant, and it is everything after the country + // code sans the separator between the country code and the variant. + int endIndexOfCountryCode = localeString.indexOf(country) + country.length(); + // Strip off any leading '_' and whitespace, what's left is the variant. + variant = trimLeadingWhitespace(localeString.substring(endIndexOfCountryCode)); + if (variant.startsWith("_")) { + variant = trimLeadingCharacter(variant, '_'); + } + } + return (language.length() > 0 ? new Locale(language, country, variant) : null); + } + + private static void validateLocalePart(String localePart) { + for (int i = 0; i < localePart.length(); i++) { + char ch = localePart.charAt(i); + if (ch != '_' && ch != ' ' && !Character.isLetterOrDigit(ch)) { + throw new IllegalArgumentException("Locale part \"" + localePart + "\" contains invalid characters"); + } + } + } + + /** + * Determine the RFC 3066 compliant language tag, + * as used for the HTTP "Accept-Language" header. + * + * @param locale the Locale to transform to a language tag + * @return the RFC 3066 compliant language tag as String + */ + public static String toLanguageTag(Locale locale) { + return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : ""); + } + + + //--------------------------------------------------------------------- + // Convenience methods for working with String arrays + //--------------------------------------------------------------------- + + /** + * Append the given String to the given String array, returning a new array + * consisting of the input array contents plus the given String. + * + * @param array the array to append to (can be null) + * @param str the String to append + * @return the new array (never null) + */ + public static String[] addStringToArray(String[] array, String str) { + if (Objects.isEmpty(array)) { + return new String[]{str}; + } + String[] newArr = new String[array.length + 1]; + System.arraycopy(array, 0, newArr, 0, array.length); + newArr[array.length] = str; + return newArr; + } + + /** + * Concatenate the given String arrays into one, + * with overlapping array elements included twice. + *

The order of elements in the original arrays is preserved. + * + * @param array1 the first array (can be null) + * @param array2 the second array (can be null) + * @return the new array (null if both given arrays were null) + */ + public static String[] concatenateStringArrays(String[] array1, String[] array2) { + if (Objects.isEmpty(array1)) { + return array2; + } + if (Objects.isEmpty(array2)) { + return array1; + } + String[] newArr = new String[array1.length + array2.length]; + System.arraycopy(array1, 0, newArr, 0, array1.length); + System.arraycopy(array2, 0, newArr, array1.length, array2.length); + return newArr; + } + + /** + * Merge the given String arrays into one, with overlapping + * array elements only included once. + *

The order of elements in the original arrays is preserved + * (with the exception of overlapping elements, which are only + * included on their first occurrence). + * + * @param array1 the first array (can be null) + * @param array2 the second array (can be null) + * @return the new array (null if both given arrays were null) + */ + public static String[] mergeStringArrays(String[] array1, String[] array2) { + if (Objects.isEmpty(array1)) { + return array2; + } + if (Objects.isEmpty(array2)) { + return array1; + } + List result = new ArrayList(); + result.addAll(Arrays.asList(array1)); + for (String str : array2) { + if (!result.contains(str)) { + result.add(str); + } + } + return toStringArray(result); + } + + /** + * Turn given source String array into sorted array. + * + * @param array the source array + * @return the sorted array (never null) + */ + public static String[] sortStringArray(String[] array) { + if (Objects.isEmpty(array)) { + return new String[0]; + } + Arrays.sort(array); + return array; + } + + /** + * Copy the given Collection into a String array. + * The Collection must contain String elements only. + * + * @param collection the Collection to copy + * @return the String array (null if the passed-in + * Collection was null) + */ + public static String[] toStringArray(Collection collection) { + if (collection == null) { + return null; + } + return collection.toArray(new String[collection.size()]); + } + + /** + * Copy the given Enumeration into a String array. + * The Enumeration must contain String elements only. + * + * @param enumeration the Enumeration to copy + * @return the String array (null if the passed-in + * Enumeration was null) + */ + public static String[] toStringArray(Enumeration enumeration) { + if (enumeration == null) { + return null; + } + List list = java.util.Collections.list(enumeration); + return list.toArray(new String[list.size()]); + } + + /** + * Trim the elements of the given String array, + * calling String.trim() on each of them. + * + * @param array the original String array + * @return the resulting array (of the same size) with trimmed elements + */ + public static String[] trimArrayElements(String[] array) { + if (Objects.isEmpty(array)) { + return new String[0]; + } + String[] result = new String[array.length]; + for (int i = 0; i < array.length; i++) { + String element = array[i]; + result[i] = (element != null ? element.trim() : null); + } + return result; + } + + /** + * Remove duplicate Strings from the given array. + * Also sorts the array, as it uses a TreeSet. + * + * @param array the String array + * @return an array without duplicates, in natural sort order + */ + public static String[] removeDuplicateStrings(String[] array) { + if (Objects.isEmpty(array)) { + return array; + } + Set set = new TreeSet(); + for (String element : array) { + set.add(element); + } + return toStringArray(set); + } + + /** + * Split a String at the first occurrence of the delimiter. + * Does not include the delimiter in the result. + * + * @param toSplit the string to split + * @param delimiter to split the string up with + * @return a two element array with index 0 being before the delimiter, and + * index 1 being after the delimiter (neither element includes the delimiter); + * or null if the delimiter wasn't found in the given input String + */ + public static String[] split(String toSplit, String delimiter) { + if (!hasLength(toSplit) || !hasLength(delimiter)) { + return null; + } + int offset = toSplit.indexOf(delimiter); + if (offset < 0) { + return null; + } + String beforeDelimiter = toSplit.substring(0, offset); + String afterDelimiter = toSplit.substring(offset + delimiter.length()); + return new String[]{beforeDelimiter, afterDelimiter}; + } + + /** + * Take an array Strings and split each element based on the given delimiter. + * A Properties instance is then generated, with the left of the + * delimiter providing the key, and the right of the delimiter providing the value. + *

Will trim both the key and value before adding them to the + * Properties instance. + * + * @param array the array to process + * @param delimiter to split each element using (typically the equals symbol) + * @return a Properties instance representing the array contents, + * or null if the array to process was null or empty + */ + public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter) { + return splitArrayElementsIntoProperties(array, delimiter, null); + } + + /** + * Take an array Strings and split each element based on the given delimiter. + * A Properties instance is then generated, with the left of the + * delimiter providing the key, and the right of the delimiter providing the value. + *

Will trim both the key and value before adding them to the + * Properties instance. + * + * @param array the array to process + * @param delimiter to split each element using (typically the equals symbol) + * @param charsToDelete one or more characters to remove from each element + * prior to attempting the split operation (typically the quotation mark + * symbol), or null if no removal should occur + * @return a Properties instance representing the array contents, + * or null if the array to process was null or empty + */ + public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter, String charsToDelete) { + + if (Objects.isEmpty(array)) { + return null; + } + Properties result = new Properties(); + for (String element : array) { + if (charsToDelete != null) { + element = deleteAny(element, charsToDelete); + } + String[] splittedElement = split(element, delimiter); + if (splittedElement == null) { + continue; + } + result.setProperty(splittedElement[0].trim(), splittedElement[1].trim()); + } + return result; + } + + /** + * Tokenize the given String into a String array via a StringTokenizer. + * Trims tokens and omits empty tokens. + *

The given delimiters string is supposed to consist of any number of + * delimiter characters. Each of those characters can be used to separate + * tokens. A delimiter is always a single character; for multi-character + * delimiters, consider using delimitedListToStringArray + * + * @param str the String to tokenize + * @param delimiters the delimiter characters, assembled as String + * (each of those characters is individually considered as delimiter). + * @return an array of the tokens + * @see java.util.StringTokenizer + * @see java.lang.String#trim() + * @see #delimitedListToStringArray + */ + public static String[] tokenizeToStringArray(String str, String delimiters) { + return tokenizeToStringArray(str, delimiters, true, true); + } + + /** + * Tokenize the given String into a String array via a StringTokenizer. + *

The given delimiters string is supposed to consist of any number of + * delimiter characters. Each of those characters can be used to separate + * tokens. A delimiter is always a single character; for multi-character + * delimiters, consider using delimitedListToStringArray + * + * @param str the String to tokenize + * @param delimiters the delimiter characters, assembled as String + * (each of those characters is individually considered as delimiter) + * @param trimTokens trim the tokens via String's trim + * @param ignoreEmptyTokens omit empty tokens from the result array + * (only applies to tokens that are empty after trimming; StringTokenizer + * will not consider subsequent delimiters as token in the first place). + * @return an array of the tokens (null if the input String + * was null) + * @see java.util.StringTokenizer + * @see java.lang.String#trim() + * @see #delimitedListToStringArray + */ + public static String[] tokenizeToStringArray(String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { + + if (str == null) { + return null; + } + StringTokenizer st = new StringTokenizer(str, delimiters); + List tokens = new ArrayList(); + while (st.hasMoreTokens()) { + String token = st.nextToken(); + if (trimTokens) { + token = token.trim(); + } + if (!ignoreEmptyTokens || token.length() > 0) { + tokens.add(token); + } + } + return toStringArray(tokens); + } + + /** + * Take a String which is a delimited list and convert it to a String array. + *

A single delimiter can consists of more than one character: It will still + * be considered as single delimiter string, rather than as bunch of potential + * delimiter characters - in contrast to tokenizeToStringArray. + * + * @param str the input String + * @param delimiter the delimiter between elements (this is a single delimiter, + * rather than a bunch individual delimiter characters) + * @return an array of the tokens in the list + * @see #tokenizeToStringArray + */ + public static String[] delimitedListToStringArray(String str, String delimiter) { + return delimitedListToStringArray(str, delimiter, null); + } + + /** + * Take a String which is a delimited list and convert it to a String array. + *

A single delimiter can consists of more than one character: It will still + * be considered as single delimiter string, rather than as bunch of potential + * delimiter characters - in contrast to tokenizeToStringArray. + * + * @param str the input String + * @param delimiter the delimiter between elements (this is a single delimiter, + * rather than a bunch individual delimiter characters) + * @param charsToDelete a set of characters to delete. Useful for deleting unwanted + * line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a String. + * @return an array of the tokens in the list + * @see #tokenizeToStringArray + */ + public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) { + if (str == null) { + return new String[0]; + } + if (delimiter == null) { + return new String[]{str}; + } + List result = new ArrayList(); + if ("".equals(delimiter)) { + for (int i = 0; i < str.length(); i++) { + result.add(deleteAny(str.substring(i, i + 1), charsToDelete)); + } + } else { + int pos = 0; + int delPos; + while ((delPos = str.indexOf(delimiter, pos)) != -1) { + result.add(deleteAny(str.substring(pos, delPos), charsToDelete)); + pos = delPos + delimiter.length(); + } + if (str.length() > 0 && pos <= str.length()) { + // Add rest of String, but not in case of empty input. + result.add(deleteAny(str.substring(pos), charsToDelete)); + } + } + return toStringArray(result); + } + + /** + * Convert a CSV list into an array of Strings. + * + * @param str the input String + * @return an array of Strings, or the empty array in case of empty input + */ + public static String[] commaDelimitedListToStringArray(String str) { + return delimitedListToStringArray(str, ","); + } + + /** + * Convenience method to convert a CSV string list to a set. + * Note that this will suppress duplicates. + * + * @param str the input String + * @return a Set of String entries in the list + */ + public static Set commaDelimitedListToSet(String str) { + Set set = new TreeSet(); + String[] tokens = commaDelimitedListToStringArray(str); + for (String token : tokens) { + set.add(token); + } + return set; + } + + /** + * Convenience method to return a Collection as a delimited (e.g. CSV) + * String. E.g. useful for toString() implementations. + * + * @param coll the Collection to display + * @param delim the delimiter to use (probably a ",") + * @param prefix the String to start each element with + * @param suffix the String to end each element with + * @return the delimited String + */ + public static String collectionToDelimitedString(Collection coll, String delim, String prefix, String suffix) { + if (Collections.isEmpty(coll)) { + return ""; + } + StringBuilder sb = new StringBuilder(); + Iterator it = coll.iterator(); + while (it.hasNext()) { + sb.append(prefix).append(it.next()).append(suffix); + if (it.hasNext()) { + sb.append(delim); + } + } + return sb.toString(); + } + + /** + * Convenience method to return a Collection as a delimited (e.g. CSV) + * String. E.g. useful for toString() implementations. + * + * @param coll the Collection to display + * @param delim the delimiter to use (probably a ",") + * @return the delimited String + */ + public static String collectionToDelimitedString(Collection coll, String delim) { + return collectionToDelimitedString(coll, delim, "", ""); + } + + /** + * Convenience method to return a Collection as a CSV String. + * E.g. useful for toString() implementations. + * + * @param coll the Collection to display + * @return the delimited String + */ + public static String collectionToCommaDelimitedString(Collection coll) { + return collectionToDelimitedString(coll, ","); + } + + /** + * Convenience method to return a String array as a delimited (e.g. CSV) + * String. E.g. useful for toString() implementations. + * + * @param arr the array to display + * @param delim the delimiter to use (probably a ",") + * @return the delimited String + */ + public static String arrayToDelimitedString(Object[] arr, String delim) { + if (Objects.isEmpty(arr)) { + return ""; + } + if (arr.length == 1) { + return Objects.nullSafeToString(arr[0]); + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < arr.length; i++) { + if (i > 0) { + sb.append(delim); + } + sb.append(arr[i]); + } + return sb.toString(); + } + + /** + * Convenience method to return a String array as a CSV String. + * E.g. useful for toString() implementations. + * + * @param arr the array to display + * @return the delimited String + */ + public static String arrayToCommaDelimitedString(Object[] arr) { + return arrayToDelimitedString(arr, ","); + } + + /** + * Appends a space character (' ') if the argument is not empty, otherwise does nothing. This method + * can be thought of as "non-empty space". Using this method allows reduction of this: + *

+     * if (sb.length != 0) {
+     *     sb.append(' ');
+     * }
+     * sb.append(nextWord);
+ *

To this:

+ *
+     * nespace(sb).append(nextWord);
+ * + * @param sb the string builder to append a space to if non-empty + * @return the string builder argument for method chaining. + * @since 0.12.0 + */ + public static StringBuilder nespace(StringBuilder sb) { + if (sb == null) { + return null; + } + if (sb.length() != 0) { + sb.append(' '); + } + return sb; + } + +} + diff --git a/io/jsonwebtoken/lang/Supplier.java b/io/jsonwebtoken/lang/Supplier.java new file mode 100644 index 0000000..7a94e59 --- /dev/null +++ b/io/jsonwebtoken/lang/Supplier.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +/** + * Represents a supplier of results. + * + *

There is no requirement that a new or distinct result be returned each time the supplier is invoked.

+ * + *

This interface is the equivalent of a JDK 8 {@code java.util.function.Supplier}, backported for JJWT's use in + * JDK 7 environments.

+ * + * @param the type of object returned by this supplier + * @since 0.12.0 + */ +public interface Supplier { + + /** + * Returns a result. + * + * @return a result. + */ + T get(); +} diff --git a/io/jsonwebtoken/lang/UnknownClassException.java b/io/jsonwebtoken/lang/UnknownClassException.java new file mode 100644 index 0000000..07b44d9 --- /dev/null +++ b/io/jsonwebtoken/lang/UnknownClassException.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.lang; + +/** + * A RuntimeException equivalent of the JDK's + * ClassNotFoundException, to maintain a RuntimeException paradigm. + * + * @since 0.1 + */ +public class UnknownClassException extends RuntimeException { + + /* + /** + * Creates a new UnknownClassException. + * + public UnknownClassException() { + super(); + }*/ + + /** + * Constructs a new UnknownClassException. + * + * @param message the reason for the exception + */ + public UnknownClassException(String message) { + super(message); + } + + /* + * Constructs a new UnknownClassException. + * + * @param cause the underlying Throwable that caused this exception to be thrown. + * + public UnknownClassException(Throwable cause) { + super(cause); + } + */ + + /** + * Constructs a new UnknownClassException. + * + * @param message the reason for the exception + * @param cause the underlying Throwable that caused this exception to be thrown. + */ + public UnknownClassException(String message, Throwable cause) { + // TODO: remove in v1.0, this constructor is only exposed to allow for backward compatible behavior + super(message, cause); + } + +} \ No newline at end of file diff --git a/io/jsonwebtoken/security/AeadAlgorithm.java b/io/jsonwebtoken/security/AeadAlgorithm.java new file mode 100644 index 0000000..7e82d85 --- /dev/null +++ b/io/jsonwebtoken/security/AeadAlgorithm.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.Jwts; + +import javax.crypto.SecretKey; +import java.io.OutputStream; + +/** + * A cryptographic algorithm that performs + *
Authenticated encryption with additional data. + * Per JWE RFC 7516, Section 4.1.2, all JWEs + * MUST use an AEAD algorithm to encrypt or decrypt the JWE payload/content. Consequently, all + * JWA "enc" algorithms are AEAD + * algorithms, and they are accessible as concrete instances via {@link Jwts.ENC}. + * + *

"enc" identifier

+ * + *

{@code AeadAlgorithm} extends {@code Identifiable}: the value returned from {@link Identifiable#getId() getId()} + * will be used as the JWE "enc" protected header value.

+ * + *

Key Strength

+ * + *

Encryption strength is in part attributed to how difficult it is to discover the encryption key. As such, + * cryptographic algorithms often require keys of a minimum length to ensure the keys are difficult to discover + * and the algorithm's security properties are maintained.

+ * + *

The {@code AeadAlgorithm} interface extends the {@link KeyLengthSupplier} interface to represent the length + * in bits a key must have to be used with its implementation. If you do not want to worry about lengths and + * parameters of keys required for an algorithm, it is often easier to automatically generate a key that adheres + * to the algorithms requirements, as discussed below.

+ * + *

Key Generation

+ * + *

{@code AeadAlgorithm} extends {@link KeyBuilderSupplier} to enable {@link SecretKey} generation. Each AEAD + * algorithm instance will return a {@link KeyBuilder} that ensures any created keys will have a sufficient length + * and algorithm parameters required by that algorithm. For example:

+ * + *

+ *     SecretKey key = aeadAlgorithm.key().build();
+ * 
+ * + *

The resulting {@code key} is guaranteed to have the correct algorithm parameters and strength/length necessary for + * that exact {@code aeadAlgorithm} instance.

+ * + * @see Jwts.ENC + * @see Identifiable#getId() + * @see KeyLengthSupplier + * @see KeyBuilderSupplier + * @see KeyBuilder + * @since 0.12.0 + */ +public interface AeadAlgorithm extends Identifiable, KeyLengthSupplier, KeyBuilderSupplier { + + /** + * Encrypts plaintext and signs any {@link AeadRequest#getAssociatedData() associated data}, placing the resulting + * ciphertext, initialization vector and authentication tag in the provided {@code result}. + * + * @param req the encryption request representing the plaintext to be encrypted, any additional + * integrity-protected data and the encryption key. + * @param res the result to write ciphertext, initialization vector and AAD authentication tag (aka digest) + * @throws SecurityException if there is an encryption problem or AAD authenticity cannot be guaranteed. + */ + void encrypt(AeadRequest req, AeadResult res) throws SecurityException; + + /** + * Decrypts ciphertext and authenticates any {@link DecryptAeadRequest#getAssociatedData() associated data}, + * writing the decrypted plaintext to the provided {@code out}put stream. + * + * @param request the decryption request representing the ciphertext to be decrypted, any additional + * integrity-protected data, authentication tag, initialization vector, and decryption key + * @param out the OutputStream for writing decrypted plaintext + * @throws SecurityException if there is a decryption problem or authenticity assertions fail. + */ + void decrypt(DecryptAeadRequest request, OutputStream out) throws SecurityException; +} diff --git a/io/jsonwebtoken/security/AeadRequest.java b/io/jsonwebtoken/security/AeadRequest.java new file mode 100644 index 0000000..8287869 --- /dev/null +++ b/io/jsonwebtoken/security/AeadRequest.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; +import java.io.InputStream; + +/** + * A request to an {@link AeadAlgorithm} to perform authenticated encryption with a supplied symmetric + * {@link SecretKey}, allowing for additional data to be authenticated and integrity-protected. + * + * @see SecureRequest + * @see AssociatedDataSupplier + * @since 0.12.0 + */ +public interface AeadRequest extends SecureRequest, AssociatedDataSupplier { +} diff --git a/io/jsonwebtoken/security/AeadResult.java b/io/jsonwebtoken/security/AeadResult.java new file mode 100644 index 0000000..c8734b5 --- /dev/null +++ b/io/jsonwebtoken/security/AeadResult.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.io.OutputStream; + +/** + * The result of authenticated encryption, providing access to the ciphertext {@link #getOutputStream() output stream} + * and resulting {@link #setTag(byte[]) AAD tag} and {@link #setIv(byte[]) initialization vector}. + * The AAD tag and initialization vector must be supplied with the ciphertext to decrypt. + * + * @since 0.12.0 + */ +public interface AeadResult { + + /** + * Returns the {@code OutputStream} the AeadAlgorithm will use to write the resulting ciphertext during + * encryption or plaintext during decryption. + * + * @return the {@code OutputStream} the AeadAlgorithm will use to write the resulting ciphertext during + * encryption or plaintext during decryption. + */ + OutputStream getOutputStream(); + + /** + * Sets the AEAD authentication tag. + * + * @param tag the AEAD authentication tag. + * @return the AeadResult for method chaining. + */ + AeadResult setTag(byte[] tag); + + /** + * Sets the initialization vector used during encryption. + * + * @param iv the initialization vector used during encryption. + * @return the AeadResult for method chaining. + */ + AeadResult setIv(byte[] iv); +} diff --git a/io/jsonwebtoken/security/AssociatedDataSupplier.java b/io/jsonwebtoken/security/AssociatedDataSupplier.java new file mode 100644 index 0000000..4f5cd37 --- /dev/null +++ b/io/jsonwebtoken/security/AssociatedDataSupplier.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.io.InputStream; + +/** + * Provides any "associated data" that must be integrity protected (but not encrypted) when performing + * AEAD encryption or decryption. + * + * @see #getAssociatedData() + * @since 0.12.0 + */ +public interface AssociatedDataSupplier { + + /** + * Returns any data that must be integrity protected (but not encrypted) when performing + * AEAD encryption or decryption, or + * {@code null} if no additional data must be integrity protected. + * + * @return any data that must be integrity protected (but not encrypted) when performing + * AEAD encryption or decryption, or + * {@code null} if no additional data must be integrity protected. + */ + InputStream getAssociatedData(); +} diff --git a/io/jsonwebtoken/security/AsymmetricJwk.java b/io/jsonwebtoken/security/AsymmetricJwk.java new file mode 100644 index 0000000..b69db54 --- /dev/null +++ b/io/jsonwebtoken/security/AsymmetricJwk.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Key; + +/** + * JWK representation of an asymmetric (public or private) cryptographic key. + * + * @param the type of {@link java.security.PublicKey} or {@link java.security.PrivateKey} represented by this JWK. + * @since 0.12.0 + */ +public interface AsymmetricJwk extends Jwk, X509Accessor { + + /** + * Returns the JWK + * {@code use} (Public Key Use) + * parameter value or {@code null} if not present. {@code use} values are CaSe-SeNsItIvE. + * + *

The JWK specification defines the + * following {@code use} values:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
JWK Key Use Values
ValueKey Use
{@code sig}signature
{@code enc}encryption
+ * + *

Other values MAY be used. For best interoperability with other applications however, it is + * recommended to use only the values above.

+ * + *

When a key is used to wrap another key and a public key use designation for the first key is desired, the + * {@code enc} (encryption) key use value is used, since key wrapping is a kind of encryption. The + * {@code enc} value is also to be used for public keys used for key agreement operations.

+ * + *

Public Key Use vs Key Operations

+ * + *

Per + * JWK RFC 7517, Section 4.3, last paragraph, + * the {@code use} (Public Key Use) and {@link #getOperations() key_ops (Key Operations)} members + * SHOULD NOT be used together; however, if both are used, the information they convey MUST be + * consistent. Applications should specify which of these members they use, if either is to be used by the + * application.

+ * + * @return the JWK {@code use} value or {@code null} if not present. + */ + String getPublicKeyUse(); +} diff --git a/io/jsonwebtoken/security/AsymmetricJwkBuilder.java b/io/jsonwebtoken/security/AsymmetricJwkBuilder.java new file mode 100644 index 0000000..fe3ce7e --- /dev/null +++ b/io/jsonwebtoken/security/AsymmetricJwkBuilder.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Key; + +/** + * A {@link JwkBuilder} that builds asymmetric (public or private) JWKs. + * + * @param the type of Java key provided by the JWK. + * @param the type of asymmetric JWK created + * @param the type of the builder, for subtype method chaining + * @since 0.12.0 + */ +public interface AsymmetricJwkBuilder, T extends AsymmetricJwkBuilder> + extends JwkBuilder, X509Builder { + + /** + * Sets the JWK + * {@code use} (Public Key Use) + * parameter value. {@code use} values are CaSe-SeNsItIvE. A {@code null} value will remove the property + * from the JWK. + * + *

The JWK specification defines the + * following {@code use} values:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
JWK Key Use Values
ValueKey Use
{@code sig}signature
{@code enc}encryption
+ * + *

Other values MAY be used. For best interoperability with other applications however, it is + * recommended to use only the values above.

+ * + *

When a key is used to wrap another key and a public key use designation for the first key is desired, the + * {@code enc} (encryption) key use value is used, since key wrapping is a kind of encryption. The + * {@code enc} value is also to be used for public keys used for key agreement operations.

+ * + *

Public Key Use vs Key Operations

+ * + *

Per + * JWK RFC 7517, Section 4.3, last paragraph, + * the use (Public Key Use) and {@link #operations() key_ops (Key Operations)} members + * SHOULD NOT be used together; however, if both are used, the information they convey MUST be + * consistent. Applications should specify which of these members they use, if either is to be used by the + * application.

+ * + * @param use the JWK {@code use} value. + * @return the builder for method chaining. + * @throws IllegalArgumentException if the {@code use} value is {@code null} or empty. + */ + T publicKeyUse(String use) throws IllegalArgumentException; +} diff --git a/io/jsonwebtoken/security/Curve.java b/io/jsonwebtoken/security/Curve.java new file mode 100644 index 0000000..2cc1f42 --- /dev/null +++ b/io/jsonwebtoken/security/Curve.java @@ -0,0 +1,41 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; + +/** + * A cryptographic Elliptic Curve for use with digital signature or key agreement algorithms. + * + *

Curve Identifier

+ * + *

This interface extends {@link Identifiable}; the value returned from {@link #getId()} will + * be used as the JWK + * crv value.

+ * + *

KeyPair Generation

+ * + *

A secure-random KeyPair of sufficient strength on the curve may be obtained with its {@link #keyPair()} builder.

+ * + *

Standard Implementations

+ * + *

Constants for all JWA standard Curves are available via the {@link Jwks.CRV} registry.

+ * + * @see Jwks.CRV + * @since 0.12.0 + */ +public interface Curve extends Identifiable, KeyPairBuilderSupplier { +} diff --git a/io/jsonwebtoken/security/DecryptAeadRequest.java b/io/jsonwebtoken/security/DecryptAeadRequest.java new file mode 100644 index 0000000..5faf1f6 --- /dev/null +++ b/io/jsonwebtoken/security/DecryptAeadRequest.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; + +/** + * A request to an {@link AeadAlgorithm} to decrypt ciphertext and perform integrity-protection with a supplied + * decryption {@link SecretKey}. Extends both {@link IvSupplier} and {@link DigestSupplier} to + * ensure the respective required IV and AAD tag returned from an {@link AeadResult} are available for decryption. + * + * @since 0.12.0 + */ +public interface DecryptAeadRequest extends AeadRequest, IvSupplier, DigestSupplier { +} diff --git a/io/jsonwebtoken/security/DecryptionKeyRequest.java b/io/jsonwebtoken/security/DecryptionKeyRequest.java new file mode 100644 index 0000000..893cad9 --- /dev/null +++ b/io/jsonwebtoken/security/DecryptionKeyRequest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Key; + +/** + * A {@link KeyRequest} to obtain a decryption key that will be used to decrypt a JWE using an {@link AeadAlgorithm}. + * The AEAD algorithm used for decryption is accessible via {@link #getEncryptionAlgorithm()}. + * + *

The key used to perform cryptographic operations, for example a direct shared key, or a + * JWE "key decryption key" will be accessible via {@link #getKey()}. This is always required and + * never {@code null}.

+ * + *

Any encrypted key material (what the JWE specification calls the + * JWE Encrypted Key) will + * be accessible via {@link #getPayload()}. If present, the {@link KeyAlgorithm} will decrypt it to obtain the resulting + * Content Encryption Key (CEK). + * This may be empty however depending on which {@link KeyAlgorithm} was used during JWE encryption.

+ * + *

Finally, any public information necessary by the called {@link KeyAlgorithm} to decrypt any + * {@code JWE Encrypted Key} (such as an initialization vector, authentication tag, ephemeral key, etc) is expected + * to be available in the JWE protected header, accessible via {@link #getHeader()}.

+ * + * @param the type of {@link Key} used during the request to obtain the resulting decryption key. + * @since 0.12.0 + */ +public interface DecryptionKeyRequest extends SecureRequest, KeyRequest { +} diff --git a/io/jsonwebtoken/security/DigestAlgorithm.java b/io/jsonwebtoken/security/DigestAlgorithm.java new file mode 100644 index 0000000..daeaa6a --- /dev/null +++ b/io/jsonwebtoken/security/DigestAlgorithm.java @@ -0,0 +1,101 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.lang.Registry; + +import javax.crypto.SecretKey; +import java.io.InputStream; +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * A {@code DigestAlgorithm} is a + * Cryptographic Hash Function + * that computes and verifies cryptographic digests. There are three types of {@code DigestAlgorithm}s represented + * by subtypes, and RFC-standard implementations are available as constants in {@link Registry} singletons: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Types of {@code DigestAlgorithm}s
SubtypeStandard Implementation RegistrySecurity Model
{@link HashAlgorithm}{@link Jwks.HASH}Unsecured (unkeyed), does not require a key to compute or verify digests.
{@link MacAlgorithm}{@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}Requires a {@link SecretKey} to both compute and verify digests (aka + * "Message Authentication Codes").
{@link SignatureAlgorithm}{@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}Requires a {@link PrivateKey} to compute and {@link PublicKey} to verify digests + * (aka "Digital Signatures").
+ * + *

Standard Identifier

+ * + *

{@code DigestAlgorithm} extends {@link Identifiable}: the value returned from + * {@link Identifiable#getId() getId()} will be used as the JWT standard identifier where required.

+ * + *

For example, + * when a {@link MacAlgorithm} or {@link SignatureAlgorithm} is used to secure a JWS, the value returned from + * {@code algorithm.getId()} will be used as the JWS "alg" protected header value. Or when a + * {@link HashAlgorithm} is used to compute a {@link JwkThumbprint}, it's {@code algorithm.getId()} value will be + * used within the thumbprint's {@link JwkThumbprint#toURI() URI} per JWT RFC requirements.

+ * + * @param the type of {@link Request} used when computing a digest. + * @param the type of {@link VerifyDigestRequest} used when verifying a digest. + * @see Jwks.HASH + * @see io.jsonwebtoken.Jwts.SIG Jwts.SIG + * @since 0.12.0 + */ +public interface DigestAlgorithm, V extends VerifyDigestRequest> extends Identifiable { + + /** + * Returns a cryptographic digest of the request {@link Request#getPayload() payload}. + * + * @param request the request containing the data to be hashed, mac'd or signed. + * @return a cryptographic digest of the request {@link Request#getPayload() payload}. + * @throws SecurityException if there is invalid key input or a problem during digest creation. + */ + byte[] digest(R request) throws SecurityException; + + /** + * Returns {@code true} if the provided {@link VerifyDigestRequest#getDigest() digest} matches the expected value + * for the given {@link VerifyDigestRequest#getPayload() payload}, {@code false} otherwise. + * + * @param request the request containing the {@link VerifyDigestRequest#getDigest() digest} to verify for the + * associated {@link VerifyDigestRequest#getPayload() payload}. + * @return {@code true} if the provided {@link VerifyDigestRequest#getDigest() digest} matches the expected value + * for the given {@link VerifyDigestRequest#getPayload() payload}, {@code false} otherwise. + * @throws SecurityException if there is an invalid key input or a problem that won't allow digest verification. + */ + boolean verify(V request) throws SecurityException; +} diff --git a/io/jsonwebtoken/security/DigestSupplier.java b/io/jsonwebtoken/security/DigestSupplier.java new file mode 100644 index 0000000..4c697d9 --- /dev/null +++ b/io/jsonwebtoken/security/DigestSupplier.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * A {@code DigestSupplier} provides access to the result of a cryptographic digest algorithm, such as a + * Message Digest, MAC, Signature, or Authentication Tag. + * + * @since 0.12.0 + */ +public interface DigestSupplier { + + /** + * Returns a cryptographic digest result, such as a Message Digest, MAC, Signature, or Authentication Tag + * depending on the cryptographic algorithm that produced it. + * + * @return a cryptographic digest result, such as a Message Digest, MAC, Signature, or Authentication Tag + * * depending on the cryptographic algorithm that produced it. + */ + byte[] getDigest(); + +} diff --git a/io/jsonwebtoken/security/DynamicJwkBuilder.java b/io/jsonwebtoken/security/DynamicJwkBuilder.java new file mode 100644 index 0000000..afc9094 --- /dev/null +++ b/io/jsonwebtoken/security/DynamicJwkBuilder.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; +import java.security.Key; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.List; + +/** + * A {@link JwkBuilder} that coerces to a more type-specific builder based on the {@link Key} that will be + * represented as a JWK. + * + * @param the type of Java {@link Key} represented by the created {@link Jwk}. + * @param the type of {@link Jwk} created by the builder + * @since 0.12.0 + */ +public interface DynamicJwkBuilder> extends JwkBuilder> { + + /** + * Ensures the builder will create a {@link PublicJwk} for the specified Java {@link X509Certificate} chain. + * The first {@code X509Certificate} in the chain (at array index 0) MUST contain a {@link PublicKey} + * instance when calling the certificate's {@link X509Certificate#getPublicKey() getPublicKey()} method. + * + *

This method is provided for congruence with the other {@code chain} methods and is expected to be used when + * the calling code has a variable {@code PublicKey} reference. Based on the argument type, it will + * delegate to one of the following methods if possible: + *

    + *
  • {@link #rsaChain(List)}
  • + *
  • {@link #ecChain(List)}
  • + *
  • {@link #octetChain(List)}
  • + *
+ * + *

If the specified {@code chain} argument is not capable of being supported by one of those methods, an + * {@link UnsupportedKeyException} will be thrown.

+ * + *

Type Parameters

+ * + *

In addition to the public key type A, the public key's associated private key type + * B is parameterized as well. This ensures that any subsequent call to the builder's + * {@link PublicJwkBuilder#privateKey(PrivateKey) privateKey} method will be type-safe. For example:

+ * + *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>chain(edECPublicKeyX509CertificateChain)
+     *     .privateKey(aPrivateKey) // <-- must be an EdECPrivateKey instance
+     *     ... etc ...
+     *     .build();
+ * + * @param the type of {@link PublicKey} provided by the created public JWK. + * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce a + * {@link PrivateJwk} if desired. + * @param chain the {@link X509Certificate} chain to inspect to find the {@link PublicKey} to represent as a + * {@link PublicJwk}. + * @return the builder coerced as a {@link PublicJwkBuilder} for continued method chaining. + * @throws UnsupportedKeyException if the specified key is not a supported type and cannot be used to delegate to + * other {@code key} methods. + * @see PublicJwk + * @see PrivateJwk + */ + PublicJwkBuilder chain(List chain) + throws UnsupportedKeyException; + + /** + * Ensures the builder will create a {@link SecretJwk} for the specified Java {@link SecretKey}. + * + * @param key the {@link SecretKey} to represent as a {@link SecretJwk}. + * @return the builder coerced as a {@link SecretJwkBuilder}. + */ + SecretJwkBuilder key(SecretKey key); + + /** + * Ensures the builder will create an {@link RsaPublicJwk} for the specified Java {@link RSAPublicKey}. + * + * @param key the {@link RSAPublicKey} to represent as a {@link RsaPublicJwk}. + * @return the builder coerced as an {@link RsaPublicJwkBuilder}. + */ + RsaPublicJwkBuilder key(RSAPublicKey key); + + /** + * Ensures the builder will create an {@link RsaPrivateJwk} for the specified Java {@link RSAPrivateKey}. If + * possible, it is recommended to also call the resulting builder's + * {@link RsaPrivateJwkBuilder#publicKey(PublicKey) publicKey} method with the private key's matching + * {@link PublicKey} for better performance. See the + * {@link RsaPrivateJwkBuilder#publicKey(PublicKey) publicKey} and {@link PrivateJwk} JavaDoc for more + * information. + * + * @param key the {@link RSAPublicKey} to represent as a {@link RsaPublicJwk}. + * @return the builder coerced as an {@link RsaPrivateJwkBuilder}. + */ + RsaPrivateJwkBuilder key(RSAPrivateKey key); + + /** + * Ensures the builder will create an {@link EcPublicJwk} for the specified Java {@link ECPublicKey}. + * + * @param key the {@link ECPublicKey} to represent as a {@link EcPublicJwk}. + * @return the builder coerced as an {@link EcPublicJwkBuilder}. + */ + EcPublicJwkBuilder key(ECPublicKey key); + + /** + * Ensures the builder will create an {@link EcPrivateJwk} for the specified Java {@link ECPrivateKey}. If + * possible, it is recommended to also call the resulting builder's + * {@link EcPrivateJwkBuilder#publicKey(PublicKey) publicKey} method with the private key's matching + * {@link PublicKey} for better performance. See the + * {@link EcPrivateJwkBuilder#publicKey(PublicKey) publicKey} and {@link PrivateJwk} JavaDoc for more + * information. + * + * @param key the {@link ECPublicKey} to represent as an {@link EcPublicJwk}. + * @return the builder coerced as a {@link EcPrivateJwkBuilder}. + */ + EcPrivateJwkBuilder key(ECPrivateKey key); + + /** + * Ensures the builder will create a {@link PublicJwk} for the specified Java {@link PublicKey} argument. This + * method is provided for congruence with the other {@code key} methods and is expected to be used when + * the calling code has an untyped {@code PublicKey} reference. Based on the argument type, it will delegate to one + * of the following methods if possible: + *
    + *
  • {@link #key(RSAPublicKey)}
  • + *
  • {@link #key(ECPublicKey)}
  • + *
  • {@link #octetKey(PublicKey)}
  • + *
+ * + *

If the specified {@code key} argument is not capable of being supported by one of those methods, an + * {@link UnsupportedKeyException} will be thrown.

+ * + *

Type Parameters

+ * + *

In addition to the public key type A, the public key's associated private key type + * B is parameterized as well. This ensures that any subsequent call to the builder's + * {@link PublicJwkBuilder#privateKey(PrivateKey) privateKey} method will be type-safe. For example:

+ * + *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPublicKey)
+     *     .privateKey(aPrivateKey) // <-- must be an EdECPrivateKey instance
+     *     ... etc ...
+     *     .build();
+ * + * @param
the type of {@link PublicKey} provided by the created public JWK. + * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce a + * {@link PrivateJwk} if desired. + * @param key the {@link PublicKey} to represent as a {@link PublicJwk}. + * @return the builder coerced as a {@link PublicJwkBuilder} for continued method chaining. + * @throws UnsupportedKeyException if the specified key is not a supported type and cannot be used to delegate to + * other {@code key} methods. + * @see PublicJwk + * @see PrivateJwk + */ + PublicJwkBuilder key(A key) throws UnsupportedKeyException; + + /** + * Ensures the builder will create a {@link PrivateJwk} for the specified Java {@link PrivateKey} argument. This + * method is provided for congruence with the other {@code key} methods and is expected to be used when + * the calling code has an untyped {@code PrivateKey} reference. Based on the argument type, it will delegate to one + * of the following methods if possible: + *
    + *
  • {@link #key(RSAPrivateKey)}
  • + *
  • {@link #key(ECPrivateKey)}
  • + *
  • {@link #octetKey(PrivateKey)}
  • + *
+ * + *

If the specified {@code key} argument is not capable of being supported by one of those methods, an + * {@link UnsupportedKeyException} will be thrown.

+ * + *

Type Parameters

+ * + *

In addition to the private key type B, the private key's associated public key type + * A is parameterized as well. This ensures that any subsequent call to the builder's + * {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} method will be type-safe. For example:

+ * + *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPrivateKey)
+     *     .publicKey(aPublicKey) // <-- must be an EdECPublicKey instance
+     *     ... etc ...
+     *     .build();
+ * + * @param
the type of {@link PublicKey} paired with the {@code key} argument to produce the {@link PrivateJwk}. + * @param the type of the {@link PrivateKey} argument. + * @param key the {@link PrivateKey} to represent as a {@link PrivateJwk}. + * @return the builder coerced as a {@link PrivateJwkBuilder} for continued method chaining. + * @throws UnsupportedKeyException if the specified key is not a supported type and cannot be used to delegate to + * other {@code key} methods. + * @see PublicJwk + * @see PrivateJwk + */ + PrivateJwkBuilder key(B key) throws UnsupportedKeyException; + + /** + * Ensures the builder will create a {@link PrivateJwk} for the specified Java {@link KeyPair} argument. This + * method is provided for congruence with the other {@code keyPair} methods and is expected to be used when + * the calling code has a variable {@code PrivateKey} reference. Based on the argument's {@code PrivateKey} type, + * it will delegate to one of the following methods if possible: + *
    + *
  • {@link #key(RSAPrivateKey)}
  • + *
  • {@link #key(ECPrivateKey)}
  • + *
  • {@link #octetKey(PrivateKey)}
  • + *
+ *

and automatically set the resulting builder's {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} with + * the pair's {@code PublicKey}.

+ * + *

If the specified {@code key} argument is not capable of being supported by one of those methods, an + * {@link UnsupportedKeyException} will be thrown.

+ * + *

Type Parameters

+ * + *

In addition to the private key type B, the private key's associated public key type + * A is parameterized as well. This ensures that any subsequent call to the builder's + * {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} method will be type-safe. For example:

+ * + *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>keyPair(anEdECKeyPair)
+     *     .publicKey(aPublicKey) // <-- must be an EdECPublicKey instance
+     *     ... etc ...
+     *     .build();
+ * + * @param
the {@code keyPair} argument's {@link PublicKey} type + * @param the {@code keyPair} argument's {@link PrivateKey} type + * @param keyPair the {@code KeyPair} containing the public and private key + * @return the builder coerced as a {@link PrivateJwkBuilder} for continued method chaining. + * @throws UnsupportedKeyException if the specified {@code KeyPair}'s keys are not supported and cannot be used to + * delegate to other {@code key} methods. + * @see PublicJwk + * @see PrivateJwk + */ + PrivateJwkBuilder keyPair(KeyPair keyPair) + throws UnsupportedKeyException; + + /** + * Ensures the builder will create an {@link OctetPublicJwk} for the specified Edwards-curve {@code PublicKey} + * argument. The {@code PublicKey} must be an instance of one of the following: + * + * + *

Type Parameters

+ * + *

In addition to the public key type A, the public key's associated private key type + * B is parameterized as well. This ensures that any subsequent call to the builder's + * {@link PublicJwkBuilder#privateKey(PrivateKey) privateKey} method will be type-safe. For example:

+ * + *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPublicKey)
+     *     .privateKey(aPrivateKey) // <-- must be an EdECPrivateKey instance
+     *     ... etc ...
+     *     .build();
+ * + * @param the type of Edwards-curve {@link PublicKey} provided by the created public JWK. + * @param the type of Edwards-curve {@link PrivateKey} that may be paired with the {@link PublicKey} to produce + * an {@link OctetPrivateJwk} if desired. + * @param key the Edwards-curve {@link PublicKey} to represent as an {@link OctetPublicJwk}. + * @return the builder coerced as a {@link OctetPublicJwkBuilder} for continued method chaining. + * @throws UnsupportedKeyException if the specified key is not a supported Edwards-curve key. + * @see java.security.interfaces.XECPublicKey + * @see java.security.interfaces.EdECPublicKey + */ + OctetPublicJwkBuilder octetKey(A key); + + /** + * Ensures the builder will create an {@link OctetPrivateJwk} for the specified Edwards-curve {@code PrivateKey} + * argument. The {@code PrivateKey} must be an instance of one of the following: + * + * + *

Type Parameters

+ * + *

In addition to the private key type B, the private key's associated public key type + * A is parameterized as well. This ensures that any subsequent call to the builder's + * {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} method will be type-safe. For example:

+ * + *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPrivateKey)
+     *     .publicKey(aPublicKey) // <-- must be an EdECPublicKey instance
+     *     ... etc ...
+     *     .build();
+ * + * @param the type of the Edwards-curve {@link PrivateKey} argument. + * @param the type of Edwards-curve {@link PublicKey} paired with the {@code key} argument to produce the + * {@link OctetPrivateJwk}. + * @param key the Edwards-curve {@link PrivateKey} to represent as an {@link OctetPrivateJwk}. + * @return the builder coerced as an {@link OctetPrivateJwkBuilder} for continued method chaining. + * @throws UnsupportedKeyException if the specified key is not a supported Edwards-curve key. + * @see java.security.interfaces.XECPrivateKey + * @see java.security.interfaces.EdECPrivateKey + */ + OctetPrivateJwkBuilder octetKey(A key); + + /** + * Ensures the builder will create an {@link OctetPublicJwk} for the specified Java {@link X509Certificate} chain. + * The first {@code X509Certificate} in the chain (at list index 0) MUST + * {@link X509Certificate#getPublicKey() contain} an Edwards-curve public key as defined by + * {@link #octetKey(PublicKey)}. + * + * @param the type of Edwards-curve {@link PublicKey} contained in the first {@code X509Certificate}. + * @param the type of Edwards-curve {@link PrivateKey} that may be paired with the {@link PublicKey} to produce + * an {@link OctetPrivateJwk} if desired. + * @param chain the {@link X509Certificate} chain to inspect to find the Edwards-curve {@code PublicKey} to + * represent as an {@link OctetPublicJwk}. + * @return the builder coerced as an {@link OctetPublicJwkBuilder} for continued method chaining. + */ + OctetPublicJwkBuilder octetChain(List chain); + + /** + * Ensures the builder will create an {@link OctetPrivateJwk} for the specified Java Edwards-curve + * {@link KeyPair}. The pair's {@link KeyPair#getPublic() public key} MUST be an + * Edwards-curve public key as defined by {@link #octetKey(PublicKey)}. The pair's + * {@link KeyPair#getPrivate() private key} MUST be an Edwards-curve private key as defined by + * {@link #octetKey(PrivateKey)}. + * + * @param the type of Edwards-curve {@link PublicKey} contained in the key pair. + * @param the type of the Edwards-curve {@link PrivateKey} contained in the key pair. + * @param keyPair the Edwards-curve {@link KeyPair} to represent as an {@link OctetPrivateJwk}. + * @return the builder coerced as an {@link OctetPrivateJwkBuilder} for continued method chaining. + * @throws IllegalArgumentException if the {@code keyPair} does not contain Edwards-curve public and private key + * instances. + */ + OctetPrivateJwkBuilder octetKeyPair(KeyPair keyPair); + + /** + * Ensures the builder will create an {@link EcPublicJwk} for the specified Java {@link X509Certificate} chain. + * The first {@code X509Certificate} in the chain (at list index 0) MUST contain an {@link ECPublicKey} + * instance when calling the certificate's {@link X509Certificate#getPublicKey() getPublicKey()} method. + * + * @param chain the {@link X509Certificate} chain to inspect to find the {@link ECPublicKey} to represent as a + * {@link EcPublicJwk}. + * @return the builder coerced as an {@link EcPublicJwkBuilder}. + */ + EcPublicJwkBuilder ecChain(List chain); + + /** + * Ensures the builder will create an {@link EcPrivateJwk} for the specified Java Elliptic Curve + * {@link KeyPair}. The pair's {@link KeyPair#getPublic() public key} MUST be an + * {@link ECPublicKey} instance. The pair's {@link KeyPair#getPrivate() private key} MUST be an + * {@link ECPrivateKey} instance. + * + * @param keyPair the EC {@link KeyPair} to represent as an {@link EcPrivateJwk}. + * @return the builder coerced as an {@link EcPrivateJwkBuilder}. + * @throws IllegalArgumentException if the {@code keyPair} does not contain {@link ECPublicKey} and + * {@link ECPrivateKey} instances. + */ + EcPrivateJwkBuilder ecKeyPair(KeyPair keyPair) throws IllegalArgumentException; + + /** + * Ensures the builder will create an {@link RsaPublicJwk} for the specified Java {@link X509Certificate} chain. + * The first {@code X509Certificate} in the chain (at list index 0) MUST contain an {@link RSAPublicKey} + * instance when calling the certificate's {@link X509Certificate#getPublicKey() getPublicKey()} method. + * + * @param chain the {@link X509Certificate} chain to inspect to find the {@link RSAPublicKey} to represent as a + * {@link RsaPublicJwk}. + * @return the builder coerced as an {@link RsaPublicJwkBuilder}. + */ + RsaPublicJwkBuilder rsaChain(List chain); + + /** + * Ensures the builder will create an {@link RsaPrivateJwk} for the specified Java RSA + * {@link KeyPair}. The pair's {@link KeyPair#getPublic() public key} MUST be an + * {@link RSAPublicKey} instance. The pair's {@link KeyPair#getPrivate() private key} MUST be an + * {@link RSAPrivateKey} instance. + * + * @param keyPair the RSA {@link KeyPair} to represent as an {@link RsaPrivateJwk}. + * @return the builder coerced as an {@link RsaPrivateJwkBuilder}. + * @throws IllegalArgumentException if the {@code keyPair} does not contain {@link RSAPublicKey} and + * {@link RSAPrivateKey} instances. + */ + RsaPrivateJwkBuilder rsaKeyPair(KeyPair keyPair) throws IllegalArgumentException; +} diff --git a/io/jsonwebtoken/security/EcPrivateJwk.java b/io/jsonwebtoken/security/EcPrivateJwk.java new file mode 100644 index 0000000..b746646 --- /dev/null +++ b/io/jsonwebtoken/security/EcPrivateJwk.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; + +/** + * JWK representation of an {@link ECPrivateKey} as defined by the JWA (RFC 7518) specification sections on + * Parameters for Elliptic Curve Keys and + * Parameters for Elliptic Curve Private Keys. + * + *

Note that the various EC-specific properties are not available as separate dedicated getter methods, as most Java + * applications should rarely, if ever, need to access these individual key properties since they typically represent + * internal key material and/or serialization details. If you need to access these key properties, it is usually + * recommended to obtain the corresponding {@link ECPrivateKey} instance returned by {@link #toKey()} and + * query that instead.

+ * + *

Even so, because these properties exist and are readable by nature of every JWK being a + * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method + * using an appropriate JWK parameter id, for example:

+ *
+ * jwk.get("x");
+ * jwk.get("y");
+ * // ... etc ...
+ * + * @since 0.12.0 + */ +public interface EcPrivateJwk extends PrivateJwk { +} diff --git a/io/jsonwebtoken/security/EcPrivateJwkBuilder.java b/io/jsonwebtoken/security/EcPrivateJwkBuilder.java new file mode 100644 index 0000000..f92e6e4 --- /dev/null +++ b/io/jsonwebtoken/security/EcPrivateJwkBuilder.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; + +/** + * A {@link PrivateJwkBuilder} that creates {@link EcPrivateJwk}s. + * + * @since 0.12.0 + */ +public interface EcPrivateJwkBuilder extends PrivateJwkBuilder { +} diff --git a/io/jsonwebtoken/security/EcPublicJwk.java b/io/jsonwebtoken/security/EcPublicJwk.java new file mode 100644 index 0000000..898fc6f --- /dev/null +++ b/io/jsonwebtoken/security/EcPublicJwk.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.ECPublicKey; + +/** + * JWK representation of an {@link ECPublicKey} as defined by the JWA (RFC 7518) specification sections on + * Parameters for Elliptic Curve Keys and + * Parameters for Elliptic Curve Public Keys. + * + *

Note that the various EC-specific properties are not available as separate dedicated getter methods, as most Java + * applications should rarely, if ever, need to access these individual key properties since they typically represent + * internal key material and/or serialization details. If you need to access these key properties, it is usually + * recommended to obtain the corresponding {@link ECPublicKey} instance returned by {@link #toKey()} and + * query that instead.

+ * + *

Even so, because these properties exist and are readable by nature of every JWK being a + * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method + * using an appropriate JWK parameter id, for example:

+ *
+ * jwk.get("x");
+ * jwk.get("y");
+ * // ... etc ...
+ * + * @since 0.12.0 + */ +public interface EcPublicJwk extends PublicJwk { +} diff --git a/io/jsonwebtoken/security/EcPublicJwkBuilder.java b/io/jsonwebtoken/security/EcPublicJwkBuilder.java new file mode 100644 index 0000000..b3ed2ce --- /dev/null +++ b/io/jsonwebtoken/security/EcPublicJwkBuilder.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; + +/** + * A {@link PublicJwkBuilder} that creates {@link EcPublicJwk}s. + * + * @since 0.12.0 + */ +public interface EcPublicJwkBuilder extends PublicJwkBuilder { +} diff --git a/io/jsonwebtoken/security/HashAlgorithm.java b/io/jsonwebtoken/security/HashAlgorithm.java new file mode 100644 index 0000000..3bc4ec4 --- /dev/null +++ b/io/jsonwebtoken/security/HashAlgorithm.java @@ -0,0 +1,45 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; + +import java.io.InputStream; + +/** + * A {@link DigestAlgorithm} that computes and verifies digests without the use of a cryptographic key, such as for + * thumbprints and digital fingerprints. + * + *

Standard Identifier

+ * + *

{@code HashAlgorithm} extends {@link Identifiable}: the value returned from + * {@link Identifiable#getId() getId()} in all JWT standard hash algorithms will return one of the + * "{@code Hash Name String}" values defined in the IANA + * Named Information Hash + * Algorithm Registry. This is to ensure the correct algorithm ID is used within other JWT-standard identifiers, + * such as within JWK Thumbprint URIs.

+ * + *

IANA Standard Implementations

+ * + *

Constant definitions and utility methods for common (but not all) + * IANA Hash + * Algorithms are available via {@link Jwks.HASH}.

+ * + * @see Jwks.HASH + * @since 0.12.0 + */ +public interface HashAlgorithm extends DigestAlgorithm, VerifyDigestRequest> { +} diff --git a/io/jsonwebtoken/security/InvalidKeyException.java b/io/jsonwebtoken/security/InvalidKeyException.java new file mode 100644 index 0000000..659c89d --- /dev/null +++ b/io/jsonwebtoken/security/InvalidKeyException.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * A {@code KeyException} thrown when encountering a key that is not suitable for the required functionality, or + * when attempting to use a Key in an incorrect or prohibited manner. + * + * @since 0.10.0 + */ +public class InvalidKeyException extends KeyException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public InvalidKeyException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + * @since 0.12.0 + */ + public InvalidKeyException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/security/IvSupplier.java b/io/jsonwebtoken/security/IvSupplier.java new file mode 100644 index 0000000..f1cc3d5 --- /dev/null +++ b/io/jsonwebtoken/security/IvSupplier.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * An {@code IvSupplier} provides access to the secure-random Initialization Vector used during + * encryption, which must in turn be presented for use during decryption. To maintain the security integrity of cryptographic + * algorithms, a new secure-random Initialization Vector MUST be generated for every individual + * encryption attempt. + * + * @since 0.12.0 + */ +public interface IvSupplier { + + /** + * Returns the secure-random Initialization Vector used during encryption, which must in turn be presented for + * use during decryption. + * + * @return the secure-random Initialization Vector used during encryption, which must in turn be presented for + * use during decryption. + */ + byte[] getIv(); +} diff --git a/io/jsonwebtoken/security/Jwk.java b/io/jsonwebtoken/security/Jwk.java new file mode 100644 index 0000000..fef6420 --- /dev/null +++ b/io/jsonwebtoken/security/Jwk.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.lang.Supplier; + +import java.security.Key; +import java.util.Map; +import java.util.Set; + +/** + * A JWK is an immutable set of name/value pairs that represent a cryptographic key as defined by + * RFC 7517: JSON Web Key (JWK). The {@code Jwk} + * interface represents properties common to all JWKs. Subtypes will have additional properties specific to + * different types of cryptographic keys (e.g. Secret, Asymmetric, RSA, Elliptic Curve, etc). + * + *

Immutability

+ * + *

JWKs are immutable and cannot be changed after they are created. {@code Jwk} extends the + * {@link Map} interface purely out of convenience: to allow easy marshalling to JSON as well as name/value + * pair access and key/value iteration, and other conveniences provided by the Map interface. Attempting to call any of + * the {@link Map} interface's mutation methods however (such as {@link Map#put(Object, Object) put}, + * {@link Map#remove(Object) remove}, {@link Map#clear() clear}, etc) will throw an + * {@link UnsupportedOperationException}.

+ * + *

Identification

+ * + *

{@code Jwk} extends {@link Identifiable} to support the + * JWK {@code kid} parameter. Calling + * {@link #getId() aJwk.getId()} is the type-safe idiomatic approach to the alternative equivalent of + * {@code aJwk.get("kid")}. Either approach will return an id if one was originally set on the JWK, or {@code null} if + * an id does not exist.

+ * + *

Private and Secret Value Safety

+ * + *

JWKs often represent secret or private key data which should never be exposed publicly, nor mistakenly printed + * to application logs or {@code System.out.println} calls. As a result, all JJWT JWK + * private or secret values are 'wrapped' in a {@link io.jsonwebtoken.lang.Supplier Supplier} instance to ensure + * any attempt to call {@link String#toString() toString()} on the value will print a redacted value instead of an + * actual private or secret value.

+ * + *

For example, a {@link SecretJwk} will have an internal "{@code k}" member whose value reflects raw + * key material that should always be kept secret. If the following is called:

+ *
+ * System.out.println(aSecretJwk.get("k"));
+ *

You would see the following:

+ *
+ * <redacted>
+ *

instead of the actual/raw {@code k} value.

+ * + *

Similarly, if attempting to print the entire JWK:

+ *
+ * System.out.println(aSecretJwk);
+ *

You would see the following substring in the output:

+ *
+ * k=<redacted>
+ *

instead of the actual/raw {@code k} value.

+ * + *

Finally, because all private or secret values are wrapped as {@link io.jsonwebtoken.lang.Supplier} + * instances, if you really wanted the real internal value, you could just call the supplier's + * {@link Supplier#get() get()} method:

+ *
+ * String k = ((Supplier<String>)aSecretJwk.get("k")).get();
+ *

but BE CAREFUL: obtaining the raw value in your application code exposes greater security + * risk - you must ensure to keep that value safe and out of console or log output. It is almost always better to + * interact with the JWK's {@link #toKey() toKey()} instance directly instead of accessing + * JWK internal serialization parameters.

+ * + * @param The type of Java {@link Key} represented by this JWK + * @since 0.12.0 + */ +public interface Jwk extends Identifiable, Map { + + /** + * Returns the JWK + * {@code alg} (Algorithm) value + * or {@code null} if not present. + * + * @return the JWK {@code alg} value or {@code null} if not present. + */ + String getAlgorithm(); + + /** + * Returns the JWK {@code key_ops} + * (Key Operations) parameter values or {@code null} if not present. All JWK standard Key Operations are + * available via the {@link Jwks.OP} registry, but other (custom) values MAY be present in the returned + * set. + * + * @return the JWK {@code key_ops} value or {@code null} if not present. + * @see key_ops(Key Operations) Parameter + */ + Set getOperations(); + + /** + * Returns the required JWK + * {@code kty} (Key Type) + * parameter value. A value is required and may not be {@code null}. + * + *

The JWA specification defines the + * following {@code kty} values:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
JWK Key Types
ValueKey Type
{@code EC}Elliptic Curve [DSS]
{@code RSA}RSA [RFC 3447]
{@code oct}Octet sequence (used to represent symmetric keys)
{@code OKP}Octet Key Pair (used to represent Edwards + * Elliptic Curve keys)
+ * + * @return the JWK {@code kty} (Key Type) value. + */ + String getType(); + + /** + * Computes and returns the canonical JWK Thumbprint of this + * JWK using the {@code SHA-256} hash algorithm. This is a convenience method that delegates to + * {@link #thumbprint(HashAlgorithm)} with a {@code SHA-256} {@link HashAlgorithm} instance. + * + * @return the canonical JWK Thumbprint of this + * JWK using the {@code SHA-256} hash algorithm. + * @see #thumbprint(HashAlgorithm) + */ + JwkThumbprint thumbprint(); + + /** + * Computes and returns the canonical JWK Thumbprint of this + * JWK using the specified hash algorithm. + * + * @param alg the hash algorithm to use to compute the digest of the canonical JWK Thumbprint JSON form of this JWK. + * @return the canonical JWK Thumbprint of this + * JWK using the specified hash algorithm. + */ + JwkThumbprint thumbprint(HashAlgorithm alg); + + /** + * Represents the JWK as its corresponding Java {@link Key} instance for use with Java cryptographic + * APIs. + * + * @return the JWK's corresponding Java {@link Key} instance for use with Java cryptographic APIs. + */ + K toKey(); +} diff --git a/io/jsonwebtoken/security/JwkBuilder.java b/io/jsonwebtoken/security/JwkBuilder.java new file mode 100644 index 0000000..add7be0 --- /dev/null +++ b/io/jsonwebtoken/security/JwkBuilder.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.lang.Conjunctor; +import io.jsonwebtoken.lang.MapMutator; +import io.jsonwebtoken.lang.NestedCollection; + +import java.security.Key; + +/** + * A {@link SecurityBuilder} that produces a JWK. A JWK is an immutable set of name/value pairs that represent a + * cryptographic key as defined by + * RFC 7517: JSON Web Key (JWK). + * The {@code JwkBuilder} interface represents common JWK properties that may be specified for any type of JWK. + * Builder subtypes support additional JWK properties specific to different types of cryptographic keys + * (e.g. Secret, Asymmetric, RSA, Elliptic Curve, etc). + * + * @param the type of Java {@link Key} represented by the constructed JWK. + * @param the type of {@link Jwk} created by the builder + * @param the type of the builder, for subtype method chaining + * @see SecretJwkBuilder + * @see RsaPublicJwkBuilder + * @see RsaPrivateJwkBuilder + * @see EcPublicJwkBuilder + * @see EcPrivateJwkBuilder + * @see OctetPublicJwkBuilder + * @see OctetPrivateJwkBuilder + * @since 0.12.0 + */ +public interface JwkBuilder, T extends JwkBuilder> + extends MapMutator, SecurityBuilder, KeyOperationPolicied { + + /** + * Sets the JWK {@code alg} (Algorithm) + * Parameter. + * + *

The {@code alg} (algorithm) parameter identifies the algorithm intended for use with the key. The + * value specified should either be one of the values in the IANA + * JSON Web Signature and Encryption + * Algorithms registry or be a value that contains a {@code Collision-Resistant Name}. The {@code alg} + * must be a CaSe-SeNsItIvE ASCII string.

+ * + * @param alg the JWK {@code alg} value. + * @return the builder for method chaining. + * @throws IllegalArgumentException if {@code alg} is {@code null} or empty. + */ + T algorithm(String alg) throws IllegalArgumentException; + + /** + * Sets the JWK {@code kid} (Key ID) + * Parameter. + * + *

The {@code kid} (key ID) parameter is used to match a specific key. This is used, for instance, + * to choose among a set of keys within a {@code JWK Set} during key rollover. The structure of the + * {@code kid} value is unspecified. When {@code kid} values are used within a JWK Set, different keys + * within the {@code JWK Set} SHOULD use distinct {@code kid} values. (One example in which + * different keys might use the same {@code kid} value is if they have different {@code kty} (key type) + * values but are considered to be equivalent alternatives by the application using them.)

+ * + *

The {@code kid} value is a CaSe-SeNsItIvE string, and it is optional. When used with JWS or JWE, + * the {@code kid} value is used to match a JWS or JWE {@code kid} Header Parameter value.

+ * + * @param kid the JWK {@code kid} value. + * @return the builder for method chaining. + * @throws IllegalArgumentException if the argument is {@code null} or empty. + */ + T id(String kid) throws IllegalArgumentException; + + /** + * Sets the JWK's {@link #id(String) kid} value to be the Base64URL-encoding of its {@code SHA-256} + * {@link Jwk#thumbprint(HashAlgorithm) thumbprint}. That is, the constructed JWK's {@code kid} value will equal + * jwk.{@link Jwk#thumbprint(HashAlgorithm) thumbprint}({@link Jwks.HASH}.{@link Jwks.HASH#SHA256 SHA256}).{@link JwkThumbprint#toString() toString()}. + * + *

This is a convenience method that delegates to {@link #idFromThumbprint(HashAlgorithm)} using + * {@link Jwks.HASH}{@code .}{@link Jwks.HASH#SHA256 SHA256}.

+ * + * @return the builder for method chaining. + */ + T idFromThumbprint(); + + /** + * Sets the JWK's {@link #id(String) kid} value to be the Base64URL-encoding of its + * {@link Jwk#thumbprint(HashAlgorithm) thumbprint} using the specified {@link HashAlgorithm}. That is, the + * constructed JWK's {@code kid} value will equal + * {@link Jwk#thumbprint(HashAlgorithm) thumbprint}(alg).{@link JwkThumbprint#toString() toString()}. + * + * @param alg the hash algorithm to use to compute the thumbprint. + * @return the builder for method chaining. + * @see Jwks.HASH + */ + T idFromThumbprint(HashAlgorithm alg); + + /** + * Configures the key operations for which + * the key is intended to be used. When finished, use the collection's {@link Conjunctor#and() and()} method to + * return to the JWK builder, for example: + *
+     * jwkBuilder.operations().add(aKeyOperation).{@link Conjunctor#and() and()} // etc...
+ * + *

The {@code add()} method(s) will throw an {@link IllegalArgumentException} if any of the specified + * {@code KeyOperation}s are not permitted by the JWK's + * {@link #operationPolicy(KeyOperationPolicy) operationPolicy}. See that documentation for more + * information on security vulnerabilities when using the same key with multiple algorithms.

+ * + *

Standard {@code KeyOperation}s and Overrides

+ * + *

All RFC-standard JWK Key Operations in the {@link Jwks.OP} registry are supported via the builder's default + * {@link #operationPolicy(KeyOperationPolicy) operationPolicy}, but other (custom) values + * MAY be specified (for example, using a {@link Jwks.OP#builder()}).

+ * + *

If the {@code JwkBuilder} is being used to rebuild or parse an existing JWK however, any custom operations + * should be enabled by configuring an {@link #operationPolicy(KeyOperationPolicy) operationPolicy} + * that includes the custom values (e.g. via + * {@link Jwks.OP#policy()}.{@link KeyOperationPolicyBuilder#add(KeyOperation) add(customKeyOperation)}).

+ * + *

For best interoperability with other applications however, it is recommended to use only the {@link Jwks.OP} + * constants.

+ * + * @return the {@link NestedCollection} to use for {@code key_ops} configuration. + * @see Jwks.OP + * @see RFC 7517: key_ops (Key Operations) Parameter + */ + NestedCollection operations(); +} diff --git a/io/jsonwebtoken/security/JwkParserBuilder.java b/io/jsonwebtoken/security/JwkParserBuilder.java new file mode 100644 index 0000000..9c66db7 --- /dev/null +++ b/io/jsonwebtoken/security/JwkParserBuilder.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.io.Parser; +import io.jsonwebtoken.io.ParserBuilder; + +/** + * A builder to construct a {@link Parser} that can parse {@link Jwk}s. + * Example usage: + *
+ * Jwk<?> jwk = Jwks.parser()
+ *         .provider(aJcaProvider)     // optional
+ *         .deserializer(deserializer) // optional
+ *         .operationPolicy(policy)    // optional
+ *         .build()
+ *         .parse(jwkString);
+ * + * @since 0.12.0 + */ +public interface JwkParserBuilder extends ParserBuilder, JwkParserBuilder>, KeyOperationPolicied { +} diff --git a/io/jsonwebtoken/security/JwkSet.java b/io/jsonwebtoken/security/JwkSet.java new file mode 100644 index 0000000..c8b3ea5 --- /dev/null +++ b/io/jsonwebtoken/security/JwkSet.java @@ -0,0 +1,47 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.util.Map; +import java.util.Set; + +/** + * A JWK Set is an immutable JSON Object that represents a Set of {@link Jwk}s as defined by + * RFC 7517 JWK Set Format. Per that specification, + * any number of name/value pairs may be present in a {@code JwkSet}, but only a non-empty {@link #getKeys() keys} + * set MUST be present. + * + *

Immutability

+ * + *

JWK Sets are immutable and cannot be changed after they are created. {@code JwkSet} extends the + * {@link Map} interface purely out of convenience: to allow easy marshalling to JSON as well as name/value + * pair access and key/value iteration, and other conveniences provided by the Map interface. Attempting to call any of + * the {@link Map} interface's mutation methods however (such as {@link Map#put(Object, Object) put}, + * {@link Map#remove(Object) remove}, {@link Map#clear() clear}, etc) will throw an + * {@link UnsupportedOperationException}.

+ * + * @since 0.12.0 + */ +public interface JwkSet extends Map, Iterable> { + + /** + * Returns the non-null, non-empty set of JWKs contained within the {@code JwkSet}. + * + * @return the non-null, non-empty set of JWKs contained within the {@code JwkSet}. + */ + Set> getKeys(); + +} diff --git a/io/jsonwebtoken/security/JwkSetBuilder.java b/io/jsonwebtoken/security/JwkSetBuilder.java new file mode 100644 index 0000000..987f436 --- /dev/null +++ b/io/jsonwebtoken/security/JwkSetBuilder.java @@ -0,0 +1,66 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.lang.MapMutator; + +import java.security.Provider; +import java.util.Collection; + +/** + * A builder that produces {@link JwkSet}s containing {@link Jwk}s. {@code Jwk}s with any key + * {@link Jwk#getOperations() operations} will be validated by + * the {@link #operationPolicy(KeyOperationPolicy) operationPolicy} first before being added. + * + * @see #operationPolicy(KeyOperationPolicy) + * @see #provider(Provider) + * @since 0.12.0 + */ +public interface JwkSetBuilder extends MapMutator, + SecurityBuilder, KeyOperationPolicied { + + /** + * Appends the specified {@code jwk} to the set. If the {@code jwk} has any key + * {@link Jwk#getOperations() operations}, it will be validated with the + * {@link #operationPolicy(KeyOperationPolicy) operationPolicy} first before being added. + * + * @param jwk the jwk to add to the JWK Set. A {@code null} {@code jwk} is ignored. + * @return the builder for method chaining + */ + JwkSetBuilder add(Jwk jwk); + + /** + * Appends the specified {@code Jwk} collection to the JWK Set. If any {@code Jwk} in the collection has + * any key {@link Jwk#getOperations() operations}, it will be validated with the + * {@link #operationPolicy(KeyOperationPolicy) operationPolicy} first before being added. + * + * @param c the collection of {@code Jwk}s to add to the JWK Set. A {@code null} or empty collection is ignored. + * @return the builder for method chaining + */ + JwkSetBuilder add(Collection> c); + + /** + * Sets the {@code JwkSet} {@code keys} parameter value; per standard Java setter idioms, this is a + * full replacement operation, removing any previous keys from the set. A {@code null} or empty + * collection removes all keys from the set. + * + * @param c the (possibly null or empty) collection of {@code Jwk}s to set as the JWK set {@code keys} parameter + * value. + * @return the builder for method chaining + */ + JwkSetBuilder keys(Collection> c); + +} diff --git a/io/jsonwebtoken/security/JwkSetParserBuilder.java b/io/jsonwebtoken/security/JwkSetParserBuilder.java new file mode 100644 index 0000000..d3fcb75 --- /dev/null +++ b/io/jsonwebtoken/security/JwkSetParserBuilder.java @@ -0,0 +1,57 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.io.Parser; +import io.jsonwebtoken.io.ParserBuilder; + +/** + * A builder to construct a {@link Parser} that can parse {@link JwkSet}s. + * Example usage: + *
+ * JwkSet jwkSet = Jwks.setParser()
+ *         .provider(aJcaProvider)      // optional
+ *         .json(deserializer)          // optional
+ *         .operationPolicy(policy)     // optional
+ *         .ignoreUnsupported(aBoolean) // optional
+ *         .build()
+ *         .parse(jwkSetString);
+ * + * @since 0.12.0 + */ +public interface JwkSetParserBuilder extends ParserBuilder, KeyOperationPolicied { + + /** + * Sets whether the parser should ignore any encountered JWK it does not support, either because the JWK has an + * unrecognized {@link Jwk#getType() key type} or the JWK was malformed (missing required parameters, etc). + * The default value is {@code true} per + * RFC 7517, Section 5, last paragraph: + *
+     *    Implementations SHOULD ignore JWKs within a JWK Set that use "kty"
+     *    (key type) values that are not understood by them, that are missing
+     *    required members, or for which values are out of the supported
+     *    ranges.
+     * 
+ * + *

This value may be set to {@code false} for applications that prefer stricter parsing constraints + * and wish to react to any {@link MalformedKeyException}s or {@link UnsupportedKeyException}s that could + * occur.

+ * + * @param ignore whether to ignore unsupported or malformed JWKs encountered during parsing. + * @return the builder for method chaining. + */ + JwkSetParserBuilder ignoreUnsupported(boolean ignore); +} diff --git a/io/jsonwebtoken/security/JwkThumbprint.java b/io/jsonwebtoken/security/JwkThumbprint.java new file mode 100644 index 0000000..1c20343 --- /dev/null +++ b/io/jsonwebtoken/security/JwkThumbprint.java @@ -0,0 +1,54 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.net.URI; + +/** + * A canonical cryptographic digest of a JWK as defined by the + * JSON Web Key (JWK) Thumbprint specification. + * + * @since 0.12.0 + */ +public interface JwkThumbprint { + + /** + * Returns the {@link HashAlgorithm} used to compute the thumbprint. + * + * @return the {@link HashAlgorithm} used to compute the thumbprint. + */ + HashAlgorithm getHashAlgorithm(); + + /** + * Returns the actual thumbprint (aka digest) byte array value. + * + * @return the actual thumbprint (aka digest) byte array value. + */ + byte[] toByteArray(); + + /** + * Returns the canonical URI representation of this thumbprint as defined by the + * JWK Thumbprint URI specification. + * + * @return a canonical JWK Thumbprint URI + */ + URI toURI(); + + /** + * Returns the {@link #toByteArray()} value as a Base64URL-encoded string. + */ + String toString(); +} diff --git a/io/jsonwebtoken/security/Jwks.java b/io/jsonwebtoken/security/Jwks.java new file mode 100644 index 0000000..ee8a164 --- /dev/null +++ b/io/jsonwebtoken/security/Jwks.java @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.io.Parser; +import io.jsonwebtoken.lang.Classes; +import io.jsonwebtoken.lang.Registry; + +/** + * Utility methods for creating + * JWKs (JSON Web Keys) with a type-safe builder. + * + *

Standard JWK Thumbprint Algorithm References

+ *

Standard IANA Hash + * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid + * JWK Thumbprint URIs + * are available via the {@link Jwks.HASH} registry constants to allow for easy code-completion in IDEs. For example, when + * typing:

+ *
+ * Jwks.{@link Jwks.HASH HASH}.// press hotkeys to suggest individual hash algorithms or utility methods
+ * + * @see #builder() + * @since 0.12.0 + */ +public final class Jwks { + + private Jwks() { + } //prevent instantiation + + private static final String JWKS_BRIDGE_FQCN = "io.jsonwebtoken.impl.security.JwksBridge"; + private static final String BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultDynamicJwkBuilder"; + private static final String PARSER_BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultJwkParserBuilder"; + private static final String SET_BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultJwkSetBuilder"; + private static final String SET_PARSER_BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultJwkSetParserBuilder"; + + /** + * Return a new JWK builder instance, allowing for type-safe JWK builder coercion based on a specified key or key pair. + * + * @return a new JWK builder instance, allowing for type-safe JWK builder coercion based on a specified key or key pair. + */ + public static DynamicJwkBuilder builder() { + return Classes.newInstance(BUILDER_FQCN); + } + + /** + * Returns a new builder used to create {@link Parser}s that parse JSON into {@link Jwk} instances. For example: + *
+     * Jwk<?> jwk = Jwks.parser()
+     *         //.provider(aJcaProvider)     // optional
+     *         //.deserializer(deserializer) // optional
+     *         //.operationPolicy(policy)    // optional
+     *         .build()
+     *         .parse(jwkString);
+ * + * @return a new builder used to create {@link Parser}s that parse JSON into {@link Jwk} instances. + */ + public static JwkParserBuilder parser() { + return Classes.newInstance(PARSER_BUILDER_FQCN); + } + + /** + * Return a new builder used to create {@link JwkSet}s. For example: + *
+     * JwkSet jwkSet = Jwks.set()
+     *     //.provider(aJcaProvider)     // optional
+     *     //.operationPolicy(policy)    // optional
+     *     .add(aJwk)                    // appends a key
+     *     .add(aCollection)             // appends multiple keys
+     *     //.keys(allJwks)              // sets/replaces all keys
+     *     .build()
+     * 
+ * + * @return a new builder used to create {@link JwkSet}s + */ + public static JwkSetBuilder set() { + return Classes.newInstance(SET_BUILDER_FQCN); + } + + /** + * Returns a new builder used to create {@link Parser}s that parse JSON into {@link JwkSet} instances. For example: + *
+     * JwkSet jwkSet = Jwks.setParser()
+     *         //.provider(aJcaProvider)     // optional
+     *         //.deserializer(deserializer) // optional
+     *         //.operationPolicy(policy)    // optional
+     *         .build()
+     *         .parse(jwkSetString);
+ * + * @return a new builder used to create {@link Parser}s that parse JSON into {@link JwkSet} instances. + */ + public static JwkSetParserBuilder setParser() { + return Classes.newInstance(SET_PARSER_BUILDER_FQCN); + } + + /** + * Converts the specified {@link PublicJwk} into JSON. Because {@link PublicJwk}s do not contain secret or private + * key material, they are safe to be printed to application logs or {@code System.out}. + * + * @param publicJwk the {@code PublicJwk} to convert to JSON + * @return the JWK's canonical JSON value + */ + public static String json(PublicJwk publicJwk) { + return UNSAFE_JSON(publicJwk); // safe by nature of it being a Public JWK + } + + /** + * WARNING - UNSAFE OPERATION - RETURN VALUES CONTAIN RAW KEY MATERIAL, DO NOT LOG OR PRINT TO SYSTEM.OUT. + * Converts the specified JWK into JSON, including raw key material. If the specified JWK + * is a {@link SecretJwk} or a {@link PrivateJwk}, be very careful with the return value, ensuring it is not + * printed to application logs or system.out. + * + * @param jwk the JWK to convert to JSON + * @return the JWK's canonical JSON value + */ + public static String UNSAFE_JSON(Jwk jwk) { + return Classes.invokeStatic(JWKS_BRIDGE_FQCN, "UNSAFE_JSON", new Class[]{Jwk.class}, jwk); + } + + /** + * Constants for all standard JWK + * crv (Curve) parameter values + * defined in the JSON Web Key Elliptic + * Curve Registry (including its + * Edwards Elliptic Curve additions). + * Each standard algorithm is available as a ({@code public static final}) constant for direct type-safe + * reference in application code. For example: + *
+     * Jwks.CRV.P256.keyPair().build();
+ *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

+ * + * @see #get() + * @since 0.12.0 + */ + public static final class CRV { + + private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardCurves"; + private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); + + /** + * Returns a registry of all standard Elliptic Curves in the {@code JSON Web Key Elliptic Curve Registry} + * defined by RFC 7518, Section 7.6 + * (for Weierstrass Elliptic Curves) and + * RFC 8037, Section 5 (for Edwards Elliptic Curves). + * + * @return a registry of all standard Elliptic Curves in the {@code JSON Web Key Elliptic Curve Registry}. + */ + public static Registry get() { + return REGISTRY; + } + + /** + * {@code P-256} Elliptic Curve defined by + * RFC 7518, Section 6.2.1.1 + * using the native Java JCA {@code secp256r1} algorithm. + * + * @see Java Security Standard Algorithm Names + */ + public static final Curve P256 = get().forKey("P-256"); + + /** + * {@code P-384} Elliptic Curve defined by + * RFC 7518, Section 6.2.1.1 + * using the native Java JCA {@code secp384r1} algorithm. + * + * @see Java Security Standard Algorithm Names + */ + public static final Curve P384 = get().forKey("P-384"); + + /** + * {@code P-521} Elliptic Curve defined by + * RFC 7518, Section 6.2.1.1 + * using the native Java JCA {@code secp521r1} algorithm. + * + * @see Java Security Standard Algorithm Names + */ + public static final Curve P521 = get().forKey("P-521"); + + /** + * {@code Ed25519} Elliptic Curve defined by + * RFC 8037, Section 3.1 + * using the native Java JCA {@code Ed25519}1 algorithm. + * + *

1 Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 14 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ * + * @see Java Security Standard Algorithm Names + */ + public static final Curve Ed25519 = get().forKey("Ed25519"); + + /** + * {@code Ed448} Elliptic Curve defined by + * RFC 8037, Section 3.1 + * using the native Java JCA {@code Ed448}1 algorithm. + * + *

1 Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 14 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ * + * @see Java Security Standard Algorithm Names + */ + public static final Curve Ed448 = get().forKey("Ed448"); + + /** + * {@code X25519} Elliptic Curve defined by + * RFC 8037, Section 3.2 + * using the native Java JCA {@code X25519}1 algorithm. + * + *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ * + * @see Java Security Standard Algorithm Names + */ + public static final Curve X25519 = get().forKey("X25519"); + + /** + * {@code X448} Elliptic Curve defined by + * RFC 8037, Section 3.2 + * using the native Java JCA {@code X448}1 algorithm. + * + *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ * + * @see Java Security Standard Algorithm Names + */ + public static final Curve X448 = get().forKey("X448"); + + //prevent instantiation + private CRV() { + } + } + + /** + * Various (but not all) + * IANA Hash + * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid + * JWK Thumbprint URIs. + * Each algorithm is made available as a ({@code public static final}) constant for direct type-safe + * reference in application code. For example: + *
+     * Jwks.{@link Jwks#builder}()
+     *     // ... etc ...
+     *     .{@link JwkBuilder#idFromThumbprint(HashAlgorithm) idFromThumbprint}(Jwts.HASH.{@link Jwks.HASH#SHA256 SHA256}) // <---
+     *     .build()
+ *

or

+ *
+     * HashAlgorithm hashAlg = Jwks.HASH.{@link Jwks.HASH#SHA256 SHA256};
+     * {@link JwkThumbprint} thumbprint = aJwk.{@link Jwk#thumbprint(HashAlgorithm) thumbprint}(hashAlg);
+     * String rfcMandatoryPrefix = "urn:ietf:params:oauth:jwk-thumbprint:" + hashAlg.getId();
+     * assert thumbprint.toURI().toString().startsWith(rfcMandatoryPrefix);
+     * 
+ *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

+ * + * @see #get() + * @since 0.12.0 + */ + public static final class HASH { + + private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardHashAlgorithms"; + private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); + + /** + * Returns a registry of various (but not all) + * IANA Hash + * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid + * JWK Thumbprint URIs. + * + * @return a registry of various (but not all) + * IANA Hash + * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid + * JWK Thumbprint URIs. + */ + public static Registry get() { + return REGISTRY; + } + + /** + * IANA + * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") + * value of {@code sha-256}. It is a {@code HashAlgorithm} alias for the native + * Java JCA {@code SHA-256} {@code MessageDigest} algorithm. + */ + public static final HashAlgorithm SHA256 = get().forKey("sha-256"); + + /** + * IANA + * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") + * value of {@code sha-384}. It is a {@code HashAlgorithm} alias for the native + * Java JCA {@code SHA-384} {@code MessageDigest} algorithm. + */ + public static final HashAlgorithm SHA384 = get().forKey("sha-384"); + + /** + * IANA + * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") + * value of {@code sha-512}. It is a {@code HashAlgorithm} alias for the native + * Java JCA {@code SHA-512} {@code MessageDigest} algorithm. + */ + public static final HashAlgorithm SHA512 = get().forKey("sha-512"); + + /** + * IANA + * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") + * value of {@code sha3-256}. It is a {@code HashAlgorithm} alias for the native + * Java JCA {@code SHA3-256} {@code MessageDigest} algorithm. + *

This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath.

+ */ + public static final HashAlgorithm SHA3_256 = get().forKey("sha3-256"); + + /** + * IANA + * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") + * value of {@code sha3-384}. It is a {@code HashAlgorithm} alias for the native + * Java JCA {@code SHA3-384} {@code MessageDigest} algorithm. + *

This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath.

+ */ + public static final HashAlgorithm SHA3_384 = get().forKey("sha3-384"); + + /** + * IANA + * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") + * value of {@code sha3-512}. It is a {@code HashAlgorithm} alias for the native + * Java JCA {@code SHA3-512} {@code MessageDigest} algorithm. + *

This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath.

+ */ + public static final HashAlgorithm SHA3_512 = get().forKey("sha3-512"); + + //prevent instantiation + private HASH() { + } + } + + /** + * Constants for all standard JWK + * key_ops (Key Operations) parameter values + * defined in the JSON Web Key Operations + * Registry. Each standard key operation is available as a ({@code public static final}) constant for + * direct type-safe reference in application code. For example: + *
+     * Jwks.builder()
+     *     .operations(Jwks.OP.SIGN)
+     *     // ... etc ...
+     *     .build();
+ *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

+ * + * @see #get() + * @since 0.12.0 + */ + public static final class OP { + + private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardKeyOperations"; + private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); + + private static final String BUILDER_CLASSNAME = "io.jsonwebtoken.impl.security.DefaultKeyOperationBuilder"; + + + private static final String POLICY_BUILDER_CLASSNAME = + "io.jsonwebtoken.impl.security.DefaultKeyOperationPolicyBuilder"; + + /** + * Creates a new {@link KeyOperationBuilder} for creating custom {@link KeyOperation} instances. + * + * @return a new {@link KeyOperationBuilder} for creating custom {@link KeyOperation} instances. + */ + public static KeyOperationBuilder builder() { + return Classes.newInstance(BUILDER_CLASSNAME); + } + + /** + * Creates a new {@link KeyOperationPolicyBuilder} for creating custom {@link KeyOperationPolicy} instances. + * + * @return a new {@link KeyOperationPolicyBuilder} for creating custom {@link KeyOperationPolicy} instances. + */ + public static KeyOperationPolicyBuilder policy() { + return Classes.newInstance(POLICY_BUILDER_CLASSNAME); + } + + /** + * Returns a registry of all standard Key Operations in the {@code JSON Web Key Operations Registry} + * defined by RFC 7517, Section 8.3. + * + * @return a registry of all standard Key Operations in the {@code JSON Web Key Operations Registry}. + */ + public static Registry get() { + return REGISTRY; + } + + /** + * {@code sign} operation indicating a key is intended to be used to compute digital signatures or + * MACs. It's related operation is {@link #VERIFY}. + * + * @see #VERIFY + * @see Key Operation Registry Contents + */ + public static final KeyOperation SIGN = get().forKey("sign"); + + /** + * {@code verify} operation indicating a key is intended to be used to verify digital signatures or + * MACs. It's related operation is {@link #SIGN}. + * + * @see #SIGN + * @see Key Operation Registry Contents + */ + public static final KeyOperation VERIFY = get().forKey("verify"); + + /** + * {@code encrypt} operation indicating a key is intended to be used to encrypt content. It's + * related operation is {@link #DECRYPT}. + * + * @see #DECRYPT + * @see Key Operation Registry Contents + */ + public static final KeyOperation ENCRYPT = get().forKey("encrypt"); + + /** + * {@code decrypt} operation indicating a key is intended to be used to decrypt content. It's + * related operation is {@link #ENCRYPT}. + * + * @see #ENCRYPT + * @see Key Operation Registry Contents + */ + public static final KeyOperation DECRYPT = get().forKey("decrypt"); + + /** + * {@code wrapKey} operation indicating a key is intended to be used to encrypt another key. It's + * related operation is {@link #UNWRAP_KEY}. + * + * @see #UNWRAP_KEY + * @see Key Operation Registry Contents + */ + public static final KeyOperation WRAP_KEY = get().forKey("wrapKey"); + + /** + * {@code unwrapKey} operation indicating a key is intended to be used to decrypt another key and validate + * decryption, if applicable. It's related operation is + * {@link #WRAP_KEY}. + * + * @see #WRAP_KEY + * @see Key Operation Registry Contents + */ + public static final KeyOperation UNWRAP_KEY = get().forKey("unwrapKey"); + + /** + * {@code deriveKey} operation indicating a key is intended to be used to derive another key. It does not have + * a related operation. + * + * @see Key Operation Registry Contents + */ + public static final KeyOperation DERIVE_KEY = get().forKey("deriveKey"); + + /** + * {@code deriveBits} operation indicating a key is intended to be used to derive bits that are not to be + * used as key. It does not have a related operation. + * + * @see Key Operation Registry Contents + */ + public static final KeyOperation DERIVE_BITS = get().forKey("deriveBits"); + + //prevent instantiation + private OP() { + } + } +} diff --git a/io/jsonwebtoken/security/KeyAlgorithm.java b/io/jsonwebtoken/security/KeyAlgorithm.java new file mode 100644 index 0000000..e3cd13c --- /dev/null +++ b/io/jsonwebtoken/security/KeyAlgorithm.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.Jwts; + +import javax.crypto.SecretKey; +import java.security.Key; + +/** + * A {@code KeyAlgorithm} produces the {@link SecretKey} used to encrypt or decrypt a JWE. The {@code KeyAlgorithm} + * used for a particular JWE is {@link #getId() identified} in the JWE's + * {@code alg} header. The {@code KeyAlgorithm} + * interface is JJWT's idiomatic approach to the JWE specification's + * {@code Key Management Mode} concept. + * + *

All standard Key Algorithms are defined in + * JWA (RFC 7518), Section 4.1, + * and they are all available as concrete instances via {@link Jwts.KEY}.

+ * + *

"alg" identifier

+ * + *

{@code KeyAlgorithm} extends {@code Identifiable}: the value returned from + * {@link Identifiable#getId() keyAlgorithm.getId()} will be used as the + * JWE "alg" protected header value.

+ * + * @param The type of key to use to obtain the AEAD encryption key + * @param The type of key to use to obtain the AEAD decryption key + * @see Jwts.KEY + * @see RFC 7561, Section 2: JWE Key (Management) Algorithms + * @since 0.12.0 + */ +@SuppressWarnings("JavadocLinkAsPlainText") +public interface KeyAlgorithm extends Identifiable { + + /** + * Return the {@link SecretKey} that should be used to encrypt a JWE via the request's specified + * {@link KeyRequest#getEncryptionAlgorithm() AeadAlgorithm}. The encryption key will + * be available via the result's {@link KeyResult#getKey() result.getKey()} method. + * + *

If the key algorithm uses key encryption or key agreement to produce an encrypted key value that must be + * included in the JWE, the encrypted key ciphertext will be available via the result's + * {@link KeyResult#getPayload() result.getPayload()} method. If the key algorithm does not produce encrypted + * key ciphertext, {@link KeyResult#getPayload() result.getPayload()} will be a non-null empty byte array.

+ * + * @param request the {@code KeyRequest} containing information necessary to produce a {@code SecretKey} for + * {@link AeadAlgorithm AEAD} encryption. + * @return the {@link SecretKey} that should be used to encrypt a JWE via the request's specified + * {@link KeyRequest#getEncryptionAlgorithm() AeadAlgorithm}, along with any optional encrypted key ciphertext. + * @throws SecurityException if there is a problem obtaining or encrypting the AEAD {@code SecretKey}. + */ + KeyResult getEncryptionKey(KeyRequest request) throws SecurityException; + + /** + * Return the {@link SecretKey} that should be used to decrypt a JWE via the request's specified + * {@link DecryptionKeyRequest#getEncryptionAlgorithm() AeadAlgorithm}. + * + *

If the key algorithm used key encryption or key agreement to produce an encrypted key value, the encrypted + * key ciphertext will be available via the request's {@link DecryptionKeyRequest#getPayload() result.getPayload()} + * method. If the key algorithm did not produce encrypted key ciphertext, + * {@link DecryptionKeyRequest#getPayload() request.getPayload()} will return a non-null empty byte array.

+ * + * @param request the {@code DecryptionKeyRequest} containing information necessary to obtain a + * {@code SecretKey} for {@link AeadAlgorithm AEAD} decryption. + * @return the {@link SecretKey} that should be used to decrypt a JWE via the request's specified + * {@link DecryptionKeyRequest#getEncryptionAlgorithm() AeadAlgorithm}. + * @throws SecurityException if there is a problem obtaining or decrypting the AEAD {@code SecretKey}. + */ + SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException; +} diff --git a/io/jsonwebtoken/security/KeyBuilder.java b/io/jsonwebtoken/security/KeyBuilder.java new file mode 100644 index 0000000..9de8f00 --- /dev/null +++ b/io/jsonwebtoken/security/KeyBuilder.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; +import java.security.Key; + +/** + * A {@code KeyBuilder} produces new {@link Key}s suitable for use with an associated cryptographic algorithm. + * A new {@link Key} is created each time the builder's {@link #build()} method is called. + * + *

{@code KeyBuilder}s are provided by components that implement the {@link KeyBuilderSupplier} interface, + * ensuring the resulting {@link SecretKey}s are compatible with their associated cryptographic algorithm.

+ * + * @param the type of key to build + * @param the type of the builder, for subtype method chaining + * @see KeyBuilderSupplier + * @since 0.12.0 + */ +public interface KeyBuilder> extends SecurityBuilder { +} diff --git a/io/jsonwebtoken/security/KeyBuilderSupplier.java b/io/jsonwebtoken/security/KeyBuilderSupplier.java new file mode 100644 index 0000000..d556112 --- /dev/null +++ b/io/jsonwebtoken/security/KeyBuilderSupplier.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Key; + +/** + * Interface implemented by components that support building/creating new {@link Key}s suitable for use with + * their associated cryptographic algorithm implementation. + * + * @param type of {@link Key} created by the builder + * @param type of builder to create each time {@link #key()} is called. + * @see #key() + * @see KeyBuilder + * @since 0.12.0 + */ +public interface KeyBuilderSupplier> { + + /** + * Returns a new {@link KeyBuilder} instance that will produce new secure-random keys with a length sufficient + * to be used by the component's associated cryptographic algorithm. + * + * @return a new {@link KeyBuilder} instance that will produce new secure-random keys with a length sufficient + * to be used by the component's associated cryptographic algorithm. + */ + B key(); +} diff --git a/io/jsonwebtoken/security/KeyException.java b/io/jsonwebtoken/security/KeyException.java new file mode 100644 index 0000000..4deb866 --- /dev/null +++ b/io/jsonwebtoken/security/KeyException.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * General-purpose exception when encountering a problem with a cryptographic {@link java.security.Key} + * or {@link Jwk}. + * + * @since 0.10.0 + */ +public class KeyException extends SecurityException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public KeyException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param msg the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public KeyException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/io/jsonwebtoken/security/KeyLengthSupplier.java b/io/jsonwebtoken/security/KeyLengthSupplier.java new file mode 100644 index 0000000..f550dcf --- /dev/null +++ b/io/jsonwebtoken/security/KeyLengthSupplier.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * Provides access to the required length in bits (not bytes) of keys usable with the associated algorithm. + * + * @since 0.12.0 + */ +public interface KeyLengthSupplier { + + /** + * Returns the required length in bits (not bytes) of keys usable with the associated algorithm. + * + * @return the required length in bits (not bytes) of keys usable with the associated algorithm. + */ + int getKeyBitLength(); +} diff --git a/io/jsonwebtoken/security/KeyOperation.java b/io/jsonwebtoken/security/KeyOperation.java new file mode 100644 index 0000000..925a3a0 --- /dev/null +++ b/io/jsonwebtoken/security/KeyOperation.java @@ -0,0 +1,55 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; + +/** + * A {@code KeyOperation} identifies a behavior for which a key may be used. Key validation + * algorithms may inspect a key's operations and reject the key if it is being used in a manner inconsistent + * with its indicated operations. + * + *

KeyOperation Identifier

+ * + *

This interface extends {@link Identifiable}; the value returned from {@link #getId()} is a + * CaSe-SeNsItIvE value that uniquely identifies the operation among other KeyOperation instances.

+ * + * @see JWK key_ops (Key Operations) Parameter + * @see JSON Web Key Operations Registry + * @since 0.12.0 + */ +public interface KeyOperation extends Identifiable { + + /** + * Returns a brief description of the key operation behavior. + * + * @return a brief description of the key operation behavior. + */ + String getDescription(); + + /** + * Returns {@code true} if the specified {@code operation} is an acceptable use case for the key already assigned + * this operation, {@code false} otherwise. As described in the + * JWK key_ops (Key Operations) Parameter + * specification, Key validation algorithms will likely reject keys with inconsistent or unrelated operations + * because of the security vulnerabilities that could occur otherwise. + * + * @param operation the key operation to check if it is related to (consistent or compatible with) this operation. + * @return {@code true} if the specified {@code operation} is an acceptable use case for the key already assigned + * this operation, {@code false} otherwise. + */ + boolean isRelated(KeyOperation operation); +} diff --git a/io/jsonwebtoken/security/KeyOperationBuilder.java b/io/jsonwebtoken/security/KeyOperationBuilder.java new file mode 100644 index 0000000..9d41477 --- /dev/null +++ b/io/jsonwebtoken/security/KeyOperationBuilder.java @@ -0,0 +1,73 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.lang.Builder; + +/** + * A {@code KeyOperationBuilder} produces {@link KeyOperation} instances that may be added to a JWK's + * {@link JwkBuilder#operations() key operations} parameter. This is primarily only useful for creating + * custom (non-standard) {@code KeyOperation}s for use with a custom {@link KeyOperationPolicy}, as all standard ones + * are available already via the {@link Jwks.OP} registry singleton. + * + * @see Jwks.OP#builder() + * @see Jwks.OP#policy() + * @see JwkBuilder#operationPolicy(KeyOperationPolicy) + * @since 0.12.0 + */ +public interface KeyOperationBuilder extends Builder { + + /** + * Sets the CaSe-SeNsItIvE {@link KeyOperation#getId() id} expected to be unique compared to all other + * {@code KeyOperation}s. + * + * @param id the key operation id + * @return the builder for method chaining + */ + KeyOperationBuilder id(String id); + + /** + * Sets the key operation {@link KeyOperation#getDescription() description}. + * + * @param description the key operation description + * @return the builder for method chaining + */ + KeyOperationBuilder description(String description); + + /** + * Indicates that the {@code KeyOperation} with the given {@link KeyOperation#getId() id} is cryptographically + * related (and complementary) to this one, and may be specified together in a JWK's + * {@link Jwk#getOperations() operations} set. + * + *

More concretely, calling this method will ensure the following:

+ *
+     *     KeyOperation built = Jwks.operation()/*...*/.related(otherId).build();
+     *     KeyOperation other = getKeyOperation(otherId);
+     *     assert built.isRelated(other);
+ * + *

A {@link JwkBuilder}'s key operation {@link JwkBuilder#operationPolicy(KeyOperationPolicy) policy} is likely + * to {@link KeyOperationPolicyBuilder#unrelated() reject} any unrelated operations specified + * together due to the potential security vulnerabilities that could occur.

+ * + *

This method may be called multiple times to add/append a related {@code id} to the constructed + * {@code KeyOperation}'s total set of related ids.

+ * + * @param id the id of a KeyOperation that will be considered cryptographically related to this one. + * @return the builder for method chaining. + * @see JwkBuilder#operationPolicy(KeyOperationPolicy) + */ + KeyOperationBuilder related(String id); +} diff --git a/io/jsonwebtoken/security/KeyOperationPolicied.java b/io/jsonwebtoken/security/KeyOperationPolicied.java new file mode 100644 index 0000000..49e8938 --- /dev/null +++ b/io/jsonwebtoken/security/KeyOperationPolicied.java @@ -0,0 +1,51 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * A marker interface that indicates the implementing instance supports the ability to configure a + * {@link KeyOperationPolicy} used to validate JWK instances. + * + * @param the implementing instance for method chaining + */ +public interface KeyOperationPolicied> { + + /** + * Sets the key operation policy that determines which {@link KeyOperation}s may be assigned to a + * JWK. Unless overridden by this method, the default RFC-recommended policy is used where: + *
    + *
  • All {@link Jwks.OP RFC-standard key operations} are supported.
  • + *
  • Multiple unrelated operations may not be assigned to the JWK per the + * RFC 7517, Section 4.3 recommendation: + *
    +     * Multiple unrelated key operations SHOULD NOT be specified for a key
    +     * because of the potential vulnerabilities associated with using the
    +     * same key with multiple algorithms.  Thus, the combinations "{@link Jwks.OP#SIGN sign}"
    +     * with "{@link Jwks.OP#VERIFY verify}", "{@link Jwks.OP#ENCRYPT encrypt}" with "{@link Jwks.OP#DECRYPT decrypt}", and "{@link Jwks.OP#WRAP_KEY wrapKey}" with
    +     * "{@link Jwks.OP#UNWRAP_KEY unwrapKey}" are permitted, but other combinations SHOULD NOT be used.
    + *
  • + *
+ * + *

If you wish to enable a different policy, perhaps to support additional custom {@code KeyOperation} values, + * one can be created by using the {@link Jwks.OP#policy()} builder, or by implementing the + * {@link KeyOperationPolicy} interface directly.

+ * + * @param policy the policy that determines which {@link KeyOperation}s may be assigned to a JWK. + * @return the builder for method chaining. + * @throws IllegalArgumentException if {@code policy} is null + */ + T operationPolicy(KeyOperationPolicy policy) throws IllegalArgumentException; +} diff --git a/io/jsonwebtoken/security/KeyOperationPolicy.java b/io/jsonwebtoken/security/KeyOperationPolicy.java new file mode 100644 index 0000000..60389a2 --- /dev/null +++ b/io/jsonwebtoken/security/KeyOperationPolicy.java @@ -0,0 +1,43 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.util.Collection; + +/** + * A key operation policy determines which {@link KeyOperation}s may be assigned to a JWK. + * + * @see JwkBuilder#operationPolicy(KeyOperationPolicy) + * @since 0.12.0 + */ +public interface KeyOperationPolicy { + + /** + * Returns all supported {@code KeyOperation}s that may be assigned to a JWK. + * + * @return all supported {@code KeyOperation}s that may be assigned to a JWK. + */ + Collection getOperations(); + + /** + * Returns quietly if all of the specified key operations are allowed to be assigned to a JWK, + * or throws an {@link IllegalArgumentException} otherwise. + * + * @param ops the operations to validate + */ + @SuppressWarnings("GrazieInspection") + void validate(Collection ops) throws IllegalArgumentException; +} diff --git a/io/jsonwebtoken/security/KeyOperationPolicyBuilder.java b/io/jsonwebtoken/security/KeyOperationPolicyBuilder.java new file mode 100644 index 0000000..548a1a9 --- /dev/null +++ b/io/jsonwebtoken/security/KeyOperationPolicyBuilder.java @@ -0,0 +1,114 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.lang.Builder; +import io.jsonwebtoken.lang.CollectionMutator; + +import java.util.Collection; + + +/** + * A {@code KeyOperationPolicyBuilder} produces a {@link KeyOperationPolicy} that determines + * which {@link KeyOperation}s may be assigned to a JWK. Custom {@code KeyOperation}s (such as those created by a + * {@link Jwks.OP#builder()}) may be added to a policy via the {@link #add(KeyOperation)} or {@link #add(Collection)} + * methods. + * + * @see Jwks.OP#policy() + * @see JwkBuilder#operationPolicy(KeyOperationPolicy) + * @see Jwks.OP#builder() + * @since 0.12.0 + */ +public interface KeyOperationPolicyBuilder extends CollectionMutator, + Builder { + + /** + * Allows a JWK to have unrelated {@link KeyOperation}s in its {@code key_ops} parameter values. Be careful + * when calling this method - one should fully understand the security implications of using the same key + * with multiple algorithms in your application. + *

If this method is not called, unrelated key operations are disabled by default per the recommendations in + * RFC 7517, Section 4.3:

+ *
+     * Multiple unrelated key operations SHOULD NOT be specified for a key
+     * because of the potential vulnerabilities associated with using the
+     * same key with multiple algorithms.
+ * + * @return the builder for method chaining + * @see "key_ops" (Key Operations) + * Parameter + */ + KeyOperationPolicyBuilder unrelated(); + + /** + * Adds the specified key operation to the policy's total set of supported key operations + * used to validate a key's intended usage, replacing any existing one with an identical (CaSe-SeNsItIvE) + * {@link Identifiable#getId() id}. + * + *

Standard {@code KeyOperation}s and Overrides

+ * + *

The RFC standard {@link Jwks.OP} key operations are supported by default and do not need + * to be added via this method, but beware: If the {@code op} argument has a JWK standard + * {@link Identifiable#getId() id}, it will replace the JJWT standard operation implementation. + * This is to allow application developers to favor their own implementations over JJWT's default implementations + * if necessary (for example, to support legacy or custom behavior).

+ * + *

If a custom {@code KeyOperation} is desired, one may be easily created with a {@link Jwks.OP#builder()}.

+ * + * @param op a key operation to add to the policy's total set of supported operations, replacing any + * existing one with the same exact (CaSe-SeNsItIvE) {@link KeyOperation#getId() id}. + * @return the builder for method chaining. + * @see Jwks.OP + * @see Jwks.OP#builder() + * @see JwkBuilder#operationPolicy(KeyOperationPolicy) + * @see JwkBuilder#operations() + */ + @Override + // for better JavaDoc + KeyOperationPolicyBuilder add(KeyOperation op); + + /** + * Adds the specified key operations to the policy's total set of supported key operations + * used to validate a key's intended usage, replacing any existing ones with identical + * {@link Identifiable#getId() id}s. + * + *

There may be only one registered {@code KeyOperation} per CaSe-SeNsItIvE {@code id}, and the + * {@code ops} collection is added in iteration order; if a duplicate id is found when iterating the {@code ops} + * collection, the later operation will evict any existing operation with the same {@code id}.

+ * + *

Standard {@code KeyOperation}s and Overrides

+ * + *

The RFC standard {@link Jwks.OP} key operations are supported by default and do not need + * to be added via this method, but beware: any operation in the {@code ops} argument with a + * JWK standard {@link Identifiable#getId() id} will replace the JJWT standard operation implementation. + * This is to allow application developers to favor their own implementations over JJWT's default implementations + * if necessary (for example, to support legacy or custom behavior).

+ * + *

If custom {@code KeyOperation}s are desired, they may be easily created with a {@link Jwks.OP#builder()}.

+ * + * @param ops collection of key operations to add to the policy's total set of supported operations, replacing any + * existing ones with the same exact (CaSe-SeNsItIvE) {@link KeyOperation#getId() id}s. + * @return the builder for method chaining. + * @see Jwks.OP + * @see Jwks.OP#builder() + * @see JwkBuilder#operationPolicy(KeyOperationPolicy) + * @see JwkBuilder#operations() + */ + @Override + // for better JavaDoc + KeyOperationPolicyBuilder add(Collection ops); + +} diff --git a/io/jsonwebtoken/security/KeyPair.java b/io/jsonwebtoken/security/KeyPair.java new file mode 100644 index 0000000..edd2bd1 --- /dev/null +++ b/io/jsonwebtoken/security/KeyPair.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * Generics-capable and type-safe alternative to {@link java.security.KeyPair}. Instances may be + * converted to {@link java.security.KeyPair} if desired via {@link #toJavaKeyPair()}. + * + * @param The type of {@link PublicKey} in the key pair. + * @param The type of {@link PrivateKey} in the key pair. + * @since 0.12.0 + */ +public interface KeyPair { + + /** + * Returns the pair's public key. + * + * @return the pair's public key. + */ + A getPublic(); + + /** + * Returns the pair's private key. + * + * @return the pair's private key. + */ + B getPrivate(); + + /** + * Returns this instance as a {@link java.security.KeyPair} instance. + * + * @return this instance as a {@link java.security.KeyPair} instance. + */ + java.security.KeyPair toJavaKeyPair(); +} diff --git a/io/jsonwebtoken/security/KeyPairBuilder.java b/io/jsonwebtoken/security/KeyPairBuilder.java new file mode 100644 index 0000000..f6db1b2 --- /dev/null +++ b/io/jsonwebtoken/security/KeyPairBuilder.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.KeyPair; + +/** + * A {@code KeyPairBuilder} produces new {@link KeyPair}s suitable for use with an associated cryptographic algorithm. + * A new {@link KeyPair} is created each time the builder's {@link #build()} method is called. + * + *

{@code KeyPairBuilder}s are provided by components that implement the {@link KeyPairBuilderSupplier} interface, + * ensuring the resulting {@link KeyPair}s are compatible with their associated cryptographic algorithm.

+ * + * @see KeyPairBuilderSupplier + * @since 0.12.0 + */ +public interface KeyPairBuilder extends SecurityBuilder { +} diff --git a/io/jsonwebtoken/security/KeyPairBuilderSupplier.java b/io/jsonwebtoken/security/KeyPairBuilderSupplier.java new file mode 100644 index 0000000..98d42ea --- /dev/null +++ b/io/jsonwebtoken/security/KeyPairBuilderSupplier.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.KeyPair; + +/** + * Interface implemented by components that support building/creating new {@link KeyPair}s suitable for use with their + * associated cryptographic algorithm implementation. + * + * @see #keyPair() + * @see KeyPairBuilder + * @since 0.12.0 + */ +public interface KeyPairBuilderSupplier { + + /** + * Returns a new {@link KeyPairBuilder} that will create new secure-random {@link KeyPair}s with a length and + * parameters sufficient for use with the component's associated cryptographic algorithm. + * + * @return a new {@link KeyPairBuilder} that will create new secure-random {@link KeyPair}s with a length and + * parameters sufficient for use with the component's associated cryptographic algorithm. + */ + KeyPairBuilder keyPair(); +} diff --git a/io/jsonwebtoken/security/KeyRequest.java b/io/jsonwebtoken/security/KeyRequest.java new file mode 100644 index 0000000..ffe2206 --- /dev/null +++ b/io/jsonwebtoken/security/KeyRequest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.JweHeader; + +/** + * A request to a {@link KeyAlgorithm} to obtain the key necessary for AEAD encryption or decryption. The exact + * {@link AeadAlgorithm} that will be used is accessible via {@link #getEncryptionAlgorithm()}. + * + *

Encryption Requests

+ *

For an encryption key request, {@link #getPayload()} will return + * the encryption key to use. Additionally, any public information specific to the called + * {@link KeyAlgorithm} implementation that is required to be transmitted in the JWE (such as an initialization vector, + * authentication tag or ephemeral key, etc) may be added to the JWE protected header, accessible via + * {@link #getHeader()}. Although the JWE header is checked for authenticity and integrity, it itself is + * not encrypted, so {@link KeyAlgorithm}s should never place any secret or private information in the + * header.

+ * + *

Decryption Requests

+ *

For a decryption request, the {@code KeyRequest} instance will be + * a {@link DecryptionKeyRequest} instance, {@link #getPayload()} will return the encrypted key ciphertext (a + * byte array), and the decryption key will be available via {@link DecryptionKeyRequest#getKey()}. Additionally, + * any public information necessary by the called {@link KeyAlgorithm} (such as an initialization vector, + * authentication tag, ephemeral key, etc) is expected to be available in the JWE protected header, accessible + * via {@link #getHeader()}.

+ * + * @param the type of object relevant during key algorithm cryptographic operations. + * @see DecryptionKeyRequest + * @since 0.12.0 + */ +public interface KeyRequest extends Request { + + /** + * Returns the {@link AeadAlgorithm} that will be called for encryption or decryption after processing the + * {@code KeyRequest}. {@link KeyAlgorithm} implementations that generate an ephemeral {@code SecretKey} to use + * as what the
JWE specification calls a + * "Content Encryption Key (CEK)" should call the {@code AeadAlgorithm}'s + * {@link AeadAlgorithm#key() key()} builder to create a key suitable for that exact {@code AeadAlgorithm}. + * + * @return the {@link AeadAlgorithm} that will be called for encryption or decryption after processing the + * {@code KeyRequest}. + */ + AeadAlgorithm getEncryptionAlgorithm(); + + /** + * Returns the {@link JweHeader} that will be used to construct the final JWE header, available for + * reading or writing any {@link KeyAlgorithm}-specific information. + * + *

For an encryption key request, any public information specific to the called {@code KeyAlgorithm} + * implementation that is required to be transmitted in the JWE (such as an initialization vector, + * authentication tag or ephemeral key, etc) is expected to be added to this header. Although the header is + * checked for authenticity and integrity, it itself is not encrypted, so + * {@link KeyAlgorithm}s should never place any secret or private information in the header.

+ * + *

For a decryption request, any public information necessary by the called {@link KeyAlgorithm} + * (such as an initialization vector, authentication tag, ephemeral key, etc) is expected to be available in + * this header.

+ * + * @return the {@link JweHeader} that will be used to construct the final JWE header, available for + * reading or writing any {@link KeyAlgorithm}-specific information. + */ + JweHeader getHeader(); +} diff --git a/io/jsonwebtoken/security/KeyResult.java b/io/jsonwebtoken/security/KeyResult.java new file mode 100644 index 0000000..753909d --- /dev/null +++ b/io/jsonwebtoken/security/KeyResult.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; + +/** + * The result of a {@link KeyAlgorithm} encryption key request, containing the resulting + * {@code JWE encrypted key} and {@code JWE Content Encryption Key (CEK)}, concepts defined in + * JWE Terminology. + * + *

The result {@link #getPayload() payload} is the {@code JWE encrypted key}, which will be Base64URL-encoded + * and embedded in the resulting compact JWE string.

+ * + *

The result {@link #getKey() key} is the {@code JWE Content Encryption Key (CEK)} which will be used to encrypt + * the JWE.

+ * + * @since 0.12.0 + */ +public interface KeyResult extends Message, KeySupplier { +} diff --git a/io/jsonwebtoken/security/KeySupplier.java b/io/jsonwebtoken/security/KeySupplier.java new file mode 100644 index 0000000..2026b25 --- /dev/null +++ b/io/jsonwebtoken/security/KeySupplier.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Key; + +/** + * Provides access to a cryptographic {@link Key} necessary for signing, wrapping, encryption or decryption algorithms. + * + * @param the type of key provided by this supplier. + * @since 0.12.0 + */ +public interface KeySupplier { + + /** + * Returns the key to use for signing, wrapping, encryption or decryption depending on the type of operation. + * + * @return the key to use for signing, wrapping, encryption or decryption depending on the type of operation. + */ + K getKey(); +} diff --git a/io/jsonwebtoken/security/Keys.java b/io/jsonwebtoken/security/Keys.java new file mode 100644 index 0000000..9ae54e7 --- /dev/null +++ b/io/jsonwebtoken/security/Keys.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Classes; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; + +/** + * Utility class for securely generating {@link SecretKey}s and {@link KeyPair}s. + * + * @since 0.10.0 + */ +public final class Keys { + + private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.KeysBridge"; + private static final Class BRIDGE_CLASS = Classes.forName(BRIDGE_CLASSNAME); + private static final Class[] FOR_PASSWORD_ARG_TYPES = new Class[]{char[].class}; + private static final Class[] SECRET_BUILDER_ARG_TYPES = new Class[]{SecretKey.class}; + private static final Class[] PRIVATE_BUILDER_ARG_TYPES = new Class[]{PrivateKey.class}; + + private static T invokeStatic(String method, Class[] argTypes, Object... args) { + return Classes.invokeStatic(BRIDGE_CLASS, method, argTypes, args); + } + + //prevent instantiation + private Keys() { + } + + /** + * Creates a new SecretKey instance for use with HMAC-SHA algorithms based on the specified key byte array. + * + * @param bytes the key byte array + * @return a new SecretKey instance for use with HMAC-SHA algorithms based on the specified key byte array. + * @throws WeakKeyException if the key byte array length is less than 256 bits (32 bytes) as mandated by the + * JWT JWA Specification + * (RFC 7518, Section 3.2) + */ + public static SecretKey hmacShaKeyFor(byte[] bytes) throws WeakKeyException { + + if (bytes == null) { + throw new InvalidKeyException("SecretKey byte array cannot be null."); + } + + int bitLength = bytes.length * 8; + + //Purposefully ordered higher to lower to ensure the strongest key possible can be generated. + if (bitLength >= 512) { + return new SecretKeySpec(bytes, "HmacSHA512"); + } else if (bitLength >= 384) { + return new SecretKeySpec(bytes, "HmacSHA384"); + } else if (bitLength >= 256) { + return new SecretKeySpec(bytes, "HmacSHA256"); + } + + String msg = "The specified key byte array is " + bitLength + " bits which " + + "is not secure enough for any JWT HMAC-SHA algorithm. The JWT " + + "JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a " + + "size >= 256 bits (the key size must be greater than or equal to the hash " + + "output size). Consider using the Jwts.SIG.HS256.key() builder (or HS384.key() " + + "or HS512.key()) to create a key guaranteed to be secure enough for your preferred HMAC-SHA " + + "algorithm. See https://tools.ietf.org/html/rfc7518#section-3.2 for more information."; + throw new WeakKeyException(msg); + } + + /** + *

Deprecation Notice

+ * + *

As of JJWT 0.12.0, symmetric (secret) key algorithm instances can generate a key of suitable + * length for that specific algorithm by calling their {@code key()} builder method directly. For example:

+ * + *

+     * {@link Jwts.SIG#HS256}.key().build();
+     * {@link Jwts.SIG#HS384}.key().build();
+     * {@link Jwts.SIG#HS512}.key().build();
+     * 
+ * + *

Call those methods as needed instead of this static {@code secretKeyFor} helper method - the returned + * {@link KeyBuilder} allows callers to specify a preferred Provider or SecureRandom on the builder if + * desired, whereas this {@code secretKeyFor} method does not. Consequently this helper method will be removed + * before the 1.0 release.

+ * + *

Previous Documentation

+ * + *

Returns a new {@link SecretKey} with a key length suitable for use with the specified {@link SignatureAlgorithm}.

+ * + *

JWA Specification (RFC 7518), Section 3.2 + * requires minimum key lengths to be used for each respective Signature Algorithm. This method returns a + * secure-random generated SecretKey that adheres to the required minimum key length. The lengths are:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
JWA HMAC-SHA Key Length Requirements
AlgorithmKey Length
HS256256 bits (32 bytes)
HS384384 bits (48 bytes)
HS512512 bits (64 bytes)
+ * + * @param alg the {@code SignatureAlgorithm} to inspect to determine which key length to use. + * @return a new {@link SecretKey} instance suitable for use with the specified {@link SignatureAlgorithm}. + * @throws IllegalArgumentException for any input value other than {@link io.jsonwebtoken.SignatureAlgorithm#HS256}, + * {@link io.jsonwebtoken.SignatureAlgorithm#HS384}, or {@link io.jsonwebtoken.SignatureAlgorithm#HS512} + * @deprecated since 0.12.0. Use your preferred {@link MacAlgorithm} instance's + * {@link MacAlgorithm#key() key()} builder method directly. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + public static SecretKey secretKeyFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException { + Assert.notNull(alg, "SignatureAlgorithm cannot be null."); + SecureDigestAlgorithm salg = Jwts.SIG.get().get(alg.name()); + if (!(salg instanceof MacAlgorithm)) { + String msg = "The " + alg.name() + " algorithm does not support shared secret keys."; + throw new IllegalArgumentException(msg); + } + return ((MacAlgorithm) salg).key().build(); + } + + /** + *

Deprecation Notice

+ * + *

As of JJWT 0.12.0, asymmetric key algorithm instances can generate KeyPairs of suitable strength + * for that specific algorithm by calling their {@code keyPair()} builder method directly. For example:

+ * + *
+     * Jwts.SIG.{@link Jwts.SIG#RS256 RS256}.keyPair().build();
+     * Jwts.SIG.{@link Jwts.SIG#RS384 RS384}.keyPair().build();
+     * Jwts.SIG.{@link Jwts.SIG#RS512 RS512}.keyPair().build();
+     * ... etc ...
+     * Jwts.SIG.{@link Jwts.SIG#ES512 ES512}.keyPair().build();
+ * + *

Call those methods as needed instead of this static {@code keyPairFor} helper method - the returned + * {@link KeyPairBuilder} allows callers to specify a preferred Provider or SecureRandom on the builder if + * desired, whereas this {@code keyPairFor} method does not. Consequently this helper method will be removed + * before the 1.0 release.

+ * + *

Previous Documentation

+ * + *

Returns a new {@link KeyPair} suitable for use with the specified asymmetric algorithm.

+ * + *

If the {@code alg} argument is an RSA algorithm, a KeyPair is generated based on the following:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Generated RSA Key Sizes
JWA AlgorithmKey Size
RS2562048 bits
PS2562048 bits
RS3843072 bits
PS3843072 bits
RS5124096 bits
PS5124096 bits
+ * + *

If the {@code alg} argument is an Elliptic Curve algorithm, a KeyPair is generated based on the following:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Generated Elliptic Curve Key Parameters
JWA AlgorithmKey SizeJWA Curve NameASN1 OID Curve Name
ES256256 bits{@code P-256}{@code secp256r1}
ES384384 bits{@code P-384}{@code secp384r1}
ES512521 bits{@code P-521}{@code secp521r1}
+ * + * @param alg the {@code SignatureAlgorithm} to inspect to determine which asymmetric algorithm to use. + * @return a new {@link KeyPair} suitable for use with the specified asymmetric algorithm. + * @throws IllegalArgumentException if {@code alg} is not an asymmetric algorithm + * @deprecated since 0.12.0 in favor of your preferred + * {@link io.jsonwebtoken.security.SignatureAlgorithm} instance's + * {@link SignatureAlgorithm#keyPair() keyPair()} builder method directly. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + public static KeyPair keyPairFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException { + Assert.notNull(alg, "SignatureAlgorithm cannot be null."); + SecureDigestAlgorithm salg = Jwts.SIG.get().get(alg.name()); + if (!(salg instanceof SignatureAlgorithm)) { + String msg = "The " + alg.name() + " algorithm does not support Key Pairs."; + throw new IllegalArgumentException(msg); + } + SignatureAlgorithm asalg = ((SignatureAlgorithm) salg); + return asalg.keyPair().build(); + } + + /** + * Returns a new {@link Password} instance suitable for use with password-based key derivation algorithms. + * + *

Usage Note: Using {@code Password}s outside of key derivation contexts will likely + * fail. See the {@link Password} JavaDoc for more, and also note the Password Safety section below.

+ * + *

Password Safety

+ * + *

Instances returned by this method use a clone of the specified {@code password} character array + * argument - changes to the argument array will NOT be reflected in the returned key, and vice versa. If you wish + * to clear a {@code Password} instance to ensure it is no longer usable, call its {@link Password#destroy()} + * method will clear/overwrite its internal cloned char array. Also note that each subsequent call to + * {@link Password#toCharArray()} will also return a new clone of the underlying password character array per + * standard JCE key behavior.

+ * + * @param password the raw password character array to clone for use with password-based key derivation algorithms. + * @return a new {@link Password} instance that wraps a new clone of the specified {@code password} character array. + * @see Password#toCharArray() + * @since 0.12.0 + */ + public static Password password(char[] password) { + return invokeStatic("password", FOR_PASSWORD_ARG_TYPES, new Object[]{password}); + } + + /** + * Returns a {@code SecretKeyBuilder} that produces the specified key, allowing association with a + * {@link SecretKeyBuilder#provider(Provider) provider} that must be used with the key during cryptographic + * operations. For example: + * + *
+     * SecretKey key = Keys.builder(key).provider(mandatoryProvider).build();
+ * + *

Cryptographic algorithm implementations can inspect the resulting {@code key} instance and obtain its + * mandatory {@code Provider} if necessary.

+ * + *

This method is primarily only useful for keys that cannot expose key material, such as PKCS11 or HSM + * (Hardware Security Module) keys, and require a specific {@code Provider} to be used during cryptographic + * operations.

+ * + * @param key the secret key to use for cryptographic operations, potentially associated with a configured + * {@link Provider} + * @return a new {@code SecretKeyBuilder} that produces the specified key, potentially associated with any + * specified provider. + * @since 0.12.0 + */ + public static SecretKeyBuilder builder(SecretKey key) { + Assert.notNull(key, "SecretKey cannot be null."); + return invokeStatic("builder", SECRET_BUILDER_ARG_TYPES, key); + } + + /** + * Returns a {@code PrivateKeyBuilder} that produces the specified key, allowing association with a + * {@link PrivateKeyBuilder#publicKey(PublicKey) publicKey} to obtain public key data if necessary, or a + * {@link SecretKeyBuilder#provider(Provider) provider} that must be used with the key during cryptographic + * operations. For example: + * + *
+     * PrivateKey key = Keys.builder(privateKey).publicKey(publicKey).provider(mandatoryProvider).build();
+ * + *

Cryptographic algorithm implementations can inspect the resulting {@code key} instance and obtain its + * mandatory {@code Provider} or {@code PublicKey} if necessary.

+ * + *

This method is primarily only useful for keys that cannot expose key material, such as PKCS11 or HSM + * (Hardware Security Module) keys, and require a specific {@code Provider} or public key data to be used + * during cryptographic operations.

+ * + * @param key the private key to use for cryptographic operations, potentially associated with a configured + * {@link Provider} or {@link PublicKey}. + * @return a new {@code PrivateKeyBuilder} that produces the specified private key, potentially associated with any + * specified provider or {@code PublicKey} + * @since 0.12.0 + */ + public static PrivateKeyBuilder builder(PrivateKey key) { + Assert.notNull(key, "PrivateKey cannot be null."); + return invokeStatic("builder", PRIVATE_BUILDER_ARG_TYPES, key); + } +} diff --git a/io/jsonwebtoken/security/MacAlgorithm.java b/io/jsonwebtoken/security/MacAlgorithm.java new file mode 100644 index 0000000..e34d927 --- /dev/null +++ b/io/jsonwebtoken/security/MacAlgorithm.java @@ -0,0 +1,65 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; + +import javax.crypto.SecretKey; + +/** + * A {@link SecureDigestAlgorithm} that uses symmetric {@link SecretKey}s to both compute and verify digests as + * message authentication codes (MACs). + * + *

Standard Identifier

+ * + *

{@code MacAlgorithm} extends {@link Identifiable}: when a {@code MacAlgorithm} is used to compute the MAC of a + * JWS, the value returned from {@link Identifiable#getId() macAlgorithm.getId()} will be set as the JWS + * "alg" protected header value.

+ * + *

Key Strength

+ * + *

MAC algorithm strength is in part attributed to how difficult it is to discover the secret key. + * As such, MAC algorithms usually require keys of a minimum length to ensure the keys are difficult to discover + * and the algorithm's security properties are maintained.

+ * + *

The {@code MacAlgorithm} interface extends the {@link KeyLengthSupplier} interface to represent + * the length in bits (not bytes) a key must have to be used with its implementation. If you do not want to + * worry about lengths and parameters of keys required for an algorithm, it is often easier to automatically generate + * a key that adheres to the algorithms requirements, as discussed below.

+ * + *

Key Generation

+ * + *

{@code MacAlgorithm} extends {@link KeyBuilderSupplier} to enable {@link SecretKey} generation. + * Each {@code MacAlgorithm} algorithm instance will return a {@link KeyBuilder} that ensures any created keys will + * have a sufficient length and any algorithm parameters required by that algorithm. For example:

+ * + *
+ * SecretKey key = macAlgorithm.key().build();
+ * + *

The resulting {@code key} is guaranteed to have the correct algorithm parameters and strength/length necessary for + * that exact {@code MacAlgorithm} instance.

+ * + *

JWA Standard Implementations

+ * + *

Constant definitions and utility methods for all JWA (RFC 7518) standard MAC algorithms are + * available via {@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}.

+ * + * @see io.jsonwebtoken.Jwts.SIG Jwts.SIG + * @since 0.12.0 + */ +public interface MacAlgorithm extends SecureDigestAlgorithm, + KeyBuilderSupplier, KeyLengthSupplier { +} diff --git a/io/jsonwebtoken/security/MalformedKeyException.java b/io/jsonwebtoken/security/MalformedKeyException.java new file mode 100644 index 0000000..59c8016 --- /dev/null +++ b/io/jsonwebtoken/security/MalformedKeyException.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * Exception thrown when encountering a key or key material that is incomplete or improperly configured or + * formatted and cannot be used as expected. + * + * @since 0.12.0 + */ +public class MalformedKeyException extends InvalidKeyException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public MalformedKeyException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param msg the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public MalformedKeyException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/io/jsonwebtoken/security/MalformedKeySetException.java b/io/jsonwebtoken/security/MalformedKeySetException.java new file mode 100644 index 0000000..aa268d7 --- /dev/null +++ b/io/jsonwebtoken/security/MalformedKeySetException.java @@ -0,0 +1,44 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * Exception thrown when encountering a {@link JwkSet} that is incomplete or improperly configured or + * formatted and cannot be used as expected. + * + * @since 0.12.0 + */ +public class MalformedKeySetException extends SecurityException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public MalformedKeySetException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public MalformedKeySetException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/security/Message.java b/io/jsonwebtoken/security/Message.java new file mode 100644 index 0000000..cd5e8df --- /dev/null +++ b/io/jsonwebtoken/security/Message.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Key; + +/** + * A message contains a {@link #getPayload() payload} used as input to or output from a cryptographic algorithm. + * + * @param The type of payload in the message. + * @since 0.12.0 + */ +public interface Message { + + /** + * Returns the message payload used as input to or output from a cryptographic algorithm. This is almost always + * plaintext used for cryptographic signatures or encryption, or ciphertext for decryption, or a {@link Key} + * instance for wrapping or unwrapping algorithms. + * + * @return the message payload used as input to or output from a cryptographic algorithm. + */ + T getPayload(); //plaintext, ciphertext or Key +} diff --git a/io/jsonwebtoken/security/OctetPrivateJwk.java b/io/jsonwebtoken/security/OctetPrivateJwk.java new file mode 100644 index 0000000..cf9956f --- /dev/null +++ b/io/jsonwebtoken/security/OctetPrivateJwk.java @@ -0,0 +1,68 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.ECPrivateKey; + +/** + * JWK representation of an Edwards Curve + * {@link PrivateKey} as defined by RFC 8037, Section 2: + * Key Type "OKP". + * + *

Unlike the {@link EcPrivateJwk} interface, which only supports + * Weierstrass-form {@link ECPrivateKey}s, + * {@code OctetPrivateJwk} allows for multiple parameterized {@link PrivateKey} types + * because the JDK supports two different types of Edwards Curve private keys:

+ * + *

As such, {@code OctetPrivateJwk} is parameterized to support both key types.

+ * + *

Earlier JDK Versions

+ * + *

Even though {@code XECPrivateKey} and {@code EdECPrivateKey} were introduced in JDK 11 and JDK 15 respectively, + * JJWT supports Octet private JWKs in earlier versions when BouncyCastle is enabled in the application classpath. When + * using earlier JDK versions, the {@code OctetPrivateJwk} instance will need be parameterized with the + * generic {@code PrivateKey} type since the latter key types would not be present. For example:

+ *
+ * OctetPrivateJwk<PrivateKey> octetPrivateJwk = getKey();
+ * + *

OKP-specific Properties

+ * + *

Note that the various OKP-specific properties are not available as separate dedicated getter methods, as most Java + * applications should rarely, if ever, need to access these individual key properties since they typically represent + * internal key material and/or serialization details. If you need to access these key properties, it is usually + * recommended to obtain the corresponding {@link PrivateKey} instance returned by {@link #toKey()} and + * query that instead.

+ * + *

Even so, because these properties exist and are readable by nature of every JWK being a + * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method + * using an appropriate JWK parameter id, for example:

+ *
+ * jwk.get("x");
+ * jwk.get("d");
+ * // ... etc ...
+ * + * @param The type of Edwards-curve {@link PrivateKey} represented by this JWK (e.g. XECPrivateKey, EdECPrivateKey, etc). + * @param The type of Edwards-curve {@link PublicKey} represented by the JWK's corresponding + * {@link #toPublicJwk() public JWK}, for example XECPublicKey, EdECPublicKey, etc. + * @since 0.12.0 + */ +public interface OctetPrivateJwk extends PrivateJwk> { +} diff --git a/io/jsonwebtoken/security/OctetPrivateJwkBuilder.java b/io/jsonwebtoken/security/OctetPrivateJwkBuilder.java new file mode 100644 index 0000000..16ebd88 --- /dev/null +++ b/io/jsonwebtoken/security/OctetPrivateJwkBuilder.java @@ -0,0 +1,30 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * A {@link PrivateJwkBuilder} that creates {@link OctetPrivateJwk} instances. + * + * @param The type of {@link PrivateKey} represented by the constructed {@link OctetPrivateJwk} instance. + * @param The type of {@link PublicKey} available from the constructed {@link OctetPrivateJwk}'s associated {@link PrivateJwk#toPublicJwk() public JWK} properties. + * @since 0.12.0 + */ +public interface OctetPrivateJwkBuilder extends + PrivateJwkBuilder, OctetPrivateJwk, OctetPrivateJwkBuilder> { +} diff --git a/io/jsonwebtoken/security/OctetPublicJwk.java b/io/jsonwebtoken/security/OctetPublicJwk.java new file mode 100644 index 0000000..18a0d5d --- /dev/null +++ b/io/jsonwebtoken/security/OctetPublicJwk.java @@ -0,0 +1,63 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PublicKey; +import java.security.interfaces.ECPublicKey; + +/** + * JWK representation of an Edwards Curve + * {@link PublicKey} as defined by RFC 8037, Section 2: + * Key Type "OKP". + * + *

Unlike the {@link EcPublicJwk} interface, which only supports + * Weierstrass-form {@link ECPublicKey}s, + * {@code OctetPublicJwk} allows for multiple parameterized {@link PublicKey} types + * because the JDK supports two different types of Edwards Curve public keys:

+ * + *

As such, {@code OctetPublicJwk} is parameterized to support both key types.

+ * + *

Earlier JDK Versions

+ * + *

Even though {@code XECPublicKey} and {@code EdECPublicKey} were introduced in JDK 11 and JDK 15 respectively, + * JJWT supports Octet public JWKs in earlier versions when BouncyCastle is enabled in the application classpath. When + * using earlier JDK versions, the {@code OctetPublicJwk} instance will need be parameterized with the + * generic {@code PublicKey} type since the latter key types would not be present. For example:

+ *
OctetPublicJwk<PublicKey> octetPublicJwk = getKey();
+ * + *

OKP-specific Properties

+ * + *

Note that the various OKP-specific properties are not available as separate dedicated getter methods, as most Java + * applications should rarely, if ever, need to access these individual key properties since they typically represent + * internal key material and/or serialization details. If you need to access these key properties, it is usually + * recommended to obtain the corresponding {@link PublicKey} instance returned by {@link #toKey()} and + * query that instead.

+ * + *

Even so, because these properties exist and are readable by nature of every JWK being a + * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method + * using an appropriate JWK parameter id, for example:

+ *
+ * jwk.get("x");
+ * // ... etc ...
+ * + * @param The type of Edwards-curve {@link PublicKey} represented by this JWK (e.g. XECPublicKey, EdECPublicKey, etc). + * @since 0.12.0 + */ +public interface OctetPublicJwk extends PublicJwk { +} diff --git a/io/jsonwebtoken/security/OctetPublicJwkBuilder.java b/io/jsonwebtoken/security/OctetPublicJwkBuilder.java new file mode 100644 index 0000000..4ac24da --- /dev/null +++ b/io/jsonwebtoken/security/OctetPublicJwkBuilder.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * A {@link PublicJwkBuilder} that creates {@link OctetPublicJwk} instances. + * + * @param the type of {@link PublicKey} provided by the created {@link OctetPublicJwk} (e.g. XECPublicKey, EdECPublicKey, etc). + * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce an + * {@link OctetPrivateJwk} if desired. For example, XECPrivateKey, EdECPrivateKey, etc. + * @since 0.12.0 + */ +public interface OctetPublicJwkBuilder + extends PublicJwkBuilder, OctetPrivateJwk, OctetPrivateJwkBuilder, OctetPublicJwkBuilder> { +} diff --git a/io/jsonwebtoken/security/Password.java b/io/jsonwebtoken/security/Password.java new file mode 100644 index 0000000..0972e9b --- /dev/null +++ b/io/jsonwebtoken/security/Password.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; +import javax.security.auth.Destroyable; + +/** + * A {@code Key} suitable for use with password-based key derivation algorithms. + * + *

Usage Warning

+ * + *

Because raw passwords should never be used as direct inputs for cryptographic operations (such as authenticated + * hashing or encryption) - and only for derivation algorithms (like password-based encryption) - {@code Password} + * instances will throw an exception when used in these invalid contexts. Specifically, calling a + * {@code Password}'s {@link Password#getEncoded() getEncoded()} method (as would be done automatically by the + * JCA subsystem during direct cryptographic operations) will throw an + * {@link UnsupportedOperationException UnsupportedOperationException}.

+ * + * @see #toCharArray() + * @since 0.12.0 + */ +public interface Password extends SecretKey, Destroyable { + + /** + * Returns a new clone of the underlying password character array for use during derivation algorithms. Like all + * {@code SecretKey} implementations, if you wish to clear the backing password character array for + * safety/security reasons, call the {@link #destroy()} method, ensuring that both the character array is cleared + * and the {@code Password} instance can no longer be used. + * + *

Usage

+ * + *

Because a new clone is returned from this method each time it is invoked, it is expected that callers will + * clear the resulting clone from memory as soon as possible to reduce probability of password exposure. For + * example:

+ * + *

+     * char[] clonedPassword = aPassword.toCharArray();
+     * try {
+     *     doSomethingWithPassword(clonedPassword);
+     * } finally {
+     *     // guarantee clone is cleared regardless of any Exception thrown:
+     *     java.util.Arrays.fill(clonedPassword, '\u0000');
+     * }
+     * 
+ * + * @return a clone of the underlying password character array. + */ + char[] toCharArray(); +} diff --git a/io/jsonwebtoken/security/PrivateJwk.java b/io/jsonwebtoken/security/PrivateJwk.java new file mode 100644 index 0000000..2eb3bf2 --- /dev/null +++ b/io/jsonwebtoken/security/PrivateJwk.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * JWK representation of a {@link PrivateKey}. + * + *

JWK Private Key vs Java {@code PrivateKey} differences

+ * + *

Unlike the Java cryptography APIs, the JWK specification requires all public key and private key + * properties to be contained within every private JWK. As such, a {@code PrivateJwk} indeed represents + * private key values as its name implies, but it is probably more similar to the Java JCA concept of a + * {@link java.security.KeyPair} since it contains everything for both keys.

+ * + *

Consequently a {@code PrivateJwk} is capable of providing two additional convenience methods:

+ *
    + *
  • {@link #toPublicJwk()} - a method to obtain a {@link PublicJwk} instance that contains only the JWK public + * key properties, and
  • + *
  • {@link #toKeyPair()} - a method to obtain both Java {@link PublicKey} and {@link PrivateKey}s in aggregate + * as a {@link KeyPair} instance if desired.
  • + *
+ * + * @param The type of {@link PrivateKey} represented by this JWK + * @param The type of {@link PublicKey} represented by the JWK's corresponding {@link #toPublicJwk() public JWK}. + * @param The type of {@link PublicJwk} reflected by the JWK's public properties. + * @since 0.12.0 + */ +public interface PrivateJwk> extends AsymmetricJwk { + + /** + * Returns the private JWK's corresponding {@link PublicJwk}, containing only the key's public properties. + * + * @return the private JWK's corresponding {@link PublicJwk}, containing only the key's public properties. + */ + M toPublicJwk(); + + /** + * Returns the key's corresponding Java {@link PrivateKey} and {@link PublicKey} in aggregate as a + * type-safe {@link KeyPair} instance. + * + * @return the key's corresponding Java {@link PrivateKey} and {@link PublicKey} in aggregate as a + * type-safe {@link KeyPair} instance. + */ + KeyPair toKeyPair(); +} diff --git a/io/jsonwebtoken/security/PrivateJwkBuilder.java b/io/jsonwebtoken/security/PrivateJwkBuilder.java new file mode 100644 index 0000000..bbd24b8 --- /dev/null +++ b/io/jsonwebtoken/security/PrivateJwkBuilder.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * An {@link AsymmetricJwkBuilder} that creates {@link PrivateJwk} instances. + * + * @param the type of Java {@link PrivateKey} provided by the created private JWK. + * @param the type of Java {@link PublicKey} paired with the private key. + * @param the type of {@link PrivateJwk} created + * @param the type of {@link PublicJwk} paired with the created private JWK. + * @param the type of the builder, for subtype method chaining + * @see #publicKey(PublicKey) + * @since 0.12.0 + */ +public interface PrivateJwkBuilder, M extends PrivateJwk, + T extends PrivateJwkBuilder> extends AsymmetricJwkBuilder { + + /** + * Allows specifying of the {@link PublicKey} associated with the builder's existing {@link PrivateKey}, + * offering a reasonable performance enhancement when building the final private JWK. Application developers + * should prefer to use this method when possible when building private JWKs. + * + *

As discussed in the {@link PrivateJwk} documentation, the JWK and JWA specifications require private JWKs to + * contain both private key and public key data. If a public key is not provided via this + * {@code publicKey} method, the builder implementation must go through the work to derive the + * {@code PublicKey} instance based on the {@code PrivateKey} to obtain the necessary public key information.

+ * + *

Calling this method with the {@code PrivateKey}'s matching {@code PublicKey} instance eliminates the need + * for the builder to do that work.

+ * + * @param publicKey the {@link PublicKey} that matches the builder's existing {@link PrivateKey}. + * @return the builder for method chaining. + */ + T publicKey(L publicKey); +} diff --git a/io/jsonwebtoken/security/PrivateKeyBuilder.java b/io/jsonwebtoken/security/PrivateKeyBuilder.java new file mode 100644 index 0000000..5bdee74 --- /dev/null +++ b/io/jsonwebtoken/security/PrivateKeyBuilder.java @@ -0,0 +1,38 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; + +/** + * A builder that allows a {@code PrivateKey} to be transparently associated with a {@link #provider(Provider)} or + * {@link #publicKey(PublicKey)} if necessary for algorithms that require them. + * + * @since 0.12.0 + */ +public interface PrivateKeyBuilder extends KeyBuilder { + + /** + * Sets the private key's corresponding {@code PublicKey} so that its public key material will be available to + * algorithms that require it. + * + * @param publicKey the private key's corresponding {@code PublicKey} + * @return the builder for method chaining. + */ + PrivateKeyBuilder publicKey(PublicKey publicKey); +} diff --git a/io/jsonwebtoken/security/PublicJwk.java b/io/jsonwebtoken/security/PublicJwk.java new file mode 100644 index 0000000..6f1eb20 --- /dev/null +++ b/io/jsonwebtoken/security/PublicJwk.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PublicKey; + +/** + * JWK representation of a {@link PublicKey}. + * + * @param The type of {@link PublicKey} represented by this JWK + * @since 0.12.0 + */ +public interface PublicJwk extends AsymmetricJwk { +} diff --git a/io/jsonwebtoken/security/PublicJwkBuilder.java b/io/jsonwebtoken/security/PublicJwkBuilder.java new file mode 100644 index 0000000..eada333 --- /dev/null +++ b/io/jsonwebtoken/security/PublicJwkBuilder.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * An {@link AsymmetricJwkBuilder} that creates {@link PublicJwk} instances. + * + * @param the type of {@link PublicKey} provided by the created public JWK. + * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce a {@link PrivateJwk} if desired. + * @param the type of {@link PublicJwk} created + * @param the type of {@link PrivateJwk} that matches the created {@link PublicJwk} + * @param

the type of {@link PrivateJwkBuilder} that matches this builder if a {@link PrivateJwk} is desired. + * @param the type of the builder, for subtype method chaining + * @see #privateKey(PrivateKey) + * @since 0.12.0 + */ +public interface PublicJwkBuilder, M extends PrivateJwk, + P extends PrivateJwkBuilder, + T extends PublicJwkBuilder> extends AsymmetricJwkBuilder { + + /** + * Sets the {@link PrivateKey} that pairs with the builder's existing {@link PublicKey}, converting this builder + * into a {@link PrivateJwkBuilder} which will produce a corresponding {@link PrivateJwk} instance. The + * specified {@code privateKey} MUST be the exact private key paired with the builder's public key. + * + * @param privateKey the {@link PrivateKey} that pairs with the builder's existing {@link PublicKey} + * @return the builder coerced as a {@link PrivateJwkBuilder} which will produce a corresponding {@link PrivateJwk}. + */ + P privateKey(L privateKey); +} diff --git a/io/jsonwebtoken/security/Request.java b/io/jsonwebtoken/security/Request.java new file mode 100644 index 0000000..77e0d32 --- /dev/null +++ b/io/jsonwebtoken/security/Request.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Provider; +import java.security.SecureRandom; + +/** + * A {@code Request} aggregates various parameters that may be used by a particular cryptographic algorithm. It and + * any of its subtypes implemented as a single object submitted to an algorithm effectively reflect the + * Parameter Object design pattern. This + * provides for a much cleaner request/result algorithm API instead of polluting the API with an excessive number of + * overloaded methods that would exist otherwise. + * + *

The {@code Request} interface specifically allows for JCA {@link Provider} and {@link SecureRandom} instances + * to be used during request execution, which allows more flexibility than forcing a single {@code Provider} or + * {@code SecureRandom} for all executions. {@code Request} subtypes provide additional parameters as necessary + * depending on the type of cryptographic algorithm invoked.

+ * + * @param the type of payload in the request. + * @see #getProvider() + * @see #getSecureRandom() + * @since 0.12.0 + */ +public interface Request extends Message { + + /** + * Returns the JCA provider that should be used for cryptographic operations during the request or + * {@code null} if the JCA subsystem preferred provider should be used. + * + * @return the JCA provider that should be used for cryptographic operations during the request or + * {@code null} if the JCA subsystem preferred provider should be used. + */ + Provider getProvider(); + + /** + * Returns the {@code SecureRandom} to use when performing cryptographic operations during the request, or + * {@code null} if a default {@link SecureRandom} should be used. + * + * @return the {@code SecureRandom} to use when performing cryptographic operations during the request, or + * {@code null} if a default {@link SecureRandom} should be used. + */ + SecureRandom getSecureRandom(); +} diff --git a/io/jsonwebtoken/security/RsaPrivateJwk.java b/io/jsonwebtoken/security/RsaPrivateJwk.java new file mode 100644 index 0000000..73d8bb5 --- /dev/null +++ b/io/jsonwebtoken/security/RsaPrivateJwk.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +/** + * JWK representation of an {@link RSAPrivateKey} as defined by the JWA (RFC 7518) specification sections on + * Parameters for RSA Keys and + * Parameters for RSA Private Keys. + * + *

Note that the various RSA-specific properties are not available as separate dedicated getter methods, as most Java + * applications should rarely, if ever, need to access these individual key properties since they typically represent + * internal key material and/or serialization details. If you need to access these key properties, it is usually + * recommended to obtain the corresponding {@link RSAPrivateKey} instance returned by {@link #toKey()} and + * query that instead.

+ * + *

Even so, because these properties exist and are readable by nature of every JWK being a + * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method + * using an appropriate JWK parameter id, for example:

+ *
+ * jwk.get("n");
+ * jwk.get("e");
+ * // ... etc ...
+ * + * @since 0.12.0 + */ +public interface RsaPrivateJwk extends PrivateJwk { +} diff --git a/io/jsonwebtoken/security/RsaPrivateJwkBuilder.java b/io/jsonwebtoken/security/RsaPrivateJwkBuilder.java new file mode 100644 index 0000000..136df69 --- /dev/null +++ b/io/jsonwebtoken/security/RsaPrivateJwkBuilder.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +/** + * A {@link PrivateJwkBuilder} that creates {@link RsaPrivateJwk}s. + * + * @since 0.12.0 + */ +public interface RsaPrivateJwkBuilder extends PrivateJwkBuilder { +} diff --git a/io/jsonwebtoken/security/RsaPublicJwk.java b/io/jsonwebtoken/security/RsaPublicJwk.java new file mode 100644 index 0000000..06e73f9 --- /dev/null +++ b/io/jsonwebtoken/security/RsaPublicJwk.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.RSAPublicKey; + +/** + * JWK representation of an {@link RSAPublicKey} as defined by the JWA (RFC 7518) specification sections on + * Parameters for RSA Keys and + * Parameters for RSA Public Keys. + * + *

Note that the various RSA-specific properties are not available as separate dedicated getter methods, as most Java + * applications should rarely, if ever, need to access these individual key properties since they typically represent + * internal key material and/or serialization details. If you need to access these key properties, it is usually + * recommended to obtain the corresponding {@link RSAPublicKey} instance returned by {@link #toKey()} and + * query that instead.

+ * + *

Even so, because these properties exist and are readable by nature of every JWK being a + * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method + * using an appropriate JWK parameter id, for example:

+ *
+ * jwk.get("n");
+ * jwk.get("e");
+ * // ... etc ...
+ * + * @since 0.12.0 + */ +public interface RsaPublicJwk extends PublicJwk { +} diff --git a/io/jsonwebtoken/security/RsaPublicJwkBuilder.java b/io/jsonwebtoken/security/RsaPublicJwkBuilder.java new file mode 100644 index 0000000..b6be07e --- /dev/null +++ b/io/jsonwebtoken/security/RsaPublicJwkBuilder.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +/** + * A {@link PublicJwkBuilder} that creates {@link RsaPublicJwk}s. + * + * @since 0.12.0 + */ +public interface RsaPublicJwkBuilder extends PublicJwkBuilder { + +} diff --git a/io/jsonwebtoken/security/SecretJwk.java b/io/jsonwebtoken/security/SecretJwk.java new file mode 100644 index 0000000..d1a3b1b --- /dev/null +++ b/io/jsonwebtoken/security/SecretJwk.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; + +/** + * JWK representation of a {@link SecretKey} as defined by the JWA (RFC 7518) specification section on + * Parameters for Symmetric Keys. + * + *

Note that the {@code SecretKey}-specific properties are not available as separate dedicated getter methods, as + * most Java applications should rarely, if ever, need to access these individual key properties since they typically + * internal key material and/or serialization details. If you need to access these key properties, it is usually + * recommended to obtain the corresponding {@link SecretKey} instance returned by {@link #toKey()} and + * query that instead.

+ * + * @since 0.12.0 + */ +public interface SecretJwk extends Jwk { +} diff --git a/io/jsonwebtoken/security/SecretJwkBuilder.java b/io/jsonwebtoken/security/SecretJwkBuilder.java new file mode 100644 index 0000000..421b5f5 --- /dev/null +++ b/io/jsonwebtoken/security/SecretJwkBuilder.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; + +/** + * A {@link JwkBuilder} that creates {@link SecretJwk}s. + * + * @since 0.12.0 + */ +public interface SecretJwkBuilder extends JwkBuilder { +} diff --git a/io/jsonwebtoken/security/SecretKeyAlgorithm.java b/io/jsonwebtoken/security/SecretKeyAlgorithm.java new file mode 100644 index 0000000..f54c08a --- /dev/null +++ b/io/jsonwebtoken/security/SecretKeyAlgorithm.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; + +/** + * A {@link KeyAlgorithm} that uses symmetric {@link SecretKey}s to obtain AEAD encryption and decryption keys. + * + * @since 0.12.0 + */ +public interface SecretKeyAlgorithm extends KeyAlgorithm, KeyBuilderSupplier, KeyLengthSupplier { +} diff --git a/io/jsonwebtoken/security/SecretKeyBuilder.java b/io/jsonwebtoken/security/SecretKeyBuilder.java new file mode 100644 index 0000000..b8219d8 --- /dev/null +++ b/io/jsonwebtoken/security/SecretKeyBuilder.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; + +/** + * A {@link KeyBuilder} that creates new secure-random {@link SecretKey}s with a length sufficient to be used by + * the security algorithm that produced this builder. + * + * @since 0.12.0 + */ +public interface SecretKeyBuilder extends KeyBuilder { +} diff --git a/io/jsonwebtoken/security/SecureDigestAlgorithm.java b/io/jsonwebtoken/security/SecureDigestAlgorithm.java new file mode 100644 index 0000000..fbe671d --- /dev/null +++ b/io/jsonwebtoken/security/SecureDigestAlgorithm.java @@ -0,0 +1,55 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; + +import java.io.InputStream; +import java.security.Key; + +/** + * A {@link DigestAlgorithm} that requires a {@link Key} to compute and verify the authenticity of digests using either + * digital signature or + * message + * authentication code algorithms. + * + *

Standard Identifier

+ * + *

{@code SecureDigestAlgorithm} extends {@link Identifiable}: when a {@code SecureDigestAlgorithm} is used to + * compute the digital signature or MAC of a JWS, the value returned from + * {@link Identifiable#getId() secureDigestAlgorithm.getId()} will be set as the JWS + * "alg" protected header value.

+ * + *

Standard Implementations

+ * + *

Constant definitions and utility methods for all JWA (RFC 7518) standard + * Cryptographic Algorithms for Digital Signatures and + * MACs are available via {@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}.

+ * + *

"alg" identifier

+ * + *

{@code SecureDigestAlgorithm} extends {@link Identifiable}: the value returned from + * {@link Identifiable#getId() getId()} will be used as the JWS "alg" protected header value.

+ * + * @param the type of {@link Key} used to create digital signatures or message authentication codes + * @param the type of {@link Key} used to verify digital signatures or message authentication codes + * @see MacAlgorithm + * @see SignatureAlgorithm + * @since 0.12.0 + */ +public interface SecureDigestAlgorithm + extends DigestAlgorithm, VerifySecureDigestRequest> { +} diff --git a/io/jsonwebtoken/security/SecureRequest.java b/io/jsonwebtoken/security/SecureRequest.java new file mode 100644 index 0000000..4e65c30 --- /dev/null +++ b/io/jsonwebtoken/security/SecureRequest.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Key; + +/** + * A request to a cryptographic algorithm requiring a {@link Key}. + * + * @param the type of payload in the request + * @param they type of key used by the algorithm during the request + * @since 0.12.0 + */ +public interface SecureRequest extends Request, KeySupplier { +} diff --git a/io/jsonwebtoken/security/SecurityBuilder.java b/io/jsonwebtoken/security/SecurityBuilder.java new file mode 100644 index 0000000..f233ec3 --- /dev/null +++ b/io/jsonwebtoken/security/SecurityBuilder.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.lang.Builder; + +import java.security.Provider; +import java.security.SecureRandom; + +/** + * A Security-specific {@link Builder} that allows configuration of common JCA API parameters that might be used + * during instance creation, such as a {@link java.security.Provider} or {@link java.security.SecureRandom}. + * + * @param The type of object that will be created each time {@link #build()} is invoked. + * @param the type of SecurityBuilder returned for method chaining + * @see #provider(Provider) + * @see #random(SecureRandom) + * @since 0.12.0 + */ +public interface SecurityBuilder> extends Builder { + + /** + * Sets the JCA Security {@link Provider} to use if necessary when calling {@link #build()}. This is an optional + * property - if not specified, the default JCA Provider will be used. + * + * @param provider the JCA Security Provider instance to use if necessary when building the new instance. + * @return the builder for method chaining. + */ + B provider(Provider provider); + + /** + * Sets the {@link SecureRandom} to use if necessary when calling {@link #build()}. This is an optional property + * - if not specified and one is required, a default {@code SecureRandom} will be used. + * + * @param random the {@link SecureRandom} instance to use if necessary when building the new instance. + * @return the builder for method chaining. + */ + B random(SecureRandom random); +} diff --git a/io/jsonwebtoken/security/SecurityException.java b/io/jsonwebtoken/security/SecurityException.java new file mode 100644 index 0000000..107600d --- /dev/null +++ b/io/jsonwebtoken/security/SecurityException.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.JwtException; + +/** + * A {@code JwtException} attributed to a problem with security-related elements, such as + * cryptographic keys, algorithms, or the underlying Java JCA API. + * + * @since 0.10.0 + */ +public class SecurityException extends JwtException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public SecurityException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public SecurityException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/security/SignatureAlgorithm.java b/io/jsonwebtoken/security/SignatureAlgorithm.java new file mode 100644 index 0000000..2df975f --- /dev/null +++ b/io/jsonwebtoken/security/SignatureAlgorithm.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * A digital signature algorithm computes and + * verifies digests using asymmetric public/private key cryptography. + * + *

Standard Identifier

+ * + *

{@code SignatureAlgorithm} extends {@link Identifiable}: when a {@code SignatureAlgorithm} is used to compute + * a JWS digital signature, the value returned from {@link Identifiable#getId() signatureAlgorithm.getId()} will be + * set as the JWS "alg" protected header value.

+ * + *

Key Pair Generation

+ * + *

{@code SignatureAlgorithm} extends {@link KeyPairBuilderSupplier} to enable + * {@link KeyPair} generation. Each {@code SignatureAlgorithm} instance will return a + * {@link KeyPairBuilder} that ensures any created key pairs will have a sufficient length and algorithm parameters + * required by that algorithm. For example:

+ * + *
+ * KeyPair pair = signatureAlgorithm.keyPair().build();
+ * + *

The resulting {@code pair} is guaranteed to have the correct algorithm parameters and length/strength necessary + * for that exact {@code signatureAlgorithm} instance.

+ * + *

JWA Standard Implementations

+ * + *

Constant definitions and utility methods for all JWA (RFC 7518) standard signature algorithms are + * available via {@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}.

+ * + * @see io.jsonwebtoken.Jwts.SIG Jwts.SIG + * @since 0.12.0 + */ +public interface SignatureAlgorithm extends SecureDigestAlgorithm, KeyPairBuilderSupplier { +} diff --git a/io/jsonwebtoken/security/SignatureException.java b/io/jsonwebtoken/security/SignatureException.java new file mode 100644 index 0000000..ad8a167 --- /dev/null +++ b/io/jsonwebtoken/security/SignatureException.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * Exception thrown if there is problem calculating or verifying a digital signature or message authentication code. + * + * @since 0.10.0 + */ +@SuppressWarnings("deprecation") +public class SignatureException extends io.jsonwebtoken.SignatureException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public SignatureException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public SignatureException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/io/jsonwebtoken/security/UnsupportedKeyException.java b/io/jsonwebtoken/security/UnsupportedKeyException.java new file mode 100644 index 0000000..7937ee6 --- /dev/null +++ b/io/jsonwebtoken/security/UnsupportedKeyException.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * Exception thrown when encountering a key or key material that is not supported or recognized. + * + * @since 0.12.0 + */ +public class UnsupportedKeyException extends KeyException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public UnsupportedKeyException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param msg the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public UnsupportedKeyException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/io/jsonwebtoken/security/VerifyDigestRequest.java b/io/jsonwebtoken/security/VerifyDigestRequest.java new file mode 100644 index 0000000..34fbf16 --- /dev/null +++ b/io/jsonwebtoken/security/VerifyDigestRequest.java @@ -0,0 +1,33 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.io.InputStream; + +/** + * A request to verify a previously-computed cryptographic digest (available via {@link #getDigest()}) against the + * digest to be computed for the specified {@link #getPayload() payload}. + * + *

Secure digest algorithms that use keys to perform + * digital signature or + * message + * authentication code verification will use {@link VerifySecureDigestRequest} instead.

+ * + * @see VerifySecureDigestRequest + * @since 0.12.0 + */ +public interface VerifyDigestRequest extends Request, DigestSupplier { +} diff --git a/io/jsonwebtoken/security/VerifySecureDigestRequest.java b/io/jsonwebtoken/security/VerifySecureDigestRequest.java new file mode 100644 index 0000000..a1ddbd5 --- /dev/null +++ b/io/jsonwebtoken/security/VerifySecureDigestRequest.java @@ -0,0 +1,34 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.io.InputStream; +import java.security.Key; + +/** + * A request to a {@link SecureDigestAlgorithm} to verify a previously-computed + * digital signature or + * message + * authentication code. + * + *

The content to verify will be available via {@link #getPayload()}, the previously-computed signature or MAC will + * be available via {@link #getDigest()}, and the verification key will be available via {@link #getKey()}.

+ * + * @param the type of {@link Key} used to verify a digital signature or message authentication code + * @since 0.12.0 + */ +public interface VerifySecureDigestRequest extends SecureRequest, VerifyDigestRequest { +} diff --git a/io/jsonwebtoken/security/WeakKeyException.java b/io/jsonwebtoken/security/WeakKeyException.java new file mode 100644 index 0000000..8b466d0 --- /dev/null +++ b/io/jsonwebtoken/security/WeakKeyException.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * Exception thrown when encountering a key that is not strong enough (of sufficient length) to be used with + * a particular algorithm or in a particular security context. + * + * @since 0.10.0 + */ +public class WeakKeyException extends InvalidKeyException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public WeakKeyException(String message) { + super(message); + } +} diff --git a/io/jsonwebtoken/security/X509Accessor.java b/io/jsonwebtoken/security/X509Accessor.java new file mode 100644 index 0000000..587e0d0 --- /dev/null +++ b/io/jsonwebtoken/security/X509Accessor.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.JweHeader; +import io.jsonwebtoken.JwsHeader; + +import java.net.URI; +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * Accessor methods of X.509-specific properties of a + * {@link io.jsonwebtoken.ProtectedHeader ProtectedHeader} or {@link AsymmetricJwk}, guaranteeing consistent behavior + * across similar but distinct JWT concepts with identical parameter names. + * + * @see io.jsonwebtoken.ProtectedHeader + * @see AsymmetricJwk + * @since 0.12.0 + */ +public interface X509Accessor { + + /** + * Returns the {@code x5u} (X.509 URL) that refers to a resource for the associated X.509 public key certificate + * or certificate chain, or {@code null} if not present. + * + *

When present, the URI MUST refer to a resource for an X.509 public key certificate or certificate + * chain that conforms to RFC 5280 in PEM-encoded form, + * with each certificate delimited as specified in + * Section 6.1 of RFC 4945. + * The key in the first certificate MUST match the public key represented by other members of the + * associated ProtectedHeader or JWK. The protocol used to acquire the resource MUST provide integrity + * protection; an HTTP GET request to retrieve the certificate MUST use + * HTTP over TLS; the identity of the server + * MUST be validated, as per + * Section 6 of RFC 6125.

+ * + *
    + *
  • When present in a {@link JwsHeader}, the certificate or first certificate in the chain corresponds + * the public key complement of the private key used to digitally sign the JWS.
  • + *
  • When present in a {@link JweHeader}, the certificate or certificate chain corresponds to the + * public key to which the JWE was encrypted, and may be used to determine the private key needed to + * decrypt the JWE.
  • + *
  • When present in an {@link AsymmetricJwk}, the certificate or first certificate in the chain + * MUST contain the public key represented by the JWK.
  • + *
+ * + * @return the {@code x5u} (X.509 URL) that refers to a resource for the associated X.509 public key certificate or + * certificate chain. + * @see JWK {@code x5u} (X.509 URL) Parameter + * @see JWS {@code x5u} (X.509 URL) Header Parameter + * @see JWE {@code x5u} (X.509 URL) Header Parameter + */ + URI getX509Url(); + + /** + * Returns the associated {@code x5c} (X.509 Certificate Chain), or {@code null} if not present. The initial + * certificate MAY be followed by additional certificates, with each subsequent certificate being the + * one used to certify the previous one. + * + *
    + *
  • When present in a {@link JwsHeader}, the first certificate (at list index 0) MUST contain + * the public key complement of the private key used to digitally sign the JWS.
  • + *
  • When present in a {@link JweHeader}, the first certificate (at list index 0) MUST contain + * the public key to which the JWE was encrypted, and may be used to determine the private key needed to + * decrypt the JWE.
  • + *
  • When present in an {@link AsymmetricJwk}, the first certificate (at list index 0) + * MUST contain the public key represented by the JWK.
  • + *
+ * + * @return the associated {@code x5c} (X.509 Certificate Chain), or {@code null} if not present. + * @see JWK x5c (X.509 Certificate Chain) Parameter + * @see JWS x5c (X.509 Certificate Chain) Header Parameter + * @see JWE x5c (X.509 Certificate Chain) Header Parameter + */ + List getX509Chain(); + + /** + * Returns the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * associated X.509 Certificate, or {@code null} if not present. + * + *

Note that certificate thumbprints are also sometimes known as certificate fingerprints.

+ * + *
    + *
  • When present in a {@link JwsHeader}, it is the SHA-1 thumbprint of the X.509 certificate complement + * of the private key used to digitally sign the JWS.
  • + *
  • When present in a {@link JweHeader}, it is the SHA-1 thumbprint of the X.509 Certificate containing + * the public key to which the JWE was encrypted, and may be used to determine the private key + * needed to decrypt the JWE.
  • + *
  • When present in an {@link AsymmetricJwk}, it is the SHA-1 thumbprint of the X.509 certificate + * containing the public key represented by the JWK.
  • + *
+ * + * @return the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * associated X.509 Certificate, or {@code null} if not present + * @see JWK x5t (X.509 Certificate SHA-1 Thumbprint) Parameter + * @see JWS x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter + * @see JWE x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter + */ + byte[] getX509Sha1Thumbprint(); + + /** + * Returns the {@code x5t#S256} (X.509 Certificate SHA-256 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * associated X.509 Certificate, or {@code null} if not present. + * + *

Note that certificate thumbprints are also sometimes known as certificate fingerprints.

+ * + *
    + *
  • When present in a {@link JwsHeader}, it is the SHA-256 thumbprint of the X.509 certificate complement + * of the private key used to digitally sign the JWS.
  • + *
  • When present in a {@link JweHeader}, it is the SHA-256 thumbprint of the X.509 Certificate containing + * the public key to which the JWE was encrypted, and may be used to determine the private key + * needed to decrypt the JWE.
  • + *
  • When present in an {@link AsymmetricJwk}, it is the SHA-256 thumbprint of the X.509 certificate + * containing the public key represented by the JWK.
  • + *
+ * + * @return the {@code x5t#S256} (X.509 Certificate SHA-256 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * associated X.509 Certificate, or {@code null} if not present + * @see JWK x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Parameter + * @see JWS x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter + * @see JWE x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter + */ + byte[] getX509Sha256Thumbprint(); +} diff --git a/io/jsonwebtoken/security/X509Builder.java b/io/jsonwebtoken/security/X509Builder.java new file mode 100644 index 0000000..84315be --- /dev/null +++ b/io/jsonwebtoken/security/X509Builder.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * Additional X.509-specific builder methods for constructing an associated JWT Header or JWK, enabling method chaining. + * + * @param the mutator subtype, for method chaining + * @since 0.12.0 + */ +public interface X509Builder> extends X509Mutator { + + /** + * If the {@code enable} argument is {@code true}, compute the SHA-1 thumbprint of the first + * {@link X509Certificate} in the configured {@link #x509Chain(List) x509CertificateChain}, and set + * the resulting value as the {@link #x509Sha1Thumbprint(byte[])} parameter. + * + *

If no chain has been configured, or {@code enable} is {@code false}, the builder will not compute nor add a + * {@code x5t} value.

+ * + * @param enable whether to compute the SHA-1 thumbprint on the first available X.509 Certificate and set + * the resulting value as the {@code x5t} value. + * @return the builder for method chaining. + */ + T x509Sha1Thumbprint(boolean enable); + + /** + * If the {@code enable} argument is {@code true}, compute the SHA-256 thumbprint of the first + * {@link X509Certificate} in the configured {@link #x509Chain(List) x509CertificateChain}, and set + * the resulting value as the {@link #x509Sha256Thumbprint(byte[])} parameter. + * + *

If no chain has been configured, or {@code enable} is {@code false}, the builder will not compute nor add a + * {@code x5t#S256} value.

+ * + * @param enable whether to compute the SHA-256 thumbprint on the first available X.509 Certificate and set + * the resulting value as the {@code x5t#S256} value. + * @return the builder for method chaining. + */ + T x509Sha256Thumbprint(boolean enable); +} diff --git a/io/jsonwebtoken/security/X509Mutator.java b/io/jsonwebtoken/security/X509Mutator.java new file mode 100644 index 0000000..afe50fc --- /dev/null +++ b/io/jsonwebtoken/security/X509Mutator.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.JweHeader; +import io.jsonwebtoken.JwsHeader; + +import java.net.URI; +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * Mutation (modifications) of X.509-specific properties of an associated JWT Header or JWK, enabling method chaining. + * + * @param the mutator subtype, for method chaining + * @since 0.12.0 + */ +public interface X509Mutator> { + + /** + * Sets the {@code x5u} (X.509 URL) that refers to a resource containing the X.509 public key certificate or + * certificate chain of the associated JWT or JWK. A {@code null} value will remove the property from the JSON map. + * + *

The URI MUST refer to a resource for an X.509 public key certificate or certificate chain that + * conforms to RFC 5280 in PEM-encoded form, with + * each certificate delimited as specified in + * Section 6.1 of RFC 4945. + * The key in the first certificate MUST match the public key represented by other members of the + * associated JWT or JWK. The protocol used to acquire the resource MUST provide integrity protection; + * an HTTP GET request to retrieve the certificate MUST use + * HTTP over TLS; the identity of the server + * MUST be validated, as per + * Section 6 of RFC 6125.

+ * + *
    + *
  • When set for a {@link JwsHeader}, the certificate or first certificate in the chain contains + * the public key complement of the private key used to digitally sign the JWS.
  • + *
  • When set for {@link JweHeader}, the certificate or first certificate in the chain contains the + * public key to which the JWE was encrypted, and may be used to determine the private key needed to + * decrypt the JWE.
  • + *
  • When set for an {@link AsymmetricJwk}, the certificate or first certificate in the chain + * MUST contain the public key represented by the JWK.
  • + *
+ * + * @param uri the {@code x5u} (X.509 URL) that refers to a resource for the X.509 public key certificate or + * certificate chain associated with the JWT or JWK. + * @return the mutator/builder for method chaining. + * @see JWK x5u (X.509 URL) Parameter + * @see JWS x5u (X.509 URL) Header Parameter + * @see JWE x5u (X.509 URL) Header Parameter + */ + T x509Url(URI uri); + + /** + * Sets the {@code x5c} (X.509 Certificate Chain) of the associated JWT or JWK. A {@code null} value will remove the + * property from the JSON map. The initial certificate MAY be followed by additional certificates, with + * each subsequent certificate being the one used to certify the previous one. + * + *
    + *
  • When set for a {@link JwsHeader}, the first certificate (at list index 0) MUST contain + * the public key complement of the private key used to digitally sign the JWS.
  • + *
  • When set for {@link JweHeader}, the first certificate (at list index 0) MUST contain the + * public key to which the JWE was encrypted, and may be used to determine the private key needed to + * decrypt the JWE.
  • + *
  • When set for an {@link AsymmetricJwk}, the first certificate (at list index 0) MUST contain + * the public key represented by the JWK.
  • + *
+ * + * @param chain the {@code x5c} (X.509 Certificate Chain) of the associated JWT or JWK. + * @return the header/builder for method chaining. + * @see JWK x5c (X.509 Certificate Chain) Parameter + * @see JWS x5c (X.509 Certificate Chain) Header Parameter + * @see JWE x5c (X.509 Certificate Chain) Header Parameter + */ + T x509Chain(List chain); + + /** + * Sets the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * X.509 Certificate associated with the JWT or JWK. A {@code null} value will remove the + * property from the JSON map. + * + *

Note that certificate thumbprints are also sometimes known as certificate fingerprints.

+ * + *
    + *
  • When set for a {@link JwsHeader}, it is the SHA-1 thumbprint of the X.509 certificate complement of + * the private key used to digitally sign the JWS.
  • + *
  • When set for {@link JweHeader}, it is the thumbprint of the X.509 Certificate containing the + * public key to which the JWE was encrypted, and may be used to determine the private key needed to + * decrypt the JWE.
  • + *
  • When set for an {@link AsymmetricJwk}, it is the thumbprint of the X.509 certificate containing the + * public key represented by the JWK.
  • + *
+ * + * @param thumbprint the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * X.509 Certificate associated with the JWT or JWK + * @return the header for method chaining + * @see JWK x5t (X.509 Certificate SHA-1 Thumbprint) Parameter + * @see JWS x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter + * @see JWE x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter + */ + T x509Sha1Thumbprint(byte[] thumbprint); + + /** + * Sets the {@code x5t#S256} (X.509 Certificate SHA-256 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * X.509 Certificate associated with the JWT or JWK. A {@code null} value will remove the + * property from the JSON map. + * + *

Note that certificate thumbprints are also sometimes known as certificate fingerprints.

+ * + *
    + *
  • When set for a {@link JwsHeader}, it is the SHA-256 thumbprint of the X.509 certificate complement + * of the private key used to digitally sign the JWS.
  • + *
  • When set for {@link JweHeader}, it is the SHA-256 thumbprint of the X.509 Certificate containing the + * public key to which the JWE was encrypted, and may be used to determine the private key needed to + * decrypt the JWE.
  • + *
  • When set for a {@link AsymmetricJwk}, it is the SHA-256 thumbprint of the X.509 certificate + * containing the public key represented by the JWK.
  • + *
+ * + * @param thumbprint the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * X.509 Certificate associated with the JWT or JWK + * @return the header for method chaining + * @see JWK x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Parameter + * @see JWS x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter + * @see JWE x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter + */ + T x509Sha256Thumbprint(byte[] thumbprint); +} From add88e91e4063e465ce3e5885a1edbf6abb01c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=B8?= <91dlwnsgh@naver.com> Date: Tue, 14 Jul 2026 10:55:03 +0900 Subject: [PATCH 22/55] =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=9D=BC?= =?UTF-8?q?=EC=9D=BC=EC=97=85=EB=AC=B4=20=EB=82=A0=EC=A7=9C=EA=B0=92=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/aims/backend/domain/mainpage/TaskStatus.java | 7 ------- .../java/com/aims/backend/domain/mainpage/UserTask.java | 3 --- .../com/aims/backend/dto/mainpage/UserTaskResponse.java | 6 +----- .../java/com/aims/backend/service/MainPageService.java | 6 ++++-- 4 files changed, 5 insertions(+), 17 deletions(-) delete mode 100644 src/main/java/com/aims/backend/domain/mainpage/TaskStatus.java diff --git a/src/main/java/com/aims/backend/domain/mainpage/TaskStatus.java b/src/main/java/com/aims/backend/domain/mainpage/TaskStatus.java deleted file mode 100644 index 687be62..0000000 --- a/src/main/java/com/aims/backend/domain/mainpage/TaskStatus.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.aims.backend.domain.mainpage; - -public enum TaskStatus { - TODO, - PROGRESS, - DONE -} diff --git a/src/main/java/com/aims/backend/domain/mainpage/UserTask.java b/src/main/java/com/aims/backend/domain/mainpage/UserTask.java index 407252a..97af479 100644 --- a/src/main/java/com/aims/backend/domain/mainpage/UserTask.java +++ b/src/main/java/com/aims/backend/domain/mainpage/UserTask.java @@ -35,9 +35,6 @@ public class UserTask extends BaseEntity { @Column(name = "task_title", nullable = false, length = 255) private String taskTitle; - @Enumerated(EnumType.STRING) - @Column(name = "task_status", nullable = false, length = 20) - private TaskStatus taskStatus; @Column(name = "scheduled_at", nullable = false) private LocalDateTime scheduledAt; diff --git a/src/main/java/com/aims/backend/dto/mainpage/UserTaskResponse.java b/src/main/java/com/aims/backend/dto/mainpage/UserTaskResponse.java index 19d8b1f..7c6ecf7 100644 --- a/src/main/java/com/aims/backend/dto/mainpage/UserTaskResponse.java +++ b/src/main/java/com/aims/backend/dto/mainpage/UserTaskResponse.java @@ -1,6 +1,5 @@ package com.aims.backend.dto.mainpage; -import com.aims.backend.domain.mainpage.TaskStatus; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; @@ -21,10 +20,7 @@ public static class MainPageTaskDTO { @Schema(description = "업무명", example = "프레스 라인 점검") private String taskTitle; - @Schema(description = "업무 상태", example = "TODO") - private TaskStatus taskStatus; - @Schema(description = "업무 시간", example = "2026-06-11T09:00:00") - private LocalDateTime scheduledAt; + private String scheduledAt; } } diff --git a/src/main/java/com/aims/backend/service/MainPageService.java b/src/main/java/com/aims/backend/service/MainPageService.java index 6b89b3b..c31e2c7 100644 --- a/src/main/java/com/aims/backend/service/MainPageService.java +++ b/src/main/java/com/aims/backend/service/MainPageService.java @@ -12,6 +12,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.stream.Collectors; @@ -23,6 +24,8 @@ public class MainPageService { private final UserTaskRepository mainPageUserTaskRepository; private final InspectionSummaryRepository inspectionSummaryRepository; + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm"); + public List getUserTasks(Long userId) { LocalDateTime currentTime = LocalDateTime.now() .withMinute(0) @@ -33,8 +36,7 @@ public List getUserTasks(Long userId) { return userTasks.stream() .map(task -> UserTaskResponse.MainPageTaskDTO.builder() .taskTitle(task.getTaskTitle()) - .taskStatus(task.getTaskStatus()) - .scheduledAt(task.getScheduledAt()) + .scheduledAt(task.getScheduledAt().format(formatter)) .build()) .collect(Collectors.toList()); } From 697bd5e962b2766d8fa4f5572a6730a7f4065198 Mon Sep 17 00:00:00 2001 From: hyein0514 Date: Tue, 14 Jul 2026 11:01:37 +0900 Subject: [PATCH 23/55] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=9A=B0=EC=84=A0=EC=88=9C=EC=9C=84=20=EC=A0=90=EC=88=98=20?= =?UTF-8?q?=EC=82=B0=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alert/AlertEventRepository.java | 13 +-- .../alert/AlertPrioritySummaryService.java | 7 +- .../alert/AlertEventRepositoryTest.java | 100 +++++++++++++++--- .../AlertPrioritySummaryServiceTest.java | 4 + 4 files changed, 105 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/aims/backend/repository/alert/AlertEventRepository.java b/src/main/java/com/aims/backend/repository/alert/AlertEventRepository.java index 90bcc94..299822c 100644 --- a/src/main/java/com/aims/backend/repository/alert/AlertEventRepository.java +++ b/src/main/java/com/aims/backend/repository/alert/AlertEventRepository.java @@ -62,12 +62,12 @@ long countByEventKeyAndActionStatus( @Query(""" SELECT COUNT(e) AS totalCount, - SUM(e.priorityScore) AS priorityScoreSum, - COUNT(e.priorityScore) AS priorityScoreCount, - SUM(e.riskScore) AS riskScoreSum, - COUNT(e.riskScore) AS riskScoreCount, - SUM(e.occurrenceScore) AS occurrenceScoreSum, - COUNT(e.occurrenceScore) AS occurrenceScoreCount, + SUM(CASE WHEN e.actionStatus = :incompleteStatus THEN e.priorityScore ELSE 0 END) AS priorityScoreSum, + COUNT(CASE WHEN e.actionStatus = :incompleteStatus THEN e.priorityScore ELSE NULL END) AS priorityScoreCount, + SUM(CASE WHEN e.actionStatus = :incompleteStatus THEN e.riskScore ELSE 0 END) AS riskScoreSum, + COUNT(CASE WHEN e.actionStatus = :incompleteStatus THEN e.riskScore ELSE NULL END) AS riskScoreCount, + SUM(CASE WHEN e.actionStatus = :incompleteStatus THEN e.occurrenceScore ELSE 0 END) AS occurrenceScoreSum, + COUNT(CASE WHEN e.actionStatus = :incompleteStatus THEN e.occurrenceScore ELSE NULL END) AS occurrenceScoreCount, SUM(CASE WHEN e.actionStatus = :completedStatus THEN 1 ELSE 0 END) AS completedCount FROM AlertEvent e WHERE e.createdAt >= :from @@ -76,6 +76,7 @@ SELECT COUNT(e) AS totalCount, PrioritySummaryProjection findPrioritySummary( @Param("from") LocalDateTime from, @Param("to") LocalDateTime to, + @Param("incompleteStatus") AlertActionStatus incompleteStatus, @Param("completedStatus") AlertActionStatus completedStatus ); } diff --git a/src/main/java/com/aims/backend/service/alert/AlertPrioritySummaryService.java b/src/main/java/com/aims/backend/service/alert/AlertPrioritySummaryService.java index bcbcb30..ac13f20 100644 --- a/src/main/java/com/aims/backend/service/alert/AlertPrioritySummaryService.java +++ b/src/main/java/com/aims/backend/service/alert/AlertPrioritySummaryService.java @@ -25,7 +25,12 @@ public AlertPrioritySummaryResponse getPrioritySummary(int days) { LocalDateTime from = to.minusDays(days); AlertEventRepository.PrioritySummaryProjection summary = - alertEventRepository.findPrioritySummary(from, to, AlertActionStatus.COMPLETED); + alertEventRepository.findPrioritySummary( + from, + to, + AlertActionStatus.INCOMPLETE, + AlertActionStatus.COMPLETED + ); if (summary == null || summary.getTotalCount() == 0) { return emptyResponse(days); diff --git a/src/test/java/com/aims/backend/repository/alert/AlertEventRepositoryTest.java b/src/test/java/com/aims/backend/repository/alert/AlertEventRepositoryTest.java index 161138a..517868b 100644 --- a/src/test/java/com/aims/backend/repository/alert/AlertEventRepositoryTest.java +++ b/src/test/java/com/aims/backend/repository/alert/AlertEventRepositoryTest.java @@ -19,7 +19,7 @@ @SpringBootTest @Transactional -@Sql(statements = "CREATE TABLE IF NOT EXISTS alert_event (log_no VARCHAR(20) NOT NULL PRIMARY KEY, event_id VARCHAR(100) NOT NULL UNIQUE, alert_type VARCHAR(20) NOT NULL, process_code VARCHAR(20) NOT NULL, equipment_id BIGINT NULL, event_key VARCHAR(150) NOT NULL, risk_score DECIMAL(5,2) NULL, occurrence_score DECIMAL(6,4) NULL, detection_score DECIMAL(6,4) NULL, priority_score DECIMAL(10,2) NULL, severity VARCHAR(20) NULL, title VARCHAR(100) NOT NULL, contents VARCHAR(500) NOT NULL, action_by VARCHAR(50) NULL, action_status VARCHAR(20) NULL, reason VARCHAR(500) NULL, score_calculated_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, resolved_at TIMESTAMP NULL)") +@Sql(statements = "CREATE TABLE IF NOT EXISTS alert_event (log_no VARCHAR(20) NOT NULL PRIMARY KEY, event_id VARCHAR(100) NOT NULL UNIQUE, alert_type VARCHAR(20) NOT NULL, process_code VARCHAR(20) NOT NULL, equipment_id BIGINT NULL, event_key VARCHAR(150) NOT NULL, risk_score DECIMAL(5,2) NULL, occurrence_score DECIMAL(6,4) NULL, detection_score DECIMAL(6,4) NULL, priority_score DECIMAL(10,2) NULL, severity VARCHAR(20) NULL, title VARCHAR(100) NOT NULL, contents VARCHAR(500) NOT NULL, action_by VARCHAR(50) NULL, action_status VARCHAR(20) NULL, reason VARCHAR(500) NULL, score_calculated_at TIMESTAMP NULL, image_url VARCHAR(255) NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, resolved_at TIMESTAMP NULL)") class AlertEventRepositoryTest { @Autowired @@ -70,42 +70,118 @@ void savesAndFindsAlertEventByEventId() { } @Test - void aggregatesScoresAndCompletedActionsWithinPeriod() { + void aggregatesIncompleteScoresAndCompletedActionsWithinPeriod() { LocalDateTime from = LocalDateTime.of(2026, 7, 6, 12, 0); LocalDateTime to = LocalDateTime.of(2026, 7, 13, 12, 0); alertEventRepository.save(summaryEvent( - "summary-1", from.plusDays(1), "100.00", "80.00", "0.5000", AlertActionStatus.COMPLETED + "summary-1", from.plusDays(1), "120.00", "50.00", "0.4000", AlertActionStatus.INCOMPLETE )); alertEventRepository.save(summaryEvent( - "summary-2", from.plusDays(2), "200.00", null, "1.0000", AlertActionStatus.NOT_NEEDED + "summary-2", from.plusDays(2), "80.00", "30.00", "0.6000", AlertActionStatus.INCOMPLETE )); alertEventRepository.save(summaryEvent( - "summary-3", from.minusSeconds(1), "999.00", "99.00", "0.9000", AlertActionStatus.COMPLETED + "summary-3", from.plusDays(3), "200.00", "90.00", "0.9000", AlertActionStatus.COMPLETED )); alertEventRepository.save(summaryEvent( - "summary-4", from.plusDays(3), null, "40.00", null, AlertActionStatus.INCOMPLETE + "summary-4", from.minusSeconds(1), "999.00", "99.00", "0.9000", AlertActionStatus.INCOMPLETE )); alertEventRepository.flush(); setCreatedAt("AL-summary-1", from.plusDays(1)); setCreatedAt("AL-summary-2", from.plusDays(2)); - setCreatedAt("AL-summary-3", from.minusSeconds(1)); - setCreatedAt("AL-summary-4", from.plusDays(3)); + setCreatedAt("AL-summary-3", from.plusDays(3)); + setCreatedAt("AL-summary-4", from.minusSeconds(1)); entityManager.clear(); AlertEventRepository.PrioritySummaryProjection summary = - alertEventRepository.findPrioritySummary(from, to, AlertActionStatus.COMPLETED); + findPrioritySummary(from, to); assertThat(summary.getTotalCount()).isEqualTo(3); - assertThat(summary.getPriorityScoreSum()).isEqualByComparingTo(new BigDecimal("300.00")); + assertThat(summary.getPriorityScoreSum()).isEqualByComparingTo(new BigDecimal("200.00")); assertThat(summary.getPriorityScoreCount()).isEqualTo(2); - assertThat(summary.getRiskScoreSum()).isEqualByComparingTo(new BigDecimal("120.00")); + assertThat(summary.getRiskScoreSum()).isEqualByComparingTo(new BigDecimal("80.00")); assertThat(summary.getRiskScoreCount()).isEqualTo(2); - assertThat(summary.getOccurrenceScoreSum()).isEqualByComparingTo(new BigDecimal("1.5000")); + assertThat(summary.getOccurrenceScoreSum()).isEqualByComparingTo(new BigDecimal("1.0000")); assertThat(summary.getOccurrenceScoreCount()).isEqualTo(2); assertThat(summary.getCompletedCount()).isEqualTo(1); } + @Test + void excludesEventFromAveragesAfterActionIsCompleted() { + LocalDateTime from = LocalDateTime.of(2026, 7, 6, 12, 0); + LocalDateTime to = LocalDateTime.of(2026, 7, 13, 12, 0); + AlertEvent first = summaryEvent( + "transition-1", from.plusDays(1), "120.00", "50.00", "0.4000", AlertActionStatus.INCOMPLETE + ); + AlertEvent second = summaryEvent( + "transition-2", from.plusDays(2), "80.00", "30.00", "0.6000", AlertActionStatus.INCOMPLETE + ); + alertEventRepository.saveAll(java.util.List.of(first, second)); + alertEventRepository.flush(); + setCreatedAt("AL-transition-1", from.plusDays(1)); + setCreatedAt("AL-transition-2", from.plusDays(2)); + entityManager.clear(); + + AlertEventRepository.PrioritySummaryProjection before = findPrioritySummary(from, to); + assertThat(before.getPriorityScoreSum()).isEqualByComparingTo("200.00"); + assertThat(before.getPriorityScoreCount()).isEqualTo(2); + assertThat(before.getCompletedCount()).isZero(); + + AlertEvent eventToComplete = alertEventRepository.findById(first.getLogNo()).orElseThrow(); + eventToComplete.updateAction("user01", AlertActionStatus.COMPLETED, "done"); + alertEventRepository.flush(); + entityManager.clear(); + + AlertEventRepository.PrioritySummaryProjection after = findPrioritySummary(from, to); + assertThat(after.getPriorityScoreSum()).isEqualByComparingTo("80.00"); + assertThat(after.getPriorityScoreCount()).isEqualTo(1); + assertThat(after.getCompletedCount()).isEqualTo(1); + assertThat(after.getTotalCount()).isEqualTo(2); + } + + @Test + void excludesCompletedNotNeededAndNullPriorityScoresFromPriorityAverage() { + LocalDateTime from = LocalDateTime.of(2026, 7, 6, 12, 0); + LocalDateTime to = LocalDateTime.of(2026, 7, 13, 12, 0); + alertEventRepository.save(summaryEvent( + "excluded-1", from.plusDays(1), "200.00", "90.00", "0.9000", AlertActionStatus.COMPLETED + )); + alertEventRepository.save(summaryEvent( + "excluded-2", from.plusDays(2), "300.00", "80.00", "0.8000", AlertActionStatus.NOT_NEEDED + )); + alertEventRepository.save(summaryEvent( + "excluded-3", from.plusDays(3), null, "40.00", "0.4000", AlertActionStatus.INCOMPLETE + )); + alertEventRepository.flush(); + setCreatedAt("AL-excluded-1", from.plusDays(1)); + setCreatedAt("AL-excluded-2", from.plusDays(2)); + setCreatedAt("AL-excluded-3", from.plusDays(3)); + entityManager.clear(); + + AlertEventRepository.PrioritySummaryProjection summary = findPrioritySummary(from, to); + + assertThat(summary.getTotalCount()).isEqualTo(3); + assertThat(summary.getPriorityScoreSum()).isZero(); + assertThat(summary.getPriorityScoreCount()).isZero(); + assertThat(summary.getRiskScoreSum()).isEqualByComparingTo("40.00"); + assertThat(summary.getRiskScoreCount()).isEqualTo(1); + assertThat(summary.getOccurrenceScoreSum()).isEqualByComparingTo("0.4000"); + assertThat(summary.getOccurrenceScoreCount()).isEqualTo(1); + assertThat(summary.getCompletedCount()).isEqualTo(1); + } + + private AlertEventRepository.PrioritySummaryProjection findPrioritySummary( + LocalDateTime from, + LocalDateTime to + ) { + return alertEventRepository.findPrioritySummary( + from, + to, + AlertActionStatus.INCOMPLETE, + AlertActionStatus.COMPLETED + ); + } + private AlertEvent summaryEvent( String suffix, LocalDateTime createdAt, diff --git a/src/test/java/com/aims/backend/service/alert/AlertPrioritySummaryServiceTest.java b/src/test/java/com/aims/backend/service/alert/AlertPrioritySummaryServiceTest.java index fe6beff..9f97551 100644 --- a/src/test/java/com/aims/backend/service/alert/AlertPrioritySummaryServiceTest.java +++ b/src/test/java/com/aims/backend/service/alert/AlertPrioritySummaryServiceTest.java @@ -38,6 +38,7 @@ void calculatesPrioritySummary() { when(alertEventRepository.findPrioritySummary( any(LocalDateTime.class), any(LocalDateTime.class), + eq(AlertActionStatus.INCOMPLETE), eq(AlertActionStatus.COMPLETED) )).thenReturn(projection); when(projection.getTotalCount()).thenReturn(4L); @@ -63,6 +64,7 @@ void returnsZerosWhenThereAreNoEvents() { when(alertEventRepository.findPrioritySummary( any(LocalDateTime.class), any(LocalDateTime.class), + eq(AlertActionStatus.INCOMPLETE), eq(AlertActionStatus.COMPLETED) )).thenReturn(projection); when(projection.getTotalCount()).thenReturn(0L); @@ -80,6 +82,7 @@ void returnsZeroOnlyForMissingScoreAverages() { when(alertEventRepository.findPrioritySummary( any(LocalDateTime.class), any(LocalDateTime.class), + eq(AlertActionStatus.INCOMPLETE), eq(AlertActionStatus.COMPLETED) )).thenReturn(projection); when(projection.getTotalCount()).thenReturn(2L); @@ -96,6 +99,7 @@ void clampsPercentagesToValidRange() { when(alertEventRepository.findPrioritySummary( any(LocalDateTime.class), any(LocalDateTime.class), + eq(AlertActionStatus.INCOMPLETE), eq(AlertActionStatus.COMPLETED) )).thenReturn(projection); when(projection.getTotalCount()).thenReturn(2L); From d3b267030e3428640306fce44a265e458c1a928a Mon Sep 17 00:00:00 2001 From: kimgeon Date: Tue, 14 Jul 2026 11:19:02 +0900 Subject: [PATCH 24/55] fix : alertevent test data update --- .../aims/backend/service/alert/AlertEventSaveService.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java b/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java index a4d1a55..361cbc1 100644 --- a/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java +++ b/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java @@ -131,8 +131,11 @@ private CalculatedAlert toCalculatedAlert(JsonNode root) { defaultContents(alertType, processCode, equipmentId, text(root, "contents", "message", "description")); String eventKey = eventKey(root, alertType, processCode, equipmentId, title, eventId); - String imageUrl = - text(root, "imageUrl", "image_url"); + String imageUrl = root.hasNonNull("imageUrl") + ? root.get("imageUrl").asText() + : root.hasNonNull("image_url") + ? root.get("image_url").asText() + : null; BigDecimal riskScore = score(root, BigDecimal.ZERO, BigDecimal.valueOf(100), "riskScore", "risk_score"); if (riskScore == null) { From 6f69107cc8194fd75b69431976e98db7cf2e8bd1 Mon Sep 17 00:00:00 2001 From: haseokyung6 Date: Tue, 14 Jul 2026 13:32:37 +0900 Subject: [PATCH 25/55] fix: kafka bootstrap update --- .github/workflows/deploy-backend.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 96d14c7..1f1c06e 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -165,7 +165,13 @@ jobs: --region "$AWS_REGION" \ --query "Parameter.Value" \ --output text) - + + KAFKA_BOOTSTRAP_SERVERS=$(aws ssm get-parameter \ + --name "/aims/dev/msk/bootstrap-servers" \ + --region "$AWS_REGION" \ + --query "Parameter.Value" \ + --output text) + for VALUE in \ "$RDS_SECRET_ARN" \ "$RDS_HOST" \ @@ -174,7 +180,8 @@ jobs: "$SAMPLE_DB_NAME" \ "$JWT_SECRET_KEY" \ "$REDIS_HOST" \ - "$REDIS_PORT"; do + "$REDIS_PORT" \ + "$KAFKA_BOOTSTRAP_SERVERS"; do if [ -z "$VALUE" ] || [ "$VALUE" = "None" ]; then echo "Required backend SSM parameter is empty" @@ -193,6 +200,7 @@ jobs: echo "JWT_SECRET_KEY=$JWT_SECRET_KEY" >> "$GITHUB_ENV" echo "REDIS_HOST=$REDIS_HOST" >> "$GITHUB_ENV" echo "REDIS_PORT=$REDIS_PORT" >> "$GITHUB_ENV" + echo "KAFKA_BOOTSTRAP_SERVERS=$KAFKA_BOOTSTRAP_SERVERS" >> "$GITHUB_ENV" - name: Create or update backend Kubernetes Secret run: | @@ -237,6 +245,7 @@ jobs: --from-literal=JWT_SECRET_KEY="$JWT_SECRET_KEY" \ --from-literal=REDIS_HOST="$REDIS_HOST" \ --from-literal=REDIS_PORT="$REDIS_PORT" \ + --from-literal=KAFKA_BOOTSTRAP_SERVERS="$KAFKA_BOOTSTRAP_SERVERS" \ --dry-run=client \ -o yaml | kubectl apply -f - From 0dfdf1f7e9604b25fe6310c6d1ad9a5d6f3b8eda Mon Sep 17 00:00:00 2001 From: kimgeon Date: Tue, 14 Jul 2026 13:42:17 +0900 Subject: [PATCH 26/55] feat : kafka msg image Url log.info add --- .../aims/backend/service/alert/AlertEventConsumer.java | 2 ++ .../backend/service/alert/AlertEventSaveService.java | 9 ++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/aims/backend/service/alert/AlertEventConsumer.java b/src/main/java/com/aims/backend/service/alert/AlertEventConsumer.java index edbd7d3..b744936 100644 --- a/src/main/java/com/aims/backend/service/alert/AlertEventConsumer.java +++ b/src/main/java/com/aims/backend/service/alert/AlertEventConsumer.java @@ -22,6 +22,8 @@ public class AlertEventConsumer { groupId = "${app.kafka.group-id:backend-local}" ) public void consume(String message) { + log.info("===== Kafka Alert Message Received ====="); + log.info("rawMessage={}", message); log.info("Alert Kafka message received. eventId={}", eventId(message)); try { diff --git a/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java b/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java index 361cbc1..c154a35 100644 --- a/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java +++ b/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java @@ -131,11 +131,9 @@ private CalculatedAlert toCalculatedAlert(JsonNode root) { defaultContents(alertType, processCode, equipmentId, text(root, "contents", "message", "description")); String eventKey = eventKey(root, alertType, processCode, equipmentId, title, eventId); - String imageUrl = root.hasNonNull("imageUrl") - ? root.get("imageUrl").asText() - : root.hasNonNull("image_url") - ? root.get("image_url").asText() - : null; + String imageUrl = + text(root, "imageUrl", "image_url"); + BigDecimal riskScore = score(root, BigDecimal.ZERO, BigDecimal.valueOf(100), "riskScore", "risk_score"); if (riskScore == null) { @@ -205,6 +203,7 @@ private void publishRealtimeAlert(CalculatedAlert calculatedAlert) { ); try { + log.info("Kafka Message = {}", message); log.info( "Alert websocket publish start. destination={}, eventId={}", AlertWebSocketPublisher.ALERT_DESTINATION, From 87d89449ea264d202013cf939f96d193d9122441 Mon Sep 17 00:00:00 2001 From: chani2104 Date: Tue, 14 Jul 2026 14:06:03 +0900 Subject: [PATCH 27/55] fix: yaml fix --- .../dashboard/ManufacturingEventConsumer.java | 157 ------------------ 1 file changed, 157 deletions(-) delete mode 100644 src/main/java/com/aims/backend/service/dashboard/ManufacturingEventConsumer.java diff --git a/src/main/java/com/aims/backend/service/dashboard/ManufacturingEventConsumer.java b/src/main/java/com/aims/backend/service/dashboard/ManufacturingEventConsumer.java deleted file mode 100644 index 08ecad9..0000000 --- a/src/main/java/com/aims/backend/service/dashboard/ManufacturingEventConsumer.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.aims.backend.service.dashboard; - -import com.aims.backend.dto.dashboard.ManufacturingEventRequest; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; -import java.util.Map; - -@Slf4j -@Component -@RequiredArgsConstructor -public class ManufacturingEventConsumer { - - private final ObjectMapper objectMapper; - private final AgvSimulationService agvSimulationService; - - /*@KafkaListener( - topics = "factory.manufacturing.raw", - groupId = "main-agv-group" - ) - public void consume(String message) { - - try { - - // 1. Kafka 원본 메시지 확인 - log.info(""" - - ============================== - Kafka 원본 메시지 수신 - {} - ============================== - - """, message); - - ManufacturingRawEventKafkaDto raw = - objectMapper.readValue( - message, - ManufacturingRawEventKafkaDto.class - ); - - Map processMetrics = - raw.eventJson() == null - ? null - : (Map) raw.eventJson() - .get("processMetrics"); - - ManufacturingEventRequest event = - ManufacturingEventRequest.builder() - .eventId(raw.eventId()) - .carMasterId(raw.carMasterId()) - .processCode(raw.processCode()) - .eventTime(raw.eventTime().toString()) - .processingTimeSec( - getInt(processMetrics, "processingTimeSec") - ) - .waitingTimeSec( - getInt(processMetrics, "waitingTimeSec") - ) - .stationDelaySec( - getInt(processMetrics, "stationDelaySec") - ) - .build(); - - // 2. DTO 변환 결과 확인 - log.info(""" - - ============================== - ManufacturingEventRequest 변환 완료 - - eventId : {} - carMasterId : {} - processCode : {} - eventTime : {} - processingTimeSec : {} - waitingTimeSec : {} - stationDelaySec : {} - - ============================== - - """, - event.getEventId(), - event.getCarMasterId(), - event.getProcessCode(), - event.getEventTime(), - event.getProcessingTimeSec(), - event.getWaitingTimeSec(), - event.getStationDelaySec() - ); - - // 3. AGV 시뮬레이터 호출 - agvSimulationService.handleManufacturingEvent(event); - - log.info( - "[AGV 처리 완료] eventId={}, processCode={}", - event.getEventId(), - event.getProcessCode() - ); - - } catch (Exception e) { - - log.error(""" - - ===================================== - Kafka 메시지 처리 실패 - - message={} - - ===================================== - - """, - message, - e - ); - } - } - - private Integer getInt( - Map map, - String key - ) { - - if (map == null || !map.containsKey(key)) { - return 0; - } - - Object value = map.get(key); - - if (value instanceof Number number) { - return number.intValue(); - } - - return 0; - } - - /** - * assembly-service ManufacturingRawEvent 대응 DTO - */ - public record ManufacturingRawEventKafkaDto( - Long id, - String eventId, - LocalDateTime eventTime, - Long carMasterId, - Long equipmentId, - String processCode, - String stationCode, - String equipmentCode, - String equipmentType, - String equipmentStatus, - String eventType, - Map eventJson - ) { - } -} \ No newline at end of file From 878906f5fa20cedc6cbc68b7e8d1f91d26e85744 Mon Sep 17 00:00:00 2001 From: chani2104 Date: Tue, 14 Jul 2026 14:06:50 +0900 Subject: [PATCH 28/55] fix: yaml kafka bootstrap fix --- src/main/resources/application.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 9759ec4..72433ad 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -23,7 +23,7 @@ spring: port: 6379 timeout: ${REDIS_TIMEOUT:3s} kafka: - bootstrap-servers: ${MSK_BOOTSTRAP_SERVERS} + bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS} properties: security.protocol: SASL_SSL @@ -85,7 +85,7 @@ app: host: ${OPENSEARCH_HOST:localhost} port: ${OPENSEARCH_PORT:9200} kafka: - bootstrap-servers: ${MSK_BOOTSTRAP_SERVERS} + bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS} group-id: ${KAFKA_GROUP_ID:main-dev} auto-offset-reset: ${KAFKA_AUTO_OFFSET_RESET:earliest} listeners-enabled: ${KAFKA_LISTENERS_ENABLED:true} From c5a1a02dceb973cda499cf454994a9b2fc2ed7b7 Mon Sep 17 00:00:00 2001 From: hyein0514 Date: Tue, 14 Jul 2026 14:20:13 +0900 Subject: [PATCH 29/55] =?UTF-8?q?fix:websocket=20config=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/aims/backend/config/SecurityConfig.java | 3 ++- src/main/java/com/aims/backend/config/WebSocketConfig.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/aims/backend/config/SecurityConfig.java b/src/main/java/com/aims/backend/config/SecurityConfig.java index 45c9224..fa365f6 100644 --- a/src/main/java/com/aims/backend/config/SecurityConfig.java +++ b/src/main/java/com/aims/backend/config/SecurityConfig.java @@ -53,7 +53,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/api/auth/refresh", "/api/event/**", "/api/main/process-flow", - "/ws/**" + "/ws/**", + "/api/ws/**" ).permitAll() .anyRequest().authenticated() ) diff --git a/src/main/java/com/aims/backend/config/WebSocketConfig.java b/src/main/java/com/aims/backend/config/WebSocketConfig.java index bd15e02..73155ad 100644 --- a/src/main/java/com/aims/backend/config/WebSocketConfig.java +++ b/src/main/java/com/aims/backend/config/WebSocketConfig.java @@ -13,7 +13,7 @@ public void registerStompEndpoints( StompEndpointRegistry registry ) { - registry.addEndpoint("/ws") + registry.addEndpoint("/ws","api/ws") .setAllowedOriginPatterns("*") .withSockJS(); From af3c6eac7c53585c519bbb8aae99826f110ed446 Mon Sep 17 00:00:00 2001 From: kimgeon Date: Tue, 14 Jul 2026 15:01:32 +0900 Subject: [PATCH 30/55] fix : application yaml update --- src/main/resources/application.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 72433ad..84ea0e7 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -23,7 +23,8 @@ spring: port: 6379 timeout: ${REDIS_TIMEOUT:3s} kafka: - bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS} + bootstrap-servers: + - ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092} properties: security.protocol: SASL_SSL From 90707bdcfbb86f1256d8f3611a18e9ba135bf86b Mon Sep 17 00:00:00 2001 From: kimgeon Date: Tue, 14 Jul 2026 16:01:26 +0900 Subject: [PATCH 31/55] fix kafka_bootstrap_servers update --- .github/workflows/deploy-backend.yml | 26 +++++++++++++++++-------- src/main/resources/application-dev.yaml | 4 +++- src/main/resources/application.yaml | 2 +- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 1f1c06e..1cfbd88 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -166,11 +166,18 @@ jobs: --query "Parameter.Value" \ --output text) - KAFKA_BOOTSTRAP_SERVERS=$(aws ssm get-parameter \ - --name "/aims/dev/msk/bootstrap-servers" \ - --region "$AWS_REGION" \ - --query "Parameter.Value" \ - --output text) + KAFKA_BOOTSTRAP_SERVER_1=$(aws ssm get-parameter \ + --name "/aims/dev/msk/bootstrap-server-1" \ + --region "$AWS_REGION" \ + --query "Parameter.Value" \ + --output text) + + + KAFKA_BOOTSTRAP_SERVER_2=$(aws ssm get-parameter \ + --name "/aims/dev/msk/bootstrap-server-2" \ + --region "$AWS_REGION" \ + --query "Parameter.Value" \ + --output text) for VALUE in \ "$RDS_SECRET_ARN" \ @@ -181,7 +188,8 @@ jobs: "$JWT_SECRET_KEY" \ "$REDIS_HOST" \ "$REDIS_PORT" \ - "$KAFKA_BOOTSTRAP_SERVERS"; do + "$KAFKA_BOOTSTRAP_SERVER_1" \ + "$KAFKA_BOOTSTRAP_SERVER_2"; do if [ -z "$VALUE" ] || [ "$VALUE" = "None" ]; then echo "Required backend SSM parameter is empty" @@ -200,7 +208,8 @@ jobs: echo "JWT_SECRET_KEY=$JWT_SECRET_KEY" >> "$GITHUB_ENV" echo "REDIS_HOST=$REDIS_HOST" >> "$GITHUB_ENV" echo "REDIS_PORT=$REDIS_PORT" >> "$GITHUB_ENV" - echo "KAFKA_BOOTSTRAP_SERVERS=$KAFKA_BOOTSTRAP_SERVERS" >> "$GITHUB_ENV" + echo "KAFKA_BOOTSTRAP_SERVER_1=$KAFKA_BOOTSTRAP_SERVER_1" >> "$GITHUB_ENV" + echo "KAFKA_BOOTSTRAP_SERVER_2=$KAFKA_BOOTSTRAP_SERVER_2" >> "$GITHUB_ENV" - name: Create or update backend Kubernetes Secret run: | @@ -245,7 +254,8 @@ jobs: --from-literal=JWT_SECRET_KEY="$JWT_SECRET_KEY" \ --from-literal=REDIS_HOST="$REDIS_HOST" \ --from-literal=REDIS_PORT="$REDIS_PORT" \ - --from-literal=KAFKA_BOOTSTRAP_SERVERS="$KAFKA_BOOTSTRAP_SERVERS" \ + --from-literal=KAFKA_BOOTSTRAP_SERVER_1="$KAFKA_BOOTSTRAP_SERVER_2" \ + --from-literal=KAFKA_BOOTSTRAP_SERVER_2="$KAFKA_BOOTSTRAP_SERVER_2" \ --dry-run=client \ -o yaml | kubectl apply -f - diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 3e79417..69219a8 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -6,7 +6,9 @@ spring: timeout: ${REDIS_TIMEOUT:3s} kafka: - bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092} + bootstrap-servers: + - ${KAFKA_BOOTSTRAP_SERVER_1} + - ${KAFKA_BOOTSTRAP_SERVER_2} app: redis: diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 84ea0e7..7a26073 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -86,7 +86,7 @@ app: host: ${OPENSEARCH_HOST:localhost} port: ${OPENSEARCH_PORT:9200} kafka: - bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS} + bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:${KAFKA_BOOTSTRAP_SERVER_1},${KAFKA_BOOTSTRAP_SERVER_2}} group-id: ${KAFKA_GROUP_ID:main-dev} auto-offset-reset: ${KAFKA_AUTO_OFFSET_RESET:earliest} listeners-enabled: ${KAFKA_LISTENERS_ENABLED:true} From aa4248999a23cff3564f2b26e11dab01e419cea3 Mon Sep 17 00:00:00 2001 From: kimgeon Date: Tue, 14 Jul 2026 16:08:27 +0900 Subject: [PATCH 32/55] fix kafka_bootstrap_servers update 2 --- .github/workflows/deploy-backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 1cfbd88..ea18f0b 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -254,7 +254,7 @@ jobs: --from-literal=JWT_SECRET_KEY="$JWT_SECRET_KEY" \ --from-literal=REDIS_HOST="$REDIS_HOST" \ --from-literal=REDIS_PORT="$REDIS_PORT" \ - --from-literal=KAFKA_BOOTSTRAP_SERVER_1="$KAFKA_BOOTSTRAP_SERVER_2" \ + --from-literal=KAFKA_BOOTSTRAP_SERVER_1="$KAFKA_BOOTSTRAP_SERVER_1" \ --from-literal=KAFKA_BOOTSTRAP_SERVER_2="$KAFKA_BOOTSTRAP_SERVER_2" \ --dry-run=client \ -o yaml | From aa255e0f323bc2ed943c178592c77d8255d82a41 Mon Sep 17 00:00:00 2001 From: kimgeon Date: Tue, 14 Jul 2026 16:10:14 +0900 Subject: [PATCH 33/55] fix kafka_bootstrap_servers update 3 --- src/main/resources/application-dev.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 69219a8..25d41de 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -6,9 +6,7 @@ spring: timeout: ${REDIS_TIMEOUT:3s} kafka: - bootstrap-servers: - - ${KAFKA_BOOTSTRAP_SERVER_1} - - ${KAFKA_BOOTSTRAP_SERVER_2} + bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:${KAFKA_BOOTSTRAP_SERVER_1},${KAFKA_BOOTSTRAP_SERVER_2}} app: redis: From 3089b98af8c45e4ddaf94a034243093a01d6c6d6 Mon Sep 17 00:00:00 2001 From: haseokyung6 Date: Tue, 14 Jul 2026 16:17:43 +0900 Subject: [PATCH 34/55] fix: update kafka bootstrap --- src/main/resources/application.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 7a26073..91d2476 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -86,7 +86,9 @@ app: host: ${OPENSEARCH_HOST:localhost} port: ${OPENSEARCH_PORT:9200} kafka: - bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:${KAFKA_BOOTSTRAP_SERVER_1},${KAFKA_BOOTSTRAP_SERVER_2}} + bootstrap-servers: + - ${KAFKA_BOOTSTRAP_SERVER_1} + - ${KAFKA_BOOTSTRAP_SERVER_2} group-id: ${KAFKA_GROUP_ID:main-dev} auto-offset-reset: ${KAFKA_AUTO_OFFSET_RESET:earliest} listeners-enabled: ${KAFKA_LISTENERS_ENABLED:true} From ce2e5e04fbb18cd875f42021f18daeaa5bc497db Mon Sep 17 00:00:00 2001 From: haseokyung6 Date: Tue, 14 Jul 2026 16:33:45 +0900 Subject: [PATCH 35/55] fix: kafka bootstrap update --- src/main/resources/application-dev.yaml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 69219a8..0fa8261 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -5,11 +5,13 @@ spring: port: ${REDIS_PORT:6379} timeout: ${REDIS_TIMEOUT:3s} + # Spring Boot 자동 설정이 참조할 Kafka 주소 kafka: bootstrap-servers: - ${KAFKA_BOOTSTRAP_SERVER_1} - ${KAFKA_BOOTSTRAP_SERVER_2} + app: redis: cache: @@ -20,24 +22,26 @@ app: host: ${OPENSEARCH_HOST:localhost} port: ${OPENSEARCH_PORT:9200} + # Backend의 KafkaConfig.java가 실제로 읽는 설정 kafka: bootstrap-servers: - - ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092} + - ${KAFKA_BOOTSTRAP_SERVER_1} + - ${KAFKA_BOOTSTRAP_SERVER_2} - group-id: ${KAFKA_GROUP_ID:assembly-dev} + group-id: ${KAFKA_GROUP_ID:main-dev} auto-offset-reset: ${KAFKA_AUTO_OFFSET_RESET:earliest} + listeners-enabled: ${KAFKA_LISTENERS_ENABLED:true} consumer: agv-group-id: ${AGV_CONSUMER_GROUP_ID:main-agv-group} + logging: level: root: info com.aims.backend: debug org.hibernate.SQL: debug - file: - path: ${LOG_PATH:logs/dev} management: health: From 652c26232ba264d9f12602070945324de156f578 Mon Sep 17 00:00:00 2001 From: kimgeon Date: Tue, 14 Jul 2026 16:36:08 +0900 Subject: [PATCH 36/55] fix kafka_bootstrap_servers update 4 --- src/main/resources/application-dev.yaml | 4 +++- src/main/resources/application.yaml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 25d41de..e995aac 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -6,7 +6,9 @@ spring: timeout: ${REDIS_TIMEOUT:3s} kafka: - bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:${KAFKA_BOOTSTRAP_SERVER_1},${KAFKA_BOOTSTRAP_SERVER_2}} + bootstrap-servers: + - ${KAFKA_BOOTSTRAP_SERVER_1:localhost:9092} + - ${KAFKA_BOOTSTRAP_SERVER_2:localhost:9092} app: redis: diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 7a26073..e4d5c6c 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -86,7 +86,9 @@ app: host: ${OPENSEARCH_HOST:localhost} port: ${OPENSEARCH_PORT:9200} kafka: - bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:${KAFKA_BOOTSTRAP_SERVER_1},${KAFKA_BOOTSTRAP_SERVER_2}} + bootstrap-servers: + - ${KAFKA_BOOTSTRAP_SERVER_1:localhost:9092} + - ${KAFKA_BOOTSTRAP_SERVER_2:localhost:9092} group-id: ${KAFKA_GROUP_ID:main-dev} auto-offset-reset: ${KAFKA_AUTO_OFFSET_RESET:earliest} listeners-enabled: ${KAFKA_LISTENERS_ENABLED:true} From 49d82485aa73da59589eaa5411919e88e9aa9d1c Mon Sep 17 00:00:00 2001 From: haseokyung6 Date: Tue, 14 Jul 2026 16:36:11 +0900 Subject: [PATCH 37/55] fix: check --- src/main/resources/application.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 91d2476..730e19c 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -89,6 +89,7 @@ app: bootstrap-servers: - ${KAFKA_BOOTSTRAP_SERVER_1} - ${KAFKA_BOOTSTRAP_SERVER_2} + group-id: ${KAFKA_GROUP_ID:main-dev} auto-offset-reset: ${KAFKA_AUTO_OFFSET_RESET:earliest} listeners-enabled: ${KAFKA_LISTENERS_ENABLED:true} From 83187b5de7cf8cfbf09d6c471fdeff517deac7ea Mon Sep 17 00:00:00 2001 From: kimgeon Date: Tue, 14 Jul 2026 16:59:52 +0900 Subject: [PATCH 38/55] fix : kafkaConfig update --- src/main/java/com/aims/backend/config/KafkaConfig.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/aims/backend/config/KafkaConfig.java b/src/main/java/com/aims/backend/config/KafkaConfig.java index b7d9b07..96a6105 100644 --- a/src/main/java/com/aims/backend/config/KafkaConfig.java +++ b/src/main/java/com/aims/backend/config/KafkaConfig.java @@ -28,6 +28,14 @@ public class KafkaConfig { private final KafkaCustomProperties kafkaCustomProperties; + private String getBootstrapServers() { + + return String.join( + ",", + kafkaCustomProperties.getBootstrapServers() + ); + } + @Bean public ProducerFactory producerFactory() { @@ -35,7 +43,7 @@ public ProducerFactory producerFactory() { properties.put( ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, - kafkaCustomProperties.getBootstrapServers() + getBootstrapServers() ); properties.put( From fdf2e6ed43063361c4a671d62195f0a4ed7e6295 Mon Sep 17 00:00:00 2001 From: kimgeon Date: Wed, 15 Jul 2026 09:30:15 +0900 Subject: [PATCH 39/55] feat : alert event Imageurl update --- .../backend/config/AlertImageProperties.java | 46 +++++++++++++++++++ .../service/alert/AlertEventSaveService.java | 7 ++- src/main/resources/application.yaml | 19 ++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/aims/backend/config/AlertImageProperties.java diff --git a/src/main/java/com/aims/backend/config/AlertImageProperties.java b/src/main/java/com/aims/backend/config/AlertImageProperties.java new file mode 100644 index 0000000..4666e81 --- /dev/null +++ b/src/main/java/com/aims/backend/config/AlertImageProperties.java @@ -0,0 +1,46 @@ +package com.aims.backend.config; + +import com.aims.backend.domain.alert.AlertSeverity; +import com.aims.backend.domain.alert.AlertType; +import com.aims.backend.domain.dashboard.enums.ProcessCode; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.EnumMap; +import java.util.Map; + +@Getter +@Setter +@Component +@ConfigurationProperties(prefix = "app.alert.image") +public class AlertImageProperties { + + private Map processImages = new EnumMap<>(ProcessCode.class); + + /** processCode + alertType + severity 조합으로 S3 이미지 URL 결정 */ + public String resolve(ProcessCode processCode, AlertType alertType, AlertSeverity severity) { + + ImageSet imageSet = processImages.get(processCode); + if (imageSet == null || severity == null) { + return null; + } + + if (severity == AlertSeverity.CAUTION) { + return imageSet.getWarning(); // 설비/공정 관계없이 동일 이미지 + } + + return alertType == AlertType.EQUIPMENT + ? imageSet.getDangerEquipment() + : imageSet.getDangerProcess(); + } + + @Getter + @Setter + public static class ImageSet { + private String dangerEquipment; + private String dangerProcess; + private String warning; + } +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java b/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java index c154a35..00421f1 100644 --- a/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java +++ b/src/main/java/com/aims/backend/service/alert/AlertEventSaveService.java @@ -1,5 +1,6 @@ package com.aims.backend.service.alert; +import com.aims.backend.config.AlertImageProperties; import com.aims.backend.domain.alert.AlertActionStatus; import com.aims.backend.domain.alert.AlertEvent; import com.aims.backend.domain.alert.AlertSeverity; @@ -131,8 +132,6 @@ private CalculatedAlert toCalculatedAlert(JsonNode root) { defaultContents(alertType, processCode, equipmentId, text(root, "contents", "message", "description")); String eventKey = eventKey(root, alertType, processCode, equipmentId, title, eventId); - String imageUrl = - text(root, "imageUrl", "image_url"); BigDecimal riskScore = score(root, BigDecimal.ZERO, BigDecimal.valueOf(100), "riskScore", "risk_score"); @@ -153,6 +152,9 @@ private CalculatedAlert toCalculatedAlert(JsonNode root) { LocalDateTime scoreCalculatedAt = LocalDateTime.now(); + String imageUrl = + alertImageProperties.resolve(processCode, alertType, severity); + log.info( "Adaptive eRPN calculated. eventId={}, riskScore={}, occurrenceScore={}, detectionScore={}, priorityScore={}, severity={}", eventId, @@ -181,6 +183,7 @@ private CalculatedAlert toCalculatedAlert(JsonNode root) { scoreCalculatedAt ); } + private final AlertImageProperties alertImageProperties; private void publishRealtimeAlert(CalculatedAlert calculatedAlert) { diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 730e19c..99bf11d 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -53,6 +53,25 @@ management: enabled: false app: + alert: + image: + process-images: + PRESS: + danger-equipment: s3://event-image-858507113889-ap-northeast-2-an/press_1.png + danger-process: s3://event-image-858507113889-ap-northeast-2-an/press_2.png + warning: s3://event-image-858507113889-ap-northeast-2-an/press_3.png + BODY: + danger-equipment: s3://event-image-858507113889-ap-northeast-2-an/body_1.png + danger-process: s3://event-image-858507113889-ap-northeast-2-an/body_2.png + warning: s3://event-image-858507113889-ap-northeast-2-an/body_3.png + PAINT: + danger-equipment: s3://event-image-858507113889-ap-northeast-2-an/paint_1.png + danger-process: s3://event-image-858507113889-ap-northeast-2-an/paint_2.png + warning: s3://event-image-858507113889-ap-northeast-2-an/paint_3.png + ASSEMBLY: + danger-equipment: s3://event-image-858507113889-ap-northeast-2-an/assamble_1.png + danger-process: s3://event-image-858507113889-ap-northeast-2-an/assamble_2.png + warning: s3://event-image-858507113889-ap-northeast-2-an/assamble_3.png datasource: main: driver-class-name: ${MAIN_DB_DRIVER_CLASS_NAME:com.mysql.cj.jdbc.Driver} From 315708a062f94c994f5491dd73dd0e3797ffc70b Mon Sep 17 00:00:00 2001 From: kimgeon Date: Wed, 15 Jul 2026 10:39:29 +0900 Subject: [PATCH 40/55] fix : alertimageproperties update --- .../backend/config/AlertImageProperties.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/aims/backend/config/AlertImageProperties.java b/src/main/java/com/aims/backend/config/AlertImageProperties.java index 4666e81..32d67e1 100644 --- a/src/main/java/com/aims/backend/config/AlertImageProperties.java +++ b/src/main/java/com/aims/backend/config/AlertImageProperties.java @@ -3,14 +3,17 @@ import com.aims.backend.domain.alert.AlertSeverity; import com.aims.backend.domain.alert.AlertType; import com.aims.backend.domain.dashboard.enums.ProcessCode; +import jakarta.annotation.PostConstruct; import lombok.Getter; import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.EnumMap; import java.util.Map; +@Slf4j @Getter @Setter @Component @@ -19,16 +22,28 @@ public class AlertImageProperties { private Map processImages = new EnumMap<>(ProcessCode.class); - /** processCode + alertType + severity 조합으로 S3 이미지 URL 결정 */ + @PostConstruct + public void logLoadedImages() { + log.info("AlertImageProperties loaded. size={}, keys={}", + processImages.size(), processImages.keySet()); + processImages.forEach((code, set) -> + log.info(" {} -> dangerEquipment={}, dangerProcess={}, warning={}", + code, set.getDangerEquipment(), set.getDangerProcess(), set.getWarning())); + } + public String resolve(ProcessCode processCode, AlertType alertType, AlertSeverity severity) { ImageSet imageSet = processImages.get(processCode); + + log.info("resolve() called. processCode={}, alertType={}, severity={}, imageSetFound={}", + processCode, alertType, severity, imageSet != null); + if (imageSet == null || severity == null) { return null; } if (severity == AlertSeverity.CAUTION) { - return imageSet.getWarning(); // 설비/공정 관계없이 동일 이미지 + return imageSet.getWarning(); } return alertType == AlertType.EQUIPMENT From 4e3f77bc8256253b3a477e5b1f38ddd1f5e4e6f2 Mon Sep 17 00:00:00 2001 From: chani2104 Date: Wed, 15 Jul 2026 11:21:45 +0900 Subject: [PATCH 41/55] fix: Agv Dispatch Scheduler --- .../dashboard/AgvDispatchScheduler.java | 62 +++++++++++++++---- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java index 0840eaf..774eb05 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java @@ -3,6 +3,8 @@ import com.aims.backend.dto.dashboard.DispatchRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import net.javacrumbs.shedlock.core.LockAssert; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -22,58 +24,92 @@ public class AgvDispatchScheduler { private final AgvDispatchQueueService dispatchQueueService; private final AgvSimulationService agvSimulationService; + + /** + * 같은 JVM 내부에서 Scheduler 중복 실행 방지 + */ private final AtomicBoolean running = new AtomicBoolean(false); - @Scheduled(fixedDelayString = "${app.agv.dispatch-scheduler.fixed-delay-ms:1000}") + @Scheduled( + fixedDelayString = + "${app.agv.dispatch-scheduler.fixed-delay-ms:1000}" + ) + @SchedulerLock( + name = "agvDispatchScheduler", + lockAtMostFor = "PT10S", + lockAtLeastFor = "PT0.5S" + ) public void dispatchQueuedEvents() { + + LockAssert.assertLocked(); + if (!running.compareAndSet(false, true)) { - log.debug("이전 AGV Dispatch Scheduler 작업이 진행 중이므로 이번 실행을 건너뜁니다."); + log.debug( + "[AGV DISPATCH SCHEDULER] 이전 작업이 아직 진행 중입니다." + ); return; } try { + for (String routeCode : dispatchQueueService.routeCodes()) { dispatchOne(routeCode); } + } finally { running.set(false); } } private void dispatchOne(String routeCode) { - dispatchQueueService.poll(routeCode) + + dispatchQueueService + .poll(routeCode) .ifPresent(request -> dispatch(routeCode, request)); + } private void dispatch( String routeCode, DispatchRequest request ) { + try { - boolean dispatched = agvSimulationService.dispatchAgv( - request.eventId(), - request.carMasterId(), - request.processCode() - ); + + boolean dispatched = + agvSimulationService.dispatchAgv( + request.eventId(), + request.carMasterId(), + request.processCode() + ); if (!dispatched) { - dispatchQueueService.requeueFirst(routeCode, request); + + dispatchQueueService.requeueFirst( + routeCode, + request + ); log.debug( - "[AGV DISPATCH SCHEDULER][{}] 사용 가능한 AGV 없음. eventId={} 재대기", + "[AGV DISPATCH][{}] AGV 없음. eventId={} 재대기", routeCode, request.eventId() ); } + } catch (Exception e) { - dispatchQueueService.requeueFirst(routeCode, request); + + dispatchQueueService.requeueFirst( + routeCode, + request + ); log.error( - "[AGV DISPATCH SCHEDULER][{}] 배정 실패. eventId={} 재대기", + "[AGV DISPATCH][{}] dispatch 실패. eventId={}", routeCode, request.eventId(), e ); } } -} +} \ No newline at end of file From d8762fbff0989c6731f3239cff8f17856f216b23 Mon Sep 17 00:00:00 2001 From: chani2104 Date: Wed, 15 Jul 2026 11:23:10 +0900 Subject: [PATCH 42/55] fix: Agv Dispatch Scheduler --- .../com/aims/backend/service/dashboard/AgvDispatchScheduler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java index 774eb05..db34990 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvDispatchScheduler.java @@ -109,6 +109,7 @@ private void dispatch( routeCode, request.eventId(), e + ); } } From 29a821bbf8032a54a4ead2b337c253d7a6023da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=B8?= <91dlwnsgh@naver.com> Date: Wed, 15 Jul 2026 16:05:58 +0900 Subject: [PATCH 43/55] =?UTF-8?q?=EC=8B=9C=EB=8B=88=EC=96=B4=20=EC=B6=94?= =?UTF-8?q?=EC=B2=9C=EC=A1=B0=EC=B9=98=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/client/AssemblyArrivalClient.java | 2 +- .../backend/common/status/ErrorStatus.java | 31 ++ .../backend/config/jpa/MainJpaConfig.java | 8 +- .../alert/AlertEventController.java | 23 ++ .../eventAnalysis/AssemblyAnalysisResult.java | 41 ++ .../eventAnalysis/BodyAnalysisResult.java | 41 ++ .../ManufacturingAnalysisResult.java | 61 +++ .../eventAnalysis/PaintAnalysisResult.java | 43 ++ .../eventAnalysis/PressAnalysisResult.java | 40 ++ .../eventAnalysis/ActionTimelineResponse.java | 33 ++ .../AssemblyAnalysisResultDto.java | 31 ++ .../eventAnalysis/BodyAnalysisResultDto.java | 31 ++ .../ManufacturingAnalysisResultDto.java | 44 ++ .../eventAnalysis/PaintAnalysisResultDto.java | 33 ++ .../eventAnalysis/PressAnalysisResultDto.java | 31 ++ .../eventAnalysis/RecommendationResponse.java | 28 ++ .../dto/eventAnalysis/SimilarResult.java | 14 + .../AssemblyAnalysisResultRepository.java | 31 ++ .../BodyAnalysisResultRepository.java | 30 ++ ...ManufacturingAnalysisResultRepository.java | 20 + .../PaintAnalysisResultRepository.java | 31 ++ .../PressAnalysisResultRepository.java | 36 ++ .../AlertRecommendationService.java | 387 ++++++++++++++++++ 23 files changed, 1068 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/aims/backend/domain/eventAnalysis/AssemblyAnalysisResult.java create mode 100644 src/main/java/com/aims/backend/domain/eventAnalysis/BodyAnalysisResult.java create mode 100644 src/main/java/com/aims/backend/domain/eventAnalysis/ManufacturingAnalysisResult.java create mode 100644 src/main/java/com/aims/backend/domain/eventAnalysis/PaintAnalysisResult.java create mode 100644 src/main/java/com/aims/backend/domain/eventAnalysis/PressAnalysisResult.java create mode 100644 src/main/java/com/aims/backend/dto/eventAnalysis/ActionTimelineResponse.java create mode 100644 src/main/java/com/aims/backend/dto/eventAnalysis/AssemblyAnalysisResultDto.java create mode 100644 src/main/java/com/aims/backend/dto/eventAnalysis/BodyAnalysisResultDto.java create mode 100644 src/main/java/com/aims/backend/dto/eventAnalysis/ManufacturingAnalysisResultDto.java create mode 100644 src/main/java/com/aims/backend/dto/eventAnalysis/PaintAnalysisResultDto.java create mode 100644 src/main/java/com/aims/backend/dto/eventAnalysis/PressAnalysisResultDto.java create mode 100644 src/main/java/com/aims/backend/dto/eventAnalysis/RecommendationResponse.java create mode 100644 src/main/java/com/aims/backend/dto/eventAnalysis/SimilarResult.java create mode 100644 src/main/java/com/aims/backend/repository/eventAnalysis/AssemblyAnalysisResultRepository.java create mode 100644 src/main/java/com/aims/backend/repository/eventAnalysis/BodyAnalysisResultRepository.java create mode 100644 src/main/java/com/aims/backend/repository/eventAnalysis/ManufacturingAnalysisResultRepository.java create mode 100644 src/main/java/com/aims/backend/repository/eventAnalysis/PaintAnalysisResultRepository.java create mode 100644 src/main/java/com/aims/backend/repository/eventAnalysis/PressAnalysisResultRepository.java create mode 100644 src/main/java/com/aims/backend/service/eventAnalysis/AlertRecommendationService.java diff --git a/src/main/java/com/aims/backend/client/AssemblyArrivalClient.java b/src/main/java/com/aims/backend/client/AssemblyArrivalClient.java index 0dae786..8f3dcb1 100644 --- a/src/main/java/com/aims/backend/client/AssemblyArrivalClient.java +++ b/src/main/java/com/aims/backend/client/AssemblyArrivalClient.java @@ -41,7 +41,7 @@ public void notifyAgvArrived( Assembly 도착 API 요청 url={} - eventId={} + {} ============================== diff --git a/src/main/java/com/aims/backend/common/status/ErrorStatus.java b/src/main/java/com/aims/backend/common/status/ErrorStatus.java index df09a71..2cd2858 100644 --- a/src/main/java/com/aims/backend/common/status/ErrorStatus.java +++ b/src/main/java/com/aims/backend/common/status/ErrorStatus.java @@ -10,6 +10,37 @@ @AllArgsConstructor public enum ErrorStatus implements BaseErrorCode { + + RECOMMENDATION_ANALYSIS_NOT_FOUND( + HttpStatus.NOT_FOUND, + "RECOMMEND404_1", + "분석 결과를 찾을 수 없습니다." + ), + + RECOMMENDATION_PRESS_ANALYSIS_NOT_FOUND( + HttpStatus.NOT_FOUND, + "RECOMMEND404_2", + "PRESS 분석 결과를 찾을 수 없습니다." + ), + + RECOMMENDATION_SIMILAR_EVENT_NOT_FOUND( + HttpStatus.NOT_FOUND, + "RECOMMEND404_3", + "유사 이벤트를 찾을 수 없습니다." + ), + + RECOMMENDATION_ALERT_EVENT_NOT_FOUND( + HttpStatus.NOT_FOUND, + "RECOMMEND404_4", + "유사 이벤트 정보를 찾을 수 없습니다." + ), + + RECOMMENDATION_TIMELINE_NOT_FOUND( + HttpStatus.NOT_FOUND, + "RECOMMEND404_5", + "유사 이벤트의 조치 이력이 존재하지 않습니다." + ), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 내부 오류가 발생했습니다."), BAD_REQUEST(HttpStatus.BAD_REQUEST, "COMMON400", "잘못된 요청입니다."), UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "COMMON401", "인증이 필요합니다."), diff --git a/src/main/java/com/aims/backend/config/jpa/MainJpaConfig.java b/src/main/java/com/aims/backend/config/jpa/MainJpaConfig.java index 725d535..061c22d 100644 --- a/src/main/java/com/aims/backend/config/jpa/MainJpaConfig.java +++ b/src/main/java/com/aims/backend/config/jpa/MainJpaConfig.java @@ -44,7 +44,13 @@ public DataSource mainDataSource() { public LocalContainerEntityManagerFactoryBean mainEntityManagerFactory() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(mainDataSource()); - em.setPackagesToScan("com.aims.backend.domain.commons", "com.aims.backend.domain.mainpage", "com.aims.backend.domain.user", "com.aims.backend.domain.dashboard", "com.aims.backend.domain.alert"); + em.setPackagesToScan( + "com.aims.backend.domain.commons", + "com.aims.backend.domain.mainpage", + "com.aims.backend.domain.user", + "com.aims.backend.domain.dashboard", + "com.aims.backend.domain.alert", + "com.aims.backend.domain.eventAnalysis"); em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); return em; } diff --git a/src/main/java/com/aims/backend/controller/alert/AlertEventController.java b/src/main/java/com/aims/backend/controller/alert/AlertEventController.java index 82611b1..ee52a3e 100644 --- a/src/main/java/com/aims/backend/controller/alert/AlertEventController.java +++ b/src/main/java/com/aims/backend/controller/alert/AlertEventController.java @@ -6,6 +6,11 @@ import com.aims.backend.dto.alert.AlertEventResponse; import com.aims.backend.dto.alert.AlertPrioritySummaryResponse; import com.aims.backend.dto.alert.AlertSearchRequest; +import com.aims.backend.dto.eventAnalysis.RecommendationResponse; +import com.aims.backend.service.eventAnalysis.AlertRecommendationService; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; import com.aims.backend.dto.alert.ActionTimelineResponse; import com.aims.backend.service.alert.AlertPrioritySummaryService; import com.aims.backend.dto.alert.ActionTimelineCreateRequest; @@ -42,6 +47,7 @@ public class AlertEventController { private final AlertPrioritySummaryService alertPrioritySummaryService; private final ActionTimelineService actionTimelineService; private final TokenProvider tokenProvider; + private final AlertRecommendationService alertRecommendationService; @GetMapping public ApiResponse> getAlerts(@ModelAttribute AlertSearchRequest request) { @@ -98,4 +104,21 @@ public ApiResponse createActionTimeline( return ApiResponse.success(actionTimelineService.createTimeline(logNo, request, principal)); } + + @Operation( + summary = "유사 장애 조치 추천", + description = "현재 이벤트와 가장 유사한 과거 이벤트를 찾아 추천 조치 방법을 반환합니다." + ) + @GetMapping("/{logNo}/recommendation") + public ApiResponse getRecommendation( + @PathVariable String logNo) { + + RecommendationResponse response = + alertRecommendationService.getRecommendation(logNo); + + return ApiResponse.success( + response, + "유사 장애 추천 조회 성공" + ); + } } diff --git a/src/main/java/com/aims/backend/domain/eventAnalysis/AssemblyAnalysisResult.java b/src/main/java/com/aims/backend/domain/eventAnalysis/AssemblyAnalysisResult.java new file mode 100644 index 0000000..2f16aa6 --- /dev/null +++ b/src/main/java/com/aims/backend/domain/eventAnalysis/AssemblyAnalysisResult.java @@ -0,0 +1,41 @@ +package com.aims.backend.domain.eventAnalysis; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Table(name = "assembly_analysis_result") + +public class AssemblyAnalysisResult { + + @Id + @Column(name = "analysis_result_id") + private Long analysisResultId; + + @Column(name = "expected_sequence") + private String expectedSequence; + + @Column(name = "actual_sequence") + private String actualSequence; + + @Column(name = "sequence_error_count") + private Integer sequenceErrorCount; + + @Column(name = "missing_part_count") + private Integer missingPartCount; + + @Column(name = "fastening_error_count") + private Integer fasteningErrorCount; + + @Column(name = "created_at") + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/aims/backend/domain/eventAnalysis/BodyAnalysisResult.java b/src/main/java/com/aims/backend/domain/eventAnalysis/BodyAnalysisResult.java new file mode 100644 index 0000000..61706cc --- /dev/null +++ b/src/main/java/com/aims/backend/domain/eventAnalysis/BodyAnalysisResult.java @@ -0,0 +1,41 @@ +package com.aims.backend.domain.eventAnalysis; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Table(name = "body_analysis_result") + +public class BodyAnalysisResult { + + @Id + @Column(name = "analysis_result_id") + private Long analysisResultId; + + @Column(name = "robot_motion_status") + private String robotMotionStatus; + + @Column(name = "robot_operation_mode") + private String robotOperationMode; + + @Column(name = "robot_vibration_score") + private Double robotVibrationScore; + + @Column(name = "frequency_peak_band") + private String frequencyPeakBand; + + @Column(name = "frequency_peak_value") + private Double frequencyPeakValue; + + @Column(name = "created_at") + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/aims/backend/domain/eventAnalysis/ManufacturingAnalysisResult.java b/src/main/java/com/aims/backend/domain/eventAnalysis/ManufacturingAnalysisResult.java new file mode 100644 index 0000000..4b536e1 --- /dev/null +++ b/src/main/java/com/aims/backend/domain/eventAnalysis/ManufacturingAnalysisResult.java @@ -0,0 +1,61 @@ +package com.aims.backend.domain.eventAnalysis; + +import com.aims.backend.domain.dashboard.enums.ProcessCode; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Table(name = "manufacturing_analysis_result") + +public class ManufacturingAnalysisResult { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "event_id", nullable = false, unique = true) + private String eventId; + + @Column(name = "car_master_id") + private Long carMasterId; + + @Column(name = "equipment_id") + private Long equipmentId; + + @Enumerated(EnumType.STRING) + @Column(name = "process_code", nullable = false) + private ProcessCode processCode; + + @Column(name = "event_time") + private LocalDateTime eventTime; + + @Column(name = "is_abnormal") + private Integer isAbnormal; + + @Column(name = "abnormal_type") + private String abnormalType; + + @Column(name = "severity") + private String severity; + + @Column(name = "risk_score") + private Double riskScore; + + @Column(name = "analysis_message") + private String analysisMessage; + + @Column(name = "analyzed_at") + private LocalDateTime analyzedAt; + + @Column(name = "created_at") + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/aims/backend/domain/eventAnalysis/PaintAnalysisResult.java b/src/main/java/com/aims/backend/domain/eventAnalysis/PaintAnalysisResult.java new file mode 100644 index 0000000..7ee2a73 --- /dev/null +++ b/src/main/java/com/aims/backend/domain/eventAnalysis/PaintAnalysisResult.java @@ -0,0 +1,43 @@ +package com.aims.backend.domain.eventAnalysis; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Table(name = "paint_analysis_result") +public class PaintAnalysisResult { + + @Id + @Column(name = "analysis_result_id") + private Long analysisResultId; + + @Column(name = "image_position") + private String imagePosition; + + @Column(name = "thermal_std_temp") + private Double thermalStdTemp; + + @Column(name = "thickness_value") + private Double thicknessValue; + + @Column(name = "defeat_score") + private Double defeatScore; + + @Column(name = "vision_label") + private String visionLabel; + + @Column(name = "surface_quality_score") + private Double surfaceQualityScore; + + @Column(name = "created_at") + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/domain/eventAnalysis/PressAnalysisResult.java b/src/main/java/com/aims/backend/domain/eventAnalysis/PressAnalysisResult.java new file mode 100644 index 0000000..e226fa7 --- /dev/null +++ b/src/main/java/com/aims/backend/domain/eventAnalysis/PressAnalysisResult.java @@ -0,0 +1,40 @@ +package com.aims.backend.domain.eventAnalysis; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Table(name = "press_analysis_result") + +public class PressAnalysisResult { + @Id + @Column(name = "analysis_result_id") + private Long analysisResultId; + + @Column(name = "count_increase_yn") + private Integer countIncreaseYn; + + @Column(name = "target_cycle_time_sec") + private Double targetCycleTimeSec; + + @Column(name = "actual_cycle_time_sec") + private Double actualCycleTimeSec; + + @Column(name = "cycle_time_gap_sec") + private Double cycleTimeGapSec; + + @Column(name = "timestamp_delay_sec") + private Double timestampDelaySec; + + @Column(name = "created_at") + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/aims/backend/dto/eventAnalysis/ActionTimelineResponse.java b/src/main/java/com/aims/backend/dto/eventAnalysis/ActionTimelineResponse.java new file mode 100644 index 0000000..784ed8e --- /dev/null +++ b/src/main/java/com/aims/backend/dto/eventAnalysis/ActionTimelineResponse.java @@ -0,0 +1,33 @@ +package com.aims.backend.dto.eventAnalysis; + +import com.aims.backend.domain.alert.ActionCategory; +import com.aims.backend.domain.user.UserRole; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ActionTimelineResponse { + + private Long actionId; + + private LocalDateTime actionTime; + + private String empNo; + + private String empName; + + private UserRole empRole; + + private ActionCategory actionCategory; + + private String actionContent; + + private String actionResult; +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/dto/eventAnalysis/AssemblyAnalysisResultDto.java b/src/main/java/com/aims/backend/dto/eventAnalysis/AssemblyAnalysisResultDto.java new file mode 100644 index 0000000..1ba988d --- /dev/null +++ b/src/main/java/com/aims/backend/dto/eventAnalysis/AssemblyAnalysisResultDto.java @@ -0,0 +1,31 @@ +package com.aims.backend.dto.eventAnalysis; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AssemblyAnalysisResultDto { + + private Long analysisResultId; + + private String expectedSequence; + + private String actualSequence; + + private Integer sequenceErrorCount; + + private Integer missingPartCount; + + private Integer fasteningErrorCount; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/aims/backend/dto/eventAnalysis/BodyAnalysisResultDto.java b/src/main/java/com/aims/backend/dto/eventAnalysis/BodyAnalysisResultDto.java new file mode 100644 index 0000000..6fb81c5 --- /dev/null +++ b/src/main/java/com/aims/backend/dto/eventAnalysis/BodyAnalysisResultDto.java @@ -0,0 +1,31 @@ +package com.aims.backend.dto.eventAnalysis; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BodyAnalysisResultDto { + + private Long analysisResultId; + + private String robotMotionStatus; + + private String robotOperationMode; + + private Double robotVibrationScore; + + private String frequencyPeakBand; + + private Double frequencyPeakValue; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/aims/backend/dto/eventAnalysis/ManufacturingAnalysisResultDto.java b/src/main/java/com/aims/backend/dto/eventAnalysis/ManufacturingAnalysisResultDto.java new file mode 100644 index 0000000..d30bfe3 --- /dev/null +++ b/src/main/java/com/aims/backend/dto/eventAnalysis/ManufacturingAnalysisResultDto.java @@ -0,0 +1,44 @@ +package com.aims.backend.dto.eventAnalysis; + +import com.aims.backend.domain.dashboard.enums.ProcessCode; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ManufacturingAnalysisResultDto { + + private Long id; + + private String eventId; + + private Long carMasterId; + + private Long equipmentId; + + private ProcessCode processCode; + + private LocalDateTime eventTime; + + private Integer isAbnormal; + + private String abnormalType; + + private String severity; + + private Double riskScore; + + private String analysisMessage; + + private LocalDateTime analyzedAt; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/aims/backend/dto/eventAnalysis/PaintAnalysisResultDto.java b/src/main/java/com/aims/backend/dto/eventAnalysis/PaintAnalysisResultDto.java new file mode 100644 index 0000000..b820e58 --- /dev/null +++ b/src/main/java/com/aims/backend/dto/eventAnalysis/PaintAnalysisResultDto.java @@ -0,0 +1,33 @@ +package com.aims.backend.dto.eventAnalysis; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PaintAnalysisResultDto { + + private Long analysisResultId; + + private String imagePosition; + + private Double thermalStdTemp; + + private Double thicknessValue; + + private Double defeatScore; + + private String visionLabel; + + private Double surfaceQualityScore; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/aims/backend/dto/eventAnalysis/PressAnalysisResultDto.java b/src/main/java/com/aims/backend/dto/eventAnalysis/PressAnalysisResultDto.java new file mode 100644 index 0000000..28c7749 --- /dev/null +++ b/src/main/java/com/aims/backend/dto/eventAnalysis/PressAnalysisResultDto.java @@ -0,0 +1,31 @@ +package com.aims.backend.dto.eventAnalysis; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PressAnalysisResultDto { + + private Long analysisResultId; + + private Integer countIncreaseYn; + + private Double targetCycleTimeSec; + + private Double actualCycleTimeSec; + + private Double cycleTimeGapSec; + + private Double timestampDelaySec; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/aims/backend/dto/eventAnalysis/RecommendationResponse.java b/src/main/java/com/aims/backend/dto/eventAnalysis/RecommendationResponse.java new file mode 100644 index 0000000..ede5d14 --- /dev/null +++ b/src/main/java/com/aims/backend/dto/eventAnalysis/RecommendationResponse.java @@ -0,0 +1,28 @@ +package com.aims.backend.dto.eventAnalysis; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RecommendationResponse { + + private String similarLogNo; + + private Double confidence; + + private String handler; + + private String recommendedAction; + + private String recommendationReason; + + private List actionTimeline; +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/dto/eventAnalysis/SimilarResult.java b/src/main/java/com/aims/backend/dto/eventAnalysis/SimilarResult.java new file mode 100644 index 0000000..8945213 --- /dev/null +++ b/src/main/java/com/aims/backend/dto/eventAnalysis/SimilarResult.java @@ -0,0 +1,14 @@ +package com.aims.backend.dto.eventAnalysis; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Builder; + +@Builder +@Getter +@AllArgsConstructor +public class SimilarResult { + private String similarLogNo; + private double confidence; + private String logNo; +} diff --git a/src/main/java/com/aims/backend/repository/eventAnalysis/AssemblyAnalysisResultRepository.java b/src/main/java/com/aims/backend/repository/eventAnalysis/AssemblyAnalysisResultRepository.java new file mode 100644 index 0000000..235e490 --- /dev/null +++ b/src/main/java/com/aims/backend/repository/eventAnalysis/AssemblyAnalysisResultRepository.java @@ -0,0 +1,31 @@ +package com.aims.backend.repository.eventAnalysis; + +import com.aims.backend.domain.eventAnalysis.AssemblyAnalysisResult; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.data.domain.Pageable; + +import java.util.Optional; +import java.util.List; + +public interface AssemblyAnalysisResultRepository + extends JpaRepository { + + Optional findByAnalysisResultId(Long id); + + List findAll(); + + @Query(""" + SELECT a + FROM AssemblyAnalysisResult a + WHERE a.analysisResultId <> :currentId + ORDER BY ABS(a.sequenceErrorCount - :sequenceErrorCount) + """) + List findTop20Similar( + @Param("currentId") Long currentId, + @Param("sequenceErrorCount") Integer sequenceErrorCount, + Pageable pageable + ); + +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/repository/eventAnalysis/BodyAnalysisResultRepository.java b/src/main/java/com/aims/backend/repository/eventAnalysis/BodyAnalysisResultRepository.java new file mode 100644 index 0000000..096c62e --- /dev/null +++ b/src/main/java/com/aims/backend/repository/eventAnalysis/BodyAnalysisResultRepository.java @@ -0,0 +1,30 @@ +package com.aims.backend.repository.eventAnalysis; + +import com.aims.backend.domain.eventAnalysis.BodyAnalysisResult; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.data.domain.Pageable; + +import java.util.Optional; +import java.util.List; + +public interface BodyAnalysisResultRepository + extends JpaRepository { + + Optional findByAnalysisResultId(Long id); + + List findAll(); + + @Query(""" + SELECT b + FROM BodyAnalysisResult b + WHERE b.analysisResultId <> :currentId + ORDER BY ABS(b.robotVibrationScore - :robotVibrationScore) + """) + List findTop20Similar( + @Param("currentId") Long currentId, + @Param("robotVibrationScore") Double robotVibrationScore, + Pageable pageable + ); +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/repository/eventAnalysis/ManufacturingAnalysisResultRepository.java b/src/main/java/com/aims/backend/repository/eventAnalysis/ManufacturingAnalysisResultRepository.java new file mode 100644 index 0000000..5b53a94 --- /dev/null +++ b/src/main/java/com/aims/backend/repository/eventAnalysis/ManufacturingAnalysisResultRepository.java @@ -0,0 +1,20 @@ +package com.aims.backend.repository.eventAnalysis; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.aims.backend.domain.dashboard.enums.ProcessCode; +import com.aims.backend.domain.eventAnalysis.ManufacturingAnalysisResult; + +import java.util.Optional; +import java.util.List; + +public interface ManufacturingAnalysisResultRepository + extends JpaRepository { + + Optional findByEventId(String eventId); + + Optional findById(Long id); + + List findByProcessCode(ProcessCode processCode); + +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/repository/eventAnalysis/PaintAnalysisResultRepository.java b/src/main/java/com/aims/backend/repository/eventAnalysis/PaintAnalysisResultRepository.java new file mode 100644 index 0000000..7ba6362 --- /dev/null +++ b/src/main/java/com/aims/backend/repository/eventAnalysis/PaintAnalysisResultRepository.java @@ -0,0 +1,31 @@ +package com.aims.backend.repository.eventAnalysis; + +import com.aims.backend.domain.eventAnalysis.PaintAnalysisResult; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.data.domain.Pageable; + +import java.util.Optional; +import java.util.List; + +public interface PaintAnalysisResultRepository + extends JpaRepository { + Optional findByAnalysisResultId(Long id); + + List findAll(); + + @Query(""" + SELECT p + FROM PaintAnalysisResult p + WHERE p.analysisResultId <> :currentId + AND p.visionLabel = :visionLabel + ORDER BY ABS(p.surfaceQualityScore - :surfaceQualityScore) + """) + List findTop20Similar( + @Param("currentId") Long currentId, + @Param("visionLabel") String visionLabel, + @Param("surfaceQualityScore") Double surfaceQualityScore, + Pageable pageable + ); +} \ No newline at end of file diff --git a/src/main/java/com/aims/backend/repository/eventAnalysis/PressAnalysisResultRepository.java b/src/main/java/com/aims/backend/repository/eventAnalysis/PressAnalysisResultRepository.java new file mode 100644 index 0000000..72e5a1c --- /dev/null +++ b/src/main/java/com/aims/backend/repository/eventAnalysis/PressAnalysisResultRepository.java @@ -0,0 +1,36 @@ +package com.aims.backend.repository.eventAnalysis; + +import com.aims.backend.domain.eventAnalysis.PressAnalysisResult; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.data.domain.Pageable; + +import java.util.Optional; +import java.util.List; + + +public interface PressAnalysisResultRepository + extends JpaRepository { + + Optional findByAnalysisResultId(Long id); + + List findAll(); + + @Query(""" + SELECT p + FROM PressAnalysisResult p + WHERE p.analysisResultId <> :currentId + AND p.countIncreaseYn = :countIncreaseYn + ORDER BY + ABS(p.cycleTimeGapSec - :cycleGap), + ABS(COALESCE(p.timestampDelaySec, 0.0) - COALESCE(:timestampDelay, 0.0)) + """) + List findMostSimilar( + @Param("currentId") Long currentId, + @Param("countIncreaseYn") Integer countIncreaseYn, + @Param("cycleGap") Double cycleGap, + @Param("timestampDelay") Double timestampDelay, + Pageable pageable + ); +} diff --git a/src/main/java/com/aims/backend/service/eventAnalysis/AlertRecommendationService.java b/src/main/java/com/aims/backend/service/eventAnalysis/AlertRecommendationService.java new file mode 100644 index 0000000..175bfa3 --- /dev/null +++ b/src/main/java/com/aims/backend/service/eventAnalysis/AlertRecommendationService.java @@ -0,0 +1,387 @@ +package com.aims.backend.service.eventAnalysis; + +import com.aims.backend.domain.alert.ActionTimeline; +import com.aims.backend.domain.alert.AlertEvent; +import com.aims.backend.domain.dashboard.enums.ProcessCode; +import com.aims.backend.domain.eventAnalysis.*; +import com.aims.backend.dto.eventAnalysis.*; +import com.aims.backend.repository.alert.ActionTimelineRepository; +import com.aims.backend.repository.alert.AlertEventRepository; +import com.aims.backend.repository.eventAnalysis.*; +import lombok.RequiredArgsConstructor; + +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import com.aims.backend.exception.GeneralException; +import com.aims.backend.common.status.ErrorStatus; + +import java.util.List; + + + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AlertRecommendationService { + + private final AlertEventRepository alertEventRepository; + private final ActionTimelineRepository actionTimelineRepository; + + private final ManufacturingAnalysisResultRepository manufacturingAnalysisResultRepository; + + private final PressAnalysisResultRepository pressAnalysisResultRepository; + private final BodyAnalysisResultRepository bodyAnalysisResultRepository; + private final PaintAnalysisResultRepository paintAnalysisResultRepository; + private final AssemblyAnalysisResultRepository assemblyAnalysisResultRepository; + + private String getLogNo(Long analysisResultId) { + + ManufacturingAnalysisResult analysisResult = + manufacturingAnalysisResultRepository + .findById(analysisResultId) + .orElseThrow(() -> + new GeneralException( + ErrorStatus.RECOMMENDATION_ALERT_EVENT_NOT_FOUND, + "AlertEvent를 찾을 수 없습니다. eventId=" + analysisResultId)); + + ManufacturingAnalysisResult analysis = + manufacturingAnalysisResultRepository.findById(analysisResultId) + .orElseThrow(() -> + new IllegalArgumentException( + "분석 결과를 찾을 수 없습니다. analysisResultId=" + analysisResultId)); + + AlertEvent alertEvent = + alertEventRepository.findByEventId(analysis.getEventId()) + .orElseThrow(() -> + new IllegalArgumentException( + "유사 장애의 AlertEvent를 찾을 수 없습니다. eventId=" + + analysis.getEventId())); + + return alertEvent.getLogNo(); + } + + @Transactional(readOnly = true) + public RecommendationResponse getRecommendation(String logNo) { + + AlertEvent alertEvent = alertEventRepository.findById(logNo) + .orElseThrow(() -> + new GeneralException( + ErrorStatus.RECOMMENDATION_ANALYSIS_NOT_FOUND)); + + + + ManufacturingAnalysisResult analysis = + manufacturingAnalysisResultRepository + .findByEventId(alertEvent.getEventId()) + .orElseThrow(() -> + new IllegalArgumentException("분석 결과를 찾을 수 없습니다.")); + + String similarLogNo = switch (analysis.getProcessCode()) { + + case PRESS -> + findSimilarPressEvent(analysis); + + case BODY -> + findSimilarBodyEvent(analysis); + + case PAINT -> + findSimilarPaintEvent(analysis); + + case ASSEMBLY -> + findSimilarAssemblyEvent(analysis); + + default -> + throw new IllegalArgumentException( + "지원하지 않는 공정입니다. : " + analysis.getProcessCode()); + }; + + List timelines = + actionTimelineRepository + .findByLogNoOrderByActionTimeAsc(similarLogNo); + + if (timelines.isEmpty()) { + throw new GeneralException( + ErrorStatus.RECOMMENDATION_TIMELINE_NOT_FOUND); + } + + return buildRecommendationResponse( + similarLogNo, + timelines + ); + } + + private String findSimilarPressEvent( + ManufacturingAnalysisResult currentAnalysis) { + + PressAnalysisResult current = pressAnalysisResultRepository + .findByAnalysisResultId(currentAnalysis.getId()) + .orElseThrow(() -> + new GeneralException( + ErrorStatus.RECOMMENDATION_PRESS_ANALYSIS_NOT_FOUND)); + + PressAnalysisResult best = + pressAnalysisResultRepository.findMostSimilar( + current.getAnalysisResultId(), + current.getCountIncreaseYn(), + current.getCycleTimeGapSec(), + current.getTimestampDelaySec(), + PageRequest.of(0, 1) + ) + .stream() + .findFirst() + .orElseThrow(() -> + new GeneralException( + ErrorStatus.RECOMMENDATION_SIMILAR_EVENT_NOT_FOUND)); + + return getLogNo(best.getAnalysisResultId()); + } + + + private String findSimilarBodyEvent( + ManufacturingAnalysisResult currentAnalysis) { + + BodyAnalysisResult current = + bodyAnalysisResultRepository + .findByAnalysisResultId(currentAnalysis.getId()) + .orElseThrow(() -> + new IllegalArgumentException("현재 BODY 분석 결과가 없습니다.")); + + List candidates = + bodyAnalysisResultRepository.findAll(); + + BodyAnalysisResult best = null; + double minScore = Double.MAX_VALUE; + + for (BodyAnalysisResult candidate : candidates) { + + if (candidate.getAnalysisResultId().equals(current.getAnalysisResultId())) { + continue; + } + + double score = 0; + + if (current.getRobotVibrationScore() != null && + candidate.getRobotVibrationScore() != null) { + + score += Math.abs( + current.getRobotVibrationScore() + - candidate.getRobotVibrationScore()); + } + + if (current.getFrequencyPeakValue() != null && + candidate.getFrequencyPeakValue() != null) { + + score += Math.abs( + current.getFrequencyPeakValue() + - candidate.getFrequencyPeakValue()); + } + + if (current.getFrequencyPeakBand() != null && + candidate.getFrequencyPeakBand() != null && + !current.getFrequencyPeakBand() + .equals(candidate.getFrequencyPeakBand())) { + + score += 5; + } + + if (current.getRobotMotionStatus() != null && + candidate.getRobotMotionStatus() != null && + !current.getRobotMotionStatus() + .equals(candidate.getRobotMotionStatus())) { + + score += 3; + } + + if (current.getRobotOperationMode() != null && + candidate.getRobotOperationMode() != null && + !current.getRobotOperationMode() + .equals(candidate.getRobotOperationMode())) { + + score += 2; + } + + if (score < minScore) { + minScore = score; + best = candidate; + } + } + + if (best == null) { + throw new IllegalArgumentException("유사한 BODY 이벤트가 없습니다."); + } + + return getLogNo(best.getAnalysisResultId()); + } + + private String findSimilarPaintEvent( + ManufacturingAnalysisResult currentAnalysis) { + + PaintAnalysisResult current = + paintAnalysisResultRepository + .findByAnalysisResultId(currentAnalysis.getId()) + .orElseThrow(() -> + new IllegalArgumentException("현재 PAINT 분석 결과가 없습니다.")); + + List candidates = + paintAnalysisResultRepository.findTop20Similar( + current.getAnalysisResultId(), + current.getVisionLabel(), + current.getSurfaceQualityScore(), + PageRequest.of(0, 20) + ); + + PaintAnalysisResult best = null; + double minScore = Double.MAX_VALUE; + + for (PaintAnalysisResult candidate : candidates) { + + if (candidate.getAnalysisResultId().equals(current.getAnalysisResultId())) { + continue; + } + + double score = 0; + + if (current.getThermalStdTemp() != null && + candidate.getThermalStdTemp() != null) { + score += Math.abs( + current.getThermalStdTemp() + - candidate.getThermalStdTemp()); + } + + if (current.getThicknessValue() != null && + candidate.getThicknessValue() != null) { + score += Math.abs( + current.getThicknessValue() + - candidate.getThicknessValue()); + } + + if (current.getDefeatScore() != null && + candidate.getDefeatScore() != null) { + score += Math.abs( + current.getDefeatScore() + - candidate.getDefeatScore()); + } + + if (current.getSurfaceQualityScore() != null && + candidate.getSurfaceQualityScore() != null) { + score += Math.abs( + current.getSurfaceQualityScore() + - candidate.getSurfaceQualityScore()); + } + + if (current.getVisionLabel() != null && + candidate.getVisionLabel() != null && + !current.getVisionLabel().equals(candidate.getVisionLabel())) { + score += 5; + } + + if (score < minScore) { + minScore = score; + best = candidate; + } + } + + if (best == null) { + throw new IllegalArgumentException("유사한 PAINT 이벤트를 찾을 수 없습니다."); + } + + return getLogNo(best.getAnalysisResultId()); + } + + private String findSimilarAssemblyEvent( + ManufacturingAnalysisResult currentAnalysis) { + + AssemblyAnalysisResult current = + assemblyAnalysisResultRepository + .findByAnalysisResultId(currentAnalysis.getId()) + .orElseThrow(() -> + new IllegalArgumentException("현재 ASSEMBLY 분석 결과가 없습니다.")); + + List candidates = + assemblyAnalysisResultRepository.findTop20Similar( + current.getAnalysisResultId(), + current.getSequenceErrorCount(), + PageRequest.of(0, 20) + ); + + AssemblyAnalysisResult best = null; + double minScore = Double.MAX_VALUE; + + for (AssemblyAnalysisResult candidate : candidates) { + + double score = 0; + + if (current.getSequenceErrorCount() != null && + candidate.getSequenceErrorCount() != null) { + score += Math.abs( + current.getSequenceErrorCount() + - candidate.getSequenceErrorCount()); + } + if (current.getMissingPartCount() != null && + candidate.getMissingPartCount() != null) { + score += Math.abs( + current.getMissingPartCount() + - candidate.getMissingPartCount()); + } + if (current.getFasteningErrorCount() != null && + candidate.getFasteningErrorCount() != null) { + score += Math.abs( + current.getFasteningErrorCount() + - candidate.getFasteningErrorCount()); + } + if (current.getExpectedSequence() != null && + candidate.getExpectedSequence() != null && + !current.getExpectedSequence().equals(candidate.getExpectedSequence())) { + score += 5; + } + if (current.getActualSequence() != null && + candidate.getActualSequence() != null && + !current.getActualSequence().equals(candidate.getActualSequence())) { + score += 8; + } + if (score < minScore) { + minScore = score; + best = candidate; + } + } + if (best == null) { + throw new IllegalArgumentException("유사한 ASSEMBLY 이벤트를 찾을 수 없습니다."); + } + + return getLogNo(best.getAnalysisResultId()); + } + + + private RecommendationResponse buildRecommendationResponse( + String similarLogNo, + List timelines + ) { + + ActionTimeline lastTimeline = timelines.get(timelines.size() - 1); + + List timelineResponses = timelines.stream() + .map(timeline -> ActionTimelineResponse.builder() + .actionId(timeline.getActionId()) + .actionTime(timeline.getActionTime()) + .empNo(timeline.getEmpNo()) + .empName(timeline.getEmpName()) + .empRole(timeline.getEmpRole()) + .actionCategory(timeline.getActionCategory()) + .actionContent(timeline.getActionContent()) + .actionResult(timeline.getActionResult()) + .build()) + .toList(); + + return RecommendationResponse.builder() + .similarLogNo(similarLogNo) + .confidence(1.0) + .handler(lastTimeline.getEmpName()) + .recommendedAction(lastTimeline.getActionContent()) + .recommendationReason(lastTimeline.getActionResult()) + .actionTimeline(timelineResponses) + .build(); + } +} \ No newline at end of file From dd7adc896a5587c24d57641f5f1dcd6b4e8c28d5 Mon Sep 17 00:00:00 2001 From: chani2104 Date: Fri, 17 Jul 2026 10:40:01 +0900 Subject: [PATCH 44/55] =?UTF-8?q?fix:=20Agv=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EA=B3=84=EC=82=B0=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/aims/backend/dto/dashboard/AgvOperationResponse.java | 5 +++-- .../com/aims/backend/dto/dashboard/AgvRealtimeState.java | 5 +++-- .../aims/backend/service/dashboard/AgvSimulationService.java | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/aims/backend/dto/dashboard/AgvOperationResponse.java b/src/main/java/com/aims/backend/dto/dashboard/AgvOperationResponse.java index 321f597..b9affb2 100644 --- a/src/main/java/com/aims/backend/dto/dashboard/AgvOperationResponse.java +++ b/src/main/java/com/aims/backend/dto/dashboard/AgvOperationResponse.java @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import java.time.Instant; import java.time.LocalDateTime; @Getter @@ -25,9 +26,9 @@ public class AgvOperationResponse { private Integer delaySeconds; - private LocalDateTime startedAt; + private Instant startedAt; - private LocalDateTime expectedArrivalTime; + private Instant expectedArrivalTime; private String routeCode; diff --git a/src/main/java/com/aims/backend/dto/dashboard/AgvRealtimeState.java b/src/main/java/com/aims/backend/dto/dashboard/AgvRealtimeState.java index 56eff2c..6f908e2 100644 --- a/src/main/java/com/aims/backend/dto/dashboard/AgvRealtimeState.java +++ b/src/main/java/com/aims/backend/dto/dashboard/AgvRealtimeState.java @@ -7,6 +7,7 @@ import lombok.Setter; import java.time.Duration; +import java.time.Instant; import java.time.LocalDateTime; @Getter @@ -38,9 +39,9 @@ public class AgvRealtimeState { private Integer delaySeconds; - private LocalDateTime startedAt; + private Instant startedAt; - private LocalDateTime expectedArrivalTime; + private Instant expectedArrivalTime; public static AgvRealtimeState empty(Long agvId) { return AgvRealtimeState.builder() diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java b/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java index 960789e..15755db 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java @@ -13,6 +13,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.support.TransactionTemplate; +import java.time.Instant; import java.time.LocalDateTime; import java.util.List; import java.util.Map; @@ -196,8 +197,8 @@ private void startMovingSession( Long carMasterId, RouteInfo routeInfo ) { - LocalDateTime startedAt = LocalDateTime.now(); - LocalDateTime expectedArrivalTime = + Instant startedAt = Instant.now(); + Instant expectedArrivalTime = startedAt.plusSeconds(MOVE_DURATION_SECONDS); AgvRealtimeState state = AgvRealtimeState.builder() From 91431bf5c7798fac09895bd1e663f2a3c5ee203e Mon Sep 17 00:00:00 2001 From: chani2104 Date: Fri, 17 Jul 2026 11:00:36 +0900 Subject: [PATCH 45/55] =?UTF-8?q?fix:=20Agv=20=C3=AC=C2=8B=20AGV=20Logic?= =?UTF-8?q?=20Refix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aims/backend/dto/dashboard/AgvRealtimeState.java | 3 +-- .../service/dashboard/AgvSimulationService.java | 11 +++++------ .../service/dashboard/AgvTransportStateScheduler.java | 6 +++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/aims/backend/dto/dashboard/AgvRealtimeState.java b/src/main/java/com/aims/backend/dto/dashboard/AgvRealtimeState.java index 6f908e2..50dfd70 100644 --- a/src/main/java/com/aims/backend/dto/dashboard/AgvRealtimeState.java +++ b/src/main/java/com/aims/backend/dto/dashboard/AgvRealtimeState.java @@ -8,7 +8,6 @@ import java.time.Duration; import java.time.Instant; -import java.time.LocalDateTime; @Getter @Setter @@ -51,7 +50,7 @@ public static AgvRealtimeState empty(Long agvId) { .build(); } - public void calculateProgress(LocalDateTime now) { + public void calculateProgress(Instant now) { if (startedAt == null || expectedArrivalTime == null) { this.progressRate = 0.0; return; diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java b/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java index 15755db..4982b2e 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvSimulationService.java @@ -14,7 +14,6 @@ import org.springframework.transaction.support.TransactionTemplate; import java.time.Instant; -import java.time.LocalDateTime; import java.util.List; import java.util.Map; @@ -284,8 +283,8 @@ private void startUnloadingSession( Long carMasterId, RouteInfo routeInfo ) { - LocalDateTime startedAt = LocalDateTime.now(); - LocalDateTime expectedEndTime = + Instant startedAt = Instant.now(); + Instant expectedEndTime = startedAt.plusSeconds(UNLOADING_DURATION_SECONDS); AgvRealtimeState state = AgvRealtimeState.builder() @@ -348,8 +347,8 @@ private void saveReturningState( Long agvId, RouteInfo routeInfo ) { - LocalDateTime startedAt = LocalDateTime.now(); - LocalDateTime expectedArrivalTime = + Instant startedAt = Instant.now(); + Instant expectedArrivalTime = startedAt.plusSeconds(RETURN_DURATION_SECONDS); AgvRealtimeState state = AgvRealtimeState.builder() @@ -509,7 +508,7 @@ private AgvOperationResponse toResponse(AgvOperation agv) { AgvRealtimeState state = agvRealtimeRedisService.get(agv.getId()); - state.calculateProgress(LocalDateTime.now()); + state.calculateProgress(Instant.now()); return new AgvOperationResponse( agv.getId(), diff --git a/src/main/java/com/aims/backend/service/dashboard/AgvTransportStateScheduler.java b/src/main/java/com/aims/backend/service/dashboard/AgvTransportStateScheduler.java index aeb9d83..f1a22b5 100644 --- a/src/main/java/com/aims/backend/service/dashboard/AgvTransportStateScheduler.java +++ b/src/main/java/com/aims/backend/service/dashboard/AgvTransportStateScheduler.java @@ -9,7 +9,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; +import java.time.Instant; @Slf4j @Component @@ -37,7 +37,7 @@ public class AgvTransportStateScheduler { public void advanceExpiredStates() { LockAssert.assertLocked(); - LocalDateTime now = LocalDateTime.now(); + Instant now = Instant.now(); for (AgvRealtimeState state : agvRealtimeRedisService.findAll()) { if (!isExpired(state, now)) { @@ -59,7 +59,7 @@ public void advanceExpiredStates() { private boolean isExpired( AgvRealtimeState state, - LocalDateTime now + Instant now ) { return state != null && state.getAgvId() != null From 04f312c2222a1520ec54c0aaaada1795b621ca71 Mon Sep 17 00:00:00 2001 From: chani2104 Date: Fri, 17 Jul 2026 11:09:52 +0900 Subject: [PATCH 46/55] fix: Dashboard Service type fix --- .../com/aims/backend/service/dashboard/DashboardService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/aims/backend/service/dashboard/DashboardService.java b/src/main/java/com/aims/backend/service/dashboard/DashboardService.java index 0763b3e..966f5c6 100644 --- a/src/main/java/com/aims/backend/service/dashboard/DashboardService.java +++ b/src/main/java/com/aims/backend/service/dashboard/DashboardService.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.time.Instant; import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; @@ -204,7 +205,7 @@ private AgvOperationResponse toResponse( agv.getId() ); - realtimeState.calculateProgress(LocalDateTime.now()); + realtimeState.calculateProgress(Instant.now()); return new AgvOperationResponse( agv.getId(), From 0cc03bfdb22862734e81002b67218f5c8f881c7f Mon Sep 17 00:00:00 2001 From: kimgeon Date: Tue, 21 Jul 2026 09:34:37 +0900 Subject: [PATCH 47/55] feat : Security config update --- .../aims/backend/config/SecurityConfig.java | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/aims/backend/config/SecurityConfig.java b/src/main/java/com/aims/backend/config/SecurityConfig.java index fa365f6..9e1cb59 100644 --- a/src/main/java/com/aims/backend/config/SecurityConfig.java +++ b/src/main/java/com/aims/backend/config/SecurityConfig.java @@ -39,23 +39,22 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .accessDeniedHandler(accessDeniedHandler) ) .authorizeHttpRequests(auth -> auth + + // 인증 필요 없는 API .requestMatchers( - "/", - "/api/health", - "/api/test", - "/actuator/health/**", - "/swagger-ui/**", - "/v3/api-docs/**", - "/swagger-resources/**", - "/webjars/**", "/api/auth/login", "/api/auth/signup", "/api/auth/refresh", - "/api/event/**", - "/api/main/process-flow", - "/ws/**", - "/api/ws/**" + "/swagger-ui/**", + "/v3/api-docs/**" ).permitAll() + + // USER 이상 + .requestMatchers("/api/manufacturing/**").authenticated() + .requestMatchers("/api/events/**").authenticated() + .requestMatchers("/api/inspection/**").authenticated() + .requestMatchers("/api/main/**").authenticated() + .anyRequest().authenticated() ) .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) From dbdc5660e287fac90535548672d6a7bd30c9a5c7 Mon Sep 17 00:00:00 2001 From: kimgeon Date: Tue, 21 Jul 2026 12:58:39 +0900 Subject: [PATCH 48/55] fix : security config update cancle --- .../aims/backend/config/SecurityConfig.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/aims/backend/config/SecurityConfig.java b/src/main/java/com/aims/backend/config/SecurityConfig.java index 9e1cb59..fa365f6 100644 --- a/src/main/java/com/aims/backend/config/SecurityConfig.java +++ b/src/main/java/com/aims/backend/config/SecurityConfig.java @@ -39,22 +39,23 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .accessDeniedHandler(accessDeniedHandler) ) .authorizeHttpRequests(auth -> auth - - // 인증 필요 없는 API .requestMatchers( + "/", + "/api/health", + "/api/test", + "/actuator/health/**", + "/swagger-ui/**", + "/v3/api-docs/**", + "/swagger-resources/**", + "/webjars/**", "/api/auth/login", "/api/auth/signup", "/api/auth/refresh", - "/swagger-ui/**", - "/v3/api-docs/**" + "/api/event/**", + "/api/main/process-flow", + "/ws/**", + "/api/ws/**" ).permitAll() - - // USER 이상 - .requestMatchers("/api/manufacturing/**").authenticated() - .requestMatchers("/api/events/**").authenticated() - .requestMatchers("/api/inspection/**").authenticated() - .requestMatchers("/api/main/**").authenticated() - .anyRequest().authenticated() ) .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) From 34784d4e7b7d06c5e34c9e3495025af732ff399f Mon Sep 17 00:00:00 2001 From: mmije0ng Date: Thu, 23 Jul 2026 09:38:48 +0900 Subject: [PATCH 49/55] =?UTF-8?q?docs:=20README=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EB=B0=8F=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 384 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 211 insertions(+), 173 deletions(-) diff --git a/README.md b/README.md index b665cbf..635b85d 100644 --- a/README.md +++ b/README.md @@ -1,225 +1,263 @@ -# backend-main +# AIMS - Backend +### AIMS (Auto Intelligence Manufacturing System) - AI 기반 자동차 스마트팩토리 관제 시스템 + +`backend`는 AIMS의 **Spring Boot 기반 Main Backend 서비스**입니다. 제조/분석 서비스가 Kafka로 전달한 알림 및 공정 분석 이벤트를 소비하고, 알림 이력·분석 결과·AGV 운영 상태를 운영 화면에서 조회할 수 있도록 API와 실시간 WebSocket 메시지를 제공합니다. + +이 Backend가 담당하는 핵심 기능은 다음과 같습니다. + +- Kafka의 `factory.manufacturing.alert` 이벤트를 검증·중복 제거·점수화하여 `AlertEvent`로 저장합니다. +- Kafka의 `factory.manufacturing.analysis` 이벤트 중 `PROCESS_RISK_ANALYSIS` 결과를 이용해 정상 공정의 AGV 배차를 요청합니다. +- 알림 조치 이력과 공정별 분석 결과를 바탕으로 유사 장애 조치 추천을 제공합니다. 이는 현재 코드상 별도 AI 모델 호출이 아니라 Main DB의 유사 분석 결과와 조치 타임라인을 조회하는 방식입니다. +- Redis에 AGV 대기열 및 실시간 운행 상태를 관리하고, `/topic/alerts`와 `/topic/agv`로 운영 화면에 변경 사항을 전달합니다. +- OpenSearch Client 연결 설정은 존재하지만, 이 저장소에서 OpenSearch로 분석 결과를 색인하거나 조회하는 서비스 로직은 확인되지 않습니다. 현재 주된 영속화는 Main DB와 Redis입니다. + +## 핵심 구조 + +```mermaid +flowchart LR + Producer[제조 assembly-service] + Kafka[(Kafka / MSK)] + Backend[AIMS Backend] + MainDB[(MySQL Main DB)] + Redis[(Redis)] + Client[Dashboard Client] + Assembly[Assembly Service] + + Producer -->|factory.manufacturing.alert| Kafka + Producer -->|factory.manufacturing.analysis| Kafka + Kafka -->|AlertEventConsumer| Backend + Kafka -->|ManufacturingAnalysisConsumer| Backend + Backend --> MainDB + Backend --> Redis + Backend -->|STOMP /topic/alerts, /topic/agv| Client + Backend -->|POST /api/internal/agv-arrivals| Assembly +``` -`backend-main`은 AIMS 알림/이벤트 처리 API를 제공하는 Spring Boot 백엔드 서비스입니다. 알림 조회, 상세 확인, 조치 처리, AI 대응 추천, 인증, Gateway 연동을 담당합니다. +현재 Backend 코드에는 `KafkaTemplate` Producer Bean이 정의되어 있지만, 애플리케이션 서비스에서 Kafka로 메시지를 발행하는 호출은 확인되지 않습니다. 따라서 실제 입력 Producer는 외부 제조/분석 서비스로 보는 것이 맞습니다. -## 실행 프로파일 +## 알림 처리 -`backend-main`은 `local`, `dev`, `prod` 프로파일을 사용합니다. 기본 프로파일은 `local`이며, `SPRING_PROFILES_ACTIVE` 환경 변수로 변경합니다. +알림은 `factory.manufacturing.alert` 토픽을 `AlertEventConsumer`가 소비합니다. `AlertEventSaveService`는 JSON을 정규화하고 중복 이벤트를 걸러낸 뒤 점수를 계산하여 DB에 저장합니다. -| 프로파일 | 용도 | 주요 설정 | -| --- | --- | --- | -| `local` | 로컬 개발 환경 | `.env` 기반 로컬 DB/Redis/Kafka/OpenSearch 접속, 상세 SQL 로그 활성화 | -| `dev` | 개발 서버 환경 | 개발 서버용 외부 의존성 접속, Kafka group 기본값 `backend-dev`, 개발 로그 레벨 | -| `prod` | 운영 환경 | 운영 서버용 외부 의존성 접속, Kafka group 기본값 `backend-prod`, 운영 로그 레벨 | +### 처리 순서 -프로파일 변경 예시: +```mermaid +sequenceDiagram + participant A as Alert Producer + participant K as Kafka
factory.manufacturing.alert + participant C as AlertEventConsumer + participant S as AlertEventSaveService + participant W as STOMP WebSocket + participant DB as Main DB + participant UI as Dashboard -```properties -SPRING_PROFILES_ACTIVE=local + A->>K: alert JSON(eventId, alertType, processCode, riskScore) + K->>C: consume(message) + C->>S: save(message) + S->>S: eventId 중복 확인 + S->>S: occurrence / detection / priority 계산 + S->>W: /topic/alerts 실시간 알림 publish + S->>DB: AlertEvent saveAndFlush + W-->>UI: AlertRealtimeMessage ``` -Windows PowerShell: +### 점수와 상태 -```powershell -$env:SPRING_PROFILES_ACTIVE = "dev" -.\gradlew.bat bootRun -``` +- 지원 공정: `PRESS`, `BODY`, `PAINT`, `ASSEMBLY` +- 알림 유형: `PROCESS`, `EQUIPMENT` +- `riskScore`는 0~100 범위이며 필수입니다. +- `occurrenceScore`: 같은 `eventKey`의 최근 30일 발생 빈도 기반 +- `detectionScore`: 과거 조치 상태(`COMPLETED`, `INCOMPLETE`, `NOT_NEEDED`) 기반 +- `priorityScore`: `riskScore × (1 + occurrenceScore) × (1 + detectionScore)` +- `priorityScore >= 250`이면 `DANGER`, 그 미만이면 `CAUTION` +- 같은 `eventId`가 이미 저장되어 있으면 중복 처리하지 않습니다. + +알림 실시간 메시지는 `/topic/alerts`로 발행되며, 알림 이력과 조치 정보는 Main DB의 `alert_event`, `action_timeline`을 통해 조회합니다. WebSocket publish가 실패하더라도 DB 저장 흐름 자체는 계속 진행하도록 처리되어 있습니다. + +### 알림 API -프로파일별 설정 파일: +| 기능 | HTTP API | +| --- | --- | +| 알림 목록/검색 | `GET /api/event` | +| 우선순위 요약 | `GET /api/event/priority-summary?days=7` | +| 알림 상세 | `GET /api/event/{logNo}` | +| eventId로 조회 | `GET /api/event/by-event-id/{eventId}` | +| 조치 상태 변경 | `PATCH /api/event/{logNo}/action` | +| 조치 타임라인 조회 | `GET /api/event/{logNo}/action-timeline` | +| 조치 타임라인 등록 | `POST /api/event/{logNo}/action-timeline` | +| 유사 장애 조치 추천 | `GET /api/event/{logNo}/recommendation` | + +## AGV 배차 및 운반 + +AGV는 분석 결과를 직접 Kafka로 재발행하지 않고, 분석 이벤트를 소비한 뒤 Redis 대기열과 DB 상태를 조합하여 시뮬레이션합니다. + +### 분석 이벤트 → 배차 흐름 + +`ManufacturingAnalysisConsumer`는 `factory.manufacturing.analysis`를 소비하고, `analysisType == PROCESS_RISK_ANALYSIS`인 이벤트만 처리합니다. 동일 `eventId`는 Redis의 `agv:analysis:processed:{eventId}` 키로 1분 동안 중복 방지합니다. 분석 결과가 abnormal이면 AGV를 배차하지 않습니다. + +```mermaid +flowchart TD + E[factory.manufacturing.analysis] --> C[ManufacturingAnalysisConsumer] + C --> T{analysisType == PROCESS_RISK_ANALYSIS?} + T -- 아니오 --> I[무시] + T -- 예 --> D["Redis 중복 확인
agv:analysis:processed:{eventId}"] + D --> X{isAbnormal?} + X -- 예 --> N[AGV 배차하지 않음] + X -- 아니오 --> Q[route별 Redis Queue 적재] + Q --> S[AgvDispatchScheduler
1초 주기 + ShedLock] + S --> A{대기 AGV 존재?} + A -- 아니오 --> R[Queue 선두 재적재 후 대기] + A -- 예 --> DB[agv_operation을 MOVING으로 변경] + DB --> RT[Redis realtime 상태 저장] + RT --> WS[/topic/agv publish] +``` -- 공통 설정: `src/main/resources/application.yaml` -- 로컬 설정: `src/main/resources/application-local.yaml` -- 개발 설정: `src/main/resources/application-dev.yaml` -- 운영 설정: `src/main/resources/application-prod.yaml` +### Route와 Redis 자료구조 -## 주요 기능 +| 출발 공정 | 도착 공정 | routeCode | 배차 Queue 키 | +| --- | --- | --- | --- | +| `PRESS` | `BODY` | `PRESS_BODY` | `agv:dispatch:queue:PRESS_BODY` | +| `BODY` | `PAINT` | `BODY_PAINT` | `agv:dispatch:queue:BODY_PAINT` | +| `PAINT` | `ASSEMBLY` | `PAINT_ASSEMBLY` | `agv:dispatch:queue:PAINT_ASSEMBLY` | +| `ASSEMBLY` | `INSPECTION` | `ASSEMBLY_INSPECTION` | `agv:dispatch:queue:ASSEMBLY_INSPECTION` | -### event +각 route에는 중복 방지용 Set(`agv:dispatch:event-ids:{routeCode}`)도 함께 사용합니다. 사용 가능한 AGV가 없거나 배차 중 예외가 발생하면 요청을 Queue 앞에 다시 넣습니다. -알림과 이벤트 처리 흐름을 담당합니다. +### AGV 상태 머신 -| 기능 | 설명 | 권장 API | -| --- | --- | --- | -| 알림 목록 조회 | 조건, 상태, 기간, 키워드 기반 필터 검색 | `GET /api/events/alerts` | -| 알림 상세 조회 | 단일 알림의 상세 정보 조회 | `GET /api/events/alerts/{alertId}` | -| 조치 이력 조회 | 알림별 조치 이력 조회 | `GET /api/events/alerts/{alertId}/actions` | -| AI 대응 추천 조회 | 알림 원인과 상황 기반 AI 대응 추천 조회 | `GET /api/events/alerts/{alertId}/ai/recommendation` | -| AI 매뉴얼 | 알림 유형별 AI 매뉴얼 조회 | `GET /api/events/alerts/{alertId}/ai/manual` | -| AI 신뢰성 평가 | AI 추천 결과에 대한 신뢰도/근거 평가 조회 | `GET /api/events/alerts/{alertId}/ai/reliability` | -| 알림 요약 조회 | 알림 상태, 유형, 심각도 기준 요약 조회 | `GET /api/events/alerts/summary` | -| 오늘 이벤트 현황 | 당일 발생 이벤트 통계 조회 | `GET /api/events/today` | -| 알림 조치 처리 | 담당자 조치 내용 등록 및 상태 변경 | `POST /api/events/alerts/{alertId}/actions` | -| 조치 불필요 처리 | 알림을 조치 불필요 상태로 변경 | `PATCH /api/events/alerts/{alertId}/dismiss` | - -## 인증 - -인증은 JWT Bearer Token 기반입니다. - -- 인증 헤더: `Authorization: Bearer {accessToken}` -- Access Token 만료 시간: `JWT_ACCESS_EXPIRATION` -- Refresh Token 만료 시간: `JWT_REFRESH_EXPIRATION` -- JWT secret: `JWT_SECRET_KEY` - -현재 Security 설정: - -- Stateless session -- CSRF, form login, HTTP basic 비활성화 -- JWT 필터: `JwtAuthenticationFilter` -- 토큰 생성/검증: `TokenProvider` -- 공개 경로: - - `/` - - `/api/test` - - `/actuator/health` - - `/swagger-ui/**` - - `/v3/api-docs/**` - - `/swagger-resources/**` - - `/webjars/**` -- 그 외 API는 인증 필요 - -## Gateway 설정 - -Gateway는 외부 요청을 `backend-main`으로 라우팅하고 JWT 인증 헤더를 전달합니다. - -권장 라우팅: - -```yaml -spring: - cloud: - gateway: - routes: - - id: backend-main - uri: http://backend-main:8080 - predicates: - - Path=/api/events/**,/api/test/**,/v3/api-docs/backend/** - filters: - - StripPrefix=0 +```mermaid +stateDiagram-v2 + [*] --> WAITING + WAITING --> MOVING: 배차 성공 + MOVING --> UNLOADING: 이동 30초 만료 + UNLOADING --> RETURNING: 하역 5초 만료 + RETURNING --> WAITING: 복귀 30초 만료 ``` -Gateway 연동 시 유지해야 하는 헤더: +- 영속 상태: Main DB `agv_operation` +- 실시간 진행률/예상 도착: Redis `agv:realtime:{agvId}` +- 상태 전이 확인: `AgvTransportStateScheduler`가 1초마다 만료 상태를 검사 +- 다중 Pod 중복 실행 방지: Redis 기반 ShedLock +- Pod 재시작 시 DB 상태와 Redis 상태를 비교하여 하역/복귀 세션을 복구 +- `MOVING` 도착 시 Assembly Service에 `POST {ASSEMBLY_SERVICE_URL}/api/internal/agv-arrivals`로 `eventId`를 전달 +- Assembly 도착 API가 실패해도 AGV 시뮬레이션은 계속 진행 -- `Authorization` -- `Content-Type` -- `X-Request-Id` +AGV 상태가 변경될 때 전체 AGV 목록을 `/topic/agv`로 publish합니다. REST 조회는 다음 API를 사용합니다. -CORS는 `WebConfig`에서 처리하며, 허용 origin은 `cors.allowed-origins` 설정으로 관리합니다. +| 기능 | HTTP API | +| --- | --- | +| AGV 상태 요약 | `GET /api/main/agv-status` | +| 공정 흐름 및 AGV 상세 | `GET /api/main/process-flow` | + +## Kafka 설계 + +### 토픽과 Consumer Group + +| 토픽 | 기본 Partition 수 | Backend Consumer | Consumer Group | 목적 | +| --- | ---: | --- | --- | --- | +| `factory.manufacturing.alert` | 2 | `AlertEventConsumer` | `app.kafka.group-id` | 알림 저장 및 WebSocket 전달 | +| `factory.manufacturing.analysis` | 2 | `ManufacturingAnalysisConsumer` | `app.kafka.consumer.agv-group-id` | 정상 공정 분석 이벤트 기반 AGV 배차 | +| `factory.manufacturing.raw` | 4 | 현재 Backend Listener 없음 | - | 토픽 설정에 정의된 원천 이벤트 채널 | +| `factory.manufacturing.equipment` | 2 | 현재 Backend Listener 없음 | - | 토픽 설정에 정의된 설비 이벤트 채널 | + +`raw`와 `equipment`는 `KafkaCustomProperties`의 기본 토픽 목록에는 있으나 현재 Backend Consumer가 연결되어 있지 않습니다. 토픽 목록에 등록되어 있다는 사실과 실제 소비 중인 토픽을 구분해야 합니다. + +```mermaid +flowchart LR + subgraph K[Kafka / AWS MSK] + RAW[factory.manufacturing.raw
4 partitions] + ANALYSIS[factory.manufacturing.analysis
2 partitions] + ALERT[factory.manufacturing.alert
2 partitions] + EQUIP[factory.manufacturing.equipment
2 partitions] + end + + RAW -. 현재 Backend Listener 없음 .-> B[ AIMS Backend ] + EQUIP -. 현재 Backend Listener 없음 .-> B + ANALYSIS -->|AGV group
main-agv-group*| AC[ManufacturingAnalysisConsumer
concurrency=2] + ALERT -->|일반 backend group
main-*/backend-*| NC[AlertEventConsumer] + AC --> RQ[Redis AGV Queue] + NC --> DB[MySQL AlertEvent] + NC --> WS[STOMP /topic/alerts] +``` -## 기술 스택 +### Kafka Client 동작 -- Java 17 -- Spring Boot 4.0.6 -- Spring WebMVC -- Spring Security -- Spring Data JPA -- QueryDSL -- MySQL, H2 -- Redis Cache -- Kafka -- OpenSearch Java Client -- Spring Cloud OpenFeign -- Springdoc OpenAPI -- JWT -- Spring Boot Actuator -- Spring Boot Admin Client +`KafkaConfig`에서 다음과 같이 구성합니다. -## 패키지 구조 +- Producer/Consumer payload: `String` +- Producer: `acks=all`, `enable.idempotence=true` +- Consumer: auto commit 비활성화, `AckMode.RECORD` +- 기본 offset: `earliest` (local profile은 `latest`로 덮어씀) +- 운영/개발 MSK: `SASL_SSL` + `AWS_MSK_IAM` +- 로컬 기본값: `localhost:9092`, `PLAINTEXT` +- Listener 전체 비활성화: `app.kafka.listeners-enabled=false` -현재 기본 패키지는 `com.aims.backend`입니다. +`AlertEventConsumer`는 `app.kafka.listeners-enabled`가 true일 때만 등록됩니다. 반면 AGV 분석 Consumer는 코드상 `app.kafka.consumer.agv-group-id`를 사용하므로 해당 프로퍼티와 Kafka 접속 정보가 실행 환경에 있어야 합니다. -```text -src/main/java/com/aims/backend -+-- BackendApplication.java -+-- common -| +-- code -| +-- response -| +-- status -+-- config -| +-- jwt -| +-- security -+-- controller -+-- domain -| +-- commons -+-- dto -| +-- auth -| +-- test -+-- exception -| +-- handler -+-- mapper -+-- properties -+-- repository -+-- service -+-- utils -``` +## WebSocket / STOMP -권장 event 패키지 확장: +STOMP endpoint는 `/ws`와 `/api/ws`이며 SockJS를 지원합니다. 서버 브로커 prefix는 `/topic`입니다. -```text -com.aims.backend -+-- controller/event -+-- dto/event -+-- domain/event -+-- repository/event -+-- service/event -+-- mapper/event -``` +| 구독 destination | 메시지 | 발생 시점 | +| --- | --- | --- | +| `/topic/alerts` | `AlertRealtimeMessage` | 새로운 알림을 Kafka에서 수신하고 점수 계산 후 | +| `/topic/agv` | AGV 전체 상태 목록 | 배차, 이동 도착, 하역 시작, 복귀 시작/완료 시 | ## 설정 -프로젝트 루트의 `.env`를 자동으로 읽습니다. +설정 파일은 다음 순서로 관리합니다. -```yaml -spring: - config: - import: optional:file:.env[.properties] -``` +- 공통: `src/main/resources/application.yaml` +- local: `application-local.yaml` +- dev: `application-dev.yaml` +- prod: `application-prod.yaml` -주요 환경 변수: +주요 환경 변수는 다음과 같습니다. | 변수 | 설명 | | --- | --- | -| `APP_NAME` | Spring application name | -| `SPRING_PROFILES_ACTIVE` | 실행 profile | -| `MAIN_DB_JDBC_URL` | Main DB JDBC URL | -| `MAIN_DB_USERNAME` | Main DB 계정 | -| `MAIN_DB_PASSWORD` | Main DB 비밀번호 | -| `SAMPLE_DB_JDBC_URL` | Sample DB JDBC URL | -| `REDIS_HOST` | Redis host | -| `REDIS_PORT` | Redis port | -| `KAFKA_BOOTSTRAP_SERVERS` | Kafka bootstrap servers | -| `KAFKA_GROUP_ID` | Kafka consumer group id | -| `OPENSEARCH_HOST` | OpenSearch host | -| `OPENSEARCH_PORT` | OpenSearch port | -| `JWT_SECRET_KEY` | JWT signing key | +| `SPRING_PROFILES_ACTIVE` | 실행 profile (`local`, `dev`, `prod`) | +| `KAFKA_BOOTSTRAP_SERVER_1`, `KAFKA_BOOTSTRAP_SERVER_2` | Kafka/MSK Broker 주소 | +| `KAFKA_GROUP_ID` | 알림 Consumer Group | +| `AGV_CONSUMER_GROUP_ID` | AGV 분석 Consumer Group | +| `KAFKA_LISTENERS_ENABLED` | Kafka Listener 활성화 여부 | +| `REDIS_HOST`, `REDIS_PORT` | Redis 접속 정보 | +| `MAIN_DB_JDBC_URL`, `MAIN_DB_USERNAME`, `MAIN_DB_PASSWORD` | Main DB 접속 정보 | +| `ASSEMBLY_SERVICE_URL` | Assembly Service 주소 | +| `JWT_SECRET_KEY` | JWT 서명 키 | ## 실행 -```bash -./gradlew bootRun -``` - -Windows: +프로젝트 루트의 `.env`를 준비한 후 실행합니다. ```powershell +$env:SPRING_PROFILES_ACTIVE = "local" .\gradlew.bat bootRun ``` -## 테스트 - -```bash -./gradlew test -``` - -Windows: +테스트: ```powershell .\gradlew.bat test ``` -테스트 profile은 H2 in-memory DB를 사용합니다. +Swagger UI: -## API 문서 +```text +http://localhost:8081/swagger-ui/index.html +``` -로컬 실행 후 Swagger UI에서 API 문서를 확인할 수 있습니다. +## 주요 패키지 ```text -http://localhost:8080/swagger-ui/index.html +src/main/java/com/aims/backend +├─ config/ # Kafka, WebSocket, Security, DB 설정 +├─ controller/alert # 알림 REST API +├─ controller/dashboard # 대시보드·AGV REST API +├─ service/alert # Kafka 알림 소비, 저장, WebSocket publish +├─ service/dashboard # 분석 소비, AGV Queue·Scheduler·상태 전이 +├─ domain/alert # AlertEvent, ActionTimeline +├─ domain/dashboard # AgvOperation 및 공정 도메인 +├─ repository/alert +└─ repository/dashboard ``` From d942da43b1eff3543d64eed7b33e194698031847 Mon Sep 17 00:00:00 2001 From: mmije0ng Date: Thu, 23 Jul 2026 09:40:26 +0900 Subject: [PATCH 50/55] =?UTF-8?q?remove:=20io=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- io/jsonwebtoken/ClaimJwtException.java | 98 -- io/jsonwebtoken/Claims.java | 166 -- io/jsonwebtoken/ClaimsBuilder.java | 29 - io/jsonwebtoken/ClaimsMutator.java | 270 ---- io/jsonwebtoken/Clock.java | 33 - io/jsonwebtoken/CompressionCodec.java | 70 - io/jsonwebtoken/CompressionCodecResolver.java | 50 - io/jsonwebtoken/CompressionCodecs.java | 56 - io/jsonwebtoken/CompressionException.java | 46 - io/jsonwebtoken/ExpiredJwtException.java | 48 - io/jsonwebtoken/Header.java | 166 -- io/jsonwebtoken/HeaderMutator.java | 131 -- io/jsonwebtoken/Identifiable.java | 92 -- io/jsonwebtoken/IncorrectClaimException.java | 52 - io/jsonwebtoken/InvalidClaimException.java | 86 -- io/jsonwebtoken/Jwe.java | 65 - io/jsonwebtoken/JweHeader.java | 170 -- io/jsonwebtoken/JweHeaderMutator.java | 116 -- io/jsonwebtoken/Jws.java | 66 - io/jsonwebtoken/JwsHeader.java | 107 -- io/jsonwebtoken/Jwt.java | 96 -- io/jsonwebtoken/JwtBuilder.java | 1056 ------------- io/jsonwebtoken/JwtException.java | 43 - io/jsonwebtoken/JwtHandler.java | 102 -- io/jsonwebtoken/JwtHandlerAdapter.java | 98 -- io/jsonwebtoken/JwtParser.java | 422 ----- io/jsonwebtoken/JwtParserBuilder.java | 826 ---------- io/jsonwebtoken/JwtVisitor.java | 68 - io/jsonwebtoken/Jwts.java | 1077 ------------- io/jsonwebtoken/Locator.java | 41 - io/jsonwebtoken/LocatorAdapter.java | 112 -- io/jsonwebtoken/MalformedJwtException.java | 43 - io/jsonwebtoken/MissingClaimException.java | 55 - io/jsonwebtoken/PrematureJwtException.java | 50 - io/jsonwebtoken/ProtectedHeader.java | 85 - io/jsonwebtoken/ProtectedHeaderMutator.java | 117 -- io/jsonwebtoken/ProtectedJwt.java | 37 - io/jsonwebtoken/RequiredTypeException.java | 44 - io/jsonwebtoken/SignatureAlgorithm.java | 656 -------- io/jsonwebtoken/SignatureException.java | 47 - io/jsonwebtoken/SigningKeyResolver.java | 75 - .../SigningKeyResolverAdapter.java | 123 -- io/jsonwebtoken/SupportedJwtVisitor.java | 200 --- io/jsonwebtoken/UnsupportedJwtException.java | 47 - io/jsonwebtoken/io/AbstractDeserializer.java | 84 - io/jsonwebtoken/io/AbstractSerializer.java | 75 - io/jsonwebtoken/io/Base64.java | 681 -------- io/jsonwebtoken/io/Base64Decoder.java | 41 - io/jsonwebtoken/io/Base64Encoder.java | 41 - io/jsonwebtoken/io/Base64Support.java | 33 - io/jsonwebtoken/io/Base64UrlDecoder.java | 29 - io/jsonwebtoken/io/Base64UrlEncoder.java | 29 - io/jsonwebtoken/io/CodecException.java | 43 - io/jsonwebtoken/io/CompressionAlgorithm.java | 65 - io/jsonwebtoken/io/Decoder.java | 35 - io/jsonwebtoken/io/Decoders.java | 41 - io/jsonwebtoken/io/DecodingException.java | 43 - .../io/DeserializationException.java | 43 - io/jsonwebtoken/io/Deserializer.java | 48 - io/jsonwebtoken/io/Encoder.java | 35 - io/jsonwebtoken/io/Encoders.java | 41 - io/jsonwebtoken/io/EncodingException.java | 34 - .../io/ExceptionPropagatingDecoder.java | 60 - .../io/ExceptionPropagatingEncoder.java | 60 - io/jsonwebtoken/io/IOException.java | 46 - io/jsonwebtoken/io/Parser.java | 68 - io/jsonwebtoken/io/ParserBuilder.java | 54 - io/jsonwebtoken/io/SerialException.java | 43 - .../io/SerializationException.java | 43 - io/jsonwebtoken/io/Serializer.java | 51 - io/jsonwebtoken/lang/Arrays.java | 119 -- io/jsonwebtoken/lang/Assert.java | 558 ------- io/jsonwebtoken/lang/Builder.java | 32 - io/jsonwebtoken/lang/Classes.java | 416 ----- io/jsonwebtoken/lang/CollectionMutator.java | 61 - io/jsonwebtoken/lang/Collections.java | 576 ------- io/jsonwebtoken/lang/Conjunctor.java | 33 - io/jsonwebtoken/lang/DateFormats.java | 98 -- .../lang/InstantiationException.java | 34 - io/jsonwebtoken/lang/MapMutator.java | 75 - io/jsonwebtoken/lang/Maps.java | 94 -- io/jsonwebtoken/lang/NestedCollection.java | 32 - io/jsonwebtoken/lang/Objects.java | 1031 ------------- io/jsonwebtoken/lang/Registry.java | 51 - io/jsonwebtoken/lang/RuntimeEnvironment.java | 86 -- io/jsonwebtoken/lang/Strings.java | 1371 ----------------- io/jsonwebtoken/lang/Supplier.java | 37 - .../lang/UnknownClassException.java | 64 - io/jsonwebtoken/security/AeadAlgorithm.java | 91 -- io/jsonwebtoken/security/AeadRequest.java | 30 - io/jsonwebtoken/security/AeadResult.java | 53 - .../security/AssociatedDataSupplier.java | 39 - io/jsonwebtoken/security/AsymmetricJwk.java | 75 - .../security/AsymmetricJwkBuilder.java | 81 - io/jsonwebtoken/security/Curve.java | 41 - .../security/DecryptAeadRequest.java | 28 - .../security/DecryptionKeyRequest.java | 42 - io/jsonwebtoken/security/DigestAlgorithm.java | 101 -- io/jsonwebtoken/security/DigestSupplier.java | 35 - .../security/DynamicJwkBuilder.java | 388 ----- io/jsonwebtoken/security/EcPrivateJwk.java | 43 - .../security/EcPrivateJwkBuilder.java | 27 - io/jsonwebtoken/security/EcPublicJwk.java | 42 - .../security/EcPublicJwkBuilder.java | 27 - io/jsonwebtoken/security/HashAlgorithm.java | 45 - .../security/InvalidKeyException.java | 45 - io/jsonwebtoken/security/IvSupplier.java | 36 - io/jsonwebtoken/security/Jwk.java | 177 --- io/jsonwebtoken/security/JwkBuilder.java | 138 -- .../security/JwkParserBuilder.java | 35 - io/jsonwebtoken/security/JwkSet.java | 47 - io/jsonwebtoken/security/JwkSetBuilder.java | 66 - .../security/JwkSetParserBuilder.java | 57 - io/jsonwebtoken/security/JwkThumbprint.java | 54 - io/jsonwebtoken/security/Jwks.java | 482 ------ io/jsonwebtoken/security/KeyAlgorithm.java | 84 - io/jsonwebtoken/security/KeyBuilder.java | 34 - .../security/KeyBuilderSupplier.java | 40 - io/jsonwebtoken/security/KeyException.java | 44 - .../security/KeyLengthSupplier.java | 31 - io/jsonwebtoken/security/KeyOperation.java | 55 - .../security/KeyOperationBuilder.java | 73 - .../security/KeyOperationPolicied.java | 51 - .../security/KeyOperationPolicy.java | 43 - .../security/KeyOperationPolicyBuilder.java | 114 -- io/jsonwebtoken/security/KeyPair.java | 51 - io/jsonwebtoken/security/KeyPairBuilder.java | 31 - .../security/KeyPairBuilderSupplier.java | 38 - io/jsonwebtoken/security/KeyRequest.java | 77 - io/jsonwebtoken/security/KeyResult.java | 34 - io/jsonwebtoken/security/KeySupplier.java | 34 - io/jsonwebtoken/security/Keys.java | 332 ---- io/jsonwebtoken/security/MacAlgorithm.java | 65 - .../security/MalformedKeyException.java | 44 - .../security/MalformedKeySetException.java | 44 - io/jsonwebtoken/security/Message.java | 36 - io/jsonwebtoken/security/OctetPrivateJwk.java | 68 - .../security/OctetPrivateJwkBuilder.java | 30 - io/jsonwebtoken/security/OctetPublicJwk.java | 63 - .../security/OctetPublicJwkBuilder.java | 31 - io/jsonwebtoken/security/Password.java | 63 - io/jsonwebtoken/security/PrivateJwk.java | 61 - .../security/PrivateJwkBuilder.java | 53 - .../security/PrivateKeyBuilder.java | 38 - io/jsonwebtoken/security/PublicJwk.java | 27 - .../security/PublicJwkBuilder.java | 47 - io/jsonwebtoken/security/Request.java | 57 - io/jsonwebtoken/security/RsaPrivateJwk.java | 43 - .../security/RsaPrivateJwkBuilder.java | 27 - io/jsonwebtoken/security/RsaPublicJwk.java | 42 - .../security/RsaPublicJwkBuilder.java | 28 - io/jsonwebtoken/security/SecretJwk.java | 33 - .../security/SecretJwkBuilder.java | 26 - .../security/SecretKeyAlgorithm.java | 26 - .../security/SecretKeyBuilder.java | 27 - .../security/SecureDigestAlgorithm.java | 55 - io/jsonwebtoken/security/SecureRequest.java | 28 - io/jsonwebtoken/security/SecurityBuilder.java | 52 - .../security/SecurityException.java | 46 - .../security/SignatureAlgorithm.java | 55 - .../security/SignatureException.java | 44 - .../security/UnsupportedKeyException.java | 43 - .../security/VerifyDigestRequest.java | 33 - .../security/VerifySecureDigestRequest.java | 34 - .../security/WeakKeyException.java | 34 - io/jsonwebtoken/security/X509Accessor.java | 138 -- io/jsonwebtoken/security/X509Builder.java | 56 - io/jsonwebtoken/security/X509Mutator.java | 141 -- 168 files changed, 19300 deletions(-) delete mode 100644 io/jsonwebtoken/ClaimJwtException.java delete mode 100644 io/jsonwebtoken/Claims.java delete mode 100644 io/jsonwebtoken/ClaimsBuilder.java delete mode 100644 io/jsonwebtoken/ClaimsMutator.java delete mode 100644 io/jsonwebtoken/Clock.java delete mode 100644 io/jsonwebtoken/CompressionCodec.java delete mode 100644 io/jsonwebtoken/CompressionCodecResolver.java delete mode 100644 io/jsonwebtoken/CompressionCodecs.java delete mode 100644 io/jsonwebtoken/CompressionException.java delete mode 100644 io/jsonwebtoken/ExpiredJwtException.java delete mode 100644 io/jsonwebtoken/Header.java delete mode 100644 io/jsonwebtoken/HeaderMutator.java delete mode 100644 io/jsonwebtoken/Identifiable.java delete mode 100644 io/jsonwebtoken/IncorrectClaimException.java delete mode 100644 io/jsonwebtoken/InvalidClaimException.java delete mode 100644 io/jsonwebtoken/Jwe.java delete mode 100644 io/jsonwebtoken/JweHeader.java delete mode 100644 io/jsonwebtoken/JweHeaderMutator.java delete mode 100644 io/jsonwebtoken/Jws.java delete mode 100644 io/jsonwebtoken/JwsHeader.java delete mode 100644 io/jsonwebtoken/Jwt.java delete mode 100644 io/jsonwebtoken/JwtBuilder.java delete mode 100644 io/jsonwebtoken/JwtException.java delete mode 100644 io/jsonwebtoken/JwtHandler.java delete mode 100644 io/jsonwebtoken/JwtHandlerAdapter.java delete mode 100644 io/jsonwebtoken/JwtParser.java delete mode 100644 io/jsonwebtoken/JwtParserBuilder.java delete mode 100644 io/jsonwebtoken/JwtVisitor.java delete mode 100644 io/jsonwebtoken/Jwts.java delete mode 100644 io/jsonwebtoken/Locator.java delete mode 100644 io/jsonwebtoken/LocatorAdapter.java delete mode 100644 io/jsonwebtoken/MalformedJwtException.java delete mode 100644 io/jsonwebtoken/MissingClaimException.java delete mode 100644 io/jsonwebtoken/PrematureJwtException.java delete mode 100644 io/jsonwebtoken/ProtectedHeader.java delete mode 100644 io/jsonwebtoken/ProtectedHeaderMutator.java delete mode 100644 io/jsonwebtoken/ProtectedJwt.java delete mode 100644 io/jsonwebtoken/RequiredTypeException.java delete mode 100644 io/jsonwebtoken/SignatureAlgorithm.java delete mode 100644 io/jsonwebtoken/SignatureException.java delete mode 100644 io/jsonwebtoken/SigningKeyResolver.java delete mode 100644 io/jsonwebtoken/SigningKeyResolverAdapter.java delete mode 100644 io/jsonwebtoken/SupportedJwtVisitor.java delete mode 100644 io/jsonwebtoken/UnsupportedJwtException.java delete mode 100644 io/jsonwebtoken/io/AbstractDeserializer.java delete mode 100644 io/jsonwebtoken/io/AbstractSerializer.java delete mode 100644 io/jsonwebtoken/io/Base64.java delete mode 100644 io/jsonwebtoken/io/Base64Decoder.java delete mode 100644 io/jsonwebtoken/io/Base64Encoder.java delete mode 100644 io/jsonwebtoken/io/Base64Support.java delete mode 100644 io/jsonwebtoken/io/Base64UrlDecoder.java delete mode 100644 io/jsonwebtoken/io/Base64UrlEncoder.java delete mode 100644 io/jsonwebtoken/io/CodecException.java delete mode 100644 io/jsonwebtoken/io/CompressionAlgorithm.java delete mode 100644 io/jsonwebtoken/io/Decoder.java delete mode 100644 io/jsonwebtoken/io/Decoders.java delete mode 100644 io/jsonwebtoken/io/DecodingException.java delete mode 100644 io/jsonwebtoken/io/DeserializationException.java delete mode 100644 io/jsonwebtoken/io/Deserializer.java delete mode 100644 io/jsonwebtoken/io/Encoder.java delete mode 100644 io/jsonwebtoken/io/Encoders.java delete mode 100644 io/jsonwebtoken/io/EncodingException.java delete mode 100644 io/jsonwebtoken/io/ExceptionPropagatingDecoder.java delete mode 100644 io/jsonwebtoken/io/ExceptionPropagatingEncoder.java delete mode 100644 io/jsonwebtoken/io/IOException.java delete mode 100644 io/jsonwebtoken/io/Parser.java delete mode 100644 io/jsonwebtoken/io/ParserBuilder.java delete mode 100644 io/jsonwebtoken/io/SerialException.java delete mode 100644 io/jsonwebtoken/io/SerializationException.java delete mode 100644 io/jsonwebtoken/io/Serializer.java delete mode 100644 io/jsonwebtoken/lang/Arrays.java delete mode 100644 io/jsonwebtoken/lang/Assert.java delete mode 100644 io/jsonwebtoken/lang/Builder.java delete mode 100644 io/jsonwebtoken/lang/Classes.java delete mode 100644 io/jsonwebtoken/lang/CollectionMutator.java delete mode 100644 io/jsonwebtoken/lang/Collections.java delete mode 100644 io/jsonwebtoken/lang/Conjunctor.java delete mode 100644 io/jsonwebtoken/lang/DateFormats.java delete mode 100644 io/jsonwebtoken/lang/InstantiationException.java delete mode 100644 io/jsonwebtoken/lang/MapMutator.java delete mode 100644 io/jsonwebtoken/lang/Maps.java delete mode 100644 io/jsonwebtoken/lang/NestedCollection.java delete mode 100644 io/jsonwebtoken/lang/Objects.java delete mode 100644 io/jsonwebtoken/lang/Registry.java delete mode 100644 io/jsonwebtoken/lang/RuntimeEnvironment.java delete mode 100644 io/jsonwebtoken/lang/Strings.java delete mode 100644 io/jsonwebtoken/lang/Supplier.java delete mode 100644 io/jsonwebtoken/lang/UnknownClassException.java delete mode 100644 io/jsonwebtoken/security/AeadAlgorithm.java delete mode 100644 io/jsonwebtoken/security/AeadRequest.java delete mode 100644 io/jsonwebtoken/security/AeadResult.java delete mode 100644 io/jsonwebtoken/security/AssociatedDataSupplier.java delete mode 100644 io/jsonwebtoken/security/AsymmetricJwk.java delete mode 100644 io/jsonwebtoken/security/AsymmetricJwkBuilder.java delete mode 100644 io/jsonwebtoken/security/Curve.java delete mode 100644 io/jsonwebtoken/security/DecryptAeadRequest.java delete mode 100644 io/jsonwebtoken/security/DecryptionKeyRequest.java delete mode 100644 io/jsonwebtoken/security/DigestAlgorithm.java delete mode 100644 io/jsonwebtoken/security/DigestSupplier.java delete mode 100644 io/jsonwebtoken/security/DynamicJwkBuilder.java delete mode 100644 io/jsonwebtoken/security/EcPrivateJwk.java delete mode 100644 io/jsonwebtoken/security/EcPrivateJwkBuilder.java delete mode 100644 io/jsonwebtoken/security/EcPublicJwk.java delete mode 100644 io/jsonwebtoken/security/EcPublicJwkBuilder.java delete mode 100644 io/jsonwebtoken/security/HashAlgorithm.java delete mode 100644 io/jsonwebtoken/security/InvalidKeyException.java delete mode 100644 io/jsonwebtoken/security/IvSupplier.java delete mode 100644 io/jsonwebtoken/security/Jwk.java delete mode 100644 io/jsonwebtoken/security/JwkBuilder.java delete mode 100644 io/jsonwebtoken/security/JwkParserBuilder.java delete mode 100644 io/jsonwebtoken/security/JwkSet.java delete mode 100644 io/jsonwebtoken/security/JwkSetBuilder.java delete mode 100644 io/jsonwebtoken/security/JwkSetParserBuilder.java delete mode 100644 io/jsonwebtoken/security/JwkThumbprint.java delete mode 100644 io/jsonwebtoken/security/Jwks.java delete mode 100644 io/jsonwebtoken/security/KeyAlgorithm.java delete mode 100644 io/jsonwebtoken/security/KeyBuilder.java delete mode 100644 io/jsonwebtoken/security/KeyBuilderSupplier.java delete mode 100644 io/jsonwebtoken/security/KeyException.java delete mode 100644 io/jsonwebtoken/security/KeyLengthSupplier.java delete mode 100644 io/jsonwebtoken/security/KeyOperation.java delete mode 100644 io/jsonwebtoken/security/KeyOperationBuilder.java delete mode 100644 io/jsonwebtoken/security/KeyOperationPolicied.java delete mode 100644 io/jsonwebtoken/security/KeyOperationPolicy.java delete mode 100644 io/jsonwebtoken/security/KeyOperationPolicyBuilder.java delete mode 100644 io/jsonwebtoken/security/KeyPair.java delete mode 100644 io/jsonwebtoken/security/KeyPairBuilder.java delete mode 100644 io/jsonwebtoken/security/KeyPairBuilderSupplier.java delete mode 100644 io/jsonwebtoken/security/KeyRequest.java delete mode 100644 io/jsonwebtoken/security/KeyResult.java delete mode 100644 io/jsonwebtoken/security/KeySupplier.java delete mode 100644 io/jsonwebtoken/security/Keys.java delete mode 100644 io/jsonwebtoken/security/MacAlgorithm.java delete mode 100644 io/jsonwebtoken/security/MalformedKeyException.java delete mode 100644 io/jsonwebtoken/security/MalformedKeySetException.java delete mode 100644 io/jsonwebtoken/security/Message.java delete mode 100644 io/jsonwebtoken/security/OctetPrivateJwk.java delete mode 100644 io/jsonwebtoken/security/OctetPrivateJwkBuilder.java delete mode 100644 io/jsonwebtoken/security/OctetPublicJwk.java delete mode 100644 io/jsonwebtoken/security/OctetPublicJwkBuilder.java delete mode 100644 io/jsonwebtoken/security/Password.java delete mode 100644 io/jsonwebtoken/security/PrivateJwk.java delete mode 100644 io/jsonwebtoken/security/PrivateJwkBuilder.java delete mode 100644 io/jsonwebtoken/security/PrivateKeyBuilder.java delete mode 100644 io/jsonwebtoken/security/PublicJwk.java delete mode 100644 io/jsonwebtoken/security/PublicJwkBuilder.java delete mode 100644 io/jsonwebtoken/security/Request.java delete mode 100644 io/jsonwebtoken/security/RsaPrivateJwk.java delete mode 100644 io/jsonwebtoken/security/RsaPrivateJwkBuilder.java delete mode 100644 io/jsonwebtoken/security/RsaPublicJwk.java delete mode 100644 io/jsonwebtoken/security/RsaPublicJwkBuilder.java delete mode 100644 io/jsonwebtoken/security/SecretJwk.java delete mode 100644 io/jsonwebtoken/security/SecretJwkBuilder.java delete mode 100644 io/jsonwebtoken/security/SecretKeyAlgorithm.java delete mode 100644 io/jsonwebtoken/security/SecretKeyBuilder.java delete mode 100644 io/jsonwebtoken/security/SecureDigestAlgorithm.java delete mode 100644 io/jsonwebtoken/security/SecureRequest.java delete mode 100644 io/jsonwebtoken/security/SecurityBuilder.java delete mode 100644 io/jsonwebtoken/security/SecurityException.java delete mode 100644 io/jsonwebtoken/security/SignatureAlgorithm.java delete mode 100644 io/jsonwebtoken/security/SignatureException.java delete mode 100644 io/jsonwebtoken/security/UnsupportedKeyException.java delete mode 100644 io/jsonwebtoken/security/VerifyDigestRequest.java delete mode 100644 io/jsonwebtoken/security/VerifySecureDigestRequest.java delete mode 100644 io/jsonwebtoken/security/WeakKeyException.java delete mode 100644 io/jsonwebtoken/security/X509Accessor.java delete mode 100644 io/jsonwebtoken/security/X509Builder.java delete mode 100644 io/jsonwebtoken/security/X509Mutator.java diff --git a/io/jsonwebtoken/ClaimJwtException.java b/io/jsonwebtoken/ClaimJwtException.java deleted file mode 100644 index 756b367..0000000 --- a/io/jsonwebtoken/ClaimJwtException.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * ClaimJwtException is a subclass of the {@link JwtException} that is thrown after a validation of an JWT claim failed. - * - * @since 0.5 - */ -public abstract class ClaimJwtException extends JwtException { - - /** - * Deprecated as this is an implementation detail accidentally exposed in the JJWT 0.5 public API. It is no - * longer referenced anywhere in JJWT's implementation and will be removed in a future release. - * - * @deprecated will be removed in a future release. - */ - @Deprecated - public static final String INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was: %s."; - - /** - * Deprecated as this is an implementation detail accidentally exposed in the JJWT 0.5 public API. It is no - * longer referenced anywhere in JJWT's implementation and will be removed in a future release. - * - * @deprecated will be removed in a future release. - */ - @Deprecated - public static final String MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was not present in the JWT claims."; - - /** - * The header associated with the Claims that failed validation. - */ - private final Header header; - - /** - * The Claims that failed validation. - */ - private final Claims claims; - - /** - * Creates a new instance with the specified header, claims and exception message. - * - * @param header the header inspected - * @param claims the claims obtained - * @param message the exception message - */ - protected ClaimJwtException(Header header, Claims claims, String message) { - super(message); - this.header = header; - this.claims = claims; - } - - /** - * Creates a new instance with the specified header, claims and exception message as a result of encountering - * the specified {@code cause}. - * - * @param header the header inspected - * @param claims the claims obtained - * @param message the exception message - * @param cause the exception that caused this ClaimJwtException to be thrown. - */ - protected ClaimJwtException(Header header, Claims claims, String message, Throwable cause) { - super(message, cause); - this.header = header; - this.claims = claims; - } - - /** - * Returns the {@link Claims} that failed validation. - * - * @return the {@link Claims} that failed validation. - */ - public Claims getClaims() { - return claims; - } - - /** - * Returns the header associated with the {@link #getClaims() claims} that failed validation. - * - * @return the header associated with the {@link #getClaims() claims} that failed validation. - */ - public Header getHeader() { - return header; - } -} diff --git a/io/jsonwebtoken/Claims.java b/io/jsonwebtoken/Claims.java deleted file mode 100644 index 4f8589f..0000000 --- a/io/jsonwebtoken/Claims.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import java.util.Date; -import java.util.Map; -import java.util.Set; - -/** - * A JWT Claims set. - * - *

This is an immutable JSON map with convenient type-safe getters for JWT standard claim names.

- * - *

Additionally, this interface also extends Map<String, Object>, so you can use standard - * {@code Map} accessor/iterator methods as desired, for example:

- * - *
- * claims.get("someKey");
- * - *

However, because {@code Claims} instances are immutable, calling any of the map mutation methods - * (such as {@code Map.}{@link Map#put(Object, Object) put}, etc) will result in a runtime exception. The - * {@code Map} interface is implemented specifically for the convenience of working with existing Map-based utilities - * and APIs.

- * - * @since 0.1 - */ -public interface Claims extends Map, Identifiable { - - /** - * JWT {@code Issuer} claims parameter name: "iss" - */ - String ISSUER = "iss"; - - /** - * JWT {@code Subject} claims parameter name: "sub" - */ - String SUBJECT = "sub"; - - /** - * JWT {@code Audience} claims parameter name: "aud" - */ - String AUDIENCE = "aud"; - - /** - * JWT {@code Expiration} claims parameter name: "exp" - */ - String EXPIRATION = "exp"; - - /** - * JWT {@code Not Before} claims parameter name: "nbf" - */ - String NOT_BEFORE = "nbf"; - - /** - * JWT {@code Issued At} claims parameter name: "iat" - */ - String ISSUED_AT = "iat"; - - /** - * JWT {@code JWT ID} claims parameter name: "jti" - */ - String ID = "jti"; - - /** - * Returns the JWT - * iss (issuer) value or {@code null} if not present. - * - * @return the JWT {@code iss} value or {@code null} if not present. - */ - String getIssuer(); - - /** - * Returns the JWT - * sub (subject) value or {@code null} if not present. - * - * @return the JWT {@code sub} value or {@code null} if not present. - */ - String getSubject(); - - /** - * Returns the JWT - * aud (audience) value or {@code null} if not present. - * - * @return the JWT {@code aud} value or {@code null} if not present. - */ - Set getAudience(); - - /** - * Returns the JWT - * exp (expiration) timestamp or {@code null} if not present. - * - *

A JWT obtained after this timestamp should not be used.

- * - * @return the JWT {@code exp} value or {@code null} if not present. - */ - Date getExpiration(); - - /** - * Returns the JWT - * nbf (not before) timestamp or {@code null} if not present. - * - *

A JWT obtained before this timestamp should not be used.

- * - * @return the JWT {@code nbf} value or {@code null} if not present. - */ - Date getNotBefore(); - - /** - * Returns the JWT - * iat (issued at) timestamp or {@code null} if not present. - * - *

If present, this value is the timestamp when the JWT was created.

- * - * @return the JWT {@code iat} value or {@code null} if not present. - */ - Date getIssuedAt(); - - /** - * Returns the JWTs - * jti (JWT ID) value or {@code null} if not present. - * - *

This value is a CaSe-SenSiTiVe unique identifier for the JWT. If available, this value is expected to be - * assigned in a manner that ensures that there is a negligible probability that the same value will be - * accidentally - * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

- * - * @return the JWT {@code jti} value or {@code null} if not present. - */ - @Override - // just for JavaDoc specific to the JWT spec - String getId(); - - /** - * Returns the JWTs claim ({@code claimName}) value as a {@code requiredType} instance, or {@code null} if not - * present. - * - *

JJWT only converts simple String, Date, Long, Integer, Short and Byte types automatically. Anything more - * complex is expected to be already converted to your desired type by the JSON parser. You may specify a custom - * JSON processor using the {@code JwtParserBuilder}'s - * {@link JwtParserBuilder#json(io.jsonwebtoken.io.Deserializer) json(Deserializer)} method. See the JJWT - * documentation on custom JSON processors for more - * information. If using Jackson, you can specify custom claim POJO types as described in - * custom claim types. - * - * @param claimName name of claim - * @param requiredType the type of the value expected to be returned - * @param the type of the value expected to be returned - * @return the JWT {@code claimName} value or {@code null} if not present. - * @throws RequiredTypeException throw if the claim value is not null and not of type {@code requiredType} - * @see JJWT JSON Support - */ - T get(String claimName, Class requiredType); -} diff --git a/io/jsonwebtoken/ClaimsBuilder.java b/io/jsonwebtoken/ClaimsBuilder.java deleted file mode 100644 index eabd5b5..0000000 --- a/io/jsonwebtoken/ClaimsBuilder.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.lang.Builder; -import io.jsonwebtoken.lang.MapMutator; - -/** - * {@link Builder} used to create an immutable {@link Claims} instance. - * - * @see JwtBuilder - * @see Claims - * @since 0.12.0 - */ -public interface ClaimsBuilder extends MapMutator, ClaimsMutator, Builder { -} diff --git a/io/jsonwebtoken/ClaimsMutator.java b/io/jsonwebtoken/ClaimsMutator.java deleted file mode 100644 index 1fdca1e..0000000 --- a/io/jsonwebtoken/ClaimsMutator.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.lang.NestedCollection; - -import java.util.Collection; -import java.util.Date; - -/** - * Mutation (modifications) to a {@link io.jsonwebtoken.Claims Claims} instance. - * - * @param the type of mutator - * @see io.jsonwebtoken.JwtBuilder - * @see io.jsonwebtoken.Claims - * @since 0.2 - */ -public interface ClaimsMutator> { - - /** - * Sets the JWT - * iss (issuer) claim. A {@code null} value will remove the property from the JSON Claims map. - * - * @param iss the JWT {@code iss} value or {@code null} to remove the property from the JSON map. - * @return the {@code Claims} instance for method chaining. - * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named - * {@link #issuer(String)}. This method will be removed before the JJWT 1.0 release. - */ - @Deprecated - T setIssuer(String iss); - - /** - * Sets the JWT - * iss (issuer) claim. A {@code null} value will remove the property from the JSON Claims map. - * - * @param iss the JWT {@code iss} value or {@code null} to remove the property from the JSON map. - * @return the {@code Claims} instance for method chaining. - * @since 0.12.0 - */ - T issuer(String iss); - - /** - * Sets the JWT - * sub (subject) claim. A {@code null} value will remove the property from the JSON Claims map. - * - * @param sub the JWT {@code sub} value or {@code null} to remove the property from the JSON map. - * @return the {@code Claims} instance for method chaining. - * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named - * {@link #subject(String)}. This method will be removed before the JJWT 1.0 release. - */ - @Deprecated - T setSubject(String sub); - - /** - * Sets the JWT - * sub (subject) claim. A {@code null} value will remove the property from the JSON Claims map. - * - * @param sub the JWT {@code sub} value or {@code null} to remove the property from the JSON map. - * @return the {@code Claims} instance for method chaining. - * @since 0.12.0 - */ - T subject(String sub); - - /** - * Sets the JWT aud (audience) - * claim as a single String, NOT a String array. This method exists only for producing - * JWTs sent to legacy recipients that are unable to interpret the {@code aud} value as a JSON String Array; it is - * strongly recommended to avoid calling this method whenever possible and favor the - * {@link #audience()}.{@link AudienceCollection#add(Object) add(String)} and - * {@link AudienceCollection#add(Collection) add(Collection)} methods instead, as they ensure a single - * deterministic data type for recipients. - * - * @param aud the JWT {@code aud} value or {@code null} to remove the property from the JSON map. - * @return the {@code Claims} instance for method chaining. - * @deprecated since 0.12.0 in favor of {@link #audience()}. This method will be removed before - * the JJWT 1.0 release. - */ - @Deprecated - T setAudience(String aud); - - /** - * Configures the JWT - * aud (audience) Claim - * set, quietly ignoring any null, empty, whitespace-only, or existing value already in the set. - * - *

When finished, the {@code audience} collection's {@link AudienceCollection#and() and()} method may be used - * to continue configuration. For example:

- *
-     *  Jwts.builder() // or Jwts.claims()
-     *
-     *     .audience().add("anAudience").and() // return parent
-     *
-     *  .subject("Joe") // resume configuration...
-     *  // etc...
-     * 
- * - * @return the {@link AudienceCollection AudienceCollection} to use for {@code aud} configuration. - * @see AudienceCollection AudienceCollection - * @see AudienceCollection#single(String) AudienceCollection.single(String) - * @since 0.12.0 - */ - AudienceCollection audience(); - - /** - * Sets the JWT - * exp (expiration) timestamp claim. A {@code null} value will remove the property from the - * JSON Claims map. - * - *

A JWT obtained after this timestamp should not be used.

- * - * @param exp the JWT {@code exp} value or {@code null} to remove the property from the JSON map. - * @return the {@code Claims} instance for method chaining. - * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named - * {@link #expiration(Date)}. This method will be removed before the JJWT 1.0 release. - */ - @Deprecated - T setExpiration(Date exp); - - /** - * Sets the JWT - * exp (expiration) timestamp claim. A {@code null} value will remove the property from the - * JSON Claims map. - * - *

A JWT obtained after this timestamp should not be used.

- * - * @param exp the JWT {@code exp} value or {@code null} to remove the property from the JSON map. - * @return the {@code Claims} instance for method chaining. - * @since 0.12.0 - */ - T expiration(Date exp); - - /** - * Sets the JWT - * nbf (not before) timestamp claim. A {@code null} value will remove the property from the - * JSON Claims map. - * - *

A JWT obtained before this timestamp should not be used.

- * - * @param nbf the JWT {@code nbf} value or {@code null} to remove the property from the JSON map. - * @return the {@code Claims} instance for method chaining. - * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named - * {@link #notBefore(Date)}. This method will be removed before the JJWT 1.0 release. - */ - @Deprecated - T setNotBefore(Date nbf); - - /** - * Sets the JWT - * nbf (not before) timestamp claim. A {@code null} value will remove the property from the - * JSON Claims map. - * - *

A JWT obtained before this timestamp should not be used.

- * - * @param nbf the JWT {@code nbf} value or {@code null} to remove the property from the JSON map. - * @return the {@code Claims} instance for method chaining. - * @since 0.12.0 - */ - T notBefore(Date nbf); - - /** - * Sets the JWT - * iat (issued at) timestamp claim. A {@code null} value will remove the property from the - * JSON Claims map. - * - *

The value is the timestamp when the JWT was created.

- * - * @param iat the JWT {@code iat} value or {@code null} to remove the property from the JSON map. - * @return the {@code Claims} instance for method chaining. - * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named - * {@link #issuedAt(Date)}. This method will be removed before the JJWT 1.0 release. - */ - @Deprecated - T setIssuedAt(Date iat); - - /** - * Sets the JWT - * iat (issued at) timestamp claim. A {@code null} value will remove the property from the - * JSON Claims map. - * - *

The value is the timestamp when the JWT was created.

- * - * @param iat the JWT {@code iat} value or {@code null} to remove the property from the JSON map. - * @return the {@code Claims} instance for method chaining. - * @since 0.12.0 - */ - T issuedAt(Date iat); - - /** - * Sets the JWT - * jti (JWT ID) claim. A {@code null} value will remove the property from the JSON Claims map. - * - *

This value is a CaSe-SenSiTiVe unique identifier for the JWT. If specified, this value MUST be assigned in a - * manner that ensures that there is a negligible probability that the same value will be accidentally - * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

- * - * @param jti the JWT {@code jti} value or {@code null} to remove the property from the JSON map. - * @return the {@code Claims} instance for method chaining. - * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named - * {@link #id(String)}. This method will be removed before the JJWT 1.0 release. - */ - @Deprecated - T setId(String jti); - - /** - * Sets the JWT - * jti (JWT ID) claim. A {@code null} value will remove the property from the JSON Claims map. - * - *

This value is a CaSe-SenSiTiVe unique identifier for the JWT. If specified, this value MUST be assigned in a - * manner that ensures that there is a negligible probability that the same value will be accidentally - * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

- * - * @param jti the JWT {@code jti} value or {@code null} to remove the property from the JSON map. - * @return the {@code Claims} instance for method chaining. - * @since 0.12.0 - */ - T id(String jti); - - /** - * A {@code NestedCollection} for setting {@link #audience()} values that also allows overriding the collection - * to be a {@link #single(String) single string value} for legacy JWT recipients if necessary. - * - *

Because this interface extends {@link NestedCollection}, the {@link #and()} method may be used to continue - * parent configuration. For example:

- *
-     *  Jwts.builder() // or Jwts.claims()
-     *
-     *     .audience().add("anAudience").and() // return parent
-     *
-     *  .subject("Joe") // resume parent configuration...
-     *  // etc...
- * - * @param

the type of ClaimsMutator to return for method chaining. - * @see #single(String) - * @since 0.12.0 - */ - interface AudienceCollection

extends NestedCollection { - - /** - * Sets the JWT aud (audience) - * Claim as a single String, NOT a String array. This method exists only for producing - * JWTs sent to legacy recipients that are unable to interpret the {@code aud} value as a JSON String Array; - * it is strongly recommended to avoid calling this method whenever possible and favor the - * {@link #add(Object) add(String)} or {@link #add(Collection)} methods instead, as they ensure a single - * deterministic data type for recipients. - * - * @param aud the value to use as the {@code aud} Claim single-String value (and not an array of Strings), or - * {@code null}, empty or whitespace to remove the property from the JSON map. - * @return the instance for method chaining - * @since 0.12.0 - * @deprecated This is technically not deprecated because the JWT RFC mandates support for single string values, - * but it is marked as deprecated to discourage its use when possible. - */ - // DO NOT REMOVE EVER. This is a required RFC feature, but marked as deprecated to discourage its use - @Deprecated - P single(String aud); - } -} diff --git a/io/jsonwebtoken/Clock.java b/io/jsonwebtoken/Clock.java deleted file mode 100644 index 584dd60..0000000 --- a/io/jsonwebtoken/Clock.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import java.util.Date; - -/** - * A clock represents a time source that can be used when creating and verifying JWTs. - * - * @since 0.7.0 - */ -public interface Clock { - - /** - * Returns the clock's current timestamp at the instant the method is invoked. - * - * @return the clock's current timestamp at the instant the method is invoked. - */ - Date now(); -} diff --git a/io/jsonwebtoken/CompressionCodec.java b/io/jsonwebtoken/CompressionCodec.java deleted file mode 100644 index b3b9228..0000000 --- a/io/jsonwebtoken/CompressionCodec.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2015 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.io.CompressionAlgorithm; - -/** - * Compresses and decompresses byte arrays according to a compression algorithm. - * - *

"zip" identifier

- * - *

{@code CompressionCodec} extends {@code Identifiable}; the value returned from - * {@link Identifiable#getId() getId()} will be used as the JWT - * zip header value.

- * - * @see Jwts.ZIP#DEF - * @see Jwts.ZIP#GZIP - * @since 0.6.0 - * @deprecated since 0.12.0 in favor of {@link io.jsonwebtoken.io.CompressionAlgorithm} to equal the RFC name for this concept. - */ -@Deprecated -public interface CompressionCodec extends CompressionAlgorithm { - - /** - * The algorithm name to use as the JWT - * zip header value. - * - * @return the algorithm name to use as the JWT - * zip header value. - * @deprecated since 0.12.0 in favor of {@link #getId()} to ensure congruence with - * all other identifiable algorithms. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - String getAlgorithmName(); - - /** - * Compresses the specified byte array, returning the compressed byte array result. - * - * @param content bytes to compress - * @return compressed bytes - * @throws CompressionException if the specified byte array cannot be compressed. - */ - @Deprecated - byte[] compress(byte[] content) throws CompressionException; - - /** - * Decompresses the specified compressed byte array, returning the decompressed byte array result. The - * specified byte array must already be in compressed form. - * - * @param compressed compressed bytes - * @return decompressed bytes - * @throws CompressionException if the specified byte array cannot be decompressed. - */ - @Deprecated - byte[] decompress(byte[] compressed) throws CompressionException; -} diff --git a/io/jsonwebtoken/CompressionCodecResolver.java b/io/jsonwebtoken/CompressionCodecResolver.java deleted file mode 100644 index 58df740..0000000 --- a/io/jsonwebtoken/CompressionCodecResolver.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2015 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * Looks for a JWT {@code zip} header, and if found, returns the corresponding {@link CompressionCodec} the parser - * can use to decompress the JWT body. - * - *

JJWT's default {@link JwtParser} implementation supports both the - * {@link Jwts.ZIP#DEF DEFLATE} and {@link Jwts.ZIP#GZIP GZIP} algorithms by default - you do not need to - * specify a {@code CompressionCodecResolver} in these cases.

- * - *

However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you can implement - * your own {@link CompressionCodecResolver} and specify that when - * {@link io.jsonwebtoken.JwtBuilder#compressWith(io.jsonwebtoken.io.CompressionAlgorithm) building} and - * {@link io.jsonwebtoken.JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.

- * - * @see JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver) - * @see JwtParserBuilder#zip() - * @since 0.6.0 - * @deprecated in favor of {@link JwtParserBuilder#zip()} - */ -@SuppressWarnings("DeprecatedIsStillUsed") -@Deprecated -public interface CompressionCodecResolver { - - /** - * Looks for a JWT {@code zip} header, and if found, returns the corresponding {@link CompressionCodec} the parser - * can use to decompress the JWT body. - * - * @param header of the JWT - * @return CompressionCodec matching the {@code zip} header, or null if there is no {@code zip} header. - * @throws CompressionException if a {@code zip} header value is found and not supported. - */ - CompressionCodec resolveCompressionCodec(Header header) throws CompressionException; - -} diff --git a/io/jsonwebtoken/CompressionCodecs.java b/io/jsonwebtoken/CompressionCodecs.java deleted file mode 100644 index b1797a5..0000000 --- a/io/jsonwebtoken/CompressionCodecs.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * Provides default implementations of the {@link CompressionCodec} interface. - * - * @see Jwts.ZIP#DEF - * @see Jwts.ZIP#GZIP - * @since 0.7.0 - * @deprecated in favor of {@link Jwts.ZIP}. - */ -@Deprecated //TODO: delete for 1.0 -public final class CompressionCodecs { - - private CompressionCodecs() { - } //prevent external instantiation - - /** - * Codec implementing the JWA standard - * deflate compression algorithm - * - * @deprecated in favor of {@link Jwts.ZIP#DEF}. - */ - @Deprecated - public static final CompressionCodec DEFLATE = (CompressionCodec) Jwts.ZIP.DEF; - - /** - * Codec implementing the gzip compression algorithm. - * - *

Compatibility Warning

- * - *

This is not a standard JWA compression algorithm. Be sure to use this only when you are confident - * that all parties accessing the token support the gzip algorithm.

- * - *

If you're concerned about compatibility, the {@link Jwts.ZIP#DEF DEF} code is JWA standards-compliant.

- * - * @deprecated in favor of {@link Jwts.ZIP#GZIP} - */ - @Deprecated - public static final CompressionCodec GZIP = (CompressionCodec) Jwts.ZIP.GZIP; - -} diff --git a/io/jsonwebtoken/CompressionException.java b/io/jsonwebtoken/CompressionException.java deleted file mode 100644 index fd2c045..0000000 --- a/io/jsonwebtoken/CompressionException.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2015 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.io.IOException; - -/** - * Exception indicating that either compressing or decompressing a JWT body failed. - * - * @since 0.6.0 - */ -public class CompressionException extends IOException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public CompressionException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public CompressionException(String message, Throwable cause) { - super(message, cause); - } - -} \ No newline at end of file diff --git a/io/jsonwebtoken/ExpiredJwtException.java b/io/jsonwebtoken/ExpiredJwtException.java deleted file mode 100644 index 815a15c..0000000 --- a/io/jsonwebtoken/ExpiredJwtException.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * Exception indicating that a JWT was accepted after it expired and must be rejected. - * - * @since 0.3 - */ -public class ExpiredJwtException extends ClaimJwtException { - - /** - * Creates a new instance with the specified header, claims, and explanation message. - * - * @param header jwt header - * @param claims jwt claims (body) - * @param message the message explaining why the exception is thrown. - */ - public ExpiredJwtException(Header header, Claims claims, String message) { - super(header, claims, message); - } - - /** - * Creates a new instance with the specified header, claims, explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - * @param header jwt header - * @param claims jwt claims (body) - * @since 0.5 - */ - public ExpiredJwtException(Header header, Claims claims, String message, Throwable cause) { - super(header, claims, message, cause); - } -} diff --git a/io/jsonwebtoken/Header.java b/io/jsonwebtoken/Header.java deleted file mode 100644 index f6dd1f9..0000000 --- a/io/jsonwebtoken/Header.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import java.util.Map; - -/** - * A JWT JOSE header. - * - *

This is an immutable JSON map with convenient type-safe getters for JWT standard header parameter names.

- * - *

Because this interface extends Map<String, Object>, you can use standard {@code Map} - * accessor/iterator methods as desired, for example:

- * - *
- * header.get("someKey");
- * - *

However, because {@code Header} instances are immutable, calling any of the map mutation methods - * (such as {@code Map.}{@link Map#put(Object, Object) put}, etc) will result in a runtime exception.

- * - *

Security

- * - *

The {@code Header} interface itself makes no implications of integrity protection via either digital signatures or - * encryption. Instead, {@link JwsHeader} and {@link JweHeader} represent this information for respective - * {@link Jws} and {@link Jwe} instances.

- * - * @see ProtectedHeader - * @see JwsHeader - * @see JweHeader - * @since 0.1 - */ -public interface Header extends Map { - - /** - * JWT {@code Type} (typ) value: "JWT" - * - * @deprecated since 0.12.0 - this constant is never used within the JJWT codebase. - */ - @Deprecated - String JWT_TYPE = "JWT"; - - /** - * JWT {@code Type} header parameter name: "typ" - * @deprecated since 0.12.0 in favor of {@link #getType()}. - */ - @Deprecated - String TYPE = "typ"; - - /** - * JWT {@code Content Type} header parameter name: "cty" - * @deprecated since 0.12.0 in favor of {@link #getContentType()}. - */ - @Deprecated - String CONTENT_TYPE = "cty"; - - /** - * JWT {@code Algorithm} header parameter name: "alg". - * - * @see JWS Algorithm Header - * @see JWE Algorithm Header - * @deprecated since 0.12.0 in favor of {@link #getAlgorithm()}. - */ - @Deprecated - String ALGORITHM = "alg"; - - /** - * JWT {@code Compression Algorithm} header parameter name: "zip" - * @deprecated since 0.12.0 in favor of {@link #getCompressionAlgorithm()} - */ - @Deprecated - String COMPRESSION_ALGORITHM = "zip"; - - /** - * JJWT legacy/deprecated compression algorithm header parameter name: "calg" - * - * @deprecated use {@link #COMPRESSION_ALGORITHM} instead. - */ - @Deprecated - String DEPRECATED_COMPRESSION_ALGORITHM = "calg"; - - /** - * Returns the - * typ (Type) header value or {@code null} if not present. - * - * @return the {@code typ} header value or {@code null} if not present. - */ - String getType(); - - /** - * Returns the - * cty (Content Type) header value or {@code null} if not present. - * - *

The cty (Content Type) Header Parameter is used by applications to declare the - * IANA MediaType of the content - * (the payload). This is intended for use by the application when more than - * one kind of object could be present in the Payload; the application can use this value to disambiguate among - * the different kinds of objects that might be present. It will typically not be used by applications when - * the kind of object is already known. This parameter is ignored by JWT implementations (like JJWT); any - * processing of this parameter is performed by the JWS application. Use of this Header Parameter is OPTIONAL.

- * - *

To keep messages compact in common situations, it is RECOMMENDED that producers omit an - * application/ prefix of a media type value in a {@code cty} Header Parameter when - * no other '/' appears in the media type value. A recipient using the media type value MUST - * treat it as if application/ were prepended to any {@code cty} value not containing a - * '/'. For instance, a {@code cty} value of example SHOULD be used to - * represent the application/example media type, whereas the media type - * application/example;part="1/2" cannot be shortened to - * example;part="1/2".

- * - * @return the {@code typ} header parameter value or {@code null} if not present. - */ - String getContentType(); - - /** - * Returns the JWT {@code alg} (Algorithm) header value or {@code null} if not present. - * - *
    - *
  • If the JWT is a Signed JWT (a JWS), the - * alg (Algorithm) header parameter identifies the cryptographic algorithm used to secure the - * JWS. Consider using {@link Jwts.SIG}.{@link io.jsonwebtoken.lang.Registry#get(Object) get(id)} - * to convert this string value to a type-safe {@code SecureDigestAlgorithm} instance.
  • - *
  • If the JWT is an Encrypted JWT (a JWE), the - * alg (Algorithm) header parameter - * identifies the cryptographic key management algorithm used to encrypt or determine the value of the Content - * Encryption Key (CEK). The encrypted content is not usable if the alg value does not represent a - * supported algorithm, or if the recipient does not have a key that can be used with that algorithm. Consider - * using {@link Jwts.KEY}.{@link io.jsonwebtoken.lang.Registry#get(Object) get(id)} to convert this string value - * to a type-safe {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm} instance.
  • - *
- * - * @return the {@code alg} header value or {@code null} if not present. This will always be - * {@code non-null} on validly constructed JWT instances, but could be {@code null} during construction. - * @since 0.12.0 - */ - String getAlgorithm(); - - /** - * Returns the JWT zip - * (Compression Algorithm) header parameter value or {@code null} if not present. - * - *

Compatibility Note

- * - *

While the JWT family of specifications only defines the zip header in the JWE - * (JSON Web Encryption) specification, JJWT will also support compression for JWS as well if you choose to use it. - * However, be aware that if you use compression when creating a JWS token, other libraries may not be able to - * parse the JWS. However, compression when creating JWE tokens should be universally accepted for any library - * that supports JWE.

- * - * @return the {@code zip} header parameter value or {@code null} if not present. - * @since 0.6.0 - */ - String getCompressionAlgorithm(); -} diff --git a/io/jsonwebtoken/HeaderMutator.java b/io/jsonwebtoken/HeaderMutator.java deleted file mode 100644 index acaf687..0000000 --- a/io/jsonwebtoken/HeaderMutator.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.lang.MapMutator; - -/** - * Mutation (modifications) to a {@link Header Header} instance. - * - * @param the mutator subtype, for method chaining - * @since 0.12.0 - */ -public interface HeaderMutator> extends MapMutator { - - //IMPLEMENTOR NOTE: if this `algorithm` method ever needs to be exposed in the public API, it might be better to - // have it in the Jwts.HeaderBuilder interface and NOT this one: in the context of - // JwtBuilder.Header, there is never a reason for an application developer to call algorithm(id) - // directly because the KeyAlgorithm or SecureDigestAlgorithm instance must always be provided - // via the signWith or encryptWith methods. The JwtBuilder will always set the algorithm - // header based on these two instances, so there is no need for an app dev to do so. - /* - * Sets the JWT {@code alg} (Algorithm) header value. A {@code null} value will remove the property - * from the JSON map. - *
    - *
  • If the JWT is a Signed JWT (a JWS), the - * {@code alg} (Algorithm) header - * parameter identifies the cryptographic algorithm used to secure the JWS.
  • - *
  • If the JWT is an Encrypted JWT (a JWE), the - * alg (Algorithm) header parameter - * identifies the cryptographic key management algorithm used to encrypt or determine the value of the Content - * Encryption Key (CEK). The encrypted content is not usable if the alg value does not represent a - * supported algorithm, or if the recipient does not have a key that can be used with that algorithm.
  • - *
- * - * @param alg the {@code alg} header value - * @return this header for method chaining - * @since 0.12.0 - * - T algorithm(String alg); - */ - - /** - * Sets the JWT - * typ (Type) header value. A {@code null} value will remove the property from the JSON map. - * - * @param typ the JWT JOSE {@code typ} header value or {@code null} to remove the property from the JSON map. - * @return the instance for method chaining. - */ - T type(String typ); - - /** - * Sets the compact - * cty (Content Type) header parameter value, used by applications to declare the - * IANA MediaType of the JWT - * payload. A {@code null} value will remove the property from the JSON map. - * - *

Compact Media Type Identifier

- * - *

This method will automatically remove any application/ prefix from the - * {@code cty} string if possible according to the rules defined in the last paragraph of - * RFC 7517, Section 4.1.10:

- *
-     *     To keep messages compact in common situations, it is RECOMMENDED that
-     *     producers omit an "application/" prefix of a media type value in a
-     *     "cty" Header Parameter when no other '/' appears in the media type
-     *     value.  A recipient using the media type value MUST treat it as if
-     *     "application/" were prepended to any "cty" value not containing a
-     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
-     *     represent the "application/example" media type, whereas the media
-     *     type "application/example;part="1/2"" cannot be shortened to
-     *     "example;part="1/2"".
- * - *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the - * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as - * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media - * Type identifiers without needing JWT-specific prefix conditional logic in application code. - *

- * - * @param cty the JWT {@code cty} header value or {@code null} to remove the property from the JSON map. - * @return the instance for method chaining. - */ - T contentType(String cty); - - /** - * Deprecated since of 0.12.0, delegates to {@link #type(String)}. - * - * @param typ the JWT JOSE {@code typ} header value or {@code null} to remove the property from the JSON map. - * @return the instance for method chaining. - * @see #type(String) - * @deprecated since 0.12.0 in favor of the more modern builder-style {@link #type(String)} method. - * This method will be removed before the 1.0 release. - */ - @Deprecated - T setType(String typ); - - /** - * Deprecated as of 0.12.0, delegates to {@link #contentType(String)}. - * - * @param cty the JWT JOSE {@code cty} header value or {@code null} to remove the property from the JSON map. - * @return the instance for method chaining. - * @see #contentType(String) - * @deprecated since 0.12.0 in favor of the more modern builder-style {@link #contentType(String)}. - */ - @Deprecated - T setContentType(String cty); - - /** - * Deprecated as of 0.12.0, there is no need to set this any longer as the {@code JwtBuilder} will - * always set the {@code zip} header as necessary. - * - * @param zip the JWT compression algorithm {@code zip} value or {@code null} to remove the property from the JSON map. - * @return the instance for method chaining. - * @since 0.6.0 - * @deprecated since 0.12.0 and will be removed before the 1.0 release. - */ - @Deprecated - T setCompressionAlgorithm(String zip); -} diff --git a/io/jsonwebtoken/Identifiable.java b/io/jsonwebtoken/Identifiable.java deleted file mode 100644 index 8872571..0000000 --- a/io/jsonwebtoken/Identifiable.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * An object that may be uniquely identified by an {@link #getId() id} relative to other instances of the same type. - * - *

The following table indicates how various JWT or JWK {@link #getId() getId()} values are used.

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
JWA Identifiable Concepts
JJWT TypeHow {@link #getId()} is Used
{@link io.jsonwebtoken.Claims Claims}JWT's {@code jti} (JWT ID) - * claim.
{@link io.jsonwebtoken.security.Jwk Jwk}JWK's {@code kid} (Key ID) - * parameter value.
{@link io.jsonwebtoken.security.Curve Curve}JWK's {@code crv} (Curve) - * parameter value.
{@link io.jsonwebtoken.io.CompressionAlgorithm CompressionAlgorithm}JWE protected header's - * {@code zip} (Compression Algorithm) - * parameter value.
{@link io.jsonwebtoken.security.HashAlgorithm HashAlgorithm}Within a {@link io.jsonwebtoken.security.JwkThumbprint JwkThumbprint}'s URI value.
{@link io.jsonwebtoken.security.MacAlgorithm MacAlgorithm}JWS protected header's - * {@code alg} (Algorithm) parameter value.
{@link io.jsonwebtoken.security.SignatureAlgorithm SignatureAlgorithm}JWS protected header's - * {@code alg} (Algorithm) parameter value.
{@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm}JWE protected header's - * {@code alg} (Key Management Algorithm) - * parameter value.
{@link io.jsonwebtoken.security.AeadAlgorithm AeadAlgorithm}JWE protected header's - * {@code enc} (Encryption Algorithm) - * parameter value.
- * - * @since 0.12.0 - */ -public interface Identifiable { - - /** - * Returns the unique string identifier of the associated object. - * - * @return the unique string identifier of the associated object. - */ - String getId(); -} diff --git a/io/jsonwebtoken/IncorrectClaimException.java b/io/jsonwebtoken/IncorrectClaimException.java deleted file mode 100644 index 71b0b9d..0000000 --- a/io/jsonwebtoken/IncorrectClaimException.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2015 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * Exception thrown when discovering that a required claim does not equal the required value, indicating the JWT is - * invalid and may not be used. - * - * @since 0.6 - */ -public class IncorrectClaimException extends InvalidClaimException { - - /** - * Creates a new instance with the specified header, claims and explanation message. - * - * @param header the header inspected - * @param claims the claims with the incorrect claim value - * @param claimName the name of the claim that could not be validated - * @param claimValue the value of the claim that could not be validated - * @param message the exception message - */ - public IncorrectClaimException(Header header, Claims claims, String claimName, Object claimValue, String message) { - super(header, claims, claimName, claimValue, message); - } - - /** - * Creates a new instance with the specified header, claims, explanation message and underlying cause. - * - * @param header the header inspected - * @param claims the claims with the incorrect claim value - * @param claimName the name of the claim that could not be validated - * @param claimValue the value of the claim that could not be validated - * @param message the exception message - * @param cause the underlying cause that resulted in this exception being thrown - */ - public IncorrectClaimException(Header header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) { - super(header, claims, claimName, claimValue, message, cause); - } -} diff --git a/io/jsonwebtoken/InvalidClaimException.java b/io/jsonwebtoken/InvalidClaimException.java deleted file mode 100644 index eba777c..0000000 --- a/io/jsonwebtoken/InvalidClaimException.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2015 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * Exception indicating a parsed claim is invalid in some way. Subclasses reflect the specific - * reason the claim is invalid. - * - * @see IncorrectClaimException - * @see MissingClaimException - * @since 0.6 - */ -public class InvalidClaimException extends ClaimJwtException { - - /** - * The name of the invalid claim. - */ - private final String claimName; - - /** - * The claim value that could not be validated. - */ - private final Object claimValue; - - /** - * Creates a new instance with the specified header, claims and explanation message. - * - * @param header the header inspected - * @param claims the claims obtained - * @param claimName the name of the claim that could not be validated - * @param claimValue the value of the claim that could not be validated - * @param message the exception message - */ - protected InvalidClaimException(Header header, Claims claims, String claimName, Object claimValue, String message) { - super(header, claims, message); - this.claimName = claimName; - this.claimValue = claimValue; - } - - /** - * Creates a new instance with the specified header, claims, explanation message and underlying cause. - * - * @param header the header inspected - * @param claims the claims obtained - * @param claimName the name of the claim that could not be validated - * @param claimValue the value of the claim that could not be validated - * @param message the exception message - * @param cause the underlying cause that resulted in this exception being thrown - */ - protected InvalidClaimException(Header header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) { - super(header, claims, message, cause); - this.claimName = claimName; - this.claimValue = claimValue; - } - - /** - * Returns the name of the invalid claim. - * - * @return the name of the invalid claim. - */ - public String getClaimName() { - return claimName; - } - - /** - * Returns the claim value that could not be validated. - * - * @return the claim value that could not be validated. - */ - public Object getClaimValue() { - return claimValue; - } -} diff --git a/io/jsonwebtoken/Jwe.java b/io/jsonwebtoken/Jwe.java deleted file mode 100644 index 885ddae..0000000 --- a/io/jsonwebtoken/Jwe.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * An encrypted JWT, called a "JWE", per the - * JWE (RFC 7516) Specification. - * - * @param payload type, either {@link Claims} or {@code byte[]} content. - * @since 0.12.0 - */ -public interface Jwe extends ProtectedJwt { - - /** - * Visitor implementation that ensures the visited JWT is a JSON Web Encryption ('JWE') message with an - * authenticated and decrypted {@code byte[]} array payload, and rejects all others with an - * {@link UnsupportedJwtException}. - * - * @see SupportedJwtVisitor#onDecryptedContent(Jwe) - * @since 0.12.0 - */ - @SuppressWarnings("UnnecessaryModifier") - public static final JwtVisitor> CONTENT = new SupportedJwtVisitor>() { - @Override - public Jwe onDecryptedContent(Jwe jwe) { - return jwe; - } - }; - - /** - * Visitor implementation that ensures the visited JWT is a JSON Web Encryption ('JWE') message with an - * authenticated and decrypted {@link Claims} payload, and rejects all others with an - * {@link UnsupportedJwtException}. - * - * @see SupportedJwtVisitor#onDecryptedClaims(Jwe) - * @since 0.12.0 - */ - @SuppressWarnings("UnnecessaryModifier") - public static final JwtVisitor> CLAIMS = new SupportedJwtVisitor>() { - @Override - public Jwe onDecryptedClaims(Jwe jwe) { - return jwe; - } - }; - - /** - * Returns the Initialization Vector used during JWE encryption and decryption. - * - * @return the Initialization Vector used during JWE encryption and decryption. - */ - byte[] getInitializationVector(); -} diff --git a/io/jsonwebtoken/JweHeader.java b/io/jsonwebtoken/JweHeader.java deleted file mode 100644 index 8ba6b7f..0000000 --- a/io/jsonwebtoken/JweHeader.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.security.AeadAlgorithm; -import io.jsonwebtoken.security.KeyAlgorithm; -import io.jsonwebtoken.security.PublicJwk; - -import javax.crypto.SecretKey; -import java.security.Key; - -/** - * A JWE header. - * - * @since 0.12.0 - */ -public interface JweHeader extends ProtectedHeader { - - /** - * Returns the JWE {@code enc} (Encryption - * Algorithm) header value or {@code null} if not present. - * - *

The JWE {@code enc} (encryption algorithm) Header Parameter identifies the content encryption algorithm - * used to perform authenticated encryption on the plaintext to produce the ciphertext and the JWE - * {@code Authentication Tag}.

- * - *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by - * supplying an {@link AeadAlgorithm} to a {@link JwtBuilder} via one of its - * {@link JwtBuilder#encryptWith(SecretKey, AeadAlgorithm) encryptWith(SecretKey, AeadAlgorithm)} or - * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} - * methods. JJWT will then set this {@code enc} header value automatically to the {@code AeadAlgorithm}'s - * {@link AeadAlgorithm#getId() getId()} value during encryption.

- * - * @return the JWE {@code enc} (Encryption Algorithm) header value or {@code null} if not present. This will - * always be {@code non-null} on validly-constructed JWE instances, but could be {@code null} during construction. - * @see JwtBuilder#encryptWith(SecretKey, AeadAlgorithm) - * @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) - */ - String getEncryptionAlgorithm(); - - /** - * Returns the {@code epk} (Ephemeral - * Public Key) header value created by the JWE originator for use with key agreement algorithms, or - * {@code null} if not present. - * - *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by - * supplying an ECDH-ES {@link KeyAlgorithm} to a {@link JwtBuilder} via its - * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} - * method. The ECDH-ES {@code KeyAlgorithm} implementation will then set this {@code epk} header value - * automatically when producing the encryption key.

- * - * @return the {@code epk} (Ephemeral - * Public Key) header value created by the JWE originator for use with key agreement algorithms, or - * {@code null} if not present. - * @see Jwts.KEY - * @see Jwts.KEY#ECDH_ES - * @see Jwts.KEY#ECDH_ES_A128KW - * @see Jwts.KEY#ECDH_ES_A192KW - * @see Jwts.KEY#ECDH_ES_A256KW - */ - PublicJwk getEphemeralPublicKey(); - - /** - * Returns any information about the JWE producer for use with key agreement algorithms, or {@code null} if not - * present. - * - * @return any information about the JWE producer for use with key agreement algorithms, or {@code null} if not - * present. - * @see JWE apu (Agreement PartyUInfo) Header Parameter - * @see Jwts.KEY#ECDH_ES - * @see Jwts.KEY#ECDH_ES_A128KW - * @see Jwts.KEY#ECDH_ES_A192KW - * @see Jwts.KEY#ECDH_ES_A256KW - */ - byte[] getAgreementPartyUInfo(); - - /** - * Returns any information about the JWE recipient for use with key agreement algorithms, or {@code null} if not - * present. - * - * @return any information about the JWE recipient for use with key agreement algorithms, or {@code null} if not - * present. - * @see JWE apv (Agreement PartyVInfo) Header Parameter - * @see Jwts.KEY#ECDH_ES - * @see Jwts.KEY#ECDH_ES_A128KW - * @see Jwts.KEY#ECDH_ES_A192KW - * @see Jwts.KEY#ECDH_ES_A256KW - */ - byte[] getAgreementPartyVInfo(); - - /** - * Returns the 96-bit "iv" - * (Initialization Vector) generated during key encryption, or {@code null} if not present. - * Set by AES GCM {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm} implementations. - * - *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by - * supplying an AES GCM Wrap {@link KeyAlgorithm} to a {@link JwtBuilder} via its - * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} - * method. The AES GCM Wrap {@code KeyAlgorithm} implementation will then set this {@code iv} header value - * automatically when producing the encryption key.

- * - * @return the 96-bit initialization vector generated during key encryption, or {@code null} if not present. - * @see Jwts.KEY#A128GCMKW - * @see Jwts.KEY#A192GCMKW - * @see Jwts.KEY#A256GCMKW - */ - byte[] getInitializationVector(); - - /** - * Returns the 128-bit "tag" - * (Authentication Tag) resulting from key encryption, or {@code null} if not present. - * - *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by - * supplying an AES GCM Wrap {@link KeyAlgorithm} to a {@link JwtBuilder} via its - * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} - * method. The AES GCM Wrap {@code KeyAlgorithm} implementation will then set this {@code tag} header value - * automatically when producing the encryption key.

- * - * @return the 128-bit authentication tag resulting from key encryption, or {@code null} if not present. - * @see Jwts.KEY#A128GCMKW - * @see Jwts.KEY#A192GCMKW - * @see Jwts.KEY#A256GCMKW - */ - byte[] getAuthenticationTag(); - - /** - * Returns the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, or {@code null} - * if not present. Used with password-based {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm}s. - * - * @return the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, or {@code null} - * if not present. - * @see JWE p2c (PBES2 Count) Header Parameter - * @see Jwts.KEY#PBES2_HS256_A128KW - * @see Jwts.KEY#PBES2_HS384_A192KW - * @see Jwts.KEY#PBES2_HS512_A256KW - */ - Integer getPbes2Count(); - - /** - * Returns the PBKDF2 {@code Salt Input} value necessary to derive the key used during JWE encryption, or - * {@code null} if not present. - * - *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by - * supplying a password-based {@link KeyAlgorithm} to a {@link JwtBuilder} via its - * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} - * method. The password-based {@code KeyAlgorithm} implementation will then set this {@code p2s} header value - * automatically when producing the encryption key.

- * - * @return the PBKDF2 {@code Salt Input} value necessary to derive the key used during JWE encryption, or - * {@code null} if not present. - * @see JWE p2s (PBES2 Salt Input) Header Parameter - * @see Jwts.KEY#PBES2_HS256_A128KW - * @see Jwts.KEY#PBES2_HS384_A192KW - * @see Jwts.KEY#PBES2_HS512_A256KW - */ - byte[] getPbes2Salt(); -} diff --git a/io/jsonwebtoken/JweHeaderMutator.java b/io/jsonwebtoken/JweHeaderMutator.java deleted file mode 100644 index 912136d..0000000 --- a/io/jsonwebtoken/JweHeaderMutator.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.security.KeyAlgorithm; - -/** - * Mutation (modifications) to a {@link JweHeader} instance. - * - * @param the mutator subtype, for method chaining - * @since 0.12.0 - */ -public interface JweHeaderMutator> extends ProtectedHeaderMutator { - - /** - * Sets any information about the JWE producer for use with key agreement algorithms. A {@code null} or empty value - * removes the property from the JSON map. - * - * @param info information about the JWE producer to use with key agreement algorithms. - * @return the header for method chaining. - * @see JWE apu (Agreement PartyUInfo) Header Parameter - * @see Jwts.KEY#ECDH_ES - * @see Jwts.KEY#ECDH_ES_A128KW - * @see Jwts.KEY#ECDH_ES_A192KW - * @see Jwts.KEY#ECDH_ES_A256KW - */ - T agreementPartyUInfo(byte[] info); - - /** - * Sets any information about the JWE producer for use with key agreement algorithms. A {@code null} value removes - * the property from the JSON map. - * - *

If not {@code null}, this is a convenience method that calls the equivalent of the following:

- *
-     * {@link #agreementPartyUInfo(byte[]) agreementPartyUInfo}(info.getBytes(StandardCharsets.UTF_8))
- * - * @param info information about the JWE producer to use with key agreement algorithms. - * @return the header for method chaining. - * @see JWE apu (Agreement PartyUInfo) Header Parameter - * @see Jwts.KEY#ECDH_ES - * @see Jwts.KEY#ECDH_ES_A128KW - * @see Jwts.KEY#ECDH_ES_A192KW - * @see Jwts.KEY#ECDH_ES_A256KW - */ - T agreementPartyUInfo(String info); - - /** - * Sets any information about the JWE recipient for use with key agreement algorithms. A {@code null} value removes - * the property from the JSON map. - * - * @param info information about the JWE recipient to use with key agreement algorithms. - * @return the header for method chaining. - * @see JWE apv (Agreement PartyVInfo) Header Parameter - * @see Jwts.KEY#ECDH_ES - * @see Jwts.KEY#ECDH_ES_A128KW - * @see Jwts.KEY#ECDH_ES_A192KW - * @see Jwts.KEY#ECDH_ES_A256KW - */ - T agreementPartyVInfo(byte[] info); - - /** - * Sets any information about the JWE recipient for use with key agreement algorithms. A {@code null} value removes - * the property from the JSON map. - * - *

If not {@code null}, this is a convenience method that calls the equivalent of the following:

- *
-     * {@link #agreementPartyVInfo(byte[]) setAgreementPartVUInfo}(info.getBytes(StandardCharsets.UTF_8))
- * - * @param info information about the JWE recipient to use with key agreement algorithms. - * @return the header for method chaining. - * @see JWE apv (Agreement PartyVInfo) Header Parameter - * @see Jwts.KEY#ECDH_ES - * @see Jwts.KEY#ECDH_ES_A128KW - * @see Jwts.KEY#ECDH_ES_A192KW - * @see Jwts.KEY#ECDH_ES_A256KW - */ - T agreementPartyVInfo(String info); - - /** - * Sets the number of PBKDF2 iterations necessary to derive the key used during JWE encryption. If this value - * is not set when a password-based {@link KeyAlgorithm} is used, JJWT will automatically choose a suitable - * number of iterations based on - * OWASP PBKDF2 Iteration Recommendations. - * - *

Minimum Count

- * - *

{@code IllegalArgumentException} will be thrown during encryption if a specified {@code count} is - * less than 1000 (one thousand), which is the - * minimum number recommended by the - * JWA specification. Anything less is susceptible to security attacks so the default PBKDF2 - * {@code KeyAlgorithm} implementations reject such values.

- * - * @param count the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, must be - * greater than or equal to 1000 (one thousand). - * @return the header for method chaining - * @see JWE p2c (PBES2 Count) Header Parameter - * @see Jwts.KEY#PBES2_HS256_A128KW - * @see Jwts.KEY#PBES2_HS384_A192KW - * @see Jwts.KEY#PBES2_HS512_A256KW - * @see OWASP PBKDF2 Iteration Recommendations - */ - T pbes2Count(int count); -} diff --git a/io/jsonwebtoken/Jws.java b/io/jsonwebtoken/Jws.java deleted file mode 100644 index 8c6010c..0000000 --- a/io/jsonwebtoken/Jws.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * An expanded (not compact/serialized) Signed JSON Web Token. - * - * @param

the type of the JWS payload, either a byte[] or a {@link Claims} instance. - * @since 0.1 - */ -public interface Jws

extends ProtectedJwt { - - /** - * Visitor implementation that ensures the visited JWT is a JSON Web Signature ('JWS') message with a - * cryptographically authenticated/verified {@code byte[]} array payload, and rejects all others with an - * {@link UnsupportedJwtException}. - * - * @see SupportedJwtVisitor#onVerifiedContent(Jws) - * @since 0.12.0 - */ - @SuppressWarnings("UnnecessaryModifier") - public static final JwtVisitor> CONTENT = new SupportedJwtVisitor>() { - @Override - public Jws onVerifiedContent(Jws jws) { - return jws; - } - }; - - /** - * Visitor implementation that ensures the visited JWT is a JSON Web Signature ('JWS') message with a - * cryptographically authenticated/verified {@link Claims} payload, and rejects all others with an - * {@link UnsupportedJwtException}. - * - * @see SupportedJwtVisitor#onVerifiedClaims(Jws) - * @since 0.12.0 - */ - @SuppressWarnings("UnnecessaryModifier") - public static final JwtVisitor> CLAIMS = new SupportedJwtVisitor>() { - @Override - public Jws onVerifiedClaims(Jws jws) { - return jws; - } - }; - - /** - * Returns the verified JWS signature as a Base64Url string. - * - * @return the verified JWS signature as a Base64Url string. - * @deprecated since 0.12.0 in favor of {@link #getDigest() getDigest()}. - */ - @Deprecated - String getSignature(); //TODO for 1.0: return a byte[] -} diff --git a/io/jsonwebtoken/JwsHeader.java b/io/jsonwebtoken/JwsHeader.java deleted file mode 100644 index 0afab2a..0000000 --- a/io/jsonwebtoken/JwsHeader.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * A JWS header. - * - * @since 0.1 - */ -public interface JwsHeader extends ProtectedHeader { - - /** - * JWS Algorithm Header name: the string literal alg - * - * @deprecated since 0.12.0 in favor of {@link #getAlgorithm()} - */ - @Deprecated - String ALGORITHM = "alg"; - - /** - * JWS JWK Set URL Header name: the string literal jku - * - * @deprecated since 0.12.0 in favor of {@link #getJwkSetUrl()} - */ - @Deprecated - String JWK_SET_URL = "jku"; - - /** - * JWS JSON Web Key Header name: the string literal jwk - * - * @deprecated since 0.12.0 in favor of {@link #getJwk()} - */ - @Deprecated - String JSON_WEB_KEY = "jwk"; - - /** - * JWS Key ID Header name: the string literal kid - * - * @deprecated since 0.12.0 in favor of {@link #getKeyId()} - */ - @Deprecated - String KEY_ID = "kid"; - - /** - * JWS X.509 URL Header name: the string literal x5u - * - * @deprecated since 0.12.0 in favor of {@link #getX509Url()} - */ - @Deprecated - String X509_URL = "x5u"; - - /** - * JWS X.509 Certificate Chain Header name: the string literal x5c - * - * @deprecated since 0.12.0 in favor of {@link #getX509Chain()} - */ - @Deprecated - String X509_CERT_CHAIN = "x5c"; - - /** - * JWS X.509 Certificate SHA-1 Thumbprint Header name: the string literal x5t - * - * @deprecated since 0.12.0 in favor of {@link #getX509Sha1Thumbprint()} - */ - @Deprecated - String X509_CERT_SHA1_THUMBPRINT = "x5t"; - - /** - * JWS X.509 Certificate SHA-256 Thumbprint Header name: the string literal x5t#S256 - * - * @deprecated since 0.12.0 in favor of {@link #getX509Sha256Thumbprint()} - */ - @Deprecated - String X509_CERT_SHA256_THUMBPRINT = "x5t#S256"; - - /** - * JWS Critical Header name: the string literal crit - * - * @deprecated since 0.12.0 in favor of {@link #getCritical()} - */ - @Deprecated - String CRITICAL = "crit"; - - /** - * Returns {@code true} if the payload is Base64Url-encoded per standard JWS rules, or {@code false} if the - * RFC 7797: JSON Web Signature (JWS) Unencoded Payload - * Option has been specified. - * - * @return {@code true} if the payload is Base64Url-encoded per standard JWS rules, or {@code false} if the - * RFC 7797: JSON Web Signature (JWS) Unencoded Payload - * Option has been specified. - */ - boolean isPayloadEncoded(); -} diff --git a/io/jsonwebtoken/Jwt.java b/io/jsonwebtoken/Jwt.java deleted file mode 100644 index a1bb23a..0000000 --- a/io/jsonwebtoken/Jwt.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * An expanded (not compact/serialized) JSON Web Token. - * - * @param the type of the JWT header - * @param

the type of the JWT payload, either a content byte array or a {@link Claims} instance. - * @since 0.1 - */ -public interface Jwt { - - /** - * Visitor implementation that ensures the visited JWT is an unsecured content JWT (one not cryptographically - * signed or encrypted) and rejects all others with an {@link UnsupportedJwtException}. - * - * @see SupportedJwtVisitor#onUnsecuredContent(Jwt) - * @since 0.12.0 - */ - @SuppressWarnings("UnnecessaryModifier") - public static final JwtVisitor> UNSECURED_CONTENT = new SupportedJwtVisitor>() { - @Override - public Jwt onUnsecuredContent(Jwt jwt) { - return jwt; - } - }; - - /** - * Visitor implementation that ensures the visited JWT is an unsecured {@link Claims} JWT (one not - * cryptographically signed or encrypted) and rejects all others with an {@link UnsupportedJwtException}. - * - * @see SupportedJwtVisitor#onUnsecuredClaims(Jwt) - * @since 0.12.0 - */ - @SuppressWarnings("UnnecessaryModifier") - public static final JwtVisitor> UNSECURED_CLAIMS = new SupportedJwtVisitor>() { - @Override - public Jwt onUnsecuredClaims(Jwt jwt) { - return jwt; - } - }; - - /** - * Returns the JWT {@link Header} or {@code null} if not present. - * - * @return the JWT {@link Header} or {@code null} if not present. - */ - H getHeader(); - - /** - * Returns the JWT payload, either a {@code byte[]} or a {@code Claims} instance. Use - * {@link #getPayload()} instead, as this method will be removed prior to the 1.0 release. - * - * @return the JWT payload, either a {@code byte[]} or a {@code Claims} instance. - * @deprecated since 0.12.0 because it has been renamed to {@link #getPayload()}. 'Payload' (not - * body) is what the JWT specifications call this property, so it has been renamed to reflect the correct JWT - * nomenclature/taxonomy. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - P getBody(); // TODO: remove for 1.0 - - /** - * Returns the JWT payload, either a {@code byte[]} or a {@code Claims} instance. If the payload is a byte - * array, and if the JWT creator set the (optional) {@link Header#getContentType() contentType} header - * value, the application may inspect the {@code contentType} value to determine how to convert the byte array to - * the final content type as desired. - * - * @return the JWT payload, either a {@code byte[]} or a {@code Claims} instance. - * @since 0.12.0 - */ - P getPayload(); - - /** - * Invokes the specified {@code visitor}'s appropriate type-specific {@code visit} method based on this JWT's type. - * - * @param visitor the visitor to invoke. - * @param the value type returned from the {@code visit} method. - * @return the value returned from visitor's {@code visit} method implementation. - */ - T accept(JwtVisitor visitor); -} diff --git a/io/jsonwebtoken/JwtBuilder.java b/io/jsonwebtoken/JwtBuilder.java deleted file mode 100644 index 6348008..0000000 --- a/io/jsonwebtoken/JwtBuilder.java +++ /dev/null @@ -1,1056 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.io.CompressionAlgorithm; -import io.jsonwebtoken.io.Decoder; -import io.jsonwebtoken.io.Decoders; -import io.jsonwebtoken.io.Encoder; -import io.jsonwebtoken.io.Serializer; -import io.jsonwebtoken.lang.Conjunctor; -import io.jsonwebtoken.lang.MapMutator; -import io.jsonwebtoken.security.AeadAlgorithm; -import io.jsonwebtoken.security.InvalidKeyException; -import io.jsonwebtoken.security.KeyAlgorithm; -import io.jsonwebtoken.security.Keys; -import io.jsonwebtoken.security.Password; -import io.jsonwebtoken.security.SecureDigestAlgorithm; -import io.jsonwebtoken.security.WeakKeyException; -import io.jsonwebtoken.security.X509Builder; - -import javax.crypto.SecretKey; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.Key; -import java.security.PrivateKey; -import java.security.Provider; -import java.security.SecureRandom; -import java.security.interfaces.ECKey; -import java.security.interfaces.RSAKey; -import java.util.Date; -import java.util.Map; - -/** - * A builder for constructing Unprotected JWTs, Signed JWTs (aka 'JWS's) and Encrypted JWTs (aka 'JWE's). - * - * @since 0.1 - */ -public interface JwtBuilder extends ClaimsMutator { - - /** - * Sets the JCA Provider to use during cryptographic signing or encryption operations, or {@code null} if the - * JCA subsystem preferred provider should be used. - * - * @param provider the JCA Provider to use during cryptographic signing or encryption operations, or {@code null} if the - * JCA subsystem preferred provider should be used. - * @return the builder for method chaining. - * @since 0.12.0 - */ - JwtBuilder provider(Provider provider); - - /** - * Sets the {@link SecureRandom} to use during cryptographic signing or encryption operations, or {@code null} if - * a default {@link SecureRandom} should be used. - * - * @param secureRandom the {@link SecureRandom} to use during cryptographic signing or encryption operations, or - * {@code null} if a default {@link SecureRandom} should be used. - * @return the builder for method chaining. - * @since 0.12.0 - */ - JwtBuilder random(SecureRandom secureRandom); - - /** - * Returns the {@code Header} to use to modify the constructed JWT's header name/value pairs as desired. - * When finished, callers may return to JWT construction via the {@link BuilderHeader#and() and()} method. - * For example: - * - *

-     * String jwt = Jwts.builder()
-     *
-     *     .header()
-     *         .keyId("keyId")
-     *         .add("aName", aValue)
-     *         .add(myHeaderMap)
-     *         // ... etc ...
-     *         .{@link BuilderHeader#and() and()} //return back to the JwtBuilder
-     *
-     *     .subject("Joe") // resume JwtBuilder calls
-     *     // ... etc ...
-     *     .compact();
- * - * @return the {@link BuilderHeader} to use for header construction. - * @since 0.12.0 - */ - BuilderHeader header(); - - /** - * Per standard Java idiom 'setter' conventions, this method sets (and fully replaces) any existing header with the - * specified name/value pairs. This is a wrapper method for: - * - *
-     * {@link #header()}.{@link MapMutator#empty() empty()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()}
- * - *

If you do not want to replace the existing header and only want to append to it, - * call {@link #header()}.{@link io.jsonwebtoken.lang.MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()} instead.

- * - * @param map the name/value pairs to set as (and potentially replace) the constructed JWT header. - * @return the builder for method chaining. - * @deprecated since 0.12.0 in favor of - * {@link #header()}.{@link MapMutator#empty() empty()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()} - * (to replace all header parameters) or - * {@link #header()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()} - * to only append the {@code map} entries. This method will be removed before the 1.0 release. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - JwtBuilder setHeader(Map map); - - /** - * Adds the specified name/value pairs to the header. Any parameter with an empty or null value will remove the - * entry from the header. This is a wrapper method for: - *
-     * {@link #header()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()}
- * - * @param params the header name/value pairs to append to the header. - * @return the builder for method chaining. - * @deprecated since 0.12.0 in favor of - * {@link #header()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()}. - * This method will be removed before the 1.0 release. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - JwtBuilder setHeaderParams(Map params); - - /** - * Adds the specified name/value pair to the header. If the value is {@code null} or empty, the parameter will - * be removed from the header entirely. This is a wrapper method for: - *
-     * {@link #header()}.{@link MapMutator#add(Object, Object) add(name, value)}.{@link BuilderHeader#and() and()}
- * - * @param name the header parameter name - * @param value the header parameter value - * @return the builder for method chaining. - * @deprecated since 0.12.0 in favor of - * {@link #header()}.{@link MapMutator#add(Object, Object) add(name, value)}.{@link BuilderHeader#and() and()}. - * This method will be removed before the 1.0 release. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - JwtBuilder setHeaderParam(String name, Object value); - - /** - * Since JJWT 0.12.0, this is an alias for {@link #content(String)}. This method will be removed - * before the 1.0 release. - * - * @param payload the string used to set UTF-8-encoded bytes as the JWT payload. - * @return the builder for method chaining. - * @see #content(String) - * @deprecated since 0.12.0 in favor of {@link #content(String)} - * because both Claims and Content are technically 'payloads', so this method name is misleading. This method will - * be removed before the 1.0 release. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - JwtBuilder setPayload(String payload); - - /** - * Sets the JWT payload to be the specified string's UTF-8 bytes. This is a convenience method semantically - * equivalent to calling: - * - *
-     * {@link #content(byte[]) content}(payload.getBytes(StandardCharsets.UTF_8))
- * - *

Content Type Recommendation

- * - *

Unless you are confident that the JWT recipient will always know to convert the payload bytes - * to a UTF-8 string without additional metadata, it is strongly recommended to use the - * {@link #content(String, String)} method instead of this one. That method ensures that a JWT recipient can - * inspect the {@code cty} header to know how to handle the payload bytes without ambiguity.

- * - *

Mutually Exclusive Claims and Content

- * - *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} - * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the - * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

- * - * @param content the content string to use for the JWT payload - * @return the builder for method chaining. - * @see #content(String, String) - * @see #content(byte[], String) - * @see #content(InputStream, String) - * @since 0.12.0 - */ - JwtBuilder content(String content); - - /** - * Sets the JWT payload to be the specified content byte array. This is a convenience method semantically - * equivalent to calling: - *
-     * {@link #content(InputStream) content}(new ByteArrayInputStream(content))
- * - *

Content Type Recommendation

- * - *

Unless you are confident that the JWT recipient will always know how to use the payload bytes - * without additional metadata, it is strongly recommended to also set the - * {@link Header#getContentType() contentType} header. For example:

- * - *
-     * content(bytes).{@link #header() header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
- * - *

This ensures a JWT recipient can inspect the {@code cty} header to know how to handle the payload bytes - * without ambiguity.

- * - *

Mutually Exclusive Claims and Content

- * - *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} - * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the - * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

- * - * @param content the content byte array to use as the JWT payload - * @return the builder for method chaining. - * @see #content(byte[], String) - * @since 0.12.0 - */ - JwtBuilder content(byte[] content); - - /** - * Sets the JWT payload to be the bytes in the specified content stream. - * - *

Content Type Recommendation

- * - *

Unless you are confident that the JWT recipient will always know how to use the payload bytes - * without additional metadata, it is strongly recommended to also set the - * {@link HeaderMutator#contentType(String) contentType} header. For example:

- * - *
-     * content(in).{@link #header() header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
- * - *

This ensures a JWT recipient can inspect the {@code cty} header to know how to handle the payload bytes - * without ambiguity.

- * - *

Mutually Exclusive Claims and Content

- * - *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} - * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the - * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

- * - * @param in the input stream containing the bytes to use as the JWT payload - * @return the builder for method chaining. - * @see #content(byte[], String) - * @since 0.12.0 - */ - JwtBuilder content(InputStream in); - - /** - * Sets the JWT payload to be the specified String's UTF-8 bytes, and also sets the - * {@link HeaderMutator#contentType(String) contentType} header value to a compact {@code cty} IANA Media Type - * identifier to indicate the data format of the resulting byte array. The JWT recipient can inspect the - * {@code cty} value to determine how to convert the byte array to the final content type as desired. This is a - * convenience method semantically equivalent to: - * - *
-     * {@link #content(String) content(content)}.{@link #header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
- * - *

Compact Media Type Identifier

- * - *

This method will automatically remove any application/ prefix from the - * {@code cty} string if possible according to the rules defined in the last paragraph of - * RFC 7517, Section 4.1.10:

- * - *
-     *     To keep messages compact in common situations, it is RECOMMENDED that
-     *     producers omit an "application/" prefix of a media type value in a
-     *     "cty" Header Parameter when no other '/' appears in the media type
-     *     value.  A recipient using the media type value MUST treat it as if
-     *     "application/" were prepended to any "cty" value not containing a
-     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
-     *     represent the "application/example" media type, whereas the media
-     *     type "application/example;part="1/2"" cannot be shortened to
-     *     "example;part="1/2"".
- * - *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the - * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as - * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media - * Type identifiers without needing JWT-specific prefix conditional logic in application code. - *

- * - *

Mutually Exclusive Claims and Content

- * - *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} - * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the - * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

- * - * @param content the content byte array that will be the JWT payload. Cannot be null or empty. - * @param cty the content type (media type) identifier attributed to the byte array. Cannot be null or empty. - * @return the builder for method chaining. - * @throws IllegalArgumentException if either {@code content} or {@code cty} are null or empty. - * @since 0.12.0 - */ - JwtBuilder content(String content, String cty) throws IllegalArgumentException; - - /** - * Sets the JWT payload to be the specified byte array, and also sets the - * {@link HeaderMutator#contentType(String) contentType} header value to a compact {@code cty} IANA Media Type - * identifier to indicate the data format of the byte array. The JWT recipient can inspect the - * {@code cty} value to determine how to convert the byte array to the final content type as desired. This is a - * convenience method semantically equivalent to: - * - *
-     * {@link #content(byte[]) content(content)}.{@link #header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
- * - *

Compact Media Type Identifier

- * - *

This method will automatically remove any application/ prefix from the - * {@code cty} string if possible according to the rules defined in the last paragraph of - * RFC 7517, Section 4.1.10:

- *
-     *     To keep messages compact in common situations, it is RECOMMENDED that
-     *     producers omit an "application/" prefix of a media type value in a
-     *     "cty" Header Parameter when no other '/' appears in the media type
-     *     value.  A recipient using the media type value MUST treat it as if
-     *     "application/" were prepended to any "cty" value not containing a
-     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
-     *     represent the "application/example" media type, whereas the media
-     *     type "application/example;part="1/2"" cannot be shortened to
-     *     "example;part="1/2"".
- * - *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the - * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as - * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media - * Type identifiers without needing JWT-specific prefix conditional logic in application code. - *

- * - *

Mutually Exclusive Claims and Content

- * - *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} - * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the - * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

- * - * @param content the content byte array that will be the JWT payload. Cannot be null or empty. - * @param cty the content type (media type) identifier attributed to the byte array. Cannot be null or empty. - * @return the builder for method chaining. - * @throws IllegalArgumentException if either {@code content} or {@code cty} are null or empty. - * @since 0.12.0 - */ - JwtBuilder content(byte[] content, String cty) throws IllegalArgumentException; - - /** - * Sets the JWT payload to be the specified content byte stream and also sets the - * {@link BuilderHeader#contentType(String) contentType} header value to a compact {@code cty} IANA Media Type - * identifier to indicate the data format of the byte array. The JWT recipient can inspect the - * {@code cty} value to determine how to convert the byte array to the final content type as desired. This is a - * convenience method semantically equivalent to: - * - *
-     * {@link #content(InputStream) content(content)}.{@link #header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
- * - *

Compact Media Type Identifier

- * - *

This method will automatically remove any application/ prefix from the - * {@code cty} string if possible according to the rules defined in the last paragraph of - * RFC 7517, Section 4.1.10:

- * - *
-     *     To keep messages compact in common situations, it is RECOMMENDED that
-     *     producers omit an "application/" prefix of a media type value in a
-     *     "cty" Header Parameter when no other '/' appears in the media type
-     *     value.  A recipient using the media type value MUST treat it as if
-     *     "application/" were prepended to any "cty" value not containing a
-     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
-     *     represent the "application/example" media type, whereas the media
-     *     type "application/example;part="1/2"" cannot be shortened to
-     *     "example;part="1/2"".
- * - *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the - * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as - * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media - * Type identifiers without needing JWT-specific prefix conditional logic in application code. - *

- * - *

Mutually Exclusive Claims and Content

- * - *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} - * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the - * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

- * - * @param content the content byte array that will be the JWT payload. Cannot be null. - * @param cty the content type (media type) identifier attributed to the byte array. Cannot be null or empty. - * @return the builder for method chaining. - * @throws IllegalArgumentException if either {@code content} or {@code cty} are null or empty. - * @since 0.12.0 - */ - JwtBuilder content(InputStream content, String cty) throws IllegalArgumentException; - - /** - * Returns the JWT {@code Claims} payload to modify as desired. When finished, callers may - * return to {@code JwtBuilder} configuration via the {@link BuilderClaims#and() and()} method. - * For example: - * - *
-     * String jwt = Jwts.builder()
-     *
-     *     .claims()
-     *         .issuer("me")
-     *         .subject("Joe")
-     *         .audience().add("you").and()
-     *         .add("customClaim", customValue)
-     *         .add(myClaimsMap)
-     *         // ... etc ...
-     *         .{@link BuilderClaims#and() and()} //return back to the JwtBuilder
-     *
-     *     .signWith(key) // resume JwtBuilder calls
-     *     // ... etc ...
-     *     .compact();
- * - * @return the {@link BuilderClaims} to use for Claims construction. - * @since 0.12.0 - */ - BuilderClaims claims(); - - /** - * Replaces the JWT Claims payload with the specified name/value pairs. This is an alias for: - *
-     * {@link #claims()}.{@link MapMutator#empty() empty()}.{@link MapMutator#add(Map) add(claims)}.{@link BuilderClaims#and() and()}
- * - *

The {@code content} and {@code claims} properties are mutually exclusive - only one of the two variants - * may be used.

- * - * @param claims the JWT Claims to be set as the JWT payload. - * @return the builder for method chaining. - * @see #claims() - * @see #content(String) - * @see #content(byte[]) - * @see #content(InputStream) - * @deprecated since 0.12.0 in favor of using the {@link #claims()} builder. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - JwtBuilder setClaims(Map claims); - - /** - * Adds/appends all given name/value pairs to the JSON Claims in the payload. This is an alias for: - * - *
-     * {@link #claims()}.{@link MapMutator#add(Map) add(claims)}.{@link BuilderClaims#and() and()}
- * - *

The content and claims properties are mutually exclusive - only one of the two may be used.

- * - * @param claims the JWT Claims to be added to the JWT payload. - * @return the builder for method chaining. - * @since 0.8 - * @deprecated since 0.12.0 in favor of - * {@link #claims()}.{@link BuilderClaims#add(Map) add(Map)}.{@link BuilderClaims#and() and()}. - * This method will be removed before the 1.0 release. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - JwtBuilder addClaims(Map claims); - - /** - * Sets a JWT claim, overwriting any existing claim with the same name. A {@code null} or empty - * value will remove the claim entirely. This is a convenience alias for: - *
-     * {@link #claims()}.{@link MapMutator#add(Object, Object) add(name, value)}.{@link BuilderClaims#and() and()}
- * - * @param name the JWT Claims property name - * @param value the value to set for the specified Claims property name - * @return the builder instance for method chaining. - * @since 0.2 - */ - JwtBuilder claim(String name, Object value); - - /** - * Adds all given name/value pairs to the JSON Claims in the payload, overwriting any existing claims - * with the same names. If any name has a {@code null} or empty value, that claim will be removed from the - * Claims. This is a convenience alias for: - *
-     * {@link #claims()}.{@link MapMutator#add(Map) add(claims)}.{@link BuilderClaims#and() and()}
- * - *

The content and claims properties are mutually exclusive - only one of the two may be used.

- * - * @param claims the JWT Claims to be added to the JWT payload. - * @return the builder instance for method chaining - * @since 0.12.0 - */ - JwtBuilder claims(Map claims); - - /** - * Sets the JWT Claims - * iss (issuer) claim. A {@code null} value will remove the property from the Claims. - * This is a convenience wrapper for: - *
-     * {@link #claims()}.{@link ClaimsMutator#issuer(String) issuer(iss)}.{@link BuilderClaims#and() and()}
- * - * @param iss the JWT {@code iss} value or {@code null} to remove the property from the Claims map. - * @return the builder instance for method chaining. - */ - @Override - // for better/targeted JavaDoc - JwtBuilder issuer(String iss); - - /** - * Sets the JWT Claims - * sub (subject) claim. A {@code null} value will remove the property from the Claims. - * This is a convenience wrapper for: - *
-     * {@link #claims()}.{@link ClaimsMutator#subject(String) subject(sub)}.{@link BuilderClaims#and() and()}
- * - * @param sub the JWT {@code sub} value or {@code null} to remove the property from the Claims map. - * @return the builder instance for method chaining. - */ - @Override - // for better/targeted JavaDoc - JwtBuilder subject(String sub); - - /** - * Sets the JWT Claims - * exp (expiration) claim. A {@code null} value will remove the property from the Claims. - * - *

A JWT obtained after this timestamp should not be used.

- * - *

This is a convenience wrapper for:

- *
-     * {@link #claims()}.{@link ClaimsMutator#expiration(Date) expiration(exp)}.{@link BuilderClaims#and() and()}
- * - * @param exp the JWT {@code exp} value or {@code null} to remove the property from the Claims map. - * @return the builder instance for method chaining. - */ - @Override - // for better/targeted JavaDoc - JwtBuilder expiration(Date exp); - - /** - * Sets the JWT Claims - * nbf (not before) claim. A {@code null} value will remove the property from the Claims. - * - *

A JWT obtained before this timestamp should not be used.

- * - *

This is a convenience wrapper for:

- *
-     * {@link #claims()}.{@link ClaimsMutator#notBefore(Date) notBefore(nbf)}.{@link BuilderClaims#and() and()}
- * - * @param nbf the JWT {@code nbf} value or {@code null} to remove the property from the Claims map. - * @return the builder instance for method chaining. - */ - @Override - // for better/targeted JavaDoc - JwtBuilder notBefore(Date nbf); - - /** - * Sets the JWT Claims - * iat (issued at) claim. A {@code null} value will remove the property from the Claims. - * - *

The value is the timestamp when the JWT was created.

- * - *

This is a convenience wrapper for:

- *
-     * {@link #claims()}.{@link ClaimsMutator#issuedAt(Date) issuedAt(iat)}.{@link BuilderClaims#and() and()}
- * - * @param iat the JWT {@code iat} value or {@code null} to remove the property from the Claims map. - * @return the builder instance for method chaining. - */ - @Override - // for better/targeted JavaDoc - JwtBuilder issuedAt(Date iat); - - /** - * Sets the JWT Claims - * jti (JWT ID) claim. A {@code null} value will remove the property from the Claims. - * - *

The value is a CaSe-SenSiTiVe unique identifier for the JWT. If specified, this value MUST be assigned in a - * manner that ensures that there is a negligible probability that the same value will be accidentally - * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

- * - *

This is a convenience wrapper for:

- *
-     * {@link #claims()}.{@link ClaimsMutator#id(String) id(jti)}.{@link BuilderClaims#and() and()}
- * - * @param jti the JWT {@code jti} (id) value or {@code null} to remove the property from the Claims map. - * @return the builder instance for method chaining. - */ - @Override - // for better/targeted JavaDoc - JwtBuilder id(String jti); - - /** - * Signs the constructed JWT with the specified key using the key's recommended signature algorithm - * as defined below, producing a JWS. If the recommended signature algorithm isn't sufficient for your needs, - * consider using {@link #signWith(Key, SecureDigestAlgorithm)} instead. - * - *

If you are looking to invoke this method with a byte array that you are confident may be used for HMAC-SHA - * algorithms, consider using {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to - * convert the byte array into a valid {@code Key}.

- * - *

Recommended Signature Algorithm

- * - *

The recommended signature algorithm used with a given key is chosen based on the following:

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Key Recommended Signature Algorithm
If the Key is a:And:With a key size of:The SignatureAlgorithm used will be:
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA256")1256 <= size <= 383 2{@link Jwts.SIG#HS256 HS256}
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA384")1384 <= size <= 511{@link Jwts.SIG#HS384 HS384}
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA512")1512 <= size{@link Jwts.SIG#HS512 HS512}
{@link ECKey}instanceof {@link PrivateKey}256 <= size <= 383 3{@link Jwts.SIG#ES256 ES256}
{@link ECKey}instanceof {@link PrivateKey}384 <= size <= 520 4{@link Jwts.SIG#ES384 ES384}
{@link ECKey}instanceof {@link PrivateKey}521 <= size 4{@link Jwts.SIG#ES512 ES512}
{@link RSAKey}instanceof {@link PrivateKey}2048 <= size <= 3071 5,6{@link Jwts.SIG#RS256 RS256}
{@link RSAKey}instanceof {@link PrivateKey}3072 <= size <= 4095 6{@link Jwts.SIG#RS384 RS384}
{@link RSAKey}instanceof {@link PrivateKey}4096 <= size 5{@link Jwts.SIG#RS512 RS512}
EdECKey7instanceof {@link PrivateKey}256 || 456{@link Jwts.SIG#EdDSA EdDSA}
- *

Notes:

- *
    - *
  1. {@code SecretKey} instances must have an {@link Key#getAlgorithm() algorithm} name equal - * to {@code HmacSHA256}, {@code HmacSHA384} or {@code HmacSHA512}. If not, the key bytes might not be - * suitable for HMAC signatures will be rejected with a {@link InvalidKeyException}.
  2. - *
  3. The JWT JWA Specification (RFC 7518, - * Section 3.2) mandates that HMAC-SHA-* signing keys MUST be 256 bits or greater. - * {@code SecretKey}s with key lengths less than 256 bits will be rejected with an - * {@link WeakKeyException}.
  4. - *
  5. The JWT JWA Specification (RFC 7518, - * Section 3.4) mandates that ECDSA signing key lengths MUST be 256 bits or greater. - * {@code ECKey}s with key lengths less than 256 bits will be rejected with a - * {@link WeakKeyException}.
  6. - *
  7. The ECDSA {@code P-521} curve does indeed use keys of 521 bits, not 512 as might be expected. ECDSA - * keys of 384 < size <= 520 are suitable for ES384, while ES512 requires keys >= 521 bits. The '512' part of the - * ES512 name reflects the usage of the SHA-512 algorithm, not the ECDSA key length. ES512 with ECDSA keys less - * than 521 bits will be rejected with a {@link WeakKeyException}.
  8. - *
  9. The JWT JWA Specification (RFC 7518, - * Section 3.3) mandates that RSA signing key lengths MUST be 2048 bits or greater. - * {@code RSAKey}s with key lengths less than 2048 bits will be rejected with a - * {@link WeakKeyException}.
  10. - *
  11. Technically any RSA key of length >= 2048 bits may be used with the - * {@link Jwts.SIG#RS256 RS256}, {@link Jwts.SIG#RS384 RS384}, and - * {@link Jwts.SIG#RS512 RS512} algorithms, so we assume an RSA signature algorithm based on the key - * length to parallel similar decisions in the JWT specification for HMAC and ECDSA signature algorithms. - * This is not required - just a convenience.
  12. - *
  13. EdECKeys - * require JDK >= 15 or BouncyCastle in the runtime classpath.
  14. - *
- * - *

This implementation does not use the {@link Jwts.SIG#PS256 PS256}, - * {@link Jwts.SIG#PS384 PS384}, or {@link Jwts.SIG#PS512 PS512} RSA variants for any - * specified {@link RSAKey} because the the {@link Jwts.SIG#RS256 RS256}, - * {@link Jwts.SIG#RS384 RS384}, and {@link Jwts.SIG#RS512 RS512} algorithms are - * available in the JDK by default while the {@code PS}* variants require either JDK 11 or an additional JCA - * Provider (like BouncyCastle). If you wish to use a {@code PS}* variant with your key, use the - * {@link #signWith(Key, SecureDigestAlgorithm)} method instead.

- * - *

Finally, this method will throw an {@link InvalidKeyException} for any key that does not match the - * heuristics and requirements documented above, since that inevitably means the Key is either insufficient, - * unsupported, or explicitly disallowed by the JWT specification.

- * - * @param key the key to use for signing - * @return the builder instance for method chaining. - * @throws InvalidKeyException if the Key is insufficient, unsupported, or explicitly disallowed by the JWT - * specification as described above in recommended signature algorithms. - * @see Jwts.SIG - * @see #signWith(Key, SecureDigestAlgorithm) - * @since 0.10.0 - */ - JwtBuilder signWith(Key key) throws InvalidKeyException; - - /** - * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS. - * - *

Deprecation Notice: Deprecated as of 0.10.0

- * - *

Use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to - * obtain the {@code Key} and then invoke {@link #signWith(Key)} or - * {@link #signWith(Key, SecureDigestAlgorithm)}.

- * - *

This method will be removed in the 1.0 release.

- * - * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. - * @param secretKey the algorithm-specific signing key to use to digitally sign the JWT. - * @return the builder for method chaining. - * @throws InvalidKeyException if the Key is insufficient for the specified algorithm or explicitly disallowed by - * the JWT specification. - * @deprecated as of 0.10.0: use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to - * obtain the {@code Key} and then invoke {@link #signWith(Key)} or - * {@link #signWith(Key, SecureDigestAlgorithm)}. - * This method will be removed in the 1.0 release. - */ - @Deprecated - JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey) throws InvalidKeyException; - - /** - * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS. - * - *

This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting - * byte array is used to invoke {@link #signWith(SignatureAlgorithm, byte[])}.

- * - *

Deprecation Notice: Deprecated as of 0.10.0, will be removed in the 1.0 release.

- * - *

This method has been deprecated because the {@code key} argument for this method can be confusing: keys for - * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were - * obtained from the String argument.

- * - *

This method always expected a String argument that was effectively the same as the result of the following - * (pseudocode):

- * - *

{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}

- * - *

However, a non-trivial number of JJWT users were confused by the method signature and attempted to - * use raw password strings as the key argument - for example {@code with(HS256, myPassword)} - which is - * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.

- * - *

See this - * - * StackOverflow answer explaining why raw (non-base64-encoded) strings are almost always incorrect for - * signature operations.

- * - *

To perform the correct logic with base64EncodedSecretKey strings with JJWT >= 0.10.0, you may do this:

- *

-     * byte[] keyBytes = {@link Decoders Decoders}.{@link Decoders#BASE64 BASE64}.{@link Decoder#decode(Object) decode(base64EncodedSecretKey)};
-     * Key key = {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(keyBytes)};
-     * jwtBuilder.with(key); //or {@link #signWith(Key, SignatureAlgorithm)}
-     * 
- * - *

This method will be removed in the 1.0 release.

- * - * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. - * @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signing key to use to digitally sign the - * JWT. - * @return the builder for method chaining. - * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as - * described by {@link SignatureAlgorithm#forSigningKey(Key)}. - * @deprecated as of 0.10.0: use {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)} instead. This - * method will be removed in the 1.0 release. - */ - @Deprecated - JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) throws InvalidKeyException; - - /** - * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS. - * - *

It is typically recommended to call the {@link #signWith(Key)} instead for simplicity. - * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if - * you want explicit control over the signature algorithm used with the specified key.

- * - * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. - * @param key the algorithm-specific signing key to use to digitally sign the JWT. - * @return the builder for method chaining. - * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for - * the specified algorithm. - * @see #signWith(Key) - * @deprecated since 0.10.0. Use {@link #signWith(Key, SecureDigestAlgorithm)} instead. - * This method will be removed before the 1.0 release. - */ - @Deprecated - JwtBuilder signWith(SignatureAlgorithm alg, Key key) throws InvalidKeyException; - - /** - *

Deprecation Notice

- * - *

This has been deprecated since 0.12.0. Use - * {@link #signWith(Key, SecureDigestAlgorithm)} instead. Standard JWA algorithms - * are represented as instances of this new interface in the {@link Jwts.SIG} - * algorithm registry.

- * - *

Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS.

- * - *

It is typically recommended to call the {@link #signWith(Key)} instead for simplicity. - * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if - * you want explicit control over the signature algorithm used with the specified key.

- * - * @param key the signing key to use to digitally sign the JWT. - * @param alg the JWS algorithm to use with the key to digitally sign the JWT, thereby producing a JWS. - * @return the builder for method chaining. - * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for - * the specified algorithm. - * @see #signWith(Key) - * @since 0.10.0 - * @deprecated since 0.12.0 to use the more flexible {@link #signWith(Key, SecureDigestAlgorithm)}. - */ - @Deprecated - JwtBuilder signWith(Key key, SignatureAlgorithm alg) throws InvalidKeyException; - - /** - * Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS. - * - *

The {@link Jwts.SIG} registry makes available all standard signature - * algorithms defined in the JWA specification.

- * - *

It is typically recommended to call the {@link #signWith(Key)} instead for simplicity. - * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if - * you want explicit control over the signature algorithm used with the specified key.

- * - * @param key the signing key to use to digitally sign the JWT. - * @param The type of key accepted by the {@code SignatureAlgorithm}. - * @param alg the JWS algorithm to use with the key to digitally sign the JWT, thereby producing a JWS. - * @return the builder for method chaining. - * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for - * the specified algorithm. - * @see #signWith(Key) - * @see Jwts.SIG - * @since 0.12.0 - */ - JwtBuilder signWith(K key, SecureDigestAlgorithm alg) throws InvalidKeyException; - - /** - * Encrypts the constructed JWT with the specified symmetric {@code key} using the provided {@code enc}ryption - * algorithm, producing a JWE. Because it is a symmetric key, the JWE recipient - * must also have access to the same key to decrypt. - * - *

This method is a convenience method that delegates to - * {@link #encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} - * based on the {@code key} argument:

- *
    - *
  • If the provided {@code key} is a {@link Password Password} instance, - * the {@code KeyAlgorithm} used will be one of the three JWA-standard password-based key algorithms - * ({@link Jwts.KEY#PBES2_HS256_A128KW PBES2_HS256_A128KW}, - * {@link Jwts.KEY#PBES2_HS384_A192KW PBES2_HS384_A192KW}, or - * {@link Jwts.KEY#PBES2_HS512_A256KW PBES2_HS512_A256KW}) as determined by the {@code enc} algorithm's - * {@link AeadAlgorithm#getKeyBitLength() key length} requirement.
  • - *
  • If the {@code key} is otherwise a standard {@code SecretKey}, the {@code KeyAlgorithm} will be - * {@link Jwts.KEY#DIRECT DIRECT}, indicating that {@code key} should be used directly with the - * {@code enc} algorithm. In this case, the {@code key} argument MUST be of sufficient strength to - * use with the specified {@code enc} algorithm, otherwise an exception will be thrown during encryption. If - * desired, secure-random keys suitable for an {@link AeadAlgorithm} may be generated using the algorithm's - * {@link AeadAlgorithm#key() key()} builder.
  • - *
- * - * @param key the symmetric encryption key to use with the {@code enc} algorithm. - * @param enc the {@link AeadAlgorithm} algorithm used to encrypt the JWE, usually one of the JWA-standard - * algorithms accessible via {@link Jwts.ENC}. - * @return the JWE builder for method chaining. - * @see Jwts.ENC - */ - JwtBuilder encryptWith(SecretKey key, AeadAlgorithm enc); - - /** - * Encrypts the constructed JWT using the specified {@code enc} algorithm with the symmetric key produced by the - * {@code keyAlg} when invoked with the given {@code key}, producing a JWE. - * - *

This behavior can be illustrated by the following pseudocode, a rough example of what happens during - * {@link #compact() compact}ion:

- *
-     *     SecretKey encryptionKey = keyAlg.getEncryptionKey(key);           // (1)
-     *     byte[] jweCiphertext = enc.encrypt(payloadBytes, encryptionKey);  // (2)
- *
    - *
  1. The {@code keyAlg} argument is first invoked with the provided {@code key} argument, resulting in a - * {@link SecretKey}.
  2. - *
  3. This {@code SecretKey} result is used to call the provided {@code enc} encryption algorithm argument, - * resulting in the final JWE ciphertext.
  4. - *
- * - *

Most application developers will reference one of the JWA - * {@link Jwts.KEY standard key algorithms} and {@link Jwts.ENC standard encryption algorithms} - * when invoking this method, but custom implementations are also supported.

- * - * @param the type of key that must be used with the specified {@code keyAlg} instance. - * @param key the key used to invoke the provided {@code keyAlg} instance. - * @param keyAlg the key management algorithm that will produce the symmetric {@code SecretKey} to use with the - * {@code enc} algorithm - * @param enc the {@link AeadAlgorithm} algorithm used to encrypt the JWE - * @return the JWE builder for method chaining. - * @see Jwts.ENC - * @see Jwts.KEY - */ - JwtBuilder encryptWith(K key, KeyAlgorithm keyAlg, AeadAlgorithm enc); - - /** - * Compresses the JWT payload using the specified {@link CompressionAlgorithm}. - * - *

If your compact JWTs are large, and you want to reduce their total size during network transmission, this - * can be useful. For example, when embedding JWTs in URLs, some browsers may not support URLs longer than a - * certain length. Using compression can help ensure the compact JWT fits within that length. However, NOTE:

- * - *

Compatibility Warning

- * - *

The JWT family of specifications defines compression only for JWE (JSON Web Encryption) - * tokens. Even so, JJWT will also support compression for JWS tokens as well if you choose to use it. - * However, be aware that if you use compression when creating a JWS token, other libraries may not be able to - * parse that JWS token. When using compression for JWS tokens, be sure that all parties accessing the - * JWS token support compression for JWS.

- * - *

Compression when creating JWE tokens however should be universally accepted for any - * library that supports JWE.

- * - * @param alg implementation of the {@link CompressionAlgorithm} to be used. - * @return the builder for method chaining. - * @see Jwts.ZIP - * @since 0.12.0 - */ - JwtBuilder compressWith(CompressionAlgorithm alg); - - /** - * Perform Base64Url encoding during {@link #compact() compaction} with the specified Encoder. - * - *

JJWT uses a spec-compliant encoder that works on all supported JDK versions, but you may call this method - * to specify a different encoder if you desire.

- * - * @param base64UrlEncoder the encoder to use when Base64Url-encoding - * @return the builder for method chaining. - * @see #b64Url(Encoder) - * @since 0.10.0 - * @deprecated since 0.12.0 in favor of {@link #b64Url(Encoder)}. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - JwtBuilder base64UrlEncodeWith(Encoder base64UrlEncoder); - - /** - * Perform Base64Url encoding during {@link #compact() compaction} with the specified {@code OutputStream} Encoder. - * The Encoder's {@link Encoder#encode(Object) encode} method will be given a target {@code OutputStream} to - * wrap, and the resulting (wrapping) {@code OutputStream} will be used for writing, ensuring automatic - * Base64URL-encoding during write operations. - * - *

JJWT uses a spec-compliant encoder that works on all supported JDK versions, but you may call this method - * to specify a different stream encoder if desired.

- * - * @param encoder the encoder to use when Base64Url-encoding - * @return the builder for method chaining. - * @since 0.12.0 - */ - JwtBuilder b64Url(Encoder encoder); - - /** - * Enables RFC 7797: JSON Web Signature (JWS) - * Unencoded Payload Option if {@code false}, or standard JWT/JWS/JWE payload encoding otherwise. The default - * value is {@code true} per standard RFC behavior rules. - * - *

This value may only be {@code false} for JWSs (signed JWTs). It may not be used for standard - * (unprotected) JWTs or encrypted JWTs (JWEs). The builder will throw an exception during {@link #compact()} if - * {@code false} and a JWS is not being created.

- * - * @param b64 whether to Base64URL-encode the JWS payload - * @return the builder for method chaining. - */ - JwtBuilder encodePayload(boolean b64); - - /** - * Performs Map-to-JSON serialization with the specified Serializer. This is used by the builder to convert - * JWT/JWS/JWE headers and claims Maps to JSON strings as required by the JWT specification. - * - *

If this method is not called, JJWT will use whatever serializer it can find at runtime, checking for the - * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found - * in the runtime classpath, an exception will be thrown when the {@link #compact()} method is invoked.

- * - * @param serializer the serializer to use when converting Map objects to JSON strings. - * @return the builder for method chaining. - * @since 0.10.0 - * @deprecated since 0.12.0 in favor of {@link #json(Serializer)} - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - JwtBuilder serializeToJsonWith(Serializer> serializer); - - /** - * Perform Map-to-JSON serialization with the specified Serializer. This is used by the builder to convert - * JWT/JWS/JWE headers and Claims Maps to JSON strings as required by the JWT specification. - * - *

If this method is not called, JJWT will use whatever Serializer it can find at runtime, checking for the - * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found - * in the runtime classpath, an exception will be thrown when the {@link #compact()} method is invoked.

- * - * @param serializer the Serializer to use when converting Map objects to JSON strings. - * @return the builder for method chaining. - * @since 0.12.0 - */ - JwtBuilder json(Serializer> serializer); - - /** - * Actually builds the JWT and serializes it to a compact, URL-safe string according to the - * JWT Compact Serialization - * rules. - * - * @return A compact URL-safe JWT string. - */ - String compact(); - - /** - * Claims for use with a {@link JwtBuilder} that supports method chaining for standard JWT Claims parameters. - * Once claims are configured, the associated {@link JwtBuilder} may be obtained with the {@link #and() and()} - * method for continued configuration. - * - * @since 0.12.0 - */ - interface BuilderClaims extends MapMutator, ClaimsMutator, - Conjunctor { - } - - /** - * Header for use with a {@link JwtBuilder} that supports method chaining for - * standard JWT, JWS and JWE header parameters. Once header parameters are configured, the associated - * {@link JwtBuilder} may be obtained with the {@link #and() and()} method for continued configuration. - * - * @since 0.12.0 - */ - interface BuilderHeader extends JweHeaderMutator, X509Builder, - Conjunctor { - } -} diff --git a/io/jsonwebtoken/JwtException.java b/io/jsonwebtoken/JwtException.java deleted file mode 100644 index e3990da..0000000 --- a/io/jsonwebtoken/JwtException.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * Base class for JWT-related runtime exceptions. - * - * @since 0.1 - */ -public class JwtException extends RuntimeException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public JwtException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public JwtException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/JwtHandler.java b/io/jsonwebtoken/JwtHandler.java deleted file mode 100644 index faf41a6..0000000 --- a/io/jsonwebtoken/JwtHandler.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * A JwtHandler is invoked by a {@link io.jsonwebtoken.JwtParser JwtParser} after parsing a JWT to indicate the exact - * type of JWT, JWS or JWE parsed. - * - * @param the type of object to return to the parser caller after handling the parsed JWT. - * @since 0.2 - * @deprecated since 0.12.0 in favor of calling {@link Jwt#accept(JwtVisitor)}. - */ -@SuppressWarnings("DeprecatedIsStillUsed") -@Deprecated -public interface JwtHandler extends JwtVisitor { - - /** - * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is - * an unsecured content JWT. An unsecured content JWT has a byte array payload that is not - * cryptographically signed or encrypted. If the JWT creator set the (optional) - * {@link Header#getContentType() contentType} header value, the application may inspect that value to determine - * how to convert the byte array to the final content type as desired. - * - * @param jwt the parsed unsecured content JWT - * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. - */ - T onContentJwt(Jwt jwt); - - /** - * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is - * a Claims JWT. A Claims JWT has a {@link Claims} payload that is not cryptographically signed or encrypted. - * - * @param jwt the parsed claims JWT - * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. - */ - T onClaimsJwt(Jwt jwt); - - /** - * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is - * a content JWS. A content JWS is a JWT with a byte array payload that has been cryptographically signed. - * If the JWT creator set the (optional) {@link Header#getContentType() contentType} header value, the - * application may inspect that value to determine how to convert the byte array to the final content type - * as desired. - * - *

This method will only be invoked if the cryptographic signature can be successfully verified.

- * - * @param jws the parsed content JWS - * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. - */ - T onContentJws(Jws jws); - - /** - * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is - * a valid Claims JWS. A Claims JWS is a JWT with a {@link Claims} payload that has been cryptographically signed. - * - *

This method will only be invoked if the cryptographic signature can be successfully verified.

- * - * @param jws the parsed claims JWS - * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. - */ - T onClaimsJws(Jws jws); - - /** - * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is - * a content JWE. A content JWE is a JWE with a byte array payload that has been encrypted. If the JWT creator set - * the (optional) {@link Header#getContentType() contentType} header value, the application may inspect that - * value to determine how to convert the byte array to the final content type as desired. - * - *

This method will only be invoked if the content JWE can be successfully decrypted.

- * - * @param jwe the parsed content jwe - * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. - * @since 0.12.0 - */ - T onContentJwe(Jwe jwe); - - /** - * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is - * a valid Claims JWE. A Claims JWE is a JWT with a {@link Claims} payload that has been encrypted. - * - *

This method will only be invoked if the Claims JWE can be successfully decrypted.

- * - * @param jwe the parsed claims jwe - * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. - * @since 0.12.0 - */ - T onClaimsJwe(Jwe jwe); - -} diff --git a/io/jsonwebtoken/JwtHandlerAdapter.java b/io/jsonwebtoken/JwtHandlerAdapter.java deleted file mode 100644 index 6d07a8f..0000000 --- a/io/jsonwebtoken/JwtHandlerAdapter.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * An Adapter implementation of the - * {@link JwtHandler} interface that allows for anonymous subclasses to process only the JWT results that are - * known/expected for a particular use case. - * - *

All of the methods in this implementation throw exceptions: overridden methods represent - * scenarios expected by calling code in known situations. It would be unexpected to receive a JWT that did - * not match parsing expectations, so all non-overridden methods throw exceptions to indicate that the JWT - * input was unexpected.

- * - * @param the type of object to return to the parser caller after handling the parsed JWT. - * @since 0.2 - */ -public abstract class JwtHandlerAdapter extends SupportedJwtVisitor implements JwtHandler { - - /** - * Default constructor, does not initialize any internal state. - */ - public JwtHandlerAdapter() { - } - - @Override - public T onUnsecuredContent(Jwt jwt) { - return onContentJwt(jwt); // bridge for existing implementations - } - - @Override - public T onUnsecuredClaims(Jwt jwt) { - return onClaimsJwt(jwt); - } - - @Override - public T onVerifiedContent(Jws jws) { - return onContentJws(jws); - } - - @Override - public T onVerifiedClaims(Jws jws) { - return onClaimsJws(jws); - } - - @Override - public T onDecryptedContent(Jwe jwe) { - return onContentJwe(jwe); - } - - @Override - public T onDecryptedClaims(Jwe jwe) { - return onClaimsJwe(jwe); - } - - @Override - public T onContentJwt(Jwt jwt) { - return super.onUnsecuredContent(jwt); - } - - @Override - public T onClaimsJwt(Jwt jwt) { - return super.onUnsecuredClaims(jwt); - } - - @Override - public T onContentJws(Jws jws) { - return super.onVerifiedContent(jws); - } - - @Override - public T onClaimsJws(Jws jws) { - return super.onVerifiedClaims(jws); - } - - @Override - public T onContentJwe(Jwe jwe) { - return super.onDecryptedContent(jwe); - } - - @Override - public T onClaimsJwe(Jwe jwe) { - return super.onDecryptedClaims(jwe); - } -} diff --git a/io/jsonwebtoken/JwtParser.java b/io/jsonwebtoken/JwtParser.java deleted file mode 100644 index df7e173..0000000 --- a/io/jsonwebtoken/JwtParser.java +++ /dev/null @@ -1,422 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.io.Parser; -import io.jsonwebtoken.security.SecurityException; -import io.jsonwebtoken.security.SignatureException; - -import java.io.InputStream; - -/** - * A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT. - * A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT. - * - * @since 0.1 - */ -public interface JwtParser extends Parser> { - - /** - * Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false} - * otherwise. - * - *

Note that if you are reasonably sure that the token is signed, it is more efficient to attempt to - * parse the token (and catching exceptions if necessary) instead of calling this method first before parsing.

- * - * @param compact the compact serialized JWT to check - * @return {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false} - * otherwise. - */ - boolean isSigned(CharSequence compact); - - /** - * Parses the specified compact serialized JWT string based on the builder's current configuration state and - * returns the resulting JWT, JWS, or JWE instance. - * - *

Because it is often cumbersome to determine if the result is a JWT, JWS or JWE, or if the payload is a Claims - * or {@code byte[]} array with {@code instanceof} checks, it may be useful to call the result's - * {@link Jwt#accept(JwtVisitor) accept(JwtVisitor)} method for a type-safe callback approach instead of using if-then-else - * {@code instanceof} conditionals. For example, instead of:

- * - *
-     * // NOT RECOMMENDED:
-     * Jwt<?,?> jwt = parser.parse(input);
-     * if (jwt instanceof Jwe<?>) {
-     *     Jwe<?> jwe = (Jwe<?>)jwt;
-     *     if (jwe.getPayload() instanceof Claims) {
-     *         Jwe<Claims> claimsJwe = (Jwe<Claims>)jwe;
-     *         // do something with claimsJwe
-     *     }
-     * }
- * - *

the following alternative is usually preferred:

- * - *
-     * Jwe<Claims> jwe = parser.parse(input).accept({@link Jwe#CLAIMS});
- * - * @param jwt the compact serialized JWT to parse - * @return the parsed JWT instance - * @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid). - * Invalid JWTs should not be trusted and should be discarded. - * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail - * signature validation should not be trusted and should be discarded. - * @throws SecurityException if the specified JWT string is a JWE and decryption fails - * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time - * before the time this method is invoked. - * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace. - * @see Jwt#accept(JwtVisitor) - */ - Jwt parse(CharSequence jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException, - SecurityException, IllegalArgumentException; - - /** - * Deprecated since 0.12.0 in favor of calling any {@code parse*} method immediately - * followed by invoking the parsed JWT's {@link Jwt#accept(JwtVisitor) accept} method with your preferred visitor. For - * example: - * - *
-     * {@link #parse(CharSequence) parse}(jwt).{@link Jwt#accept(JwtVisitor) accept}({@link JwtVisitor visitor});
- * - *

This method will be removed before the 1.0 release.

- * - * @param jwt the compact serialized JWT to parse - * @param handler the handler to invoke when encountering a specific type of JWT - * @param the type of object returned from the {@code handler} - * @return the result returned by the {@code JwtHandler} - * @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid). - * Invalid JWTs should not be trusted and should be discarded. - * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail - * signature validation should not be trusted and should be discarded. - * @throws SecurityException if the specified JWT string is a JWE and decryption fails - * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time - * before the time this method is invoked. - * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace, or if the - * {@code handler} is {@code null}. - * @see Jwt#accept(JwtVisitor) - * @since 0.2 - * @deprecated since 0.12.0 in favor of - * {@link #parse(CharSequence)}.{@link Jwt#accept(JwtVisitor) accept}({@link JwtVisitor visitor}); - */ - @Deprecated - T parse(CharSequence jwt, JwtHandler handler) throws ExpiredJwtException, UnsupportedJwtException, - MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException; - - /** - * Deprecated since 0.12.0 in favor of {@link #parseUnsecuredContent(CharSequence)}. - * - *

This method will be removed before the 1.0 release.

- * - * @param jwt a compact serialized unsecured content JWT string. - * @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string. - * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured content JWT - * @throws MalformedJwtException if the {@code jwt} string is not a valid JWT - * @throws SignatureException if the {@code jwt} string is actually a JWS and signature validation fails - * @throws SecurityException if the {@code jwt} string is actually a JWE and decryption fails - * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace - * @see #parseUnsecuredContent(CharSequence) - * @see Jwt#accept(JwtVisitor) - * @since 0.2 - * @deprecated since 0.12.0 in favor of {@link #parseUnsecuredContent(CharSequence)}. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - Jwt parseContentJwt(CharSequence jwt) throws UnsupportedJwtException, MalformedJwtException, - SignatureException, SecurityException, IllegalArgumentException; - - /** - * Deprecated since 0.12.0 in favor of {@link #parseUnsecuredClaims(CharSequence)}. - * - *

This method will be removed before the 1.0 release.

- * - * @param jwt a compact serialized unsecured Claims JWT string. - * @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string. - * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured Claims JWT - * @throws MalformedJwtException if the {@code jwt} string is not a valid JWT - * @throws SignatureException if the {@code jwt} string is actually a JWS and signature validation fails - * @throws SecurityException if the {@code jwt} string is actually a JWE and decryption fails - * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace - * @see #parseUnsecuredClaims(CharSequence) - * @see Jwt#accept(JwtVisitor) - * @since 0.2 - * @deprecated since 0.12.0 in favor of {@link #parseUnsecuredClaims(CharSequence)}. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - Jwt parseClaimsJwt(CharSequence jwt) throws ExpiredJwtException, UnsupportedJwtException, - MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException; - - /** - * Deprecated since 0.12.0 in favor of {@link #parseSignedContent(CharSequence)}. - * - *

This method will be removed before the 1.0 release.

- * - * @param jws a compact content JWS string - * @return the parsed and validated content JWS - * @throws UnsupportedJwtException if the {@code jws} argument does not represent a content JWS - * @throws MalformedJwtException if the {@code jws} string is not a valid JWS - * @throws SignatureException if the {@code jws} JWS signature validation fails - * @throws SecurityException if the {@code jws} string is actually a JWE and decryption fails - * @throws IllegalArgumentException if the {@code jws} string is {@code null} or empty or only whitespace - * @see #parseSignedContent(CharSequence) - * @see #parseEncryptedContent(CharSequence) - * @see #parse(CharSequence) - * @since 0.2 - * @deprecated since 0.12.0 in favor of {@link #parseSignedContent(CharSequence)}. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - Jws parseContentJws(CharSequence jws) throws UnsupportedJwtException, MalformedJwtException, SignatureException, - SecurityException, IllegalArgumentException; - - /** - * Deprecated since 0.12.0 in favor of {@link #parseSignedClaims(CharSequence)}. - * - * @param jws a compact Claims JWS string. - * @return the parsed and validated Claims JWS - * @throws UnsupportedJwtException if the {@code claimsJws} argument does not represent an Claims JWS - * @throws MalformedJwtException if the {@code claimsJws} string is not a valid JWS - * @throws SignatureException if the {@code claimsJws} JWS signature validation fails - * @throws SecurityException if the {@code jws} string is actually a JWE and decryption fails - * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time - * before the time this method is invoked. - * @throws IllegalArgumentException if the {@code claimsJws} string is {@code null} or empty or only whitespace - * @see #parseSignedClaims(CharSequence) - * @see #parseEncryptedClaims(CharSequence) - * @see #parse(CharSequence) - * @since 0.2 - * @deprecated since 0.12.0 in favor of {@link #parseSignedClaims(CharSequence)}. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - Jws parseClaimsJws(CharSequence jws) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, - SignatureException, SecurityException, IllegalArgumentException; - - /** - * Parses the {@code jwt} argument, expected to be an unsecured content JWT. If the JWT creator set - * the (optional) {@link Header#getContentType() contentType} header value, the application may inspect that - * value to determine how to convert the byte array to the final content type as desired. - * - *

This is a convenience method logically equivalent to the following:

- * - *
-     * {@link #parse(CharSequence) parse}(jwt).{@link Jwt#accept(JwtVisitor) accept}({@link
-     * Jwt#UNSECURED_CONTENT});
- * - * @param jwt a compact unsecured content JWT. - * @return the parsed unsecured content JWT. - * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured content JWT - * @throws JwtException if the {@code jwt} string cannot be parsed or validated as required. - * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace - * @see #parse(CharSequence) - * @see Jwt#accept(JwtVisitor) - * @since 0.12.0 - */ - Jwt parseUnsecuredContent(CharSequence jwt) throws JwtException, IllegalArgumentException; - - /** - * Parses the {@code jwt} argument, expected to be an unsecured {@code Claims} JWT. This is a - * convenience method logically equivalent to the following: - * - *
-     * {@link #parse(CharSequence) parse}(jwt).{@link Jwt#accept(JwtVisitor) accept}({@link
-     * Jwt#UNSECURED_CLAIMS});
- * - * @param jwt a compact unsecured Claims JWT. - * @return the parsed unsecured Claims JWT. - * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured Claims JWT - * @throws JwtException if the {@code jwt} string cannot be parsed or validated as required. - * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace - * @see #parse(CharSequence) - * @see Jwt#accept(JwtVisitor) - * @since 0.12.0 - */ - Jwt parseUnsecuredClaims(CharSequence jwt) throws JwtException, IllegalArgumentException; - - /** - * Parses the {@code jws} argument, expected to be a cryptographically-signed content JWS. If the JWS - * creator set the (optional) {@link Header#getContentType() contentType} header value, the application may - * inspect that value to determine how to convert the byte array to the final content type as desired. - * - *

This is a convenience method logically equivalent to the following:

- * - *
-     * {@link #parse(CharSequence) parse}(jws).{@link Jwt#accept(JwtVisitor) accept}({@link
-     * Jws#CONTENT});
- * - * @param jws a compact cryptographically-signed content JWS. - * @return the parsed cryptographically-verified content JWS. - * @throws UnsupportedJwtException if the {@code jws} argument does not represent a signed content JWS - * @throws JwtException if the {@code jws} string cannot be parsed or validated as required. - * @throws IllegalArgumentException if the {@code jws} string is {@code null} or empty or only whitespace - * @see #parse(CharSequence) - * @see Jwt#accept(JwtVisitor) - * @since 0.12.0 - */ - Jws parseSignedContent(CharSequence jws) throws JwtException, IllegalArgumentException; - - /** - * Parses a JWS known to use the - * RFC 7797: JSON Web Signature (JWS) Unencoded Payload - * Option, using the specified {@code unencodedPayload} for signature verification. - * - *

Unencoded Non-Detached Payload

- * - *

Note that if the JWS contains a valid unencoded Payload string (what RFC 7797 calls an - * "unencoded non-detached - * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes - * the payload content necessary for signature verification.

- * - * @param jws the Unencoded Payload JWS to parse. - * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. - * @return the parsed Unencoded Payload. - * @since 0.12.0 - */ - Jws parseSignedContent(CharSequence jws, byte[] unencodedPayload); - - /** - * Parses a JWS known to use the - * RFC 7797: JSON Web Signature (JWS) Unencoded Payload - * Option, using the bytes from the specified {@code unencodedPayload} stream for signature verification. - * - *

Because it is not possible to know how large the {@code unencodedPayload} stream will be, the stream bytes - * will not be buffered in memory, ensuring the resulting {@link Jws} return value's {@link Jws#getPayload()} - * is always empty. This is generally not a concern since the caller already has access to the stream bytes and - * may obtain them independently before or after calling this method if they are needed otherwise.

- * - *

Unencoded Non-Detached Payload

- * - *

Note that if the JWS contains a valid unencoded payload String (what RFC 7797 calls an - * "unencoded non-detached - * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes - * the payload content necessary for signature verification. In this case the resulting {@link Jws} return - * value's {@link Jws#getPayload()} will contain the embedded payload String's UTF-8 bytes.

- * - * @param jws the Unencoded Payload JWS to parse. - * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. - * @return the parsed Unencoded Payload. - * @since 0.12.0 - */ - Jws parseSignedContent(CharSequence jws, InputStream unencodedPayload); - - /** - * Parses the {@code jws} argument, expected to be a cryptographically-signed {@code Claims} JWS. This is a - * convenience method logically equivalent to the following: - * - *
-     * {@link #parse(CharSequence) parse}(jws).{@link Jwt#accept(JwtVisitor) accept}({@link
-     * Jws#CLAIMS});
- * - * @param jws a compact cryptographically-signed Claims JWS. - * @return the parsed cryptographically-verified Claims JWS. - * @throws UnsupportedJwtException if the {@code jwt} argument does not represent a signed Claims JWT - * @throws JwtException if the {@code jwt} string cannot be parsed or validated as required. - * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace - * @see #parse(CharSequence) - * @see Jwt#accept(JwtVisitor) - * @since 0.12.0 - */ - Jws parseSignedClaims(CharSequence jws) throws JwtException, IllegalArgumentException; - - /** - * Parses a JWS known to use the - * RFC 7797: JSON Web Signature (JWS) Unencoded Payload - * Option, using the specified {@code unencodedPayload} for signature verification. - * - *

Unencoded Non-Detached Payload

- * - *

Note that if the JWS contains a valid unencoded payload String (what RFC 7797 calls an - * "unencoded non-detached - * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes - * the payload content necessary for signature verification and claims creation.

- * - * @param jws the Unencoded Payload JWS to parse. - * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. - * @return the parsed and validated Claims JWS. - * @throws JwtException if parsing, signature verification, or JWT validation fails. - * @throws IllegalArgumentException if either the {@code jws} or {@code unencodedPayload} are null or empty. - * @since 0.12.0 - */ - Jws parseSignedClaims(CharSequence jws, byte[] unencodedPayload) throws JwtException, IllegalArgumentException; - - /** - * Parses a JWS known to use the - * RFC 7797: JSON Web Signature (JWS) Unencoded Payload - * Option, using the bytes from the specified {@code unencodedPayload} stream for signature verification and - * {@link Claims} creation. - * - *

NOTE: however, because calling this method indicates a completed - * {@link Claims} instance is desired, the specified {@code unencodedPayload} JSON stream will be fully - * read into a Claims instance. If this will be problematic for your application (perhaps if you expect extremely - * large Claims), it is recommended to use the {@link #parseSignedContent(CharSequence, InputStream)} method - * instead.

- * - *

Unencoded Non-Detached Payload

- * - *

Note that if the JWS contains a valid unencoded Payload string (what RFC 7797 calls an - * "unencoded non-detached - * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes - * the payload content necessary for signature verification and Claims creation.

- * - * @param jws the Unencoded Payload JWS to parse. - * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. - * @return the parsed and validated Claims JWS. - * @throws JwtException if parsing, signature verification, or JWT validation fails. - * @throws IllegalArgumentException if either the {@code jws} or {@code unencodedPayload} are null or empty. - * @since 0.12.0 - */ - Jws parseSignedClaims(CharSequence jws, InputStream unencodedPayload) throws JwtException, IllegalArgumentException; - - /** - * Parses the {@code jwe} argument, expected to be an encrypted content JWE. If the JWE - * creator set the (optional) {@link Header#getContentType() contentType} header value, the application may - * inspect that value to determine how to convert the byte array to the final content type as desired. - * - *

This is a convenience method logically equivalent to the following:

- * - *
-     * {@link #parse(CharSequence) parse}(jwe).{@link Jwt#accept(JwtVisitor) accept}({@link
-     * Jwe#CONTENT});
- * - * @param jwe a compact encrypted content JWE. - * @return the parsed decrypted content JWE. - * @throws UnsupportedJwtException if the {@code jwe} argument does not represent an encrypted content JWE - * @throws JwtException if the {@code jwe} string cannot be parsed or validated as required. - * @throws IllegalArgumentException if the {@code jwe} string is {@code null} or empty or only whitespace - * @see #parse(CharSequence) - * @see Jwt#accept(JwtVisitor) - * @since 0.12.0 - */ - Jwe parseEncryptedContent(CharSequence jwe) throws JwtException, IllegalArgumentException; - - /** - * Parses the {@code jwe} argument, expected to be an encrypted {@code Claims} JWE. This is a - * convenience method logically equivalent to the following: - * - *
-     * {@link #parse(CharSequence) parse}(jwe).{@link Jwt#accept(JwtVisitor) accept}({@link
-     * Jwe#CLAIMS});
- * - * @param jwe a compact encrypted Claims JWE. - * @return the parsed decrypted Claims JWE. - * @throws UnsupportedJwtException if the {@code jwe} argument does not represent an encrypted Claims JWE. - * @throws JwtException if the {@code jwe} string cannot be parsed or validated as required. - * @throws IllegalArgumentException if the {@code jwe} string is {@code null} or empty or only whitespace - * @see #parse(CharSequence) - * @see Jwt#accept(JwtVisitor) - * @since 0.12.0 - */ - Jwe parseEncryptedClaims(CharSequence jwe) throws JwtException, IllegalArgumentException; -} diff --git a/io/jsonwebtoken/JwtParserBuilder.java b/io/jsonwebtoken/JwtParserBuilder.java deleted file mode 100644 index 7993669..0000000 --- a/io/jsonwebtoken/JwtParserBuilder.java +++ /dev/null @@ -1,826 +0,0 @@ -/* - * Copyright (C) 2019 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.io.CompressionAlgorithm; -import io.jsonwebtoken.io.Decoder; -import io.jsonwebtoken.io.Deserializer; -import io.jsonwebtoken.lang.Builder; -import io.jsonwebtoken.lang.Conjunctor; -import io.jsonwebtoken.lang.NestedCollection; -import io.jsonwebtoken.security.AeadAlgorithm; -import io.jsonwebtoken.security.KeyAlgorithm; -import io.jsonwebtoken.security.SecureDigestAlgorithm; - -import javax.crypto.SecretKey; -import java.io.InputStream; -import java.security.Key; -import java.security.PrivateKey; -import java.security.Provider; -import java.security.PublicKey; -import java.util.Date; -import java.util.Map; - -/** - * A builder to construct a {@link JwtParser}. Example usage: - *
{@code
- *     Jwts.parser()
- *         .requireIssuer("https://issuer.example.com")
- *         .verifyWith(...)
- *         .build()
- *         .parse(jwtString)
- * }
- * - * @since 0.11.0 - */ -@SuppressWarnings("JavadocLinkAsPlainText") -public interface JwtParserBuilder extends Builder { - - /** - * Enables parsing of Unsecured JWTs (JWTs with an 'alg' (Algorithm) header value of - * 'none' or missing the 'alg' header entirely). Be careful when calling this method - one should fully understand - * Unsecured JWS Security Considerations - * before enabling this feature. - *

If this method is not called, Unsecured JWTs are disabled by default as mandated by - * RFC 7518, Section - * 3.6.

- * - * @return the builder for method chaining. - * @see Unsecured JWS Security Considerations - * @see Using the Algorithm "none" - * @see Jwts.SIG#NONE - * @see #unsecuredDecompression() - * @since 0.12.0 - */ - JwtParserBuilder unsecured(); - - /** - * If the parser is {@link #unsecured()}, calling this method additionally enables - * payload decompression of Unsecured JWTs (JWTs with an 'alg' (Algorithm) header value of 'none') that also have - * a 'zip' (Compression) header. This behavior is disabled by default because using compression - * algorithms with data from unverified (unauthenticated) parties can be susceptible to Denial of Service attacks - * and other data integrity problems as described in - * In the - * Compression Hornet’s Nest: A Security Study of Data Compression in Network Services. - * - *

Because this behavior is only relevant if the parser is unsecured, - * calling this method without also calling {@link #unsecured()} will result in a build exception, as the - * incongruent state could reflect a misunderstanding of both behaviors which should be remedied by the - * application developer.

- * - * As is the case for {@link #unsecured()}, be careful when calling this method - one should fully - * understand - * Unsecured JWS Security Considerations - * before enabling this feature. - * - * @return the builder for method chaining. - * @see Unsecured JWS Security Considerations - * @see In the - * Compression Hornet’s Nest: A Security Study of Data Compression in Network Services - * @see Jwts.SIG#NONE - * @see #unsecured() - * @since 0.12.0 - */ - JwtParserBuilder unsecuredDecompression(); - - /** - * Configures the {@link ProtectedHeader} parameter names used in JWT extensions supported by the application. If - * the parser encounters a Protected JWT that {@link ProtectedHeader#getCritical() requires} extensions, and - * those extensions' header names are not specified via this method, the parser will reject that JWT. - * - *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser - * configuration, for example:

- *
-     * parserBuilder.critical().add("headerName").{@link Conjunctor#and() and()} // etc...
- * - *

Extension Behavior

- * - *

The {@code critical} collection only identifies header parameter names that are used in extensions supported - * by the application. Application developers, not JJWT, MUST perform the associated extension behavior - * using the parsed JWT.

- * - *

Continued Parser Configuration

- *

When finished, use the collection's - * {@link Conjunctor#and() and()} method to continue parser configuration, for example: - *

-     * Jwts.parser()
-     *     .critical().add("headerName").{@link Conjunctor#and() and()} // return parent
-     * // resume parser configuration...
- * - * @return the {@link NestedCollection} to use for {@code crit} configuration. - * @see ProtectedHeader#getCritical() - * @since 0.12.0 - */ - NestedCollection critical(); - - /** - * Sets the JCA Provider to use during cryptographic signature and key decryption operations, or {@code null} if the - * JCA subsystem preferred provider should be used. - * - * @param provider the JCA Provider to use during cryptographic signature and decryption operations, or {@code null} - * if the JCA subsystem preferred provider should be used. - * @return the builder for method chaining. - * @since 0.12.0 - */ - JwtParserBuilder provider(Provider provider); - - /** - * Ensures that the specified {@code jti} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. - * - * @param id the required value of the {@code jti} header parameter. - * @return the parser builder for method chaining. - * @see MissingClaimException - * @see IncorrectClaimException - */ - JwtParserBuilder requireId(String id); - - /** - * Ensures that the specified {@code sub} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. - * - * @param subject the required value of the {@code sub} header parameter. - * @return the parser builder for method chaining. - * @see MissingClaimException - * @see IncorrectClaimException - */ - JwtParserBuilder requireSubject(String subject); - - /** - * Ensures that the specified {@code aud} exists in the parsed JWT. If missing or if the parsed - * value does not contain the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. - * - * @param audience the required value of the {@code aud} header parameter. - * @return the parser builder for method chaining. - * @see MissingClaimException - * @see IncorrectClaimException - */ - JwtParserBuilder requireAudience(String audience); - - /** - * Ensures that the specified {@code iss} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. - * - * @param issuer the required value of the {@code iss} header parameter. - * @return the parser builder for method chaining. - * @see MissingClaimException - * @see IncorrectClaimException - */ - JwtParserBuilder requireIssuer(String issuer); - - /** - * Ensures that the specified {@code iat} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. - * - * @param issuedAt the required value of the {@code iat} header parameter. - * @return the parser builder for method chaining. - * @see MissingClaimException - * @see IncorrectClaimException - */ - JwtParserBuilder requireIssuedAt(Date issuedAt); - - /** - * Ensures that the specified {@code exp} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. - * - * @param expiration the required value of the {@code exp} header parameter. - * @return the parser builder for method chaining. - * @see MissingClaimException - * @see IncorrectClaimException - */ - JwtParserBuilder requireExpiration(Date expiration); - - /** - * Ensures that the specified {@code nbf} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. - * - * @param notBefore the required value of the {@code npf} header parameter. - * @return the parser builder for method chaining - * @see MissingClaimException - * @see IncorrectClaimException - */ - JwtParserBuilder requireNotBefore(Date notBefore); - - /** - * Ensures that the specified {@code claimName} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. - * - * @param claimName the name of a claim that must exist - * @param value the required value of the specified {@code claimName} - * @return the parser builder for method chaining. - * @see MissingClaimException - * @see IncorrectClaimException - */ - JwtParserBuilder require(String claimName, Object value); - - /** - * Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT. - * The parser uses a default Clock implementation that simply returns {@code new Date()} when called. - * - * @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT. - * @return the parser builder for method chaining. - * @deprecated since 0.12.0 for the more modern builder-style named {@link #clock(Clock)} method. - * This method will be removed before the JJWT 1.0 release. - */ - @Deprecated - JwtParserBuilder setClock(Clock clock); - - /** - * Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT. - * The parser uses a default Clock implementation that simply returns {@code new Date()} when called. - * - * @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT. - * @return the parser builder for method chaining. - */ - JwtParserBuilder clock(Clock clock); - - /** - * Sets the amount of clock skew in seconds to tolerate when verifying the local time against the {@code exp} - * and {@code nbf} claims. - * - * @param seconds the number of seconds to tolerate for clock skew when verifying {@code exp} or {@code nbf} claims. - * @return the parser builder for method chaining. - * @throws IllegalArgumentException if {@code seconds} is a value greater than {@code Long.MAX_VALUE / 1000} as - * any such value would cause numeric overflow when multiplying by 1000 to obtain - * a millisecond value. - * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named - * {@link #clockSkewSeconds(long)}. This method will be removed before the JJWT 1.0 release. - */ - @Deprecated - JwtParserBuilder setAllowedClockSkewSeconds(long seconds) throws IllegalArgumentException; - - /** - * Sets the amount of clock skew in seconds to tolerate when verifying the local time against the {@code exp} - * and {@code nbf} claims. - * - * @param seconds the number of seconds to tolerate for clock skew when verifying {@code exp} or {@code nbf} claims. - * @return the parser builder for method chaining. - * @throws IllegalArgumentException if {@code seconds} is a value greater than {@code Long.MAX_VALUE / 1000} as - * any such value would cause numeric overflow when multiplying by 1000 to obtain - * a millisecond value. - */ - JwtParserBuilder clockSkewSeconds(long seconds) throws IllegalArgumentException; - - /** - *

Deprecation Notice

- * - *

This method has been deprecated since 0.12.0 and will be removed before 1.0. It was not - * readily obvious to many JJWT users that this method was for bytes that pertained only to HMAC - * {@code SecretKey}s, and could be confused with keys of other types. It is better to obtain a type-safe - * {@link SecretKey} instance and call {@link #verifyWith(SecretKey)} instead.

- * - *

Previous Documentation

- * - *

Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not - * a JWS (no signature), this key is not used.

- * - *

Note that this key MUST be a valid key for the signature algorithm found in the JWT header - * (as the {@code alg} header parameter).

- * - *

This method overwrites any previously set key.

- * - * @param key the algorithm-specific signature verification key used to validate any discovered JWS digital - * signature. - * @return the parser builder for method chaining. - * @deprecated since 0.12.0 in favor of {@link #verifyWith(SecretKey)} for type safety and name - * congruence with the {@link #decryptWith(SecretKey)} method. - */ - @Deprecated - JwtParserBuilder setSigningKey(byte[] key); - - /** - *

Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0

- * - *

This method has been deprecated because the {@code key} argument for this method can be confusing: keys for - * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were - * obtained from the String argument.

- * - *

This method always expected a String argument that was effectively the same as the result of the following - * (pseudocode):

- * - *

{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}

- * - *

However, a non-trivial number of JJWT users were confused by the method signature and attempted to - * use raw password strings as the key argument - for example {@code setSigningKey(myPassword)} - which is - * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.

- * - *

See this - * - * StackOverflow answer explaining why raw (non-base64-encoded) strings are almost always incorrect for - * signature operations.

- * - *

Finally, please use the {@link #verifyWith(SecretKey)} method instead, as this method (and likely - * {@link #setSigningKey(byte[])}) will be removed before the 1.0.0 release.

- * - *

Previous JavaDoc

- * - *

This is a convenience method that equates to the following:

- * - *
-     * byte[] bytes = Decoders.{@link io.jsonwebtoken.io.Decoders#BASE64 BASE64}.decode(base64EncodedSecretKey);
-     * Key key = Keys.{@link io.jsonwebtoken.security.Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor}(bytes);
-     * return {@link #verifyWith(SecretKey) verifyWith}(key);
- * - * @param base64EncodedSecretKey BASE64-encoded HMAC-SHA key bytes used to create a Key which will be used to - * verify all encountered JWS digital signatures. - * @return the parser builder for method chaining. - * @deprecated in favor of {@link #verifyWith(SecretKey)} as explained in the above Deprecation Notice, - * and will be removed in 1.0.0. - */ - @Deprecated - JwtParserBuilder setSigningKey(String base64EncodedSecretKey); - - /** - *

Deprecation Notice

- * - *

This method is being renamed to accurately reflect its purpose - the key is not technically a signing key, - * it is a signature verification key, and the two concepts can be different, especially with asymmetric key - * cryptography. The method has been deprecated since 0.12.0 in favor of - * {@link #verifyWith(SecretKey)} for type safety, to reflect accurate naming of the concept, and for name - * congruence with the {@link #decryptWith(SecretKey)} method.

- * - *

This method merely delegates directly to {@link #verifyWith(SecretKey)} or {@link #verifyWith(PublicKey)}}.

- * - * @param key the algorithm-specific signature verification key to use to verify all encountered JWS digital - * signatures. - * @return the parser builder for method chaining. - * @deprecated since 0.12.0 in favor of {@link #verifyWith(SecretKey)} for naming congruence with the - * {@link #decryptWith(SecretKey)} method. - */ - @Deprecated - JwtParserBuilder setSigningKey(Key key); - - /** - * Sets the signature verification SecretKey used to verify all encountered JWS signatures. If the encountered JWT - * string is not a JWS (e.g. unsigned or a JWE), this key is not used. - * - *

This is a convenience method to use in a specific scenario: when the parser will only ever encounter - * JWSs with signatures that can always be verified by a single SecretKey. This also implies that this key - * MUST be a valid key for the signature algorithm ({@code alg} header) used for the JWS.

- * - *

If there is any chance that the parser will also encounter JWEs, or JWSs that need different signature - * verification keys based on the JWS being parsed, it is strongly recommended to configure your own - * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

- * - *

Calling this method overrides any previously set signature verification key.

- * - * @param key the signature verification key to use to verify all encountered JWS digital signatures. - * @return the parser builder for method chaining. - * @see #verifyWith(PublicKey) - * @since 0.12.0 - */ - JwtParserBuilder verifyWith(SecretKey key); - - /** - * Sets the signature verification PublicKey used to verify all encountered JWS signatures. If the encountered JWT - * string is not a JWS (e.g. unsigned or a JWE), this key is not used. - * - *

This is a convenience method to use in a specific scenario: when the parser will only ever encounter - * JWSs with signatures that can always be verified by a single PublicKey. This also implies that this key - * MUST be a valid key for the signature algorithm ({@code alg} header) used for the JWS.

- * - *

If there is any chance that the parser will also encounter JWEs, or JWSs that need different signature - * verification keys based on the JWS being parsed, it is strongly recommended to configure your own - * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

- * - *

Calling this method overrides any previously set signature verification key.

- * - * @param key the signature verification key to use to verify all encountered JWS digital signatures. - * @return the parser builder for method chaining. - * @see #verifyWith(SecretKey) - * @since 0.12.0 - */ - JwtParserBuilder verifyWith(PublicKey key); - - /** - * Sets the decryption SecretKey used to decrypt all encountered JWEs. If the encountered JWT string is not a - * JWE (e.g. a JWS), this key is not used. - * - *

This is a convenience method to use in specific circumstances: when the parser will only ever encounter - * JWEs that can always be decrypted by a single SecretKey. This also implies that this key MUST be a valid - * key for both the key management algorithm ({@code alg} header) and the content encryption algorithm - * ({@code enc} header) used for the JWE.

- * - *

If there is any chance that the parser will also encounter JWSs, or JWEs that need different decryption - * keys based on the JWE being parsed, it is strongly recommended to configure your own - * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

- * - *

Calling this method overrides any previously set decryption key.

- * - * @param key the algorithm-specific decryption key to use to decrypt all encountered JWEs. - * @return the parser builder for method chaining. - * @see #decryptWith(PrivateKey) - * @since 0.12.0 - */ - JwtParserBuilder decryptWith(SecretKey key); - - /** - * Sets the decryption PrivateKey used to decrypt all encountered JWEs. If the encountered JWT string is not a - * JWE (e.g. a JWS), this key is not used. - * - *

This is a convenience method to use in specific circumstances: when the parser will only ever encounter JWEs - * that can always be decrypted by a single PrivateKey. This also implies that this key MUST be a valid - * key for the JWE's key management algorithm ({@code alg} header).

- * - *

If there is any chance that the parser will also encounter JWSs, or JWEs that need different decryption - * keys based on the JWE being parsed, it is strongly recommended to configure your own - * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

- * - *

Calling this method overrides any previously set decryption key.

- * - * @param key the algorithm-specific decryption key to use to decrypt all encountered JWEs. - * @return the parser builder for method chaining. - * @see #decryptWith(SecretKey) - * @since 0.12.0 - */ - JwtParserBuilder decryptWith(PrivateKey key); - - /** - * Sets the {@link Locator} used to acquire any signature verification or decryption key needed during parsing. - *
    - *
  • If the parsed String is a JWS, the {@code Locator} will be called to find the appropriate key - * necessary to verify the JWS signature.
  • - *
  • If the parsed String is a JWE, it will be called to find the appropriate decryption key.
  • - *
- * - *

A key {@code Locator} is necessary when the signature verification or decryption key is not - * already known before parsing the JWT and the JWT header must be inspected first to determine how to - * look up the verification or decryption key. Once returned by the locator, the JwtParser will then either - * verify the JWS signature or decrypt the JWE payload with the returned key. For example:

- * - *
-     * Jws<Claims> jws = Jwts.parser().keyLocator(new Locator<Key>() {
-     *         @Override
-     *         public Key locate(Header<?> header) {
-     *             if (header instanceof JwsHeader) {
-     *                 return getSignatureVerificationKey((JwsHeader)header); // implement me
-     *             } else {
-     *                 return getDecryptionKey((JweHeader)header); // implement me
-     *             }
-     *         }})
-     *     .build()
-     *     .parseSignedClaims(compact);
-     * 
- * - *

A Key {@code Locator} is invoked once during parsing before performing decryption or signature verification.

- * - *

Provider-constrained Keys

- * - *

If any verification or decryption key returned from a Key {@code Locator} must be used with a specific - * security {@link Provider} (such as for PKCS11 or Hardware Security Module (HSM) keys), you must make that - * Provider available for JWT parsing in one of 3 ways, listed in order of recommendation and simplicity:

- * - *
    - *
  1. - * Configure the Provider in the JVM, either by modifying the {@code java.security} file or by - * registering the Provider dynamically via - * {@link java.security.Security#addProvider(Provider) Security.addProvider(Provider)}. This is the - * recommended approach so you do not need to modify code anywhere that may need to parse JWTs.
  2. - *
  3. Specify the {@code Provider} as the {@code JwtParser} default via {@link #provider(Provider)}. This will - * ensure the provider is used by default with all located keys unless overridden by a - * key-specific Provider. This is only recommended when you are confident that all JWTs encountered by the - * parser instance will use keys attributed to the same {@code Provider}, unless overridden by a specific - * key.
  4. - *
  5. Associate the {@code Provider} with a specific key so it is used for that key only. This option - * is useful if some located keys require a specific provider, while other located keys can assume a - * default provider.
  6. - *
- * - *

If you need to use option #3, you associate a key for the {@code JwtParser}'s needs by using a - * key builder before returning the key as the {@code Locator} return value. For example:

- *
-     *     public Key locate(Header<?> header) {
-     *         PrivateKey key = findKey(header); // or SecretKey
-     *         Provider keySpecificProvider = getKeyProvider(key); // implement me
-     *         // associate the key with its required provider:
-     *         return Keys.builder(key).provider(keySpecificProvider).build();
-     *     }
- * - * @param keyLocator the locator used to retrieve decryption or signature verification keys. - * @return the parser builder for method chaining. - * @since 0.12.0 - */ - JwtParserBuilder keyLocator(Locator keyLocator); - - /** - *

Deprecation Notice

- * - *

This method has been deprecated as of JJWT version 0.12.0 because it only supports key location - * for JWSs (signed JWTs) instead of both signed (JWS) and encrypted (JWE) scenarios. Use the - * {@link #keyLocator(Locator) keyLocator} method instead to ensure a locator that can work for both JWS and - * JWE inputs. This method will be removed for the 1.0 release.

- * - *

Previous Documentation

- * - *

Sets the {@link SigningKeyResolver} used to acquire the signing key that should be used to verify - * a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used.

- * - *

Specifying a {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing - * the JWT and the JWT header or payload (content byte array or Claims) must be inspected first to determine how to - * look up the signing key. Once returned by the resolver, the JwtParser will then verify the JWS signature with the - * returned key. For example:

- * - *
-     * Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
-     *         @Override
-     *         public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
-     *             //inspect the header or claims, lookup and return the signing key
-     *             return getSigningKey(header, claims); //implement me
-     *         }})
-     *     .build().parseSignedClaims(compact);
-     * 
- * - *

A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.

- * - * @param signingKeyResolver the signing key resolver used to retrieve the signing key. - * @return the parser builder for method chaining. - * @deprecated since 0.12.0 in favor of {@link #keyLocator(Locator)} - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - JwtParserBuilder setSigningKeyResolver(SigningKeyResolver signingKeyResolver); - - /** - * Configures the parser's supported {@link AeadAlgorithm}s used to decrypt JWE payloads. If the parser - * encounters a JWE {@link JweHeader#getEncryptionAlgorithm() enc} header value that equals an - * AEAD algorithm's {@link Identifiable#getId() id}, that algorithm will be used to decrypt the JWT - * payload. - * - *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser - * configuration, for example:

- *
-     * parserBuilder.enc().add(anAeadAlgorithm).{@link Conjunctor#and() and()} // etc...
- * - *

Standard Algorithms and Overrides

- * - *

All JWA-standard AEAD encryption algorithms in the {@link Jwts.ENC} registry are supported by default and - * do not need to be added. The collection may be useful however for removing some algorithms (for example, - * any algorithms not used by the application, or those not compatible with application security requirements), - * or for adding custom implementations.

- * - *

Custom Implementations

- * - *

There may be only one registered {@code AeadAlgorithm} per algorithm {@code id}, and any algorithm - * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a - * duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. But beware: - * - *

- * Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will - * replace (override) the JJWT standard algorithm implementation.
- * - *

This is to allow application developers to favor their - * own implementations over JJWT's default implementations if necessary (for example, to support legacy or - * custom behavior).

- * - * @return the {@link NestedCollection} to use to configure the AEAD encryption algorithms available when parsing. - * @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) - * @see Jwts.ENC - * @see "enc" (Encryption Algorithm) Header Parameter - * @see Encryption Algorithm Name (id) requirements - * @since 0.12.0 - */ - NestedCollection enc(); - - /** - * Configures the parser's supported {@link KeyAlgorithm}s used to obtain a JWE's decryption key. If the - * parser encounters a JWE {@link JweHeader#getAlgorithm()} alg} header value that equals a {@code KeyAlgorithm}'s - * {@link Identifiable#getId() id}, that key algorithm will be used to obtain the JWE's decryption key. - * - *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser - * configuration, for example:

- *
-     * parserBuilder.key().add(aKeyAlgorithm).{@link Conjunctor#and() and()} // etc...
- * - *

Standard Algorithms and Overrides

- * - *

All JWA-standard key encryption algorithms in the {@link Jwts.KEY} registry are supported by default and - * do not need to be added. The collection may be useful however for removing some algorithms (for example, - * any algorithms not used by the application, or those not compatible with application security requirements), - * or for adding custom implementations.

- * - *

Custom Implementations

- * - *

There may be only one registered {@code KeyAlgorithm} per algorithm {@code id}, and any algorithm - * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a - * duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. But beware: - * - *

- * Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will - * replace (override) the JJWT standard algorithm implementation.
- * - *

This is to allow application developers to favor their - * own implementations over JJWT's default implementations if necessary (for example, to support legacy or - * custom behavior).

- * - * @return the {@link NestedCollection} to use to configure the key algorithms available when parsing. - * @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) - * @see Jwts.KEY - * @see JWE "alg" (Algorithm) Header Parameter - * @see Key Algorithm Name (id) requirements - * @since 0.12.0 - */ - NestedCollection, JwtParserBuilder> key(); - - /** - * Configures the parser's supported - * {@link io.jsonwebtoken.security.SignatureAlgorithm SignatureAlgorithm} and - * {@link io.jsonwebtoken.security.MacAlgorithm MacAlgorithm}s used to verify JWS signatures. If the parser - * encounters a JWS {@link ProtectedHeader#getAlgorithm() alg} header value that equals a signature or MAC - * algorithm's {@link Identifiable#getId() id}, that algorithm will be used to verify the JWS signature. - * - *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser - * configuration, for example:

- *
-     * parserBuilder.sig().add(aSignatureAlgorithm).{@link Conjunctor#and() and()} // etc...
- * - *

Standard Algorithms and Overrides

- * - *

All JWA-standard signature and MAC algorithms in the {@link Jwts.SIG} registry are supported by default and - * do not need to be added. The collection may be useful however for removing some algorithms (for example, - * any algorithms not used by the application, or those not compatible with application security requirements), or - * for adding custom implementations.

- * - *

Custom Implementations

- * - *

There may be only one registered {@code SecureDigestAlgorithm} per algorithm {@code id}, and any algorithm - * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a - * duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. But beware: - * - *

- * Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will - * replace (override) the JJWT standard algorithm implementation.
- * - *

This is to allow application developers to favor their - * own implementations over JJWT's default implementations if necessary (for example, to support legacy or - * custom behavior).

- * - * @return the {@link NestedCollection} to use to configure the signature and MAC algorithms available when parsing. - * @see JwtBuilder#signWith(Key, SecureDigestAlgorithm) - * @see Jwts.SIG - * @see JWS "alg" (Algorithm) Header Parameter - * @see Algorithm Name (id) requirements - * @since 0.12.0 - */ - NestedCollection, JwtParserBuilder> sig(); - - /** - * Configures the parser's supported {@link CompressionAlgorithm}s used to decompress JWT payloads. If the parser - * encounters a JWT {@link ProtectedHeader#getCompressionAlgorithm() zip} header value that equals a - * compression algorithm's {@link Identifiable#getId() id}, that algorithm will be used to decompress the JWT - * payload. - * - *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser - * configuration, for example:

- *
-     * parserBuilder.zip().add(aCompressionAlgorithm).{@link Conjunctor#and() and()} // etc...
- * - *

Standard Algorithms and Overrides

- * - *

All JWA-standard compression algorithms in the {@link Jwts.ZIP} registry are supported by default and - * do not need to be added. The collection may be useful however for removing some algorithms (for example, - * any algorithms not used by the application), or for adding custom implementations.

- * - *

Custom Implementations

- * - *

There may be only one registered {@code CompressionAlgorithm} per algorithm {@code id}, and any algorithm - * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a - * duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. But beware: - * - *

- * Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will - * replace (override) the JJWT standard algorithm implementation.
- * - *

This is to allow application developers to favor their - * own implementations over JJWT's default implementations if necessary (for example, to support legacy or - * custom behavior).

- * - * @return the {@link NestedCollection} to use to configure the compression algorithms available when parsing. - * @see JwtBuilder#compressWith(CompressionAlgorithm) - * @see Jwts.ZIP - * @see "zip" (Compression Algorithm) Header Parameter - * @see Compression Algorithm Name (id) requirements - * @since 0.12.0 - */ - NestedCollection zip(); - - /** - *

Deprecated as of JJWT 0.12.0. This method will be removed before the 1.0 release.

- * - *

This method has been deprecated as of JJWT version 0.12.0 because it imposed unnecessary - * implementation requirements on application developers when simply adding to a compression algorithm collection - * would suffice. Use the {@link #zip()} method instead to add - * any custom algorithm implementations without needing to also implement a Locator implementation.

- * - *

Previous Documentation

- *

- * Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to - * decompress the JWT body. If the parsed JWT is not compressed, this resolver is not used. - * - *

WARNING: Compression is not defined by the JWS Specification - only the JWE Specification - and it is - * not expected that other libraries (including JJWT versions < 0.6.0) are able to consume a compressed JWS - * body correctly.

- * - *

Default Support

- * - *

JJWT's default {@link JwtParser} implementation supports both the {@link Jwts.ZIP#DEF DEF} - * and {@link Jwts.ZIP#GZIP GZIP} algorithms by default - you do not need to - * specify a {@code CompressionCodecResolver} in these cases.

- * - * @param compressionCodecResolver the compression codec resolver used to decompress the JWT body. - * @return the parser builder for method chaining. - * @deprecated since 0.12.0 in favor of {@link #zip()}. This method will be removed before the - * 1.0 release. - */ - @Deprecated - JwtParserBuilder setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver); - - /** - * Perform Base64Url decoding with the specified Decoder - * - *

JJWT uses a spec-compliant decoder that works on all supported JDK versions, but you may call this method - * to specify a different decoder if you desire.

- * - * @param base64UrlDecoder the decoder to use when Base64Url-decoding - * @return the parser builder for method chaining. - * @deprecated since 0.12.0 in favor of {@link #b64Url(Decoder)}. This method will be removed - * before the JJWT 1.0 release. - */ - @Deprecated - JwtParserBuilder base64UrlDecodeWith(Decoder base64UrlDecoder); - - /** - * Perform Base64Url decoding during parsing with the specified {@code InputStream} Decoder. - * The Decoder's {@link Decoder#decode(Object) decode} method will be given a source {@code InputStream} to - * wrap, and the resulting (wrapping) {@code InputStream} will be used for reading , ensuring automatic - * Base64URL-decoding during read operations. - * - *

JJWT uses a spec-compliant decoder that works on all supported JDK versions, but you may call this method - * to specify a different stream decoder if desired.

- * - * @param base64UrlDecoder the stream decoder to use when Base64Url-decoding - * @return the parser builder for method chaining. - */ - JwtParserBuilder b64Url(Decoder base64UrlDecoder); - - /** - * Uses the specified deserializer to convert JSON Strings (UTF-8 byte arrays) into Java Map objects. This is - * used by the parser after Base64Url-decoding to convert JWT/JWS/JWT JSON headers and claims into Java Map - * objects. - * - *

If this method is not called, JJWT will use whatever deserializer it can find at runtime, checking for the - * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found - * in the runtime classpath, an exception will be thrown when one of the various {@code parse}* methods is - * invoked.

- * - * @param deserializer the deserializer to use when converting JSON Strings (UTF-8 byte arrays) into Map objects. - * @return the builder for method chaining. - * @deprecated since 0.12.0 in favor of {@link #json(Deserializer)}. - * This method will be removed before the JJWT 1.0 release. - */ - @Deprecated - JwtParserBuilder deserializeJsonWith(Deserializer> deserializer); - - /** - * Uses the specified JSON {@link Deserializer} to deserialize JSON (UTF-8 byte streams) into Java Map objects. - * This is used by the parser after Base64Url-decoding to convert JWT/JWS/JWT headers and Claims into Java Map - * instances. - * - *

If this method is not called, JJWT will use whatever Deserializer it can find at runtime, checking for the - * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found - * in the runtime classpath, an exception will be thrown when one of the various {@code parse}* methods is - * invoked.

- * - * @param deserializer the deserializer to use to deserialize JSON (UTF-8 byte streams) into Map instances. - * @return the builder for method chaining. - * @since 0.12.0 - */ - JwtParserBuilder json(Deserializer> deserializer); - - /** - * Returns an immutable/thread-safe {@link JwtParser} created from the configuration from this JwtParserBuilder. - * - * @return an immutable/thread-safe JwtParser created from the configuration from this JwtParserBuilder. - */ - JwtParser build(); -} diff --git a/io/jsonwebtoken/JwtVisitor.java b/io/jsonwebtoken/JwtVisitor.java deleted file mode 100644 index 3b66738..0000000 --- a/io/jsonwebtoken/JwtVisitor.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * A JwtVisitor supports the Visitor design pattern for - * {@link Jwt} instances. Visitor implementations define logic for a specific JWT subtype or payload subtype - * avoiding type-checking if-then-else conditionals in favor of type-safe method dispatch when encountering a JWT. - * - * @param the type of object to return after invoking the {@link Jwt#accept(JwtVisitor)} method. - * @since 0.12.0 - */ -public interface JwtVisitor { - - /** - * Handles an encountered Unsecured JWT that has not been cryptographically secured at all. Implementations can - * check the {@link Jwt#getPayload()} to determine if it is a {@link Claims} instance or a {@code byte[]} array. - * - *

If the payload is a {@code byte[]} array, and the JWT creator has set the (optional) - * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert - * the byte array to the final type as desired.

- * - * @param jwt the parsed Unsecured JWT. - * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. - */ - T visit(Jwt jwt); - - /** - * Handles an encountered JSON Web Signature (aka 'JWS') message that has been cryptographically - * verified/authenticated. Implementations can check the {@link Jwt#getPayload()} determine if it is a - * {@link Claims} instance or a {@code byte[]} array. - * - *

If the payload is a {@code byte[]} array, and the JWS creator has set the (optional) - * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert - * the byte array to the final type as desired.

- * - * @param jws the parsed verified/authenticated JWS. - * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. - */ - T visit(Jws jws); - - /** - * Handles an encountered JSON Web Encryption (aka 'JWE') message that has been authenticated and decrypted. - * Implementations can check the (decrypted) {@link Jwt#getPayload()} to determine if it is a {@link Claims} - * instance or a {@code byte[]} array. - * - *

If the payload is a {@code byte[]} array, and the JWE creator has set the (optional) - * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert - * the byte array to the final type as desired.

- * - * @param jwe the parsed authenticated and decrypted JWE. - * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. - */ - T visit(Jwe jwe); -} diff --git a/io/jsonwebtoken/Jwts.java b/io/jsonwebtoken/Jwts.java deleted file mode 100644 index 8efac29..0000000 --- a/io/jsonwebtoken/Jwts.java +++ /dev/null @@ -1,1077 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.io.CompressionAlgorithm; -import io.jsonwebtoken.lang.Builder; -import io.jsonwebtoken.lang.Classes; -import io.jsonwebtoken.lang.Registry; -import io.jsonwebtoken.security.AeadAlgorithm; -import io.jsonwebtoken.security.KeyAlgorithm; -import io.jsonwebtoken.security.KeyPairBuilderSupplier; -import io.jsonwebtoken.security.MacAlgorithm; -import io.jsonwebtoken.security.Password; -import io.jsonwebtoken.security.SecretKeyAlgorithm; -import io.jsonwebtoken.security.SecureDigestAlgorithm; -import io.jsonwebtoken.security.SignatureAlgorithm; -import io.jsonwebtoken.security.X509Builder; - -import javax.crypto.SecretKey; -import java.security.Key; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.util.Map; - -/** - * Factory class useful for creating instances of JWT interfaces. Using this factory class can be a good - * alternative to tightly coupling your code to implementation classes. - * - *

Standard Algorithm References

- *

Standard JSON Web Token algorithms used during JWS or JWE building or parsing are available organized by - * algorithm type. Each organized collection of algorithms is available via a constant to allow - * for easy code-completion in IDEs, showing available algorithm instances. For example, when typing:

- *
- * Jwts.// press code-completion hotkeys to suggest available algorithm registry fields
- * Jwts.{@link SIG SIG}.// press hotkeys to suggest individual Digital Signature or MAC algorithms or utility methods
- * Jwts.{@link ENC ENC}.// press hotkeys to suggest individual encryption algorithms or utility methods
- * Jwts.{@link KEY KEY}.// press hotkeys to suggest individual key algorithms or utility methods
- * - * @since 0.1 - */ -public final class Jwts { - - - // do not change this visibility. Raw type method signature not be publicly exposed: - @SuppressWarnings("unchecked") - private static T get(Registry registry, String id) { - return (T) registry.forKey(id); - } - - /** - * Constants for all standard JWA - * Cryptographic Algorithms for Content - * Encryption defined in the JSON - * Web Signature and Encryption Algorithms Registry. Each standard algorithm is available as a - * ({@code public static final}) constant for direct type-safe reference in application code. For example: - *
-     * Jwts.builder()
-     *    // ... etc ...
-     *    .encryptWith(aKey, Jwts.ENC.A256GCM) // or A128GCM, A192GCM, etc...
-     *    .build();
- *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

- * - * @see #get() - * @since 0.12.0 - */ - public static final class ENC { - - private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardEncryptionAlgorithms"; - private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); - - /** - * Returns all standard JWA Cryptographic - * Algorithms for Content Encryption defined in the - * JSON Web Signature and Encryption - * Algorithms Registry. - * - * @return all standard JWA content encryption algorithms. - */ - public static Registry get() { - return REGISTRY; - } - - // prevent instantiation - private ENC() { - } - - /** - * {@code AES_128_CBC_HMAC_SHA_256} authenticated encryption algorithm as defined by - * RFC 7518, Section 5.2.3. This algorithm - * requires a 256-bit (32 byte) key. - */ - public static final AeadAlgorithm A128CBC_HS256 = get().forKey("A128CBC-HS256"); - - /** - * {@code AES_192_CBC_HMAC_SHA_384} authenticated encryption algorithm, as defined by - * RFC 7518, Section 5.2.4. This algorithm - * requires a 384-bit (48 byte) key. - */ - public static final AeadAlgorithm A192CBC_HS384 = get().forKey("A192CBC-HS384"); - - /** - * {@code AES_256_CBC_HMAC_SHA_512} authenticated encryption algorithm, as defined by - * RFC 7518, Section 5.2.5. This algorithm - * requires a 512-bit (64 byte) key. - */ - public static final AeadAlgorithm A256CBC_HS512 = get().forKey("A256CBC-HS512"); - - /** - * "AES GCM using 128-bit key" as defined by - * RFC 7518, Section 5.31. This - * algorithm requires a 128-bit (16 byte) key. - * - *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime - * classpath.

- */ - public static final AeadAlgorithm A128GCM = get().forKey("A128GCM"); - - /** - * "AES GCM using 192-bit key" as defined by - * RFC 7518, Section 5.31. This - * algorithm requires a 192-bit (24 byte) key. - * - *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime - * classpath.

- */ - public static final AeadAlgorithm A192GCM = get().forKey("A192GCM"); - - /** - * "AES GCM using 256-bit key" as defined by - * RFC 7518, Section 5.31. This - * algorithm requires a 256-bit (32 byte) key. - * - *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime - * classpath.

- */ - public static final AeadAlgorithm A256GCM = get().forKey("A256GCM"); - } - - /** - * Constants for all JWA (RFC 7518) standard - * Cryptographic Algorithms for Digital Signatures and MACs defined in the - * JSON Web Signature and Encryption Algorithms - * Registry. Each standard algorithm is available as a ({@code public static final}) constant for - * direct type-safe reference in application code. For example: - *
-     * Jwts.builder()
-     *    // ... etc ...
-     *    .signWith(aKey, Jwts.SIG.HS512) // or RS512, PS256, EdDSA, etc...
-     *    .build();
- *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

- * - * @see #get() - * @since 0.12.0 - */ - public static final class SIG { - - private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms"; - private static final Registry> REGISTRY = Classes.newInstance(IMPL_CLASSNAME); - - //prevent instantiation - private SIG() { - } - - /** - * Returns all standard JWA Cryptographic - * Algorithms for Digital Signatures and MACs defined in the - * JSON Web Signature and Encryption - * Algorithms Registry. - * - * @return all standard JWA digital signature and MAC algorithms. - */ - public static Registry> get() { - return REGISTRY; - } - - /** - * The "none" signature algorithm as defined by - * RFC 7518, Section 3.6. This algorithm - * is used only when creating unsecured (not integrity protected) JWSs and is not usable in any other scenario. - * Any attempt to call its methods will result in an exception being thrown. - */ - public static final SecureDigestAlgorithm NONE = Jwts.get(REGISTRY, "none"); - - /** - * {@code HMAC using SHA-256} message authentication algorithm as defined by - * RFC 7518, Section 3.2. This algorithm - * requires a 256-bit (32 byte) key. - */ - public static final MacAlgorithm HS256 = Jwts.get(REGISTRY, "HS256"); - - /** - * {@code HMAC using SHA-384} message authentication algorithm as defined by - * RFC 7518, Section 3.2. This algorithm - * requires a 384-bit (48 byte) key. - */ - public static final MacAlgorithm HS384 = Jwts.get(REGISTRY, "HS384"); - - /** - * {@code HMAC using SHA-512} message authentication algorithm as defined by - * RFC 7518, Section 3.2. This algorithm - * requires a 512-bit (64 byte) key. - */ - public static final MacAlgorithm HS512 = Jwts.get(REGISTRY, "HS512"); - - /** - * {@code RSASSA-PKCS1-v1_5 using SHA-256} signature algorithm as defined by - * RFC 7518, Section 3.3. This algorithm - * requires a 2048-bit key. - */ - public static final SignatureAlgorithm RS256 = Jwts.get(REGISTRY, "RS256"); - - /** - * {@code RSASSA-PKCS1-v1_5 using SHA-384} signature algorithm as defined by - * RFC 7518, Section 3.3. This algorithm - * requires a 2048-bit key, but the JJWT team recommends a 3072-bit key. - */ - public static final SignatureAlgorithm RS384 = Jwts.get(REGISTRY, "RS384"); - - /** - * {@code RSASSA-PKCS1-v1_5 using SHA-512} signature algorithm as defined by - * RFC 7518, Section 3.3. This algorithm - * requires a 2048-bit key, but the JJWT team recommends a 4096-bit key. - */ - public static final SignatureAlgorithm RS512 = Jwts.get(REGISTRY, "RS512"); - - /** - * {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256} signature algorithm as defined by - * RFC 7518, Section 3.51. - * This algorithm requires a 2048-bit key. - * - *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime - * classpath.

- */ - public static final SignatureAlgorithm PS256 = Jwts.get(REGISTRY, "PS256"); - - /** - * {@code RSASSA-PSS using SHA-384 and MGF1 with SHA-384} signature algorithm as defined by - * RFC 7518, Section 3.51. - * This algorithm requires a 2048-bit key, but the JJWT team recommends a 3072-bit key. - * - *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime - * classpath.

- */ - public static final SignatureAlgorithm PS384 = Jwts.get(REGISTRY, "PS384"); - - /** - * {@code RSASSA-PSS using SHA-512 and MGF1 with SHA-512} signature algorithm as defined by - * RFC 7518, Section 3.51. - * This algorithm requires a 2048-bit key, but the JJWT team recommends a 4096-bit key. - * - *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime - * classpath.

- */ - public static final SignatureAlgorithm PS512 = Jwts.get(REGISTRY, "PS512"); - - /** - * {@code ECDSA using P-256 and SHA-256} signature algorithm as defined by - * RFC 7518, Section 3.4. This algorithm - * requires a 256-bit key. - */ - public static final SignatureAlgorithm ES256 = Jwts.get(REGISTRY, "ES256"); - - /** - * {@code ECDSA using P-384 and SHA-384} signature algorithm as defined by - * RFC 7518, Section 3.4. This algorithm - * requires a 384-bit key. - */ - public static final SignatureAlgorithm ES384 = Jwts.get(REGISTRY, "ES384"); - - /** - * {@code ECDSA using P-521 and SHA-512} signature algorithm as defined by - * RFC 7518, Section 3.4. This algorithm - * requires a 521-bit key. - */ - public static final SignatureAlgorithm ES512 = Jwts.get(REGISTRY, "ES512"); - - /** - * {@code EdDSA} signature algorithm defined by - * RFC 8037, Section 3.1 that requires - * either {@code Ed25519} or {@code Ed448} Edwards Elliptic Curve1 keys. - * - *

KeyPair Generation

- * - *

This instance's {@link KeyPairBuilderSupplier#keyPair() keyPair()} builder creates {@code Ed448} keys, - * and is essentially an alias for - * {@link io.jsonwebtoken.security.Jwks.CRV Jwks.CRV}.{@link io.jsonwebtoken.security.Jwks.CRV#Ed448 Ed448}.{@link KeyPairBuilderSupplier#keyPair() keyPair()}.

- * - *

If you would like to generate an {@code Ed25519} {@code KeyPair} for use with the {@code EdDSA} algorithm, - * you may use the - * {@link io.jsonwebtoken.security.Jwks.CRV Jwks.CRV}.{@link io.jsonwebtoken.security.Jwks.CRV#Ed25519 Ed25519}.{@link KeyPairBuilderSupplier#keyPair() keyPair()} - * builder instead.

- * - *

1This algorithm requires at least JDK 15 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath.

- */ - public static final SignatureAlgorithm EdDSA = Jwts.get(REGISTRY, "EdDSA"); - } - - /** - * Constants for all standard JWA (RFC 7518) - * Cryptographic Algorithms for Key Management. Each standard algorithm is available as a - * ({@code public static final}) constant for direct type-safe reference in application code. For example: - *
-     * Jwts.builder()
-     *    // ... etc ...
-     *    .encryptWith(aKey, Jwts.KEY.ECDH_ES_A256KW, Jwts.ENC.A256GCM)
-     *    .build();
- *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

- * - * @see #get() - * @since 0.12.0 - */ - public static final class KEY { - - private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardKeyAlgorithms"; - private static final Registry> REGISTRY = Classes.newInstance(IMPL_CLASSNAME); - - /** - * Returns all standard JWA standard Cryptographic - * Algorithms for Key Management.. - * - * @return all standard JWA Key Management algorithms. - */ - public static Registry> get() { - return REGISTRY; - } - - /** - * Key algorithm reflecting direct use of a shared symmetric key as the JWE AEAD encryption key, as defined - * by RFC 7518 (JWA), Section 4.5. This - * algorithm does not produce encrypted key ciphertext. - */ - public static final KeyAlgorithm DIRECT = Jwts.get(REGISTRY, "dir"); - - /** - * AES Key Wrap algorithm with default initial value using a 128-bit key, as defined by - * RFC 7518 (JWA), Section 4.4. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. - *
  3. Encrypts this newly-generated {@code SecretKey} with a 128-bit shared symmetric key using the - * AES Key Wrap algorithm, producing encrypted key ciphertext.
  4. - *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. - *
  3. Decrypts the encrypted key ciphertext with the 128-bit shared symmetric key, - * using the AES Key Unwrap algorithm, producing the decryption key plaintext.
  4. - *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. - *
- */ - public static final SecretKeyAlgorithm A128KW = Jwts.get(REGISTRY, "A128KW"); - - /** - * AES Key Wrap algorithm with default initial value using a 192-bit key, as defined by - * RFC 7518 (JWA), Section 4.4. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. - *
  3. Encrypts this newly-generated {@code SecretKey} with a 192-bit shared symmetric key using the - * AES Key Wrap algorithm, producing encrypted key ciphertext.
  4. - *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. - *
  3. Decrypts the encrypted key ciphertext with the 192-bit shared symmetric key, - * using the AES Key Unwrap algorithm, producing the decryption key plaintext.
  4. - *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. - *
- */ - public static final SecretKeyAlgorithm A192KW = Jwts.get(REGISTRY, "A192KW"); - - /** - * AES Key Wrap algorithm with default initial value using a 256-bit key, as defined by - * RFC 7518 (JWA), Section 4.4. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. - *
  3. Encrypts this newly-generated {@code SecretKey} with a 256-bit shared symmetric key using the - * AES Key Wrap algorithm, producing encrypted key ciphertext.
  4. - *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. - *
  3. Decrypts the encrypted key ciphertext with the 256-bit shared symmetric key, - * using the AES Key Unwrap algorithm, producing the decryption key plaintext.
  4. - *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. - *
- */ - public static final SecretKeyAlgorithm A256KW = Jwts.get(REGISTRY, "A256KW"); - - /** - * Key wrap algorithm with AES GCM using a 128-bit key, as defined by - * RFC 7518 (JWA), Section 4.7. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. - *
  3. Generates a new secure-random 96-bit Initialization Vector to use during key wrap/encryption.
  4. - *
  5. Encrypts this newly-generated {@code SecretKey} with a 128-bit shared symmetric key using the - * AES GCM Key Wrap algorithm with the generated Initialization Vector, producing encrypted key ciphertext - * and GCM authentication tag.
  6. - *
  7. Sets the generated initialization vector as the required - * "iv" - * (Initialization Vector) Header Parameter
  8. - *
  9. Sets the resulting GCM authentication tag as the required - * "tag" - * (Authentication Tag) Header Parameter
  10. - *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  12. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. - *
  3. Obtains the required initialization vector from the - * "iv" - * (Initialization Vector) Header Parameter
  4. - *
  5. Obtains the required GCM authentication tag from the - * "tag" - * (Authentication Tag) Header Parameter
  6. - *
  7. Decrypts the encrypted key ciphertext with the 128-bit shared symmetric key, the initialization vector - * and GCM authentication tag using the AES GCM Key Unwrap algorithm, producing the decryption key - * plaintext.
  8. - *
  9. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. - *
- */ - public static final SecretKeyAlgorithm A128GCMKW = Jwts.get(REGISTRY, "A128GCMKW"); - - /** - * Key wrap algorithm with AES GCM using a 192-bit key, as defined by - * RFC 7518 (JWA), Section 4.7. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. - *
  3. Generates a new secure-random 96-bit Initialization Vector to use during key wrap/encryption.
  4. - *
  5. Encrypts this newly-generated {@code SecretKey} with a 192-bit shared symmetric key using the - * AES GCM Key Wrap algorithm with the generated Initialization Vector, producing encrypted key ciphertext - * and GCM authentication tag.
  6. - *
  7. Sets the generated initialization vector as the required - * "iv" - * (Initialization Vector) Header Parameter
  8. - *
  9. Sets the resulting GCM authentication tag as the required - * "tag" - * (Authentication Tag) Header Parameter
  10. - *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  12. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. - *
  3. Obtains the required initialization vector from the - * "iv" - * (Initialization Vector) Header Parameter
  4. - *
  5. Obtains the required GCM authentication tag from the - * "tag" - * (Authentication Tag) Header Parameter
  6. - *
  7. Decrypts the encrypted key ciphertext with the 192-bit shared symmetric key, the initialization vector - * and GCM authentication tag using the AES GCM Key Unwrap algorithm, producing the decryption key \ - * plaintext.
  8. - *
  9. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. - *
- */ - public static final SecretKeyAlgorithm A192GCMKW = Jwts.get(REGISTRY, "A192GCMKW"); - - /** - * Key wrap algorithm with AES GCM using a 256-bit key, as defined by - * RFC 7518 (JWA), Section 4.7. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. - *
  3. Generates a new secure-random 96-bit Initialization Vector to use during key wrap/encryption.
  4. - *
  5. Encrypts this newly-generated {@code SecretKey} with a 256-bit shared symmetric key using the - * AES GCM Key Wrap algorithm with the generated Initialization Vector, producing encrypted key ciphertext - * and GCM authentication tag.
  6. - *
  7. Sets the generated initialization vector as the required - * "iv" - * (Initialization Vector) Header Parameter
  8. - *
  9. Sets the resulting GCM authentication tag as the required - * "tag" - * (Authentication Tag) Header Parameter
  10. - *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  12. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. - *
  3. Obtains the required initialization vector from the - * "iv" - * (Initialization Vector) Header Parameter
  4. - *
  5. Obtains the required GCM authentication tag from the - * "tag" - * (Authentication Tag) Header Parameter
  6. - *
  7. Decrypts the encrypted key ciphertext with the 256-bit shared symmetric key, the initialization vector - * and GCM authentication tag using the AES GCM Key Unwrap algorithm, producing the decryption key \ - * plaintext.
  8. - *
  9. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. - *
- */ - public static final SecretKeyAlgorithm A256GCMKW = Jwts.get(REGISTRY, "A256GCMKW"); - - /** - * Key encryption algorithm using PBES2 with HMAC SHA-256 and "A128KW" wrapping - * as defined by - * RFC 7518 (JWA), Section 4.8. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Determines the number of PBDKF2 iterations via the JWE header's - * {@link JweHeader#getPbes2Count() pbes2Count} value. If that value is not set, a suitable number of - * iterations will be chosen based on - * OWASP - * PBKDF2 recommendations and then that value is set as the JWE header {@code pbes2Count} value.
  2. - *
  3. Generates a new secure-random salt input and sets it as the JWE header - * {@link JweHeader#getPbes2Salt() pbes2Salt} value.
  4. - *
  5. Derives a 128-bit Key Encryption Key with the PBES2-HS256 password-based key derivation algorithm, - * using the provided password, iteration count, and input salt as arguments.
  6. - *
  7. Generates a new secure-random Content Encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  8. - *
  9. Encrypts this newly-generated Content Encryption {@code SecretKey} with the {@code A128KW} key wrap - * algorithm using the 128-bit derived password-based Key Encryption Key from step {@code #3}, - * producing encrypted key ciphertext.
  10. - *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * Content Encryption {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated - * {@link AeadAlgorithm}.
  12. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Obtains the required PBKDF2 input salt from the - * "p2s" - * (PBES2 Salt Input) Header Parameter
  2. - *
  3. Obtains the required PBKDF2 iteration count from the - * "p2c" - * (PBES2 Count) Header Parameter
  4. - *
  5. Derives the 128-bit Key Encryption Key with the PBES2-HS256 password-based key derivation algorithm, - * using the provided password, obtained salt input, and obtained iteration count as arguments.
  6. - *
  7. Obtains the encrypted key ciphertext embedded in the received JWE.
  8. - *
  9. Decrypts the encrypted key ciphertext with with the {@code A128KW} key unwrap - * algorithm using the 128-bit derived password-based Key Encryption Key from step {@code #3}, - * producing the decryption key plaintext.
  10. - *
  11. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  12. - *
- */ - public static final KeyAlgorithm PBES2_HS256_A128KW = Jwts.get(REGISTRY, "PBES2-HS256+A128KW"); - - /** - * Key encryption algorithm using PBES2 with HMAC SHA-384 and "A192KW" wrapping - * as defined by - * RFC 7518 (JWA), Section 4.8. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Determines the number of PBDKF2 iterations via the JWE header's - * {@link JweHeader#getPbes2Count() pbes2Count} value. If that value is not set, a suitable number of - * iterations will be chosen based on - * OWASP - * PBKDF2 recommendations and then that value is set as the JWE header {@code pbes2Count} value.
  2. - *
  3. Generates a new secure-random salt input and sets it as the JWE header - * {@link JweHeader#getPbes2Salt() pbes2Salt} value.
  4. - *
  5. Derives a 192-bit Key Encryption Key with the PBES2-HS384 password-based key derivation algorithm, - * using the provided password, iteration count, and input salt as arguments.
  6. - *
  7. Generates a new secure-random Content Encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  8. - *
  9. Encrypts this newly-generated Content Encryption {@code SecretKey} with the {@code A192KW} key wrap - * algorithm using the 192-bit derived password-based Key Encryption Key from step {@code #3}, - * producing encrypted key ciphertext.
  10. - *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * Content Encryption {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated - * {@link AeadAlgorithm}.
  12. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Obtains the required PBKDF2 input salt from the - * "p2s" - * (PBES2 Salt Input) Header Parameter
  2. - *
  3. Obtains the required PBKDF2 iteration count from the - * "p2c" - * (PBES2 Count) Header Parameter
  4. - *
  5. Derives the 192-bit Key Encryption Key with the PBES2-HS384 password-based key derivation algorithm, - * using the provided password, obtained salt input, and obtained iteration count as arguments.
  6. - *
  7. Obtains the encrypted key ciphertext embedded in the received JWE.
  8. - *
  9. Decrypts the encrypted key ciphertext with with the {@code A192KW} key unwrap - * algorithm using the 192-bit derived password-based Key Encryption Key from step {@code #3}, - * producing the decryption key plaintext.
  10. - *
  11. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  12. - *
- */ - public static final KeyAlgorithm PBES2_HS384_A192KW = Jwts.get(REGISTRY, "PBES2-HS384+A192KW"); - - /** - * Key encryption algorithm using PBES2 with HMAC SHA-512 and "A256KW" wrapping - * as defined by - * RFC 7518 (JWA), Section 4.8. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Determines the number of PBDKF2 iterations via the JWE header's - * {@link JweHeader#getPbes2Count() pbes2Count} value. If that value is not set, a suitable number of - * iterations will be chosen based on - * OWASP - * PBKDF2 recommendations and then that value is set as the JWE header {@code pbes2Count} value.
  2. - *
  3. Generates a new secure-random salt input and sets it as the JWE header - * {@link JweHeader#getPbes2Salt() pbes2Salt} value.
  4. - *
  5. Derives a 256-bit Key Encryption Key with the PBES2-HS512 password-based key derivation algorithm, - * using the provided password, iteration count, and input salt as arguments.
  6. - *
  7. Generates a new secure-random Content Encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  8. - *
  9. Encrypts this newly-generated Content Encryption {@code SecretKey} with the {@code A256KW} key wrap - * algorithm using the 256-bit derived password-based Key Encryption Key from step {@code #3}, - * producing encrypted key ciphertext.
  10. - *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * Content Encryption {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated - * {@link AeadAlgorithm}.
  12. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Obtains the required PBKDF2 input salt from the - * "p2s" - * (PBES2 Salt Input) Header Parameter
  2. - *
  3. Obtains the required PBKDF2 iteration count from the - * "p2c" - * (PBES2 Count) Header Parameter
  4. - *
  5. Derives the 256-bit Key Encryption Key with the PBES2-HS512 password-based key derivation algorithm, - * using the provided password, obtained salt input, and obtained iteration count as arguments.
  6. - *
  7. Obtains the encrypted key ciphertext embedded in the received JWE.
  8. - *
  9. Decrypts the encrypted key ciphertext with with the {@code A256KW} key unwrap - * algorithm using the 256-bit derived password-based Key Encryption Key from step {@code #3}, - * producing the decryption key plaintext.
  10. - *
  11. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  12. - *
- */ - public static final KeyAlgorithm PBES2_HS512_A256KW = Jwts.get(REGISTRY, "PBES2-HS512+A256KW"); - - /** - * Key Encryption with {@code RSAES-PKCS1-v1_5}, as defined by - * RFC 7518 (JWA), Section 4.2. - * This algorithm requires a key size of 2048 bits or larger. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. - *
  3. Encrypts this newly-generated {@code SecretKey} with the RSA key wrap algorithm, using the JWE - * recipient's RSA Public Key, producing encrypted key ciphertext.
  4. - *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Receives the encrypted key ciphertext embedded in the received JWE.
  2. - *
  3. Decrypts the encrypted key ciphertext with the RSA key unwrap algorithm, using the JWE recipient's - * RSA Private Key, producing the decryption key plaintext.
  4. - *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. - *
- */ - public static final KeyAlgorithm RSA1_5 = Jwts.get(REGISTRY, "RSA1_5"); - - /** - * Key Encryption with {@code RSAES OAEP using default parameters}, as defined by - * RFC 7518 (JWA), Section 4.3. - * This algorithm requires a key size of 2048 bits or larger. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. - *
  3. Encrypts this newly-generated {@code SecretKey} with the RSA OAEP with SHA-1 and MGF1 key wrap algorithm, - * using the JWE recipient's RSA Public Key, producing encrypted key ciphertext.
  4. - *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Receives the encrypted key ciphertext embedded in the received JWE.
  2. - *
  3. Decrypts the encrypted key ciphertext with the RSA OAEP with SHA-1 and MGF1 key unwrap algorithm, - * using the JWE recipient's RSA Private Key, producing the decryption key plaintext.
  4. - *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. - *
- */ - public static final KeyAlgorithm RSA_OAEP = Jwts.get(REGISTRY, "RSA-OAEP"); - - /** - * Key Encryption with {@code RSAES OAEP using SHA-256 and MGF1 with SHA-256}, as defined by - * RFC 7518 (JWA), Section 4.3. - * This algorithm requires a key size of 2048 bits or larger. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. - *
  3. Encrypts this newly-generated {@code SecretKey} with the RSA OAEP with SHA-256 and MGF1 key wrap - * algorithm, using the JWE recipient's RSA Public Key, producing encrypted key ciphertext.
  4. - *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Receives the encrypted key ciphertext embedded in the received JWE.
  2. - *
  3. Decrypts the encrypted key ciphertext with the RSA OAEP with SHA-256 and MGF1 key unwrap algorithm, - * using the JWE recipient's RSA Private Key, producing the decryption key plaintext.
  4. - *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. - *
- */ - public static final KeyAlgorithm RSA_OAEP_256 = Jwts.get(REGISTRY, "RSA-OAEP-256"); - - /** - * Key Agreement with {@code ECDH-ES using Concat KDF} as defined by - * RFC 7518 (JWA), Section 4.6. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the - * JWE recipient's EC Public Key.
  2. - *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key - * and the JWE recipient's EC Public Key.
  4. - *
  5. Derives a symmetric Content - * Encryption {@code SecretKey} with the Concat KDF algorithm using the - * generated shared secret and any available - * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and - * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. - *
  7. Sets the generated EC key pair's Public Key as the required - * "epk" - * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. - *
  9. Returns the derived symmetric {@code SecretKey} for JJWT to use to encrypt the entire JWE with the - * associated {@link AeadAlgorithm}. Encrypted key ciphertext is not produced with this algorithm, so - * the resulting JWE will not contain any embedded key ciphertext.
  10. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the - * "epk" - * (Ephemeral Public Key) Header Parameter.
  2. - *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. - *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key - * and the JWE recipient's EC Private Key.
  6. - *
  7. Derives the symmetric Content - * Encryption {@code SecretKey} with the Concat KDF algorithm using the - * obtained shared secret and any available - * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and - * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. - *
  9. Returns the derived symmetric {@code SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. - *
- */ - public static final KeyAlgorithm ECDH_ES = Jwts.get(REGISTRY, "ECDH-ES"); - - /** - * Key Agreement with Key Wrapping via - * ECDH-ES using Concat KDF and CEK wrapped with "A128KW" as defined by - * RFC 7518 (JWA), Section 4.6. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the - * JWE recipient's EC Public Key.
  2. - *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key - * and the JWE recipient's EC Public Key.
  4. - *
  5. Derives a 128-bit symmetric Key - * Encryption {@code SecretKey} with the Concat KDF algorithm using the - * generated shared secret and any available - * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and - * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. - *
  7. Sets the generated EC key pair's Public Key as the required - * "epk" - * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. - *
  9. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  10. - *
  11. Encrypts this newly-generated {@code SecretKey} with the {@code A128KW} key wrap - * algorithm using the derived symmetric Key Encryption Key from step {@code #3}, producing encrypted key ciphertext.
  12. - *
  13. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  14. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the - * "epk" - * (Ephemeral Public Key) Header Parameter.
  2. - *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. - *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key - * and the JWE recipient's EC Private Key.
  6. - *
  7. Derives the symmetric Key - * Encryption {@code SecretKey} with the Concat KDF algorithm using the - * obtained shared secret and any available - * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and - * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. - *
  9. Obtains the encrypted key ciphertext embedded in the received JWE.
  10. - *
  11. Decrypts the encrypted key ciphertext with the AES Key Unwrap algorithm using the - * 128-bit derived symmetric key from step {@code #4}, producing the decryption key plaintext.
  12. - *
  13. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  14. - *
- */ - public static final KeyAlgorithm ECDH_ES_A128KW = Jwts.get(REGISTRY, "ECDH-ES+A128KW"); - - /** - * Key Agreement with Key Wrapping via - * ECDH-ES using Concat KDF and CEK wrapped with "A192KW" as defined by - * RFC 7518 (JWA), Section 4.6. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the - * JWE recipient's EC Public Key.
  2. - *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key - * and the JWE recipient's EC Public Key.
  4. - *
  5. Derives a 192-bit symmetric Key - * Encryption {@code SecretKey} with the Concat KDF algorithm using the - * generated shared secret and any available - * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and - * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. - *
  7. Sets the generated EC key pair's Public Key as the required - * "epk" - * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. - *
  9. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  10. - *
  11. Encrypts this newly-generated {@code SecretKey} with the {@code A192KW} key wrap - * algorithm using the derived symmetric Key Encryption Key from step {@code #3}, producing encrypted key - * ciphertext.
  12. - *
  13. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  14. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the - * "epk" - * (Ephemeral Public Key) Header Parameter.
  2. - *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. - *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key - * and the JWE recipient's EC Private Key.
  6. - *
  7. Derives the 192-bit symmetric - * Key Encryption {@code SecretKey} with the Concat KDF algorithm using the - * obtained shared secret and any available - * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and - * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. - *
  9. Obtains the encrypted key ciphertext embedded in the received JWE.
  10. - *
  11. Decrypts the encrypted key ciphertext with the AES Key Unwrap algorithm using the - * 192-bit derived symmetric key from step {@code #4}, producing the decryption key plaintext.
  12. - *
  13. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  14. - *
- */ - public static final KeyAlgorithm ECDH_ES_A192KW = Jwts.get(REGISTRY, "ECDH-ES+A192KW"); - - /** - * Key Agreement with Key Wrapping via - * ECDH-ES using Concat KDF and CEK wrapped with "A256KW" as defined by - * RFC 7518 (JWA), Section 4.6. - * - *

During JWE creation, this algorithm:

- *
    - *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the - * JWE recipient's EC Public Key.
  2. - *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key - * and the JWE recipient's EC Public Key.
  4. - *
  5. Derives a 256-bit symmetric Key - * Encryption {@code SecretKey} with the Concat KDF algorithm using the - * generated shared secret and any available - * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and - * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. - *
  7. Sets the generated EC key pair's Public Key as the required - * "epk" - * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. - *
  9. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a - * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  10. - *
  11. Encrypts this newly-generated {@code SecretKey} with the {@code A256KW} key wrap - * algorithm using the derived symmetric Key Encryption Key from step {@code #3}, producing encrypted key - * ciphertext.
  12. - *
  13. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated - * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  14. - *
- *

For JWE decryption, this algorithm:

- *
    - *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the - * "epk" - * (Ephemeral Public Key) Header Parameter.
  2. - *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. - *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key - * and the JWE recipient's EC Private Key.
  6. - *
  7. Derives the 256-bit symmetric - * Key Encryption {@code SecretKey} with the Concat KDF algorithm using the - * obtained shared secret and any available - * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and - * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. - *
  9. Obtains the encrypted key ciphertext embedded in the received JWE.
  10. - *
  11. Decrypts the encrypted key ciphertext with the AES Key Unwrap algorithm using the - * 256-bit derived symmetric key from step {@code #4}, producing the decryption key plaintext.
  12. - *
  13. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire - * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  14. - *
- */ - public static final KeyAlgorithm ECDH_ES_A256KW = Jwts.get(REGISTRY, "ECDH-ES+A256KW"); - - //prevent instantiation - private KEY() { - } - } - - /** - * Constants for JWA (RFC 7518) compression algorithms referenced in the {@code zip} header defined in the - * JSON Web Encryption Compression Algorithms - * Registry. Each algorithm is available as a ({@code public static final}) constant for - * direct type-safe reference in application code. For example: - *
-     * Jwts.builder()
-     *    // ... etc ...
-     *    .compressWith(Jwts.ZIP.DEF)
-     *    .build();
- *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

- * - * @see #get() - * @since 0.12.0 - */ - public static final class ZIP { - - private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.io.StandardCompressionAlgorithms"; - private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); - - /** - * Returns various useful - * Compression Algorithms. - * - * @return various standard and non-standard useful compression algorithms. - */ - public static Registry get() { - return REGISTRY; - } - - /** - * The JWE-standard DEFLATE - * compression algorithm with a {@code zip} header value of {@code "DEF"}. - * - * @see JWE RFC 7516, Section 4.1.3 - */ - public static final CompressionAlgorithm DEF = get().forKey("DEF"); - - /** - * A commonly used, but NOT JWA-STANDARD - * gzip compression algorithm with a {@code zip} header value - * of {@code "GZIP"}. - * - *

Compatibility Warning

- * - *

This is not a standard JWE compression algorithm. Be sure to use this only when you are confident - * that all parties accessing the token support the "GZIP" identifier and associated algorithm.

- * - *

If you're concerned about compatibility, {@link #DEF DEF} is the only JWA standards-compliant algorithm.

- * - * @see #DEF - */ - public static final CompressionAlgorithm GZIP = get().forKey("GZIP"); - - //prevent instantiation - private ZIP() { - } - } - - /** - * A {@link Builder} that dynamically determines the type of {@link Header} to create based on builder state. - * - * @since 0.12.0 - */ - public interface HeaderBuilder extends JweHeaderMutator, X509Builder, Builder
{ - } - - /** - * Returns a new {@link HeaderBuilder} that can build any type of {@link Header} instance depending on - * which builder properties are set. - * - * @return a new {@link HeaderBuilder} that can build any type of {@link Header} instance depending on - * which builder properties are set. - * @since 0.12.0 - */ - public static HeaderBuilder header() { - return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtHeaderBuilder"); - } - - /** - * Returns a new {@link Claims} builder instance to be used to populate JWT claims, which in aggregate will be - * the JWT payload. - * - * @return a new {@link Claims} builder instance to be used to populate JWT claims, which in aggregate will be - * the JWT payload. - */ - public static ClaimsBuilder claims() { - return Classes.newInstance("io.jsonwebtoken.impl.DefaultClaimsBuilder"); - } - - /** - *

Deprecated since 0.12.0 in favor of - * {@code Jwts.}{@link #claims()}{@code .add(map).build()}. - * This method will be removed before 1.0.

- * - *

Returns a new {@link Claims} instance populated with the specified name/value pairs.

- * - * @param claims the name/value pairs to populate the new Claims instance. - * @return a new {@link Claims} instance populated with the specified name/value pairs. - * @deprecated since 0.12.0 in favor of {@code Jwts.}{@link #claims()}{@code .putAll(map).build()}. - * This method will be removed before 1.0. - */ - @Deprecated - public static Claims claims(Map claims) { - return claims().add(claims).build(); - } - - /** - * Returns a new {@link JwtBuilder} instance that can be configured and then used to create JWT compact serialized - * strings. - * - * @return a new {@link JwtBuilder} instance that can be configured and then used to create JWT compact serialized - * strings. - */ - public static JwtBuilder builder() { - return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtBuilder"); - } - - /** - * Returns a new {@link JwtParserBuilder} instance that can be configured to create an immutable/thread-safe {@link JwtParser}. - * - * @return a new {@link JwtParser} instance that can be configured create an immutable/thread-safe {@link JwtParser}. - */ - public static JwtParserBuilder parser() { - return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtParserBuilder"); - } - - /** - * Private constructor, prevent instantiation. - */ - private Jwts() { - } -} diff --git a/io/jsonwebtoken/Locator.java b/io/jsonwebtoken/Locator.java deleted file mode 100644 index 1d22258..0000000 --- a/io/jsonwebtoken/Locator.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import java.security.Key; - -/** - * A {@link Locator} can return an object referenced in a JWT {@link Header} that is necessary to process - * the associated JWT. - * - *

For example, a {@code Locator} implementation can inspect a header's {@code kid} (Key ID) parameter, and use the - * discovered {@code kid} value to lookup and return the associated {@link Key} instance. JJWT could then use this - * {@code key} to decrypt a JWE or verify a JWS signature.

- * - * @param the type of object that may be returned from the {@link #locate(Header)} method - * @since 0.12.0 - */ -public interface Locator { - - /** - * Returns an object referenced in the specified {@code header}, or {@code null} if the object couldn't be found. - * - * @param header the JWT header to inspect; may be an instance of {@link Header}, {@link JwsHeader} or - * {@link JweHeader} depending on if the respective JWT is an unprotected JWT, JWS or JWE. - * @return an object referenced in the specified {@code header}, or {@code null} if the object couldn't be found. - */ - T locate(Header header); -} diff --git a/io/jsonwebtoken/LocatorAdapter.java b/io/jsonwebtoken/LocatorAdapter.java deleted file mode 100644 index 43f12dc..0000000 --- a/io/jsonwebtoken/LocatorAdapter.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.lang.Assert; - -/** - * Adapter pattern implementation for the {@link Locator} interface. Subclasses can override any of the - * {@link #doLocate(Header)}, {@link #locate(ProtectedHeader)}, {@link #locate(JwsHeader)}, or - * {@link #locate(JweHeader)} methods for type-specific logic if desired when the encountered header is an - * unprotected JWT, or an integrity-protected JWT (either a JWS or JWE). - * - * @param the type of object to locate - * @since 0.12.0 - */ -public abstract class LocatorAdapter implements Locator { - - /** - * Constructs a new instance, where all default method implementations return {@code null}. - */ - public LocatorAdapter() { - } - - /** - * Inspects the specified header, and delegates to the {@link #locate(ProtectedHeader)} method if the header - * is protected (either a {@link JwsHeader} or {@link JweHeader}), or the {@link #doLocate(Header)} method - * if the header is not integrity protected. - * - * @param header the JWT header to inspect; may be an instance of {@link Header}, {@link JwsHeader}, or - * {@link JweHeader} depending on if the respective JWT is an unprotected JWT, JWS or JWE. - * @return an object referenced in the specified header, or {@code null} if the referenced object cannot be found - * or does not exist. - */ - @Override - public final T locate(Header header) { - Assert.notNull(header, "Header cannot be null."); - if (header instanceof ProtectedHeader) { - ProtectedHeader protectedHeader = (ProtectedHeader) header; - return locate(protectedHeader); - } - return doLocate(header); - } - - /** - * Returns an object referenced in the specified {@link ProtectedHeader}, or {@code null} if the referenced - * object cannot be found or does not exist. This is a convenience method that delegates to - * {@link #locate(JwsHeader)} if the {@code header} is a {@link JwsHeader} or {@link #locate(JweHeader)} if the - * {@code header} is a {@link JweHeader}. - * - * @param header the protected header of an encountered JWS or JWE. - * @return an object referenced in the specified {@link ProtectedHeader}, or {@code null} if the referenced - * object cannot be found or does not exist. - */ - protected T locate(ProtectedHeader header) { - if (header instanceof JwsHeader) { - return locate((JwsHeader) header); - } else { - Assert.isInstanceOf(JweHeader.class, header, "Unrecognized ProtectedHeader type."); - return locate((JweHeader) header); - } - } - - /** - * Returns an object referenced in the specified JWE header, or {@code null} if the referenced - * object cannot be found or does not exist. Default implementation simply returns {@code null}. - * - * @param header the header of an encountered JWE. - * @return an object referenced in the specified JWE header, or {@code null} if the referenced - * object cannot be found or does not exist. - */ - protected T locate(JweHeader header) { - return null; - } - - /** - * Returns an object referenced in the specified JWS header, or {@code null} if the referenced - * object cannot be found or does not exist. Default implementation simply returns {@code null}. - * - * @param header the header of an encountered JWS. - * @return an object referenced in the specified JWS header, or {@code null} if the referenced - * object cannot be found or does not exist. - */ - protected T locate(JwsHeader header) { - return null; - } - - /** - * Returns an object referenced in the specified unprotected JWT header, or {@code null} if the referenced - * object cannot be found or does not exist. Default implementation simply returns {@code null}. - * - * @param header the header of an encountered JWT. - * @return an object referenced in the specified unprotected JWT header, or {@code null} if the referenced - * object cannot be found or does not exist. - */ - @SuppressWarnings("unused") - protected T doLocate(Header header) { - return null; - } -} diff --git a/io/jsonwebtoken/MalformedJwtException.java b/io/jsonwebtoken/MalformedJwtException.java deleted file mode 100644 index 5729388..0000000 --- a/io/jsonwebtoken/MalformedJwtException.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * Exception indicating that a JWT was not correctly constructed and should be rejected. - * - * @since 0.2 - */ -public class MalformedJwtException extends JwtException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public MalformedJwtException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public MalformedJwtException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/MissingClaimException.java b/io/jsonwebtoken/MissingClaimException.java deleted file mode 100644 index 246748d..0000000 --- a/io/jsonwebtoken/MissingClaimException.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2015 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * Exception thrown when discovering that a required claim is not present, indicating the JWT is - * invalid and may not be used. - * - * @since 0.6 - */ -public class MissingClaimException extends InvalidClaimException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param header the header associated with the claims that did not contain the required claim - * @param claims the claims that did not contain the required claim - * @param claimName the name of the claim that could not be validated - * @param claimValue the value of the claim that could not be validated - * @param message the message explaining why the exception is thrown. - */ - public MissingClaimException(Header header, Claims claims, String claimName, Object claimValue, String message) { - super(header, claims, claimName, claimValue, message); - } - - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param header the header associated with the claims that did not contain the required claim - * @param claims the claims that did not contain the required claim - * @param claimName the name of the claim that could not be validated - * @param claimValue the value of the claim that could not be validated - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - * @deprecated since 0.12.0 since it is not used in JJWT's codebase - */ - @Deprecated - public MissingClaimException(Header header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) { - super(header, claims, claimName, claimValue, message, cause); - } -} diff --git a/io/jsonwebtoken/PrematureJwtException.java b/io/jsonwebtoken/PrematureJwtException.java deleted file mode 100644 index 4bdb2ee..0000000 --- a/io/jsonwebtoken/PrematureJwtException.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * Exception indicating that a JWT was accepted before it is allowed to be accessed and must be rejected. - * - * @since 0.3 - */ -public class PrematureJwtException extends ClaimJwtException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param header jwt header - * @param claims jwt claims (body) - * @param message the message explaining why the exception is thrown. - */ - public PrematureJwtException(Header header, Claims claims, String message) { - super(header, claims, message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param header jwt header - * @param claims jwt claims (body) - * @param message exception message - * @param cause cause - * @since 0.5 - * @deprecated since 0.12.0 since it is not used in JJWT's codebase - */ - @Deprecated - public PrematureJwtException(Header header, Claims claims, String message, Throwable cause) { - super(header, claims, message, cause); - } -} diff --git a/io/jsonwebtoken/ProtectedHeader.java b/io/jsonwebtoken/ProtectedHeader.java deleted file mode 100644 index 4c13c28..0000000 --- a/io/jsonwebtoken/ProtectedHeader.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.security.PublicJwk; -import io.jsonwebtoken.security.X509Accessor; - -import java.net.URI; -import java.util.Set; - -/** - * A JWT header that is integrity protected, either by JWS digital signature or JWE AEAD encryption. - * - * @see JwsHeader - * @see JweHeader - * @since 0.12.0 - */ -public interface ProtectedHeader extends Header, X509Accessor { - - /** - * Returns the {@code jku} (JWK Set URL) value that refers to a - * JWK Set - * resource containing JSON-encoded Public Keys, or {@code null} if not present. When present in a - * {@link JwsHeader}, the first public key in the JWK Set must be the public key complement of the private - * key used to sign the JWS. When present in a {@link JweHeader}, the first public key in the JWK Set must - * be the public key used during encryption. - * - * @return a URI that refers to a JWK Set - * resource for a set of JSON-encoded Public Keys, or {@code null} if not present. - * @see JWS JWK Set URL - * @see JWE JWK Set URL - */ - URI getJwkSetUrl(); - - /** - * Returns the {@code jwk} (JSON Web Key) associated with the JWT. When present in a {@link JwsHeader}, the - * {@code jwk} is the public key complement of the private key used to digitally sign the JWS. When present in a - * {@link JweHeader}, the {@code jwk} is the public key to which the JWE was encrypted, and may be used to - * determine the private key needed to decrypt the JWE. - * - * @return the {@code jwk} (JSON Web Key) associated with the header. - * @see JWS {@code jwk} (JSON Web Key) Header Parameter - * @see JWE {@code jwk} (JSON Web Key) Header Parameter - */ - PublicJwk getJwk(); - - /** - * Returns the JWT case-sensitive {@code kid} (Key ID) header value or {@code null} if not present. - * - *

The keyId header parameter is a hint indicating which key was used to secure a JWS or JWE. This - * parameter allows originators to explicitly signal a change of key to recipients. The structure of the keyId - * value is unspecified. Its value is a CaSe-SeNsItIvE string.

- * - *

When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.

- * - * @return the case-sensitive {@code kid} header value or {@code null} if not present. - * @see JWS Key ID - * @see JWE Key ID - */ - String getKeyId(); - - /** - * Returns the header parameter names that use extensions to the JWT or JWA specification(s) that MUST - * be understood and supported by the JWT recipient, or {@code null} if not present. - * - * @return the header parameter names that use extensions to the JWT or JWA specification(s) that MUST - * be understood and supported by the JWT recipient, or {@code null} if not present. - * @see JWS {@code crit} (Critical) Header Parameter - * @see JWS {@code crit} (Critical) Header Parameter - */ - Set getCritical(); -} diff --git a/io/jsonwebtoken/ProtectedHeaderMutator.java b/io/jsonwebtoken/ProtectedHeaderMutator.java deleted file mode 100644 index 0022506..0000000 --- a/io/jsonwebtoken/ProtectedHeaderMutator.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.lang.Conjunctor; -import io.jsonwebtoken.lang.NestedCollection; -import io.jsonwebtoken.security.PublicJwk; -import io.jsonwebtoken.security.X509Mutator; - -import java.net.URI; - -/** - * Mutation (modifications) to a {@link ProtectedHeader Header} instance. - * - * @param the mutator subtype, for method chaining - * @since 0.12.0 - */ -public interface ProtectedHeaderMutator> extends HeaderMutator, X509Mutator { - - /** - * Configures names of header parameters used by JWT or JWA specification extensions that MUST be - * understood and supported by the JWT recipient. When finished, use the collection's - * {@link Conjunctor#and() and()} method to continue header configuration, for example: - *
-     * headerBuilder
-     *     .critical().add("headerName").{@link Conjunctor#and() and()} // return parent
-     * // resume header configuration...
- * - * @return the {@link NestedCollection} to use for {@code crit} configuration. - * @see JWS crit (Critical) Header Parameter - * @see JWS crit (Critical) Header Parameter - */ - NestedCollection critical(); - - /** - * Sets the {@code jwk} (JSON Web Key) associated with the JWT. When set for a {@link JwsHeader}, the - * {@code jwk} is the public key complement of the private key used to digitally sign the JWS. When set for a - * {@link JweHeader}, the {@code jwk} is the public key to which the JWE was encrypted, and may be used to - * determine the private key needed to decrypt the JWE. - * - * @param jwk the {@code jwk} (JSON Web Key) associated with the header. - * @return the header for method chaining - * @see JWS jwk (JSON Web Key) Header Parameter - * @see JWE jwk (JSON Web Key) Header Parameter - */ - T jwk(PublicJwk jwk); - - /** - * Sets the {@code jku} (JWK Set URL) value that refers to a - * JWK Set - * resource containing JSON-encoded Public Keys, or {@code null} if not present. When set for a - * {@link JwsHeader}, the first public key in the JWK Set must be the public key complement of the - * private key used to sign the JWS. When set for a {@link JweHeader}, the first public key in the JWK Set - * must be the public key used during encryption. - * - * @param uri a URI that refers to a JWK Set - * resource containing JSON-encoded Public Keys - * @return the header for method chaining - * @see JWS JWK Set URL - * @see JWE JWK Set URL - */ - T jwkSetUrl(URI uri); - - /** - * Sets the JWT case-sensitive {@code kid} (Key ID) header value. A {@code null} value will remove the property - * from the JSON map. - * - *

The keyId header parameter is a hint indicating which key was used to secure a JWS or JWE. This parameter - * allows originators to explicitly signal a change of key to recipients. The structure of the keyId value is - * unspecified. Its value MUST be a case-sensitive string.

- * - *

When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.

- * - * @param kid the case-sensitive JWS {@code kid} header value or {@code null} to remove the property from the JSON map. - * @return the header instance for method chaining. - * @see JWS Key ID - * @see JWE Key ID - */ - T keyId(String kid); - - /** - * Deprecated since 0.12.0, delegates to {@link #keyId(String)}. - * - * @param kid the case-sensitive JWS {@code kid} header value or {@code null} to remove the property from the JSON map. - * @return the instance for method chaining. - * @see JWS Key ID - * @see JWE Key ID - * @deprecated since 0.12.0 in favor of the more modern builder-style {@link #keyId(String)} method. - */ - @Deprecated - T setKeyId(String kid); - - /** - * Deprecated as of 0.12.0, there is no need to set this any longer as the {@code JwtBuilder} will - * always set the {@code alg} header as necessary. - * - * @param alg the JWS or JWE algorithm {@code alg} value or {@code null} to remove the property from the JSON map. - * @return the instance for method chaining. - * @since 0.1 - * @deprecated since 0.12.0 and will be removed before the 1.0 release. - */ - @Deprecated - T setAlgorithm(String alg); -} diff --git a/io/jsonwebtoken/ProtectedJwt.java b/io/jsonwebtoken/ProtectedJwt.java deleted file mode 100644 index 1531a13..0000000 --- a/io/jsonwebtoken/ProtectedJwt.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.security.DigestSupplier; - -/** - * A {@code ProtectedJwt} is a {@link Jwt} that is integrity protected via a cryptographic algorithm that produces - * a cryptographic digest, such as a MAC, Digital Signature or Authentication Tag. - * - *

Cryptographic Digest

- *

This interface extends DigestSupplier to make available the {@code ProtectedJwt}'s associated cryptographic - * digest:

- *
    - *
  • If the JWT is a {@link Jws}, {@link #getDigest() getDigest() } returns the JWS signature.
  • - *
  • If the JWT is a {@link Jwe}, {@link #getDigest() getDigest() } returns the AAD Authentication Tag.
  • - *
- * - * @param the type of the JWT protected header - * @param

the type of the JWT payload, either a content byte array or a {@link Claims} instance. - * @since 0.12.0 - */ -public interface ProtectedJwt extends Jwt, DigestSupplier { -} diff --git a/io/jsonwebtoken/RequiredTypeException.java b/io/jsonwebtoken/RequiredTypeException.java deleted file mode 100644 index 77a0035..0000000 --- a/io/jsonwebtoken/RequiredTypeException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * Exception thrown when attempting to obtain a value from a JWT or JWK and the existing value does not match the - * expected type. - * - * @since 0.6 - */ -public class RequiredTypeException extends JwtException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public RequiredTypeException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public RequiredTypeException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/SignatureAlgorithm.java b/io/jsonwebtoken/SignatureAlgorithm.java deleted file mode 100644 index ee25883..0000000 --- a/io/jsonwebtoken/SignatureAlgorithm.java +++ /dev/null @@ -1,656 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.security.InvalidKeyException; -import io.jsonwebtoken.security.Keys; -import io.jsonwebtoken.security.SignatureException; -import io.jsonwebtoken.security.WeakKeyException; - -import javax.crypto.SecretKey; -import java.security.Key; -import java.security.PrivateKey; -import java.security.interfaces.ECKey; -import java.security.interfaces.RSAKey; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * Type-safe representation of standard JWT signature algorithm names as defined in the - * JSON Web Algorithms specification. - * - * @since 0.1 - * @deprecated since 0.12.0; use {@link Jwts.SIG} instead. - */ -@Deprecated -public enum SignatureAlgorithm { - - /** - * JWA name for {@code No digital signature or MAC performed} - */ - NONE("none", "No digital signature or MAC performed", "None", null, false, 0, 0), - - /** - * JWA algorithm name for {@code HMAC using SHA-256} - */ - HS256("HS256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true, 256, 256, "1.2.840.113549.2.9"), - - /** - * JWA algorithm name for {@code HMAC using SHA-384} - */ - HS384("HS384", "HMAC using SHA-384", "HMAC", "HmacSHA384", true, 384, 384, "1.2.840.113549.2.10"), - - /** - * JWA algorithm name for {@code HMAC using SHA-512} - */ - HS512("HS512", "HMAC using SHA-512", "HMAC", "HmacSHA512", true, 512, 512, "1.2.840.113549.2.11"), - - /** - * JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-256} - */ - RS256("RS256", "RSASSA-PKCS-v1_5 using SHA-256", "RSA", "SHA256withRSA", true, 256, 2048), - - /** - * JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-384} - */ - RS384("RS384", "RSASSA-PKCS-v1_5 using SHA-384", "RSA", "SHA384withRSA", true, 384, 2048), - - /** - * JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-512} - */ - RS512("RS512", "RSASSA-PKCS-v1_5 using SHA-512", "RSA", "SHA512withRSA", true, 512, 2048), - - /** - * JWA algorithm name for {@code ECDSA using P-256 and SHA-256} - */ - ES256("ES256", "ECDSA using P-256 and SHA-256", "ECDSA", "SHA256withECDSA", true, 256, 256), - - /** - * JWA algorithm name for {@code ECDSA using P-384 and SHA-384} - */ - ES384("ES384", "ECDSA using P-384 and SHA-384", "ECDSA", "SHA384withECDSA", true, 384, 384), - - /** - * JWA algorithm name for {@code ECDSA using P-521 and SHA-512} - */ - ES512("ES512", "ECDSA using P-521 and SHA-512", "ECDSA", "SHA512withECDSA", true, 512, 521), - - /** - * JWA algorithm name for {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256}. This algorithm requires - * Java 11 or later or a JCA provider like BouncyCastle to be in the runtime classpath. If on Java 10 or - * earlier, BouncyCastle will be used automatically if found in the runtime classpath. - */ - PS256("PS256", "RSASSA-PSS using SHA-256 and MGF1 with SHA-256", "RSA", "RSASSA-PSS", false, 256, 2048), - - /** - * JWA algorithm name for {@code RSASSA-PSS using SHA-384 and MGF1 with SHA-384}. This algorithm requires - * Java 11 or later or a JCA provider like BouncyCastle to be in the runtime classpath. If on Java 10 or - * earlier, BouncyCastle will be used automatically if found in the runtime classpath. - */ - PS384("PS384", "RSASSA-PSS using SHA-384 and MGF1 with SHA-384", "RSA", "RSASSA-PSS", false, 384, 2048), - - /** - * JWA algorithm name for {@code RSASSA-PSS using SHA-512 and MGF1 with SHA-512}. This algorithm requires - * Java 11 or later or a JCA provider like BouncyCastle to be in the runtime classpath. If on Java 10 or - * earlier, BouncyCastle will be used automatically if found in the runtime classpath. - */ - PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "RSA", "RSASSA-PSS", false, 512, 2048); - - //purposefully ordered higher to lower: - private static final List PREFERRED_HMAC_ALGS = Collections.unmodifiableList(Arrays.asList( - SignatureAlgorithm.HS512, SignatureAlgorithm.HS384, SignatureAlgorithm.HS256)); - //purposefully ordered higher to lower: - private static final List PREFERRED_EC_ALGS = Collections.unmodifiableList(Arrays.asList( - SignatureAlgorithm.ES512, SignatureAlgorithm.ES384, SignatureAlgorithm.ES256)); - - private final String value; - private final String description; - private final String familyName; - private final String jcaName; - private final boolean jdkStandard; - private final int digestLength; - private final int minKeyLength; - /** - * Algorithm name as given by {@link Key#getAlgorithm()} if the key was loaded from a pkcs12 Keystore. - * - * @deprecated This is just a workaround for https://bugs.openjdk.java.net/browse/JDK-8243551 - */ - @Deprecated - private final String pkcs12Name; - - SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard, - int digestLength, int minKeyLength) { - this(value, description, familyName, jcaName, jdkStandard, digestLength, minKeyLength, jcaName); - } - - SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard, - int digestLength, int minKeyLength, String pkcs12Name) { - this.value = value; - this.description = description; - this.familyName = familyName; - this.jcaName = jcaName; - this.jdkStandard = jdkStandard; - this.digestLength = digestLength; - this.minKeyLength = minKeyLength; - this.pkcs12Name = pkcs12Name; - } - - /** - * Returns the JWA algorithm name constant. - * - * @return the JWA algorithm name constant. - */ - public String getValue() { - return value; - } - - /** - * Returns the JWA algorithm description. - * - * @return the JWA algorithm description. - */ - public String getDescription() { - return description; - } - - - /** - * Returns the cryptographic family name of the signature algorithm. The value returned is according to the - * following table: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Crypto Family
SignatureAlgorithmFamily Name
HS256HMAC
HS384HMAC
HS512HMAC
RS256RSA
RS384RSA
RS512RSA
PS256RSA
PS384RSA
PS512RSA
ES256ECDSA
ES384ECDSA
ES512ECDSA
- * - * @return Returns the cryptographic family name of the signature algorithm. - * @since 0.5 - */ - public String getFamilyName() { - return familyName; - } - - /** - * Returns the name of the JCA algorithm used to compute the signature. - * - * @return the name of the JCA algorithm used to compute the signature. - */ - public String getJcaName() { - return jcaName; - } - - /** - * Returns {@code true} if the algorithm is supported by standard JDK distributions or {@code false} if the - * algorithm implementation is not in the JDK and must be provided by a separate runtime JCA Provider (like - * BouncyCastle for example). - * - * @return {@code true} if the algorithm is supported by standard JDK distributions or {@code false} if the - * algorithm implementation is not in the JDK and must be provided by a separate runtime JCA Provider (like - * BouncyCastle for example). - */ - public boolean isJdkStandard() { - return jdkStandard; - } - - /** - * Returns {@code true} if the enum instance represents an HMAC signature algorithm, {@code false} otherwise. - * - * @return {@code true} if the enum instance represents an HMAC signature algorithm, {@code false} otherwise. - */ - public boolean isHmac() { - return familyName.equals("HMAC"); - } - - /** - * Returns {@code true} if the enum instance represents an RSA public/private key pair signature algorithm, - * {@code false} otherwise. - * - * @return {@code true} if the enum instance represents an RSA public/private key pair signature algorithm, - * {@code false} otherwise. - */ - public boolean isRsa() { - return familyName.equals("RSA"); - } - - /** - * Returns {@code true} if the enum instance represents an Elliptic Curve ECDSA signature algorithm, {@code false} - * otherwise. - * - * @return {@code true} if the enum instance represents an Elliptic Curve ECDSA signature algorithm, {@code false} - * otherwise. - */ - public boolean isEllipticCurve() { - return familyName.equals("ECDSA"); - } - - /** - * Returns the minimum key length in bits (not bytes) that may be used with this algorithm according to the - * JWT JWA Specification (RFC 7518). - * - * @return the minimum key length in bits (not bytes) that may be used with this algorithm according to the - * JWT JWA Specification (RFC 7518). - * @since 0.10.0 - */ - public int getMinKeyLength() { - return this.minKeyLength; - } - - /** - * Returns quietly if the specified key is allowed to create signatures using this algorithm - * according to the JWT JWA Specification (RFC 7518) or throws an - * {@link InvalidKeyException} if the key is not allowed or not secure enough for this algorithm. - * - * @param key the key to check for validity. - * @throws InvalidKeyException if the key is not allowed or not secure enough for this algorithm. - * @since 0.10.0 - */ - public void assertValidSigningKey(Key key) throws InvalidKeyException { - assertValid(key, true); - } - - /** - * Returns quietly if the specified key is allowed to verify signatures using this algorithm - * according to the JWT JWA Specification (RFC 7518) or throws an - * {@link InvalidKeyException} if the key is not allowed or not secure enough for this algorithm. - * - * @param key the key to check for validity. - * @throws InvalidKeyException if the key is not allowed or not secure enough for this algorithm. - * @since 0.10.0 - */ - public void assertValidVerificationKey(Key key) throws InvalidKeyException { - assertValid(key, false); - } - - /** - * @since 0.10.0 to support assertValid(Key, boolean) - */ - private static String keyType(boolean signing) { - return signing ? "signing" : "verification"; - } - - /** - * @since 0.10.0 - */ - private void assertValid(Key key, boolean signing) throws InvalidKeyException { - - if (this == NONE) { - - String msg = "The 'NONE' signature algorithm does not support cryptographic keys."; - throw new InvalidKeyException(msg); - - } else if (isHmac()) { - - if (!(key instanceof SecretKey)) { - String msg = this.familyName + " " + keyType(signing) + " keys must be SecretKey instances."; - throw new InvalidKeyException(msg); - } - SecretKey secretKey = (SecretKey) key; - - byte[] encoded = secretKey.getEncoded(); - if (encoded == null) { - throw new InvalidKeyException("The " + keyType(signing) + " key's encoded bytes cannot be null."); - } - - String alg = secretKey.getAlgorithm(); - if (alg == null) { - throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm cannot be null."); - } - - // These next checks use equalsIgnoreCase per https://github.com/jwtk/jjwt/issues/381#issuecomment-412912272 - if (!HS256.jcaName.equalsIgnoreCase(alg) && - !HS384.jcaName.equalsIgnoreCase(alg) && - !HS512.jcaName.equalsIgnoreCase(alg) && - !HS256.pkcs12Name.equals(alg) && - !HS384.pkcs12Name.equals(alg) && - !HS512.pkcs12Name.equals(alg)) { - throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm '" + alg + - "' does not equal a valid HmacSHA* algorithm name and cannot be used with " + name() + "."); - } - - int size = encoded.length * 8; //size in bits - if (size < this.minKeyLength) { - String msg = "The " + keyType(signing) + " key's size is " + size + " bits which " + - "is not secure enough for the " + name() + " algorithm. The JWT " + - "JWA Specification (RFC 7518, Section 3.2) states that keys used with " + name() + " MUST have a " + - "size >= " + minKeyLength + " bits (the key size must be greater than or equal to the hash " + - "output size). Consider using the " + Keys.class.getName() + " class's " + - "'secretKeyFor(SignatureAlgorithm." + name() + ")' method to create a key guaranteed to be " + - "secure enough for " + name() + ". See " + - "https://tools.ietf.org/html/rfc7518#section-3.2 for more information."; - throw new WeakKeyException(msg); - } - - } else { //EC or RSA - - if (signing) { - if (!(key instanceof PrivateKey)) { - String msg = familyName + " signing keys must be PrivateKey instances."; - throw new InvalidKeyException(msg); - } - } - - if (isEllipticCurve()) { - - if (!(key instanceof ECKey)) { - String msg = familyName + " " + keyType(signing) + " keys must be ECKey instances."; - throw new InvalidKeyException(msg); - } - - ECKey ecKey = (ECKey) key; - int size = ecKey.getParams().getOrder().bitLength(); - if (size < this.minKeyLength) { - String msg = "The " + keyType(signing) + " key's size (ECParameterSpec order) is " + size + - " bits which is not secure enough for the " + name() + " algorithm. The JWT " + - "JWA Specification (RFC 7518, Section 3.4) states that keys used with " + - name() + " MUST have a size >= " + this.minKeyLength + - " bits. Consider using the " + Keys.class.getName() + " class's " + - "'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " + - "to be secure enough for " + name() + ". See " + - "https://tools.ietf.org/html/rfc7518#section-3.4 for more information."; - throw new WeakKeyException(msg); - } - - } else { //RSA - - if (!(key instanceof RSAKey)) { - String msg = familyName + " " + keyType(signing) + " keys must be RSAKey instances."; - throw new InvalidKeyException(msg); - } - - RSAKey rsaKey = (RSAKey) key; - int size = rsaKey.getModulus().bitLength(); - if (size < this.minKeyLength) { - - String section = name().startsWith("P") ? "3.5" : "3.3"; - - String msg = "The " + keyType(signing) + " key's size is " + size + " bits which is not secure " + - "enough for the " + name() + " algorithm. The JWT JWA Specification (RFC 7518, Section " + - section + ") states that keys used with " + name() + " MUST have a size >= " + - this.minKeyLength + " bits. Consider using the " + Keys.class.getName() + " class's " + - "'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " + - "to be secure enough for " + name() + ". See " + - "https://tools.ietf.org/html/rfc7518#section-" + section + " for more information."; - throw new WeakKeyException(msg); - } - } - } - } - - /** - * Returns the recommended signature algorithm to be used with the specified key according to the following - * heuristics: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Key Signature Algorithm
If the Key is a:And:With a key size of:The returned SignatureAlgorithm will be:
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA256")1256 <= size <= 383 2{@link SignatureAlgorithm#HS256 HS256}
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA384")1384 <= size <= 511{@link SignatureAlgorithm#HS384 HS384}
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA512")1512 <= size{@link SignatureAlgorithm#HS512 HS512}
{@link ECKey}instanceof {@link PrivateKey}256 <= size <= 383 3{@link SignatureAlgorithm#ES256 ES256}
{@link ECKey}instanceof {@link PrivateKey}384 <= size <= 511{@link SignatureAlgorithm#ES384 ES384}
{@link ECKey}instanceof {@link PrivateKey}4096 <= size{@link SignatureAlgorithm#ES512 ES512}
{@link RSAKey}instanceof {@link PrivateKey}2048 <= size <= 3071 4,5{@link SignatureAlgorithm#RS256 RS256}
{@link RSAKey}instanceof {@link PrivateKey}3072 <= size <= 4095 5{@link SignatureAlgorithm#RS384 RS384}
{@link RSAKey}instanceof {@link PrivateKey}4096 <= size 5{@link SignatureAlgorithm#RS512 RS512}
- *

Notes:

- *
    - *
  1. {@code SecretKey} instances must have an {@link Key#getAlgorithm() algorithm} name equal - * to {@code HmacSHA256}, {@code HmacSHA384} or {@code HmacSHA512}. If not, the key bytes might not be - * suitable for HMAC signatures will be rejected with a {@link InvalidKeyException}.
  2. - *
  3. The JWT JWA Specification (RFC 7518, - * Section 3.2) mandates that HMAC-SHA-* signing keys MUST be 256 bits or greater. - * {@code SecretKey}s with key lengths less than 256 bits will be rejected with an - * {@link WeakKeyException}.
  4. - *
  5. The JWT JWA Specification (RFC 7518, - * Section 3.4) mandates that ECDSA signing key lengths MUST be 256 bits or greater. - * {@code ECKey}s with key lengths less than 256 bits will be rejected with a - * {@link WeakKeyException}.
  6. - *
  7. The JWT JWA Specification (RFC 7518, - * Section 3.3) mandates that RSA signing key lengths MUST be 2048 bits or greater. - * {@code RSAKey}s with key lengths less than 2048 bits will be rejected with a - * {@link WeakKeyException}.
  8. - *
  9. Technically any RSA key of length >= 2048 bits may be used with the {@link #RS256}, {@link #RS384}, and - * {@link #RS512} algorithms, so we assume an RSA signature algorithm based on the key length to - * parallel similar decisions in the JWT specification for HMAC and ECDSA signature algorithms. - * This is not required - just a convenience.
  10. - *
- *

This implementation does not return the {@link #PS256}, {@link #PS256}, {@link #PS256} RSA variant for any - * specified {@link RSAKey} because: - *

    - *
  • The JWT JWA Specification (RFC 7518, - * Section 3.1) indicates that {@link #RS256}, {@link #RS384}, and {@link #RS512} are - * recommended algorithms while the {@code PS}* variants are simply marked as optional.
  • - *
  • The {@link #RS256}, {@link #RS384}, and {@link #RS512} algorithms are available in the JDK by default - * while the {@code PS}* variants require an additional JCA Provider (like BouncyCastle).
  • - *
- * - *

Finally, this method will throw an {@link InvalidKeyException} for any key that does not match the - * heuristics and requirements documented above, since that inevitably means the Key is either insufficient or - * explicitly disallowed by the JWT specification.

- * - * @param key the key to inspect - * @return the recommended signature algorithm to be used with the specified key - * @throws InvalidKeyException for any key that does not match the heuristics and requirements documented above, - * since that inevitably means the Key is either insufficient or explicitly disallowed by the JWT specification. - * @since 0.10.0 - */ - public static SignatureAlgorithm forSigningKey(Key key) throws InvalidKeyException { - - if (key == null) { - throw new InvalidKeyException("Key argument cannot be null."); - } - - if (!(key instanceof SecretKey || - (key instanceof PrivateKey && (key instanceof ECKey || key instanceof RSAKey)))) { - String msg = "JWT standard signing algorithms require either 1) a SecretKey for HMAC-SHA algorithms or " + - "2) a private RSAKey for RSA algorithms or 3) a private ECKey for Elliptic Curve algorithms. " + - "The specified key is of type " + key.getClass().getName(); - throw new InvalidKeyException(msg); - } - - if (key instanceof SecretKey) { - - SecretKey secretKey = (SecretKey) key; - int bitLength = io.jsonwebtoken.lang.Arrays.length(secretKey.getEncoded()) * Byte.SIZE; - - for (SignatureAlgorithm alg : PREFERRED_HMAC_ALGS) { - // ensure compatibility check is based on key length. See https://github.com/jwtk/jjwt/issues/381 - if (bitLength >= alg.minKeyLength) { - return alg; - } - } - - String msg = "The specified SecretKey is not strong enough to be used with JWT HMAC signature " + - "algorithms. The JWT specification requires HMAC keys to be >= 256 bits long. The specified " + - "key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.2 for more " + - "information."; - throw new WeakKeyException(msg); - } - - if (key instanceof RSAKey) { - - RSAKey rsaKey = (RSAKey) key; - int bitLength = rsaKey.getModulus().bitLength(); - - if (bitLength >= 4096) { - RS512.assertValidSigningKey(key); - return RS512; - } else if (bitLength >= 3072) { - RS384.assertValidSigningKey(key); - return RS384; - } else if (bitLength >= RS256.minKeyLength) { - RS256.assertValidSigningKey(key); - return RS256; - } - - String msg = "The specified RSA signing key is not strong enough to be used with JWT RSA signature " + - "algorithms. The JWT specification requires RSA keys to be >= 2048 bits long. The specified RSA " + - "key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.3 for more " + - "information."; - throw new WeakKeyException(msg); - } - - // if we've made it this far in the method, the key is an ECKey due to the instanceof assertions at the - // top of the method - - ECKey ecKey = (ECKey) key; - int bitLength = ecKey.getParams().getOrder().bitLength(); - - for (SignatureAlgorithm alg : PREFERRED_EC_ALGS) { - if (bitLength >= alg.minKeyLength) { - alg.assertValidSigningKey(key); - return alg; - } - } - - String msg = "The specified Elliptic Curve signing key is not strong enough to be used with JWT ECDSA " + - "signature algorithms. The JWT specification requires ECDSA keys to be >= 256 bits long. " + - "The specified ECDSA key is " + bitLength + " bits. See " + - "https://tools.ietf.org/html/rfc7518#section-3.4 for more information."; - throw new WeakKeyException(msg); - } - - /** - * Looks up and returns the corresponding {@code SignatureAlgorithm} enum instance based on a - * case-insensitive name comparison. - * - * @param value The case-insensitive name of the {@code SignatureAlgorithm} instance to return - * @return the corresponding {@code SignatureAlgorithm} enum instance based on a - * case-insensitive name comparison. - * @throws SignatureException if the specified value does not match any {@code SignatureAlgorithm} - * name. - */ - public static SignatureAlgorithm forName(String value) throws SignatureException { - for (SignatureAlgorithm alg : values()) { - if (alg.getValue().equalsIgnoreCase(value)) { - return alg; - } - } - - throw new SignatureException("Unsupported signature algorithm '" + value + "'"); - } -} diff --git a/io/jsonwebtoken/SignatureException.java b/io/jsonwebtoken/SignatureException.java deleted file mode 100644 index 7a54cda..0000000 --- a/io/jsonwebtoken/SignatureException.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.security.SecurityException; - -/** - * Exception indicating that either calculating a signature or verifying an existing signature of a JWT failed. - * - * @since 0.1 - * @deprecated in favor of {@link io.jsonwebtoken.security.SignatureException}; this class will be removed before 1.0 - */ -@Deprecated -public class SignatureException extends SecurityException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public SignatureException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public SignatureException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/SigningKeyResolver.java b/io/jsonwebtoken/SigningKeyResolver.java deleted file mode 100644 index 82b9edc..0000000 --- a/io/jsonwebtoken/SigningKeyResolver.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import java.security.Key; - -/** - * A {@code SigningKeyResolver} can be used by a {@link io.jsonwebtoken.JwtParser JwtParser} to find a signing key that - * should be used to verify a JWS signature. - * - *

A {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing the JWT and the - * JWT header or payload (byte array or Claims) must be inspected first to determine how to look up the signing key. - * Once returned by the resolver, the JwtParser will then verify the JWS signature with the returned key. For - * example:

- * - *
- * Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
- *         @Override
- *         public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
- *             //inspect the header or claims, lookup and return the signing key
- *             return getSigningKeyBytes(header, claims); //implement me
- *         }})
- *     .build().parseSignedClaims(compact);
- * 
- * - *

A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.

- * - *

Using an Adapter

- * - *

If you only need to resolve a signing key for a particular JWS (either a content or Claims JWS), consider using - * the {@link io.jsonwebtoken.SigningKeyResolverAdapter} and overriding only the method you need to support instead of - * implementing this interface directly.

- * - * @see io.jsonwebtoken.JwtParserBuilder#keyLocator(Locator) - * @since 0.4 - * @deprecated since 0.12.0. Implement {@link Locator} instead. - */ -@Deprecated -public interface SigningKeyResolver { - - /** - * Returns the signing key that should be used to validate a digital signature for the Claims JWS with the specified - * header and claims. - * - * @param header the header of the JWS to validate - * @param claims the Claims payload of the JWS to validate - * @return the signing key that should be used to validate a digital signature for the Claims JWS with the specified - * header and claims. - */ - Key resolveSigningKey(JwsHeader header, Claims claims); - - /** - * Returns the signing key that should be used to validate a digital signature for the content JWS with the - * specified header and byte array payload. - * - * @param header the header of the JWS to validate - * @param content the byte array payload of the JWS to validate - * @return the signing key that should be used to validate a digital signature for the content JWS with the - * specified header and byte array payload. - */ - Key resolveSigningKey(JwsHeader header, byte[] content); -} diff --git a/io/jsonwebtoken/SigningKeyResolverAdapter.java b/io/jsonwebtoken/SigningKeyResolverAdapter.java deleted file mode 100644 index 6e90ca1..0000000 --- a/io/jsonwebtoken/SigningKeyResolverAdapter.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.lang.Assert; - -import javax.crypto.spec.SecretKeySpec; -import java.security.Key; - -/** - *

Deprecation Notice

- * - *

As of JJWT 0.12.0, various Resolver concepts (including the {@code SigningKeyResolver}) have been - * unified into a single {@link Locator} interface. For key location, (for both signing and encryption keys), - * use the {@link JwtParserBuilder#keyLocator(Locator)} to configure a parser with your desired Key locator instead - * of using a {@code SigningKeyResolver}. Also see {@link LocatorAdapter} for the Adapter pattern parallel of this - * class. This {@code SigningKeyResolverAdapter} class will be removed before the 1.0 release.

- * - *

Previous Documentation

- * - *

An Adapter implementation of the - * {@link SigningKeyResolver} interface that allows subclasses to process only the type of JWS body that - * is known/expected for a particular case.

- * - *

The {@link #resolveSigningKey(JwsHeader, Claims)} and {@link #resolveSigningKey(JwsHeader, byte[])} method - * implementations delegate to the - * {@link #resolveSigningKeyBytes(JwsHeader, Claims)} and {@link #resolveSigningKeyBytes(JwsHeader, byte[])} methods - * respectively. The latter two methods simply throw exceptions: they represent scenarios expected by - * calling code in known situations, and it is expected that you override the implementation in those known situations; - * non-overridden *KeyBytes methods indicates that the JWS input was unexpected.

- * - *

If either {@link #resolveSigningKey(JwsHeader, byte[])} or {@link #resolveSigningKey(JwsHeader, Claims)} - * are not overridden, one (or both) of the *KeyBytes variants must be overridden depending on your expected - * use case. You do not have to override any method that does not represent an expected condition.

- * - * @see io.jsonwebtoken.JwtParserBuilder#keyLocator(Locator) - * @see LocatorAdapter - * @since 0.4 - * @deprecated since 0.12.0. Use {@link LocatorAdapter LocatorAdapter} with - * {@link JwtParserBuilder#keyLocator(Locator)} - */ -@SuppressWarnings("DeprecatedIsStillUsed") -@Deprecated -public class SigningKeyResolverAdapter implements SigningKeyResolver { - - /** - * Default constructor. - */ - public SigningKeyResolverAdapter() { - - } - - @Override - public Key resolveSigningKey(JwsHeader header, Claims claims) { - SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm()); - Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, Claims) implementation cannot " + - "be used for asymmetric key algorithms (RSA, Elliptic Curve). " + - "Override the resolveSigningKey(JwsHeader, Claims) method instead and return a " + - "Key instance appropriate for the " + alg.name() + " algorithm."); - byte[] keyBytes = resolveSigningKeyBytes(header, claims); - return new SecretKeySpec(keyBytes, alg.getJcaName()); - } - - @Override - public Key resolveSigningKey(JwsHeader header, byte[] content) { - SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm()); - Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, byte[]) implementation cannot " + - "be used for asymmetric key algorithms (RSA, Elliptic Curve). " + - "Override the resolveSigningKey(JwsHeader, byte[]) method instead and return a " + - "Key instance appropriate for the " + alg.name() + " algorithm."); - byte[] keyBytes = resolveSigningKeyBytes(header, content); - return new SecretKeySpec(keyBytes, alg.getJcaName()); - } - - /** - * Convenience method invoked by {@link #resolveSigningKey(JwsHeader, Claims)} that obtains the necessary signing - * key bytes. This implementation simply throws an exception: if the JWS parsed is a Claims JWS, you must - * override this method or the {@link #resolveSigningKey(JwsHeader, Claims)} method instead. - * - *

NOTE: You cannot override this method when validating RSA signatures. If you expect RSA signatures, - * you must override the {@link #resolveSigningKey(JwsHeader, Claims)} method instead.

- * - * @param header the parsed {@link JwsHeader} - * @param claims the parsed {@link Claims} - * @return the signing key bytes to use to verify the JWS signature. - */ - public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { - throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " + - "Claims JWS signing key resolution. Consider overriding either the " + - "resolveSigningKey(JwsHeader, Claims) method or, for HMAC algorithms, the " + - "resolveSigningKeyBytes(JwsHeader, Claims) method."); - } - - /** - * Convenience method invoked by {@link #resolveSigningKey(JwsHeader, byte[])} that obtains the necessary signing - * key bytes. This implementation simply throws an exception: if the JWS parsed is a content JWS, you must - * override this method or the {@link #resolveSigningKey(JwsHeader, byte[])} method instead. - * - * @param header the parsed {@link JwsHeader} - * @param content the byte array payload - * @return the signing key bytes to use to verify the JWS signature. - */ - @SuppressWarnings("unused") - public byte[] resolveSigningKeyBytes(JwsHeader header, byte[] content) { - throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " + - "content JWS signing key resolution. Consider overriding either the " + - "resolveSigningKey(JwsHeader, byte[]) method or, for HMAC algorithms, the " + - "resolveSigningKeyBytes(JwsHeader, byte[]) method."); - } -} diff --git a/io/jsonwebtoken/SupportedJwtVisitor.java b/io/jsonwebtoken/SupportedJwtVisitor.java deleted file mode 100644 index 61cdd78..0000000 --- a/io/jsonwebtoken/SupportedJwtVisitor.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -import io.jsonwebtoken.lang.Assert; - -/** - * A {@code JwtVisitor} that guarantees only supported JWT instances are handled, rejecting - * all other (unsupported) JWTs with {@link UnsupportedJwtException}s. A JWT is considered supported - * only if the type-specific handler method is overridden by a subclass. - * - * @param the type of value returned from the subclass handler method implementation. - * @since 0.12.0 - */ -public class SupportedJwtVisitor implements JwtVisitor { - - /** - * Default constructor, does not initialize any internal state. - */ - public SupportedJwtVisitor() { - } - - /** - * Handles an encountered unsecured JWT by delegating to either {@link #onUnsecuredContent(Jwt)} or - * {@link #onUnsecuredClaims(Jwt)} depending on the payload type. - * - * @param jwt the parsed unsecured JWT - * @return the value returned by either {@link #onUnsecuredContent(Jwt)} or {@link #onUnsecuredClaims(Jwt)} - * depending on the payload type. - * @throws UnsupportedJwtException if the payload is neither a {@code byte[]} nor {@code Claims}, or either - * delegate method throws the same. - */ - @SuppressWarnings("unchecked") - @Override - public T visit(Jwt jwt) { - Assert.notNull(jwt, "JWT cannot be null."); - Object payload = jwt.getPayload(); - if (payload instanceof byte[]) { - return onUnsecuredContent((Jwt) jwt); - } else { - // only other type we support: - Assert.stateIsInstance(Claims.class, payload, "Unexpected payload data type: "); - return onUnsecuredClaims((Jwt) jwt); - } - } - - /** - * Handles an encountered unsecured content JWT - one that is not cryptographically signed nor - * encrypted, and has a byte[] array payload. If the JWT creator has set the (optional) - * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert - * the byte array to the final type as desired. - * - *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that - * subclasses will override this method if the application needs to support this type of JWT.

- * - * @param jwt the parsed unsecured content JWT - * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. - * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. - */ - public T onUnsecuredContent(Jwt jwt) throws UnsupportedJwtException { - throw new UnsupportedJwtException("Unexpected unsecured content JWT."); - } - - /** - * Handles an encountered unsecured Claims JWT - one that is not cryptographically signed nor - * encrypted, and has a {@link Claims} payload. - * - *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that - * subclasses will override this method if the application needs to support this type of JWT.

- * - * @param jwt the parsed unsecured content JWT - * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. - * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. - */ - public T onUnsecuredClaims(Jwt jwt) { - throw new UnsupportedJwtException("Unexpected unsecured Claims JWT."); - } - - /** - * Handles an encountered JSON Web Token (aka 'JWS') message that has been cryptographically verified/authenticated - * by delegating to either {@link #onVerifiedContent(Jws)} or {@link #onVerifiedClaims(Jws)} depending on the payload - * type. - * - * @param jws the parsed verified/authenticated JWS. - * @return the value returned by either {@link #onVerifiedContent(Jws)} or {@link #onVerifiedClaims(Jws)} - * depending on the payload type. - * @throws UnsupportedJwtException if the payload is neither a {@code byte[]} nor {@code Claims}, or either - * delegate method throws the same. - */ - @SuppressWarnings("unchecked") - @Override - public T visit(Jws jws) { - Assert.notNull(jws, "JWS cannot be null."); - Object payload = jws.getPayload(); - if (payload instanceof byte[]) { - return onVerifiedContent((Jws) jws); - } else { - Assert.stateIsInstance(Claims.class, payload, "Unexpected payload data type: "); - return onVerifiedClaims((Jws) jws); - } - } - - /** - * Handles an encountered JWS message that has been cryptographically verified/authenticated and has - * a byte[] array payload. If the JWT creator has set the (optional) {@link Header#getContentType()} value, the - * application may inspect that value to determine how to convert the byte array to the final type as desired. - * - *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that - * subclasses will override this method if the application needs to support this type of JWT.

- * - * @param jws the parsed verified/authenticated JWS. - * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. - * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. - */ - public T onVerifiedContent(Jws jws) { - throw new UnsupportedJwtException("Unexpected content JWS."); - } - - /** - * Handles an encountered JWS message that has been cryptographically verified/authenticated and has a - * {@link Claims} payload. - * - *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that - * subclasses will override this method if the application needs to support this type of JWT.

- * - * @param jws the parsed signed (and verified) Claims JWS - * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. - * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. - */ - public T onVerifiedClaims(Jws jws) { - throw new UnsupportedJwtException("Unexpected Claims JWS."); - } - - /** - * Handles an encountered JSON Web Encryption (aka 'JWE') message that has been authenticated and decrypted by - * delegating to either {@link #onDecryptedContent(Jwe)} or {@link #onDecryptedClaims(Jwe)} depending on the - * payload type. - * - * @param jwe the parsed authenticated and decrypted JWE. - * @return the value returned by either {@link #onDecryptedContent(Jwe)} or {@link #onDecryptedClaims(Jwe)} - * depending on the payload type. - * @throws UnsupportedJwtException if the payload is neither a {@code byte[]} nor {@code Claims}, or either - * delegate method throws the same. - */ - @SuppressWarnings("unchecked") - @Override - public T visit(Jwe jwe) { - Assert.notNull(jwe, "JWE cannot be null."); - Object payload = jwe.getPayload(); - if (payload instanceof byte[]) { - return onDecryptedContent((Jwe) jwe); - } else { - Assert.stateIsInstance(Claims.class, payload, "Unexpected payload data type: "); - return onDecryptedClaims((Jwe) jwe); - } - } - - /** - * Handles an encountered JWE message that has been authenticated and decrypted, and has byte[] array payload. If - * the JWT creator has set the (optional) {@link Header#getContentType()} value, the application may inspect that - * value to determine how to convert the byte array to the final type as desired. - * - *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that - * subclasses will override this method if the application needs to support this type of JWT.

- * - * @param jwe the parsed authenticated and decrypted content JWE. - * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. - * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. - */ - public T onDecryptedContent(Jwe jwe) { - throw new UnsupportedJwtException("Unexpected content JWE."); - } - - /** - * Handles an encountered JWE message that has been authenticated and decrypted, and has a {@link Claims} payload. - * - *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that - * subclasses will override this method if the application needs to support this type of JWT.

- * - * @param jwe the parsed authenticated and decrypted content JWE. - * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. - * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. - */ - public T onDecryptedClaims(Jwe jwe) { - throw new UnsupportedJwtException("Unexpected Claims JWE."); - } -} diff --git a/io/jsonwebtoken/UnsupportedJwtException.java b/io/jsonwebtoken/UnsupportedJwtException.java deleted file mode 100644 index a1ec968..0000000 --- a/io/jsonwebtoken/UnsupportedJwtException.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken; - -/** - * Exception thrown when receiving a JWT in a particular format/configuration that does not match the format expected - * by the application. - * - *

For example, this exception would be thrown if parsing an unprotected content JWT when the application - * requires a cryptographically signed Claims JWS instead.

- * - * @since 0.2 - */ -public class UnsupportedJwtException extends JwtException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public UnsupportedJwtException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public UnsupportedJwtException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/io/AbstractDeserializer.java b/io/jsonwebtoken/io/AbstractDeserializer.java deleted file mode 100644 index 0d29faf..0000000 --- a/io/jsonwebtoken/io/AbstractDeserializer.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -import io.jsonwebtoken.lang.Assert; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.nio.charset.StandardCharsets; - -/** - * Convenient base class to use to implement {@link Deserializer}s, with subclasses only needing to implement - * {@link #doDeserialize(Reader)}. - * - * @param the type of object returned after deserialization - * @since 0.12.0 - */ -public abstract class AbstractDeserializer implements Deserializer { - - /** - * EOF (End of File) marker, equal to {@code -1}. - */ - protected static final int EOF = -1; - - private static final byte[] EMPTY_BYTES = new byte[0]; - - /** - * Default constructor, does not initialize any internal state. - */ - protected AbstractDeserializer() { - } - - /** - * {@inheritDoc} - */ - @Override - public final T deserialize(byte[] bytes) throws DeserializationException { - bytes = bytes == null ? EMPTY_BYTES : bytes; // null safe - InputStream in = new ByteArrayInputStream(bytes); - Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8); - return deserialize(reader); - } - - /** - * {@inheritDoc} - */ - @Override - public final T deserialize(Reader reader) throws DeserializationException { - Assert.notNull(reader, "Reader argument cannot be null."); - try { - return doDeserialize(reader); - } catch (Throwable t) { - if (t instanceof DeserializationException) { - throw (DeserializationException) t; - } - String msg = "Unable to deserialize: " + t.getMessage(); - throw new DeserializationException(msg, t); - } - } - - /** - * Reads the specified character stream and returns the corresponding Java object. - * - * @param reader the reader to use to read the character stream - * @return the deserialized Java object - * @throws Exception if there is a problem reading the stream or creating the expected Java object - */ - protected abstract T doDeserialize(Reader reader) throws Exception; -} diff --git a/io/jsonwebtoken/io/AbstractSerializer.java b/io/jsonwebtoken/io/AbstractSerializer.java deleted file mode 100644 index 5c74b50..0000000 --- a/io/jsonwebtoken/io/AbstractSerializer.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -import io.jsonwebtoken.lang.Objects; - -import java.io.ByteArrayOutputStream; -import java.io.OutputStream; - -/** - * Convenient base class to use to implement {@link Serializer}s, with subclasses only needing to implement - * * {@link #doSerialize(Object, OutputStream)}. - * - * @param the type of object to serialize - * @since 0.12.0 - */ -public abstract class AbstractSerializer implements Serializer { - - /** - * Default constructor, does not initialize any internal state. - */ - protected AbstractSerializer() { - } - - /** - * {@inheritDoc} - */ - @Override - public final byte[] serialize(T t) throws SerializationException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - serialize(t, out); - return out.toByteArray(); - } - - /** - * {@inheritDoc} - */ - @Override - public final void serialize(T t, OutputStream out) throws SerializationException { - try { - doSerialize(t, out); - } catch (Throwable e) { - if (e instanceof SerializationException) { - throw (SerializationException) e; - } - String msg = "Unable to serialize object of type " + Objects.nullSafeClassName(t) + ": " + e.getMessage(); - throw new SerializationException(msg, e); - } - } - - /** - * Converts the specified Java object into a formatted data byte stream, writing the bytes to the specified - * {@code out}put stream. - * - * @param t the object to convert to a byte stream - * @param out the stream to write to - * @throws Exception if there is a problem converting the object to a byte stream or writing the - * bytes to the {@code out}put stream. - * @since 0.12.0 - */ - protected abstract void doSerialize(T t, OutputStream out) throws Exception; -} diff --git a/io/jsonwebtoken/io/Base64.java b/io/jsonwebtoken/io/Base64.java deleted file mode 100644 index 81e3817..0000000 --- a/io/jsonwebtoken/io/Base64.java +++ /dev/null @@ -1,681 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -import java.util.Arrays; - -/** - * A very fast and memory efficient class to encode and decode to and from BASE64 or BASE64URL in full accordance - * with RFC 4648. - * - *

Based initially on MigBase64 with continued modifications for Base64 URL support and JDK-standard code formatting.

- * - *

This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only - * allocates the resulting array. This produces less garbage and it is possible to handle arrays twice - * as large as algorithms that create a temporary array.

- * - *

There is also a "fast" version of all decode methods that works the same way as the normal ones, but - * has a few demands on the decoded input. Normally though, these fast versions should be used if the source if - * the input is known and it hasn't bee tampered with.

- * - * @author Mikael Grev - * @author Les Hazlewood - * @since 0.10.0 - */ -@SuppressWarnings("Duplicates") -final class Base64 { //final and package-protected on purpose - - private static final char[] BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); - private static final char[] BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".toCharArray(); - private static final int[] BASE64_IALPHABET = new int[256]; - private static final int[] BASE64URL_IALPHABET = new int[256]; - private static final int IALPHABET_MAX_INDEX = BASE64_IALPHABET.length - 1; - - static { - Arrays.fill(BASE64_IALPHABET, -1); - System.arraycopy(BASE64_IALPHABET, 0, BASE64URL_IALPHABET, 0, BASE64_IALPHABET.length); - for (int i = 0, iS = BASE64_ALPHABET.length; i < iS; i++) { - BASE64_IALPHABET[BASE64_ALPHABET[i]] = i; - BASE64URL_IALPHABET[BASE64URL_ALPHABET[i]] = i; - } - BASE64_IALPHABET['='] = 0; - BASE64URL_IALPHABET['='] = 0; - } - - static final Base64 DEFAULT = new Base64(false); - static final Base64 URL_SAFE = new Base64(true); - - private final boolean urlsafe; - private final char[] ALPHABET; - private final int[] IALPHABET; - - private Base64(boolean urlsafe) { - this.urlsafe = urlsafe; - this.ALPHABET = urlsafe ? BASE64URL_ALPHABET : BASE64_ALPHABET; - this.IALPHABET = urlsafe ? BASE64URL_IALPHABET : BASE64_IALPHABET; - } - - // **************************************************************************************** - // * char[] version - // **************************************************************************************** - - private String getName() { - return urlsafe ? "base64url" : "base64"; // RFC 4648 codec names are all lowercase - } - - /** - * Encodes a raw byte array into a BASE64 char[] representation in accordance with RFC 2045. - * - * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. - * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
- * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a - * little faster. - * @return A BASE64 encoded array. Never null. - */ - private char[] encodeToChar(byte[] sArr, boolean lineSep) { - - // Check special case - int sLen = sArr != null ? sArr.length : 0; - if (sLen == 0) { - return new char[0]; - } - - int eLen = (sLen / 3) * 3; // # of bytes that can encode evenly into 24-bit chunks - int left = sLen - eLen; // # of bytes that remain after 24-bit chunking. Always 0, 1 or 2 - - int cCnt = (((sLen - 1) / 3 + 1) << 2); // # of base64-encoded characters including padding - int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned char array with padding and any line separators - - int padCount = 0; - if (left == 2) { - padCount = 1; - } else if (left == 1) { - padCount = 2; - } - - char[] dArr = new char[urlsafe ? (dLen - padCount) : dLen]; - - // Encode even 24-bits - for (int s = 0, d = 0, cc = 0; s < eLen; ) { - - // Copy next three bytes into lower 24 bits of int, paying attention to sign. - int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); - - // Encode the int into four chars - dArr[d++] = ALPHABET[(i >>> 18) & 0x3f]; - dArr[d++] = ALPHABET[(i >>> 12) & 0x3f]; - dArr[d++] = ALPHABET[(i >>> 6) & 0x3f]; - dArr[d++] = ALPHABET[i & 0x3f]; - - // Add optional line separator - if (lineSep && ++cc == 19 && d < dLen - 2) { - dArr[d++] = '\r'; - dArr[d++] = '\n'; - cc = 0; - } - } - - // Pad and encode last bits if source isn't even 24 bits. - if (left > 0) { - // Prepare the int - int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0); - - // Set last four chars - dArr[dLen - 4] = ALPHABET[i >> 12]; - dArr[dLen - 3] = ALPHABET[(i >>> 6) & 0x3f]; - //dArr[dLen - 2] = left == 2 ? ALPHABET[i & 0x3f] : '='; - //dArr[dLen - 1] = '='; - if (left == 2) { - dArr[dLen - 2] = ALPHABET[i & 0x3f]; - } else if (!urlsafe) { // if not urlsafe, we need to include the padding characters - dArr[dLen - 2] = '='; - } - if (!urlsafe) { // include padding - dArr[dLen - 1] = '='; - } - } - return dArr; - } - - /* - * Decodes a BASE64 encoded char array. All illegal characters will be ignored and can handle both arrays with - * and without line separators. - * - * @param sArr The source array. null or length 0 will return an empty array. - * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters - * (including '=') isn't divideable by 4. (I.e. definitely corrupted). - * - public final byte[] decode(char[] sArr) { - // Check special case - int sLen = sArr != null ? sArr.length : 0; - if (sLen == 0) { - return new byte[0]; - } - - // Count illegal characters (including '\r', '\n') to know what size the returned array will be, - // so we don't have to reallocate & copy it later. - int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) - for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. - if (IALPHABET[sArr[i]] < 0) { - sepCnt++; - } - } - - // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. - if ((sLen - sepCnt) % 4 != 0) { - return null; - } - - int pad = 0; - for (int i = sLen; i > 1 && IALPHABET[sArr[--i]] <= 0; ) { - if (sArr[i] == '=') { - pad++; - } - } - - int len = ((sLen - sepCnt) * 6 >> 3) - pad; - - byte[] dArr = new byte[len]; // Preallocate byte[] of exact length - - for (int s = 0, d = 0; d < len; ) { - // Assemble three bytes into an int from four "valid" characters. - int i = 0; - for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. - int c = IALPHABET[sArr[s++]]; - if (c >= 0) { - i |= c << (18 - j * 6); - } else { - j--; - } - } - // Add the bytes - dArr[d++] = (byte) (i >> 16); - if (d < len) { - dArr[d++] = (byte) (i >> 8); - if (d < len) { - dArr[d++] = (byte) i; - } - } - } - return dArr; - } - */ - - private int ctoi(char c) { - int i = c > IALPHABET_MAX_INDEX ? -1 : IALPHABET[c]; - if (i < 0) { - String msg = "Illegal " + getName() + " character: '" + c + "'"; - throw new DecodingException(msg); - } - return i; - } - - /** - * Decodes a BASE64-encoded {@code CharSequence} that is known to be reasonably well formatted. The preconditions - * are:
- * + The sequence must have a line length of 76 chars OR no line separators at all (one line).
- * + Line separator must be "\r\n", as specified in RFC 2045 - * + The sequence must not contain illegal characters within the encoded string
- * + The sequence CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
- * - * @param seq The source sequence. Length 0 will return an empty array. null will throw an exception. - * @return The decoded array of bytes. May be of length 0. - * @throws DecodingException on illegal input - */ - byte[] decodeFast(CharSequence seq) throws DecodingException { - - // Check special case - int sLen = seq != null ? seq.length() : 0; - if (sLen == 0) { - return new byte[0]; - } - - int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. - - // Trim illegal chars from start - while (sIx < eIx && IALPHABET[seq.charAt(sIx)] < 0) { - sIx++; - } - - // Trim illegal chars from end - while (eIx > 0 && IALPHABET[seq.charAt(eIx)] < 0) { - eIx--; - } - - // get the padding count (=) (0, 1 or 2) - int pad = seq.charAt(eIx) == '=' ? (seq.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end. - int cCnt = eIx - sIx + 1; // Content count including possible separators - int sepCnt = sLen > 76 ? (seq.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; - - int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes - byte[] dArr = new byte[len]; // Preallocate byte[] of exact length - - // Decode all but the last 0 - 2 bytes. - int d = 0; - for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) { - - // Assemble three bytes into an int from four "valid" characters. - int i = ctoi(seq.charAt(sIx++)) << 18 | ctoi(seq.charAt(sIx++)) << 12 | ctoi(seq.charAt(sIx++)) << 6 | ctoi(seq.charAt(sIx++)); - - // Add the bytes - dArr[d++] = (byte) (i >> 16); - dArr[d++] = (byte) (i >> 8); - dArr[d++] = (byte) i; - - // If line separator, jump over it. - if (sepCnt > 0 && ++cc == 19) { - sIx += 2; - cc = 0; - } - } - - if (d < len) { - // Decode last 1-3 bytes (incl '=') into 1-3 bytes - int i = 0; - for (int j = 0; sIx <= eIx - pad; j++) { - i |= ctoi(seq.charAt(sIx++)) << (18 - j * 6); - } - - for (int r = 16; d < len; r -= 8) { - dArr[d++] = (byte) (i >> r); - } - } - - return dArr; - } - - // **************************************************************************************** - // * byte[] version - // **************************************************************************************** - - /* - * Encodes a raw byte array into a BASE64 byte[] representation i accordance with RFC 2045. - * - * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. - * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
- * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a - * little faster. - * @return A BASE64 encoded array. Never null. - * - public final byte[] encodeToByte(byte[] sArr, boolean lineSep) { - return encodeToByte(sArr, 0, sArr != null ? sArr.length : 0, lineSep); - } - - /** - * Encodes a raw byte array into a BASE64 byte[] representation i accordance with RFC 2045. - * - * @param sArr The bytes to convert. If null an empty array will be returned. - * @param sOff The starting position in the bytes to convert. - * @param sLen The number of bytes to convert. If 0 an empty array will be returned. - * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
- * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a - * little faster. - * @return A BASE64 encoded array. Never null. - * - public final byte[] encodeToByte(byte[] sArr, int sOff, int sLen, boolean lineSep) { - - // Check special case - if (sArr == null || sLen == 0) { - return new byte[0]; - } - - int eLen = (sLen / 3) * 3; // Length of even 24-bits. - int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count - int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array - byte[] dArr = new byte[dLen]; - - // Encode even 24-bits - for (int s = sOff, d = 0, cc = 0; s < sOff + eLen; ) { - - // Copy next three bytes into lower 24 bits of int, paying attention to sign. - int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); - - // Encode the int into four chars - dArr[d++] = (byte) ALPHABET[(i >>> 18) & 0x3f]; - dArr[d++] = (byte) ALPHABET[(i >>> 12) & 0x3f]; - dArr[d++] = (byte) ALPHABET[(i >>> 6) & 0x3f]; - dArr[d++] = (byte) ALPHABET[i & 0x3f]; - - // Add optional line separator - if (lineSep && ++cc == 19 && d < dLen - 2) { - dArr[d++] = '\r'; - dArr[d++] = '\n'; - cc = 0; - } - } - - // Pad and encode last bits if source isn't an even 24 bits. - int left = sLen - eLen; // 0 - 2. - if (left > 0) { - // Prepare the int - int i = ((sArr[sOff + eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sOff + sLen - 1] & 0xff) << 2) : 0); - - // Set last four chars - dArr[dLen - 4] = (byte) ALPHABET[i >> 12]; - dArr[dLen - 3] = (byte) ALPHABET[(i >>> 6) & 0x3f]; - dArr[dLen - 2] = left == 2 ? (byte) ALPHABET[i & 0x3f] : (byte) '='; - dArr[dLen - 1] = '='; - } - return dArr; - } - - /** - * Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with - * and without line separators. - * - * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. - * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters - * (including '=') isn't divideable by 4. (I.e. definitely corrupted). - * - public final byte[] decode(byte[] sArr) { - return decode(sArr, 0, sArr.length); - } - - /** - * Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with - * and without line separators. - * - * @param sArr The source array. null will throw an exception. - * @param sOff The starting position in the source array. - * @param sLen The number of bytes to decode from the source array. Length 0 will return an empty array. - * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters - * (including '=') isn't divideable by 4. (I.e. definitely corrupted). - * - public final byte[] decode(byte[] sArr, int sOff, int sLen) { - - // Count illegal characters (including '\r', '\n') to know what size the returned array will be, - // so we don't have to reallocate & copy it later. - int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) - for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. - if (IALPHABET[sArr[sOff + i] & 0xff] < 0) { - sepCnt++; - } - } - - // Check so that legal chars (including '=') are evenly divisible by 4 as specified in RFC 2045. - if ((sLen - sepCnt) % 4 != 0) { - return null; - } - - int pad = 0; - for (int i = sLen; i > 1 && IALPHABET[sArr[sOff + --i] & 0xff] <= 0; ) { - if (sArr[sOff + i] == '=') { - pad++; - } - } - - int len = ((sLen - sepCnt) * 6 >> 3) - pad; - - byte[] dArr = new byte[len]; // Preallocate byte[] of exact length - - for (int s = 0, d = 0; d < len; ) { - // Assemble three bytes into an int from four "valid" characters. - int i = 0; - for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. - int c = IALPHABET[sArr[sOff + s++] & 0xff]; - if (c >= 0) { - i |= c << (18 - j * 6); - } else { - j--; - } - } - - // Add the bytes - dArr[d++] = (byte) (i >> 16); - if (d < len) { - dArr[d++] = (byte) (i >> 8); - if (d < len) { - dArr[d++] = (byte) i; - } - } - } - - return dArr; - } - - - /* - * Decodes a BASE64 encoded byte array that is known to be reasonably well formatted. The method is about twice as - * fast as {@link #decode(byte[])}. The preconditions are:
- * + The array must have a line length of 76 chars OR no line separators at all (one line).
- * + Line separator must be "\r\n", as specified in RFC 2045 - * + The array must not contain illegal characters within the encoded string
- * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
- * - * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. - * @return The decoded array of bytes. May be of length 0. - * - public final byte[] decodeFast(byte[] sArr) { - - // Check special case - int sLen = sArr.length; - if (sLen == 0) { - return new byte[0]; - } - - int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. - - // Trim illegal chars from start - while (sIx < eIx && IALPHABET[sArr[sIx] & 0xff] < 0) { - sIx++; - } - - // Trim illegal chars from end - while (eIx > 0 && IALPHABET[sArr[eIx] & 0xff] < 0) { - eIx--; - } - - // get the padding count (=) (0, 1 or 2) - int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. - int cCnt = eIx - sIx + 1; // Content count including possible separators - int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; - - int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes - byte[] dArr = new byte[len]; // Preallocate byte[] of exact length - - // Decode all but the last 0 - 2 bytes. - int d = 0; - for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) { - - // Assemble three bytes into an int from four "valid" characters. - int i = IALPHABET[sArr[sIx++]] << 18 | IALPHABET[sArr[sIx++]] << 12 | IALPHABET[sArr[sIx++]] << 6 | IALPHABET[sArr[sIx++]]; - - // Add the bytes - dArr[d++] = (byte) (i >> 16); - dArr[d++] = (byte) (i >> 8); - dArr[d++] = (byte) i; - - // If line separator, jump over it. - if (sepCnt > 0 && ++cc == 19) { - sIx += 2; - cc = 0; - } - } - - if (d < len) { - // Decode last 1-3 bytes (incl '=') into 1-3 bytes - int i = 0; - for (int j = 0; sIx <= eIx - pad; j++) { - i |= IALPHABET[sArr[sIx++]] << (18 - j * 6); - } - - for (int r = 16; d < len; r -= 8) { - dArr[d++] = (byte) (i >> r); - } - } - - return dArr; - } - */ - - // **************************************************************************************** - // * String version - // **************************************************************************************** - - /** - * Encodes a raw byte array into a BASE64 String representation i accordance with RFC 2045. - * - * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. - * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
- * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a - * little faster. - * @return A BASE64 encoded array. Never null. - */ - String encodeToString(byte[] sArr, boolean lineSep) { - // Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower. - return new String(encodeToChar(sArr, lineSep)); - } - - /* - * Decodes a BASE64 encoded String. All illegal characters will be ignored and can handle both strings with - * and without line separators.
- * Note! It can be up to about 2x the speed to call decode(str.toCharArray()) instead. That - * will create a temporary array though. This version will use str.charAt(i) to iterate the string. - * - * @param str The source string. null or length 0 will return an empty array. - * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters - * (including '=') isn't divideable by 4. (I.e. definitely corrupted). - * - public final byte[] decode(String str) { - - // Check special case - int sLen = str != null ? str.length() : 0; - if (sLen == 0) { - return new byte[0]; - } - - // Count illegal characters (including '\r', '\n') to know what size the returned array will be, - // so we don't have to reallocate & copy it later. - int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) - for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. - if (IALPHABET[str.charAt(i)] < 0) { - sepCnt++; - } - } - - // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. - if ((sLen - sepCnt) % 4 != 0) { - return null; - } - - // Count '=' at end - int pad = 0; - for (int i = sLen; i > 1 && IALPHABET[str.charAt(--i)] <= 0; ) { - if (str.charAt(i) == '=') { - pad++; - } - } - - int len = ((sLen - sepCnt) * 6 >> 3) - pad; - - byte[] dArr = new byte[len]; // Preallocate byte[] of exact length - - for (int s = 0, d = 0; d < len; ) { - // Assemble three bytes into an int from four "valid" characters. - int i = 0; - for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. - int c = IALPHABET[str.charAt(s++)]; - if (c >= 0) { - i |= c << (18 - j * 6); - } else { - j--; - } - } - // Add the bytes - dArr[d++] = (byte) (i >> 16); - if (d < len) { - dArr[d++] = (byte) (i >> 8); - if (d < len) { - dArr[d++] = (byte) i; - } - } - } - return dArr; - } - - /** - * Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as - * fast as {@link #decode(String)}. The preconditions are:
- * + The array must have a line length of 76 chars OR no line separators at all (one line).
- * + Line separator must be "\r\n", as specified in RFC 2045 - * + The array must not contain illegal characters within the encoded string
- * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
- * - * @param s The source string. Length 0 will return an empty array. null will throw an exception. - * @return The decoded array of bytes. May be of length 0. - * - public final byte[] decodeFast(String s) { - - // Check special case - int sLen = s.length(); - if (sLen == 0) { - return new byte[0]; - } - - int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. - - // Trim illegal chars from start - while (sIx < eIx && IALPHABET[s.charAt(sIx) & 0xff] < 0) { - sIx++; - } - - // Trim illegal chars from end - while (eIx > 0 && IALPHABET[s.charAt(eIx) & 0xff] < 0) { - eIx--; - } - - // get the padding count (=) (0, 1 or 2) - int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end. - int cCnt = eIx - sIx + 1; // Content count including possible separators - int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; - - int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes - byte[] dArr = new byte[len]; // Preallocate byte[] of exact length - - // Decode all but the last 0 - 2 bytes. - int d = 0; - for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) { - // Assemble three bytes into an int from four "valid" characters. - int i = IALPHABET[s.charAt(sIx++)] << 18 | IALPHABET[s.charAt(sIx++)] << 12 | IALPHABET[s.charAt(sIx++)] << 6 | IALPHABET[s.charAt(sIx++)]; - - // Add the bytes - dArr[d++] = (byte) (i >> 16); - dArr[d++] = (byte) (i >> 8); - dArr[d++] = (byte) i; - - // If line separator, jump over it. - if (sepCnt > 0 && ++cc == 19) { - sIx += 2; - cc = 0; - } - } - - if (d < len) { - // Decode last 1-3 bytes (incl '=') into 1-3 bytes - int i = 0; - for (int j = 0; sIx <= eIx - pad; j++) { - i |= IALPHABET[s.charAt(sIx++)] << (18 - j * 6); - } - - for (int r = 16; d < len; r -= 8) { - dArr[d++] = (byte) (i >> r); - } - } - - return dArr; - } - */ -} diff --git a/io/jsonwebtoken/io/Base64Decoder.java b/io/jsonwebtoken/io/Base64Decoder.java deleted file mode 100644 index e0cb963..0000000 --- a/io/jsonwebtoken/io/Base64Decoder.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -import io.jsonwebtoken.lang.Assert; - -/** - * Very fast Base64 decoder guaranteed to - * work in all >= Java 7 JDK and Android environments. - * - * @since 0.10.0 - */ -class Base64Decoder extends Base64Support implements Decoder { - - Base64Decoder() { - super(Base64.DEFAULT); - } - - Base64Decoder(Base64 base64) { - super(base64); - } - - @Override - public byte[] decode(CharSequence s) throws DecodingException { - Assert.notNull(s, "String argument cannot be null"); - return this.base64.decodeFast(s); - } -} \ No newline at end of file diff --git a/io/jsonwebtoken/io/Base64Encoder.java b/io/jsonwebtoken/io/Base64Encoder.java deleted file mode 100644 index 6e0a6b0..0000000 --- a/io/jsonwebtoken/io/Base64Encoder.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -import io.jsonwebtoken.lang.Assert; - -/** - * Very fast Base64 encoder guaranteed to - * work in all >= Java 7 JDK and Android environments. - * - * @since 0.10.0 - */ -class Base64Encoder extends Base64Support implements Encoder { - - Base64Encoder() { - this(Base64.DEFAULT); - } - - Base64Encoder(Base64 base64) { - super(base64); - } - - @Override - public String encode(byte[] bytes) throws EncodingException { - Assert.notNull(bytes, "byte array argument cannot be null"); - return this.base64.encodeToString(bytes, false); - } -} diff --git a/io/jsonwebtoken/io/Base64Support.java b/io/jsonwebtoken/io/Base64Support.java deleted file mode 100644 index 8f8a4c1..0000000 --- a/io/jsonwebtoken/io/Base64Support.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -import io.jsonwebtoken.lang.Assert; - -/** - * Parent class for Base64 encoders and decoders. - * - * @since 0.10.0 - */ -class Base64Support { - - protected final Base64 base64; - - Base64Support(Base64 base64) { - Assert.notNull(base64, "Base64 argument cannot be null"); - this.base64 = base64; - } -} diff --git a/io/jsonwebtoken/io/Base64UrlDecoder.java b/io/jsonwebtoken/io/Base64UrlDecoder.java deleted file mode 100644 index fcca4cb..0000000 --- a/io/jsonwebtoken/io/Base64UrlDecoder.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -/** - * Very fast Base64Url decoder guaranteed to - * work in all >= Java 7 JDK and Android environments. - * - * @since 0.10.0 - */ -class Base64UrlDecoder extends Base64Decoder { - - Base64UrlDecoder() { - super(Base64.URL_SAFE); - } -} diff --git a/io/jsonwebtoken/io/Base64UrlEncoder.java b/io/jsonwebtoken/io/Base64UrlEncoder.java deleted file mode 100644 index 1377d31..0000000 --- a/io/jsonwebtoken/io/Base64UrlEncoder.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -/** - * Very fast Base64Url encoder guaranteed to - * work in all >= Java 7 JDK and Android environments. - * - * @since 0.10.0 - */ -class Base64UrlEncoder extends Base64Encoder { - - Base64UrlEncoder() { - super(Base64.URL_SAFE); - } -} diff --git a/io/jsonwebtoken/io/CodecException.java b/io/jsonwebtoken/io/CodecException.java deleted file mode 100644 index f25d8ca..0000000 --- a/io/jsonwebtoken/io/CodecException.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -/** - * An exception thrown when encountering a problem during encoding or decoding. - * - * @since 0.10.0 - */ -public class CodecException extends IOException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public CodecException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public CodecException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/io/CompressionAlgorithm.java b/io/jsonwebtoken/io/CompressionAlgorithm.java deleted file mode 100644 index 5ad0164..0000000 --- a/io/jsonwebtoken/io/CompressionAlgorithm.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -import io.jsonwebtoken.Identifiable; -import io.jsonwebtoken.JwtBuilder; -import io.jsonwebtoken.JwtParserBuilder; -import io.jsonwebtoken.Jwts; - -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Compresses and decompresses byte streams. - * - *

"zip" identifier

- * - *

{@code CompressionAlgorithm} extends {@code Identifiable}; the value returned from - * {@link Identifiable#getId() getId()} will be used as the JWT - * zip header value.

- * - *

Custom Implementations

- * - *

A custom implementation of this interface may be used when creating a JWT by calling the - * {@link JwtBuilder#compressWith(CompressionAlgorithm)} method.

- * - *

To ensure that parsing is possible, the parser must be aware of the implementation by adding it to the - * {@link JwtParserBuilder#zip()} collection during parser construction.

- * - * @see Jwts.ZIP#DEF - * @see Jwts.ZIP#GZIP - * @see JSON Web Encryption Compression Algorithms Registry - * @since 0.12.0 - */ -public interface CompressionAlgorithm extends Identifiable { - - /** - * Wraps the specified {@code OutputStream} to ensure any stream bytes are compressed as they are written. - * - * @param out the stream to wrap for compression - * @return the stream to use for writing - */ - OutputStream compress(OutputStream out); - - /** - * Wraps the specified {@code InputStream} to ensure any stream bytes are decompressed as they are read. - * - * @param in the stream to wrap for decompression - * @return the stream to use for reading - */ - InputStream decompress(InputStream in); -} diff --git a/io/jsonwebtoken/io/Decoder.java b/io/jsonwebtoken/io/Decoder.java deleted file mode 100644 index 2cf4fe8..0000000 --- a/io/jsonwebtoken/io/Decoder.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -/** - * A decoder converts an already-encoded data value to a desired data type. - * - * @param decoding input type - * @param decoding output type - * @since 0.10.0 - */ -public interface Decoder { - - /** - * Convert the specified encoded data value into the desired data type. - * - * @param t the encoded data - * @return the resulting expected data - * @throws DecodingException if there is a problem during decoding. - */ - R decode(T t) throws DecodingException; -} diff --git a/io/jsonwebtoken/io/Decoders.java b/io/jsonwebtoken/io/Decoders.java deleted file mode 100644 index 6b7c7e6..0000000 --- a/io/jsonwebtoken/io/Decoders.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -/** - * Constant definitions for various decoding algorithms. - * - * @see #BASE64 - * @see #BASE64URL - * @since 0.10.0 - */ -public final class Decoders { - - /** - * Very fast Base64 decoder guaranteed to - * work in all >= Java 7 JDK and Android environments. - */ - public static final Decoder BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder()); - - /** - * Very fast Base64Url decoder guaranteed to - * work in all >= Java 7 JDK and Android environments. - */ - public static final Decoder BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder()); - - private Decoders() { //prevent instantiation - } -} diff --git a/io/jsonwebtoken/io/DecodingException.java b/io/jsonwebtoken/io/DecodingException.java deleted file mode 100644 index ab3df92..0000000 --- a/io/jsonwebtoken/io/DecodingException.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -/** - * An exception thrown when encountering a problem during decoding. - * - * @since 0.10.0 - */ -public class DecodingException extends CodecException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public DecodingException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public DecodingException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/io/DeserializationException.java b/io/jsonwebtoken/io/DeserializationException.java deleted file mode 100644 index 76c647c..0000000 --- a/io/jsonwebtoken/io/DeserializationException.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -/** - * Exception thrown when reconstituting a serialized byte array into a Java object. - * - * @since 0.10.0 - */ -public class DeserializationException extends SerialException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param msg the message explaining why the exception is thrown. - */ - public DeserializationException(String msg) { - super(msg); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public DeserializationException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/io/Deserializer.java b/io/jsonwebtoken/io/Deserializer.java deleted file mode 100644 index a61a28a..0000000 --- a/io/jsonwebtoken/io/Deserializer.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -import java.io.Reader; - -/** - * A {@code Deserializer} is able to convert serialized byte streams into Java objects. - * - * @param the type of object to be returned as a result of deserialization. - * @since 0.10.0 - */ -public interface Deserializer { - - /** - * Convert the specified formatted data byte array into a Java object. - * - * @param bytes the formatted data byte array to convert - * @return the reconstituted Java object - * @throws DeserializationException if there is a problem converting the byte array to an object. - * @deprecated since 0.12.0 in favor of {@link #deserialize(Reader)} - */ - @Deprecated - T deserialize(byte[] bytes) throws DeserializationException; - - /** - * Reads the specified character stream and returns the corresponding Java object. - * - * @param reader the reader to use to read the character stream - * @return the deserialized Java object - * @throws DeserializationException if there is a problem reading the stream or creating the expected Java object - * @since 0.12.0 - */ - T deserialize(Reader reader) throws DeserializationException; -} diff --git a/io/jsonwebtoken/io/Encoder.java b/io/jsonwebtoken/io/Encoder.java deleted file mode 100644 index f334ee8..0000000 --- a/io/jsonwebtoken/io/Encoder.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -/** - * An encoder converts data of one type into another formatted data value. - * - * @param the type of data to convert - * @param the type of the resulting formatted data - * @since 0.10.0 - */ -public interface Encoder { - - /** - * Convert the specified data into another formatted data value. - * - * @param t the data to convert - * @return the resulting formatted data value - * @throws EncodingException if there is a problem during encoding - */ - R encode(T t) throws EncodingException; -} diff --git a/io/jsonwebtoken/io/Encoders.java b/io/jsonwebtoken/io/Encoders.java deleted file mode 100644 index 17f03f2..0000000 --- a/io/jsonwebtoken/io/Encoders.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -/** - * Constant definitions for various encoding algorithms. - * - * @see #BASE64 - * @see #BASE64URL - * @since 0.10.0 - */ -public final class Encoders { - - /** - * Very fast Base64 encoder guaranteed to - * work in all >= Java 7 JDK and Android environments. - */ - public static final Encoder BASE64 = new ExceptionPropagatingEncoder<>(new Base64Encoder()); - - /** - * Very fast Base64Url encoder guaranteed to - * work in all >= Java 7 JDK and Android environments. - */ - public static final Encoder BASE64URL = new ExceptionPropagatingEncoder<>(new Base64UrlEncoder()); - - private Encoders() { //prevent instantiation - } -} diff --git a/io/jsonwebtoken/io/EncodingException.java b/io/jsonwebtoken/io/EncodingException.java deleted file mode 100644 index c5ee9f9..0000000 --- a/io/jsonwebtoken/io/EncodingException.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -/** - * An exception thrown when encountering a problem during encoding. - * - * @since 0.10.0 - */ -public class EncodingException extends CodecException { - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public EncodingException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/io/ExceptionPropagatingDecoder.java b/io/jsonwebtoken/io/ExceptionPropagatingDecoder.java deleted file mode 100644 index 9e5bc78..0000000 --- a/io/jsonwebtoken/io/ExceptionPropagatingDecoder.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -import io.jsonwebtoken.lang.Assert; - -/** - * Decoder that ensures any exceptions thrown that are not {@link DecodingException}s are wrapped - * and re-thrown as a {@code DecodingException}. - * - * @since 0.10.0 - */ -class ExceptionPropagatingDecoder implements Decoder { - - private final Decoder decoder; - - /** - * Creates a new instance, wrapping the specified {@code decoder} to invoke during {@link #decode(Object)}. - * - * @param decoder the decoder to wrap and call during {@link #decode(Object)} - */ - ExceptionPropagatingDecoder(Decoder decoder) { - Assert.notNull(decoder, "Decoder cannot be null."); - this.decoder = decoder; - } - - /** - * Decode the specified encoded data, delegating to the wrapped Decoder, wrapping any - * non-{@link DecodingException} as a {@code DecodingException}. - * - * @param t the encoded data - * @return the decoded data - * @throws DecodingException if there is an unexpected problem during decoding. - */ - @Override - public R decode(T t) throws DecodingException { - Assert.notNull(t, "Decode argument cannot be null."); - try { - return decoder.decode(t); - } catch (DecodingException e) { - throw e; //propagate - } catch (Exception e) { - String msg = "Unable to decode input: " + e.getMessage(); - throw new DecodingException(msg, e); - } - } -} diff --git a/io/jsonwebtoken/io/ExceptionPropagatingEncoder.java b/io/jsonwebtoken/io/ExceptionPropagatingEncoder.java deleted file mode 100644 index 8efca95..0000000 --- a/io/jsonwebtoken/io/ExceptionPropagatingEncoder.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -import io.jsonwebtoken.lang.Assert; - -/** - * Encoder that ensures any exceptions thrown that are not {@link EncodingException}s are wrapped - * and re-thrown as a {@code EncodingException}. - * - * @since 0.10.0 - */ -class ExceptionPropagatingEncoder implements Encoder { - - private final Encoder encoder; - - /** - * Creates a new instance, wrapping the specified {@code encoder} to invoke during {@link #encode(Object)}. - * - * @param encoder the encoder to wrap and call during {@link #encode(Object)} - */ - ExceptionPropagatingEncoder(Encoder encoder) { - Assert.notNull(encoder, "Encoder cannot be null."); - this.encoder = encoder; - } - - /** - * Encoded the specified data, delegating to the wrapped Encoder, wrapping any - * non-{@link EncodingException} as an {@code EncodingException}. - * - * @param t the data to encode - * @return the encoded data - * @throws EncodingException if there is an unexpected problem during encoding. - */ - @Override - public R encode(T t) throws EncodingException { - Assert.notNull(t, "Encode argument cannot be null."); - try { - return this.encoder.encode(t); - } catch (EncodingException e) { - throw e; //propagate - } catch (Exception e) { - String msg = "Unable to encode input: " + e.getMessage(); - throw new EncodingException(msg, e); - } - } -} diff --git a/io/jsonwebtoken/io/IOException.java b/io/jsonwebtoken/io/IOException.java deleted file mode 100644 index 0ccd165..0000000 --- a/io/jsonwebtoken/io/IOException.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -import io.jsonwebtoken.JwtException; - -/** - * JJWT's base exception for problems during data input or output operations, such as serialization, - * deserialization, marshalling, unmarshalling, etc. - * - * @since 0.10.0 - */ -public class IOException extends JwtException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param msg the message explaining why the exception is thrown. - */ - public IOException(String msg) { - super(msg); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public IOException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/io/Parser.java b/io/jsonwebtoken/io/Parser.java deleted file mode 100644 index b8cac46..0000000 --- a/io/jsonwebtoken/io/Parser.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -import java.io.InputStream; -import java.io.Reader; - -/** - * A Parser converts a character stream into a Java object. - * - * @param the instance type created after parsing - * @since 0.12.0 - */ -public interface Parser { - - /** - * Parse the specified character sequence into a Java object. - * - * @param input the character sequence to parse into a Java object. - * @return the Java object represented by the specified {@code input} stream. - */ - T parse(CharSequence input); - - /** - * Parse the specified character sequence with the specified bounds into a Java object. - * - * @param input The character sequence, may be {@code null} - * @param start The start index in the character sequence, inclusive - * @param end The end index in the character sequence, exclusive - * @return the Java object represented by the specified sequence bounds - * @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index - */ - T parse(CharSequence input, int start, int end); - - /** - * Parse the specified character sequence into a Java object. - * - * @param reader the reader to use to parse a Java object. - * @return the Java object represented by the specified {@code input} stream. - */ - T parse(Reader reader); - - /** - * Parses the specified {@link InputStream} assuming {@link java.nio.charset.StandardCharsets#UTF_8 UTF_8} encoding. - * This is a convenience alias for: - * - *
{@link #parse(Reader) parse}(new {@link java.io.InputStreamReader
-     * InputStreamReader}(in, {@link java.nio.charset.StandardCharsets#UTF_8
-     * StandardCharsets.UTF_8});
- * - * @param in the UTF-8 InputStream. - * @return the Java object represented by the specified {@link InputStream}. - */ - T parse(InputStream in); -} diff --git a/io/jsonwebtoken/io/ParserBuilder.java b/io/jsonwebtoken/io/ParserBuilder.java deleted file mode 100644 index 9cb0068..0000000 --- a/io/jsonwebtoken/io/ParserBuilder.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -import io.jsonwebtoken.lang.Builder; - -import java.security.Provider; -import java.util.Map; - -/** - * A {@code ParserBuilder} configures and creates new {@link Parser} instances. - * - * @param The resulting parser's {@link Parser#parse parse} output type - * @param builder type used for method chaining - * @since 0.12.0 - */ -public interface ParserBuilder> extends Builder> { - - /** - * Sets the JCA Provider to use during cryptographic operations, or {@code null} if the - * JCA subsystem preferred provider should be used. - * - * @param provider the JCA Provider to use during cryptographic key factory operations, or {@code null} - * if the JCA subsystem preferred provider should be used. - * @return the builder for method chaining. - */ - B provider(Provider provider); - - /** - * Uses the specified {@code Deserializer} to convert JSON Strings (UTF-8 byte streams) into Java Map objects. The - * resulting Maps are then used to construct respective JWT objects (JWTs, JWKs, etc). - * - *

If this method is not called, JJWT will use whatever Deserializer it can find at runtime, checking for the - * presence of well-known implementations such as Jackson, Gson, and org.json. If one of these is not found - * in the runtime classpath, an exception will be thrown when the {@link #build()} method is called. - * - * @param deserializer the Deserializer to use when converting JSON Strings (UTF-8 byte streams) into Map objects. - * @return the builder for method chaining. - */ - B json(Deserializer> deserializer); -} diff --git a/io/jsonwebtoken/io/SerialException.java b/io/jsonwebtoken/io/SerialException.java deleted file mode 100644 index 0269c96..0000000 --- a/io/jsonwebtoken/io/SerialException.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -/** - * An exception thrown during serialization or deserialization. - * - * @since 0.10.0 - */ -public class SerialException extends IOException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param msg the message explaining why the exception is thrown. - */ - public SerialException(String msg) { - super(msg); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public SerialException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/io/SerializationException.java b/io/jsonwebtoken/io/SerializationException.java deleted file mode 100644 index d978921..0000000 --- a/io/jsonwebtoken/io/SerializationException.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -/** - * Exception thrown when converting a Java object to a formatted byte array. - * - * @since 0.10.0 - */ -public class SerializationException extends SerialException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param msg the message explaining why the exception is thrown. - */ - public SerializationException(String msg) { - super(msg); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public SerializationException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/io/Serializer.java b/io/jsonwebtoken/io/Serializer.java deleted file mode 100644 index 6bc59ce..0000000 --- a/io/jsonwebtoken/io/Serializer.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.io; - -import java.io.OutputStream; - -/** - * A {@code Serializer} is able to convert a Java object into a formatted byte stream. It is expected this byte stream - * can be reconstituted back into a Java object with a matching {@link Deserializer}. - * - * @param The type of object to serialize. - * @since 0.10.0 - */ -public interface Serializer { - - /** - * Converts the specified Java object into a formatted data byte array. - * - * @param t the object to serialize - * @return the serialized byte array representing the specified object. - * @throws SerializationException if there is a problem converting the object to a byte array. - * @deprecated since 0.12.0 in favor of {@link #serialize(Object, OutputStream)} - */ - @Deprecated - byte[] serialize(T t) throws SerializationException; - - /** - * Converts the specified Java object into a formatted data byte stream, writing the bytes to the specified - * {@code out}put stream. - * - * @param t the object to convert to a byte stream - * @param out the stream to write to - * @throws SerializationException if there is a problem converting the object to a byte stream or writing the - * bytes to the {@code out}put stream. - * @since 0.12.0 - */ - void serialize(T t, OutputStream out) throws SerializationException; -} diff --git a/io/jsonwebtoken/lang/Arrays.java b/io/jsonwebtoken/lang/Arrays.java deleted file mode 100644 index 6c5b4e2..0000000 --- a/io/jsonwebtoken/lang/Arrays.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -import java.lang.reflect.Array; -import java.util.List; - -/** - * Utility methods to work with array instances. - * - * @since 0.6 - */ -public final class Arrays { - - private Arrays() { - } //prevent instantiation - - /** - * Returns the length of the array, or {@code 0} if the array is {@code null}. - * - * @param a the possibly-null array - * @param the type of elements in the array - * @return the length of the array, or zero if the array is null. - */ - public static int length(T[] a) { - return a == null ? 0 : a.length; - } - - /** - * Converts the specified array to a {@link List}. If the array is empty, an empty list will be returned. - * - * @param a the array to represent as a list - * @param the type of elements in the array - * @return the array as a list, or an empty list if the array is empty. - */ - public static List asList(T[] a) { - return Objects.isEmpty(a) ? Collections.emptyList() : java.util.Arrays.asList(a); - } - - /** - * Returns the length of the specified byte array, or {@code 0} if the byte array is {@code null}. - * - * @param bytes the array to check - * @return the length of the specified byte array, or {@code 0} if the byte array is {@code null}. - */ - public static int length(byte[] bytes) { - return bytes != null ? bytes.length : 0; - } - - /** - * Returns the byte array unaltered if it is non-null and has a positive length, otherwise {@code null}. - * - * @param bytes the byte array to check. - * @return the byte array unaltered if it is non-null and has a positive length, otherwise {@code null}. - */ - public static byte[] clean(byte[] bytes) { - return length(bytes) > 0 ? bytes : null; - } - - /** - * Creates a shallow copy of the specified object or array. - * - * @param obj the object to copy - * @return a shallow copy of the specified object or array. - */ - public static Object copy(Object obj) { - if (obj == null) { - return null; - } - Assert.isTrue(Objects.isArray(obj), "Argument must be an array."); - if (obj instanceof Object[]) { - return ((Object[]) obj).clone(); - } - if (obj instanceof boolean[]) { - return ((boolean[]) obj).clone(); - } - if (obj instanceof byte[]) { - return ((byte[]) obj).clone(); - } - if (obj instanceof char[]) { - return ((char[]) obj).clone(); - } - if (obj instanceof double[]) { - return ((double[]) obj).clone(); - } - if (obj instanceof float[]) { - return ((float[]) obj).clone(); - } - if (obj instanceof int[]) { - return ((int[]) obj).clone(); - } - if (obj instanceof long[]) { - return ((long[]) obj).clone(); - } - if (obj instanceof short[]) { - return ((short[]) obj).clone(); - } - Class componentType = obj.getClass().getComponentType(); - int length = Array.getLength(obj); - Object[] copy = (Object[]) Array.newInstance(componentType, length); - for (int i = 0; i < length; i++) { - copy[i] = Array.get(obj, i); - } - return copy; - } -} diff --git a/io/jsonwebtoken/lang/Assert.java b/io/jsonwebtoken/lang/Assert.java deleted file mode 100644 index 022d58f..0000000 --- a/io/jsonwebtoken/lang/Assert.java +++ /dev/null @@ -1,558 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -import java.util.Collection; -import java.util.Map; - -/** - * Utility methods for providing argument and state assertions to reduce repeating these patterns and otherwise - * increasing cyclomatic complexity. - */ -public final class Assert { - - private Assert() { - } //prevent instantiation - - /** - * Assert a boolean expression, throwing IllegalArgumentException - * if the test result is false. - *

Assert.isTrue(i > 0, "The value must be greater than zero");
- * - * @param expression a boolean expression - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if expression is false - */ - public static void isTrue(boolean expression, String message) { - if (!expression) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert a boolean expression, throwing IllegalArgumentException - * if the test result is false. - *
Assert.isTrue(i > 0);
- * - * @param expression a boolean expression - * @throws IllegalArgumentException if expression is false - */ - public static void isTrue(boolean expression) { - isTrue(expression, "[Assertion failed] - this expression must be true"); - } - - /** - * Assert that an object is null . - *
Assert.isNull(value, "The value must be null");
- * - * @param object the object to check - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the object is not null - */ - public static void isNull(Object object, String message) { - if (object != null) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that an object is null . - *
Assert.isNull(value);
- * - * @param object the object to check - * @throws IllegalArgumentException if the object is not null - */ - public static void isNull(Object object) { - isNull(object, "[Assertion failed] - the object argument must be null"); - } - - /** - * Assert that an object is not null . - *
Assert.notNull(clazz, "The class must not be null");
- * - * @param object the object to check - * @param the type of object - * @param message the exception message to use if the assertion fails - * @return the non-null object - * @throws IllegalArgumentException if the object is null - */ - public static T notNull(T object, String message) { - if (object == null) { - throw new IllegalArgumentException(message); - } - return object; - } - - /** - * Assert that an object is not null . - *
Assert.notNull(clazz);
- * - * @param object the object to check - * @throws IllegalArgumentException if the object is null - */ - public static void notNull(Object object) { - notNull(object, "[Assertion failed] - this argument is required; it must not be null"); - } - - /** - * Assert that the given String is not empty; that is, - * it must not be null and not the empty String. - *
Assert.hasLength(name, "Name must not be empty");
- * - * @param text the String to check - * @param message the exception message to use if the assertion fails - * @see Strings#hasLength - */ - public static void hasLength(String text, String message) { - if (!Strings.hasLength(text)) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that the given String is not empty; that is, - * it must not be null and not the empty String. - *
Assert.hasLength(name);
- * - * @param text the String to check - * @see Strings#hasLength - */ - public static void hasLength(String text) { - hasLength(text, - "[Assertion failed] - this String argument must have length; it must not be null or empty"); - } - - /** - * Assert that the given String has valid text content; that is, it must not - * be null and must contain at least one non-whitespace character. - *
Assert.hasText(name, "'name' must not be empty");
- * - * @param the type of CharSequence - * @param text the CharSequence to check - * @param message the exception message to use if the assertion fails - * @return the CharSequence if it has text - * @see Strings#hasText - */ - public static T hasText(T text, String message) { - if (!Strings.hasText(text)) { - throw new IllegalArgumentException(message); - } - return text; - } - - /** - * Assert that the given String has valid text content; that is, it must not - * be null and must contain at least one non-whitespace character. - *
Assert.hasText(name, "'name' must not be empty");
- * - * @param text the String to check - * @see Strings#hasText - */ - public static void hasText(String text) { - hasText(text, - "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank"); - } - - /** - * Assert that the given text does not contain the given substring. - *
Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");
- * - * @param textToSearch the text to search - * @param substring the substring to find within the text - * @param message the exception message to use if the assertion fails - */ - public static void doesNotContain(String textToSearch, String substring, String message) { - if (Strings.hasLength(textToSearch) && Strings.hasLength(substring) && - textToSearch.indexOf(substring) != -1) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that the given text does not contain the given substring. - *
Assert.doesNotContain(name, "rod");
- * - * @param textToSearch the text to search - * @param substring the substring to find within the text - */ - public static void doesNotContain(String textToSearch, String substring) { - doesNotContain(textToSearch, substring, - "[Assertion failed] - this String argument must not contain the substring [" + substring + "]"); - } - - - /** - * Assert that an array has elements; that is, it must not be - * null and must have at least one element. - *
Assert.notEmpty(array, "The array must have elements");
- * - * @param array the array to check - * @param message the exception message to use if the assertion fails - * @return the non-empty array for immediate use - * @throws IllegalArgumentException if the object array is null or has no elements - */ - public static Object[] notEmpty(Object[] array, String message) { - if (Objects.isEmpty(array)) { - throw new IllegalArgumentException(message); - } - return array; - } - - /** - * Assert that an array has elements; that is, it must not be - * null and must have at least one element. - *
Assert.notEmpty(array);
- * - * @param array the array to check - * @throws IllegalArgumentException if the object array is null or has no elements - */ - public static void notEmpty(Object[] array) { - notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element"); - } - - /** - * Assert that the specified byte array is not null and has at least one byte element. - * - * @param array the byte array to check - * @param msg the exception message to use if the assertion fails - * @return the byte array if the assertion passes - * @throws IllegalArgumentException if the byte array is null or empty - * @since 0.12.0 - */ - public static byte[] notEmpty(byte[] array, String msg) { - if (Objects.isEmpty(array)) { - throw new IllegalArgumentException(msg); - } - return array; - } - - /** - * Assert that the specified character array is not null and has at least one byte element. - * - * @param chars the character array to check - * @param msg the exception message to use if the assertion fails - * @return the character array if the assertion passes - * @throws IllegalArgumentException if the character array is null or empty - * @since 0.12.0 - */ - public static char[] notEmpty(char[] chars, String msg) { - if (Objects.isEmpty(chars)) { - throw new IllegalArgumentException(msg); - } - return chars; - } - - /** - * Assert that an array has no null elements. - * Note: Does not complain if the array is empty! - *
Assert.noNullElements(array, "The array must have non-null elements");
- * - * @param array the array to check - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the object array contains a null element - */ - public static void noNullElements(Object[] array, String message) { - if (array != null) { - for (int i = 0; i < array.length; i++) { - if (array[i] == null) { - throw new IllegalArgumentException(message); - } - } - } - } - - /** - * Assert that an array has no null elements. - * Note: Does not complain if the array is empty! - *
Assert.noNullElements(array);
- * - * @param array the array to check - * @throws IllegalArgumentException if the object array contains a null element - */ - public static void noNullElements(Object[] array) { - noNullElements(array, "[Assertion failed] - this array must not contain any null elements"); - } - - /** - * Assert that a collection has elements; that is, it must not be - * null and must have at least one element. - *
Assert.notEmpty(collection, "Collection must have elements");
- * - * @param collection the collection to check - * @param the type of collection - * @param message the exception message to use if the assertion fails - * @return the non-null, non-empty collection - * @throws IllegalArgumentException if the collection is null or has no elements - */ - public static > T notEmpty(T collection, String message) { - if (Collections.isEmpty(collection)) { - throw new IllegalArgumentException(message); - } - return collection; - } - - /** - * Assert that a collection has elements; that is, it must not be - * null and must have at least one element. - *
Assert.notEmpty(collection, "Collection must have elements");
- * - * @param collection the collection to check - * @throws IllegalArgumentException if the collection is null or has no elements - */ - public static void notEmpty(Collection collection) { - notEmpty(collection, - "[Assertion failed] - this collection must not be empty: it must contain at least 1 element"); - } - - /** - * Assert that a Map has entries; that is, it must not be null - * and must have at least one entry. - *
Assert.notEmpty(map, "Map must have entries");
- * - * @param map the map to check - * @param the type of Map to check - * @param message the exception message to use if the assertion fails - * @return the non-null, non-empty map - * @throws IllegalArgumentException if the map is null or has no entries - */ - public static > T notEmpty(T map, String message) { - if (Collections.isEmpty(map)) { - throw new IllegalArgumentException(message); - } - return map; - } - - /** - * Assert that a Map has entries; that is, it must not be null - * and must have at least one entry. - *
Assert.notEmpty(map);
- * - * @param map the map to check - * @throws IllegalArgumentException if the map is null or has no entries - */ - public static void notEmpty(Map map) { - notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry"); - } - - - /** - * Assert that the provided object is an instance of the provided class. - *
Assert.instanceOf(Foo.class, foo);
- * - * @param the type of instance expected - * @param clazz the required class - * @param obj the object to check - * @return the expected instance of type {@code T} - * @throws IllegalArgumentException if the object is not an instance of clazz - * @see Class#isInstance - */ - public static T isInstanceOf(Class clazz, Object obj) { - return isInstanceOf(clazz, obj, ""); - } - - /** - * Assert that the provided object is an instance of the provided class. - *
Assert.instanceOf(Foo.class, foo);
- * - * @param type the type to check against - * @param the object's expected type - * @param obj the object to check - * @param message a message which will be prepended to the message produced by - * the function itself, and which may be used to provide context. It should - * normally end in a ": " or ". " so that the function generate message looks - * ok when prepended to it. - * @return the non-null object IFF it is an instance of the specified {@code type}. - * @throws IllegalArgumentException if the object is not an instance of clazz - * @see Class#isInstance - */ - public static T isInstanceOf(Class type, Object obj, String message) { - notNull(type, "Type to check against must not be null"); - if (!type.isInstance(obj)) { - throw new IllegalArgumentException(message + - "Object of class [" + (obj != null ? obj.getClass().getName() : "null") + - "] must be an instance of " + type); - } - return type.cast(obj); - } - - /** - * Asserts that the provided object is an instance of the provided class, throwing an - * {@link IllegalStateException} otherwise. - *
Assert.stateIsInstance(Foo.class, foo);
- * - * @param type the type to check against - * @param the object's expected type - * @param obj the object to check - * @param message a message which will be prepended to the message produced by - * the function itself, and which may be used to provide context. It should - * normally end in a ": " or ". " so that the function generate message looks - * ok when prepended to it. - * @return the non-null object IFF it is an instance of the specified {@code type}. - * @throws IllegalStateException if the object is not an instance of clazz - * @see Class#isInstance - */ - public static T stateIsInstance(Class type, Object obj, String message) { - notNull(type, "Type to check cannot be null."); - if (!type.isInstance(obj)) { - String msg = message + "Object of class [" + Objects.nullSafeClassName(obj) + - "] must be an instance of " + type; - throw new IllegalStateException(msg); - } - return type.cast(obj); - } - - /** - * Assert that superType.isAssignableFrom(subType) is true. - *
Assert.isAssignable(Number.class, myClass);
- * - * @param superType the super type to check - * @param subType the sub type to check - * @throws IllegalArgumentException if the classes are not assignable - */ - public static void isAssignable(Class superType, Class subType) { - isAssignable(superType, subType, ""); - } - - /** - * Assert that superType.isAssignableFrom(subType) is true. - *
Assert.isAssignable(Number.class, myClass);
- * - * @param superType the super type to check against - * @param subType the sub type to check - * @param message a message which will be prepended to the message produced by - * the function itself, and which may be used to provide context. It should - * normally end in a ": " or ". " so that the function generate message looks - * ok when prepended to it. - * @throws IllegalArgumentException if the classes are not assignable - */ - public static void isAssignable(Class superType, Class subType, String message) { - notNull(superType, "Type to check against must not be null"); - if (subType == null || !superType.isAssignableFrom(subType)) { - throw new IllegalArgumentException(message + subType + " is not assignable to " + superType); - } - } - - /** - * Asserts that a specified {@code value} is equal to the given {@code requirement}, throwing - * an {@link IllegalArgumentException} with the given message if not. - * - * @param the type of argument - * @param value the value to check - * @param requirement the requirement that {@code value} must be greater than - * @param msg the message to use for the {@code IllegalArgumentException} if thrown. - * @return {@code value} if greater than the specified {@code requirement}. - * @since 0.12.0 - */ - public static > T eq(T value, T requirement, String msg) { - if (compareTo(value, requirement) != 0) { - throw new IllegalArgumentException(msg); - } - return value; - } - - private static > int compareTo(T value, T requirement) { - notNull(value, "value cannot be null."); - notNull(requirement, "requirement cannot be null."); - return value.compareTo(requirement); - } - - /** - * Asserts that a specified {@code value} is greater than the given {@code requirement}, throwing - * an {@link IllegalArgumentException} with the given message if not. - * - * @param the type of value to check and return if the requirement is met - * @param value the value to check - * @param requirement the requirement that {@code value} must be greater than - * @param msg the message to use for the {@code IllegalArgumentException} if thrown. - * @return {@code value} if greater than the specified {@code requirement}. - * @since 0.12.0 - */ - public static > T gt(T value, T requirement, String msg) { - if (!(compareTo(value, requirement) > 0)) { - throw new IllegalArgumentException(msg); - } - return value; - } - - /** - * Asserts that a specified {@code value} is less than or equal to the given {@code requirement}, throwing - * an {@link IllegalArgumentException} with the given message if not. - * - * @param the type of value to check and return if the requirement is met - * @param value the value to check - * @param requirement the requirement that {@code value} must be greater than - * @param msg the message to use for the {@code IllegalArgumentException} if thrown. - * @return {@code value} if greater than the specified {@code requirement}. - * @since 0.12.0 - */ - public static > T lte(T value, T requirement, String msg) { - if (compareTo(value, requirement) > 0) { - throw new IllegalArgumentException(msg); - } - return value; - } - - - /** - * Assert a boolean expression, throwing IllegalStateException - * if the test result is false. Call isTrue if you wish to - * throw IllegalArgumentException on an assertion failure. - *
Assert.state(id == null, "The id property must not already be initialized");
- * - * @param expression a boolean expression - * @param message the exception message to use if the assertion fails - * @throws IllegalStateException if expression is false - */ - public static void state(boolean expression, String message) { - if (!expression) { - throw new IllegalStateException(message); - } - } - - /** - * Assert a boolean expression, throwing {@link IllegalStateException} - * if the test result is false. - *

Call {@link #isTrue(boolean)} if you wish to - * throw {@link IllegalArgumentException} on an assertion failure. - *

Assert.state(id == null);
- * - * @param expression a boolean expression - * @throws IllegalStateException if the supplied expression is false - */ - public static void state(boolean expression) { - state(expression, "[Assertion failed] - this state invariant must be true"); - } - - /** - * Asserts that the specified {@code value} is not null, otherwise throws an - * {@link IllegalStateException} with the specified {@code msg}. Intended to be used with - * code invariants (as opposed to method arguments, like {@link #notNull(Object)}). - * - * @param value value to assert is not null - * @param msg exception message to use if {@code value} is null - * @param value type - * @return the non-null value - * @throws IllegalStateException with the specified {@code msg} if {@code value} is null. - * @since 0.12.0 - */ - public static T stateNotNull(T value, String msg) throws IllegalStateException { - if (value == null) { - throw new IllegalStateException(msg); - } - return value; - } - -} diff --git a/io/jsonwebtoken/lang/Builder.java b/io/jsonwebtoken/lang/Builder.java deleted file mode 100644 index 506c802..0000000 --- a/io/jsonwebtoken/lang/Builder.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2022 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -/** - * Type-safe interface that reflects the Builder pattern. - * - * @param The type of object that will be created when {@link #build()} is invoked. - * @since 0.12.0 - */ -public interface Builder { - - /** - * Creates and returns a new instance of type {@code T}. - * - * @return a new instance of type {@code T}. - */ - T build(); -} diff --git a/io/jsonwebtoken/lang/Classes.java b/io/jsonwebtoken/lang/Classes.java deleted file mode 100644 index 37e0752..0000000 --- a/io/jsonwebtoken/lang/Classes.java +++ /dev/null @@ -1,416 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -import java.io.InputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URL; - -/** - * Utility methods for working with {@link Class}es. - * - * @since 0.1 - */ -public final class Classes { - - private Classes() { - } //prevent instantiation - - private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() { - @Override - protected ClassLoader doGetClassLoader() { - return Thread.currentThread().getContextClassLoader(); - } - }; - - private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() { - @Override - protected ClassLoader doGetClassLoader() { - return Classes.class.getClassLoader(); - } - }; - - private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() { - @Override - protected ClassLoader doGetClassLoader() { - return ClassLoader.getSystemClassLoader(); - } - }; - - /** - * Attempts to load the specified class name from the current thread's - * {@link Thread#getContextClassLoader() context class loader}, then the - * current ClassLoader (Classes.class.getClassLoader()), then the system/application - * ClassLoader (ClassLoader.getSystemClassLoader(), in that order. If any of them cannot locate - * the specified class, an UnknownClassException is thrown (our RuntimeException equivalent of - * the JRE's ClassNotFoundException. - * - * @param fqcn the fully qualified class name to load - * @param The type of Class returned - * @return the located class - * @throws UnknownClassException if the class cannot be found. - */ - @SuppressWarnings("unchecked") - public static Class forName(String fqcn) throws UnknownClassException { - - Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn); - - if (clazz == null) { - clazz = CLASS_CL_ACCESSOR.loadClass(fqcn); - } - - if (clazz == null) { - clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn); - } - - if (clazz == null) { - String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " + - "system/application ClassLoaders. All heuristics have been exhausted. Class could not be found."; - - if (fqcn != null && fqcn.startsWith("io.jsonwebtoken.impl")) { - msg += " Have you remembered to include the jjwt-impl.jar in your runtime classpath?"; - } - - throw new UnknownClassException(msg); - } - - return (Class) clazz; - } - - /** - * Returns the specified resource by checking the current thread's - * {@link Thread#getContextClassLoader() context class loader}, then the - * current ClassLoader (Classes.class.getClassLoader()), then the system/application - * ClassLoader (ClassLoader.getSystemClassLoader(), in that order, using - * {@link ClassLoader#getResourceAsStream(String) getResourceAsStream(name)}. - * - * @param name the name of the resource to acquire from the classloader(s). - * @return the InputStream of the resource found, or null if the resource cannot be found from any - * of the three mentioned ClassLoaders. - * @since 0.8 - */ - public static InputStream getResourceAsStream(String name) { - - InputStream is = THREAD_CL_ACCESSOR.getResourceStream(name); - - if (is == null) { - is = CLASS_CL_ACCESSOR.getResourceStream(name); - } - - if (is == null) { - is = SYSTEM_CL_ACCESSOR.getResourceStream(name); - } - - return is; - } - - /** - * Returns the specified resource URL by checking the current thread's - * {@link Thread#getContextClassLoader() context class loader}, then the - * current ClassLoader (Classes.class.getClassLoader()), then the system/application - * ClassLoader (ClassLoader.getSystemClassLoader(), in that order, using - * {@link ClassLoader#getResource(String) getResource(name)}. - * - * @param name the name of the resource to acquire from the classloader(s). - * @return the URL of the resource found, or null if the resource cannot be found from any - * of the three mentioned ClassLoaders. - * @since 0.12.0 - */ - private static URL getResource(String name) { - URL url = THREAD_CL_ACCESSOR.getResource(name); - if (url == null) { - url = CLASS_CL_ACCESSOR.getResource(name); - } - if (url == null) { - return SYSTEM_CL_ACCESSOR.getResource(name); - } - return url; - } - - /** - * Returns {@code true} if the specified {@code fullyQualifiedClassName} can be found in any of the thread - * context, class, or system classloaders, or {@code false} otherwise. - * - * @param fullyQualifiedClassName the fully qualified class name to check - * @return {@code true} if the specified {@code fullyQualifiedClassName} can be found in any of the thread - * context, class, or system classloaders, or {@code false} otherwise. - */ - public static boolean isAvailable(String fullyQualifiedClassName) { - try { - forName(fullyQualifiedClassName); - return true; - } catch (UnknownClassException e) { - return false; - } - } - - /** - * Creates and returns a new instance of the class with the specified fully qualified class name using the - * classes default no-argument constructor. - * - * @param fqcn the fully qualified class name - * @param the type of object created - * @return a new instance of the specified class name - */ - @SuppressWarnings("unchecked") - public static T newInstance(String fqcn) { - return (T) newInstance(forName(fqcn)); - } - - /** - * Creates and returns a new instance of the specified fully qualified class name using the - * specified {@code args} arguments provided to the constructor with {@code ctorArgTypes} - * - * @param fqcn the fully qualified class name - * @param ctorArgTypes the argument types of the constructor to invoke - * @param args the arguments to supply when invoking the constructor - * @param the type of object created - * @return the newly created object - */ - public static T newInstance(String fqcn, Class[] ctorArgTypes, Object... args) { - Class clazz = forName(fqcn); - Constructor ctor = getConstructor(clazz, ctorArgTypes); - return instantiate(ctor, args); - } - - /** - * Creates and returns a new instance of the specified fully qualified class name using a constructor that matches - * the specified {@code args} arguments. - * - * @param fqcn fully qualified class name - * @param args the arguments to supply to the constructor - * @param the type of the object created - * @return the newly created object - */ - @SuppressWarnings("unchecked") - public static T newInstance(String fqcn, Object... args) { - return (T) newInstance(forName(fqcn), args); - } - - /** - * Creates a new instance of the specified {@code clazz} via {@code clazz.newInstance()}. - * - * @param clazz the class to invoke - * @param the type of the object created - * @return the newly created object - */ - public static T newInstance(Class clazz) { - if (clazz == null) { - String msg = "Class method parameter cannot be null."; - throw new IllegalArgumentException(msg); - } - try { - return clazz.newInstance(); - } catch (Exception e) { - throw new InstantiationException("Unable to instantiate class [" + clazz.getName() + "]", e); - } - } - - /** - * Returns a new instance of the specified {@code clazz}, invoking the associated constructor with the specified - * {@code args} arguments. - * - * @param clazz the class to invoke - * @param args the arguments matching an associated class constructor - * @param the type of the created object - * @return the newly created object - */ - public static T newInstance(Class clazz, Object... args) { - Class[] argTypes = new Class[args.length]; - for (int i = 0; i < args.length; i++) { - argTypes[i] = args[i].getClass(); - } - Constructor ctor = getConstructor(clazz, argTypes); - return instantiate(ctor, args); - } - - /** - * Returns the {@link Constructor} for the specified {@code Class} with arguments matching the specified - * {@code argTypes}. - * - * @param clazz the class to inspect - * @param argTypes the argument types for the desired constructor - * @param the type of object to create - * @return the constructor matching the specified argument types - * @throws IllegalStateException if the constructor for the specified {@code argTypes} does not exist. - */ - public static Constructor getConstructor(Class clazz, Class... argTypes) throws IllegalStateException { - try { - return clazz.getConstructor(argTypes); - } catch (NoSuchMethodException e) { - throw new IllegalStateException(e); - } - - } - - /** - * Creates a new object using the specified {@link Constructor}, invoking it with the specified constructor - * {@code args} arguments. - * - * @param ctor the constructor to invoke - * @param args the arguments to supply to the constructor - * @param the type of object to create - * @return the new object instance - * @throws InstantiationException if the constructor cannot be invoked successfully - */ - public static T instantiate(Constructor ctor, Object... args) { - try { - return ctor.newInstance(args); - } catch (Exception e) { - String msg = "Unable to instantiate instance with constructor [" + ctor + "]"; - throw new InstantiationException(msg, e); - } - } - - /** - * Invokes the fully qualified class name's method named {@code methodName} with parameters of type {@code argTypes} - * using the {@code args} as the method arguments. - * - * @param fqcn fully qualified class name to locate - * @param methodName name of the method to invoke on the class - * @param argTypes the method argument types supported by the {@code methodName} method - * @param args the runtime arguments to use when invoking the located class method - * @param the expected type of the object returned from the invoked method. - * @return the result returned by the invoked method - * @since 0.10.0 - */ - public static T invokeStatic(String fqcn, String methodName, Class[] argTypes, Object... args) { - try { - Class clazz = Classes.forName(fqcn); - return invokeStatic(clazz, methodName, argTypes, args); - } catch (Exception e) { - String msg = "Unable to invoke class method " + fqcn + "#" + methodName + ". Ensure the necessary " + - "implementation is in the runtime classpath."; - throw new IllegalStateException(msg, e); - } - } - - /** - * Invokes the {@code clazz}'s matching static method (named {@code methodName} with exact argument types - * of {@code argTypes}) with the given {@code args} arguments, and returns the method return value. - * - * @param clazz the class to invoke - * @param methodName the name of the static method on {@code clazz} to invoke - * @param argTypes the types of the arguments accepted by the method - * @param args the actual runtime arguments to use when invoking the method - * @param the type of object expected to be returned from the method - * @return the result returned by the invoked method. - * @since 0.12.0 - */ - @SuppressWarnings("unchecked") - public static T invokeStatic(Class clazz, String methodName, Class[] argTypes, Object... args) { - try { - Method method = clazz.getDeclaredMethod(methodName, argTypes); - method.setAccessible(true); - return (T) method.invoke(null, args); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - Throwable cause = e.getCause(); - if (cause instanceof RuntimeException) { - throw ((RuntimeException) cause); //propagate - } - String msg = "Unable to invoke class method " + clazz.getName() + "#" + methodName + - ". Ensure the necessary implementation is in the runtime classpath."; - throw new IllegalStateException(msg, e); - } - } - - /** - * Returns the {@code instance}'s named (declared) field value. - * - * @param instance the instance with the internal field - * @param fieldName the name of the field to inspect - * @param fieldType the type of field to inspect - * @param field instance value type - * @return the field value - */ - public static T getFieldValue(Object instance, String fieldName, Class fieldType) { - if (instance == null) return null; - try { - Field field = instance.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - Object o = field.get(instance); - return fieldType.cast(o); - } catch (Throwable t) { - String msg = "Unable to read field " + instance.getClass().getName() + - "#" + fieldName + ": " + t.getMessage(); - throw new IllegalStateException(msg, t); - } - } - - /** - * @since 1.0 - */ - private interface ClassLoaderAccessor { - Class loadClass(String fqcn); - - URL getResource(String name); - - InputStream getResourceStream(String name); - } - - /** - * @since 1.0 - */ - private static abstract class ExceptionIgnoringAccessor implements ClassLoaderAccessor { - - public Class loadClass(String fqcn) { - Class clazz = null; - ClassLoader cl = getClassLoader(); - if (cl != null) { - try { - clazz = cl.loadClass(fqcn); - } catch (ClassNotFoundException e) { - //Class couldn't be found by loader - } - } - return clazz; - } - - @Override - public URL getResource(String name) { - URL url = null; - ClassLoader cl = getClassLoader(); - if (cl != null) { - url = cl.getResource(name); - } - return url; - } - - public InputStream getResourceStream(String name) { - InputStream is = null; - ClassLoader cl = getClassLoader(); - if (cl != null) { - is = cl.getResourceAsStream(name); - } - return is; - } - - protected final ClassLoader getClassLoader() { - try { - return doGetClassLoader(); - } catch (Throwable t) { - //Unable to get ClassLoader - } - return null; - } - - protected abstract ClassLoader doGetClassLoader() throws Throwable; - } -} - diff --git a/io/jsonwebtoken/lang/CollectionMutator.java b/io/jsonwebtoken/lang/CollectionMutator.java deleted file mode 100644 index 6978ac8..0000000 --- a/io/jsonwebtoken/lang/CollectionMutator.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -import java.util.Collection; - -/** - * Mutation (modifications) to a {@link java.util.Collection} instance while also supporting method chaining. The - * {@link Collection#add(Object)}, {@link Collection#addAll(Collection)}, {@link Collection#remove(Object)}, and - * {@link Collection#clear()} methods do not support method chaining, so this interface enables that behavior. - * - * @param the type of elements in the collection - * @param the mutator subtype, for method chaining - * @since 0.12.0 - */ -public interface CollectionMutator> { - - /** - * Adds the specified element to the collection. - * - * @param e the element to add. - * @return the mutator/builder for method chaining. - */ - M add(E e); - - /** - * Adds the elements to the collection in iteration order. - * - * @param c the collection to add - * @return the mutator/builder for method chaining. - */ - M add(Collection c); - - /** - * Removes all elements in the collection. - * - * @return the mutator/builder for method chaining. - */ - M clear(); - - /** - * Removes the specified element from the collection. - * - * @param e the element to remove. - * @return the mutator/builder for method chaining. - */ - M remove(E e); -} diff --git a/io/jsonwebtoken/lang/Collections.java b/io/jsonwebtoken/lang/Collections.java deleted file mode 100644 index 01768fc..0000000 --- a/io/jsonwebtoken/lang/Collections.java +++ /dev/null @@ -1,576 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - -/** - * Utility methods for working with {@link Collection}s, {@link List}s, {@link Set}s, and {@link Maps}. - */ -@SuppressWarnings({"unused", "rawtypes"}) -public final class Collections { - - private Collections() { - } //prevent instantiation - - /** - * Returns a type-safe immutable empty {@code List}. - * - * @param list element type - * @return a type-safe immutable empty {@code List}. - */ - public static List emptyList() { - return java.util.Collections.emptyList(); - } - - /** - * Returns a type-safe immutable empty {@code Set}. - * - * @param set element type - * @return a type-safe immutable empty {@code Set}. - */ - @SuppressWarnings("unused") - public static Set emptySet() { - return java.util.Collections.emptySet(); - } - - /** - * Returns a type-safe immutable empty {@code Map}. - * - * @param map key type - * @param map value type - * @return a type-safe immutable empty {@code Map}. - */ - @SuppressWarnings("unused") - public static Map emptyMap() { - return java.util.Collections.emptyMap(); - } - - /** - * Returns a type-safe immutable {@code List} containing the specified array elements. - * - * @param elements array elements to include in the list - * @param list element type - * @return a type-safe immutable {@code List} containing the specified array elements. - */ - @SafeVarargs - public static List of(T... elements) { - if (elements == null || elements.length == 0) { - return java.util.Collections.emptyList(); - } - return java.util.Collections.unmodifiableList(Arrays.asList(elements)); - } - - /** - * Returns the specified collection as a {@link Set} instance. - * - * @param c the collection to represent as a set - * @param collection element type - * @return a type-safe immutable {@code Set} containing the specified collection elements. - * @since 0.12.0 - */ - public static Set asSet(Collection c) { - if (c instanceof Set) { - return (Set) c; - } - if (isEmpty(c)) { - return java.util.Collections.emptySet(); - } - return java.util.Collections.unmodifiableSet(new LinkedHashSet<>(c)); - } - - /** - * Returns a type-safe immutable {@code Set} containing the specified array elements. - * - * @param elements array elements to include in the set - * @param set element type - * @return a type-safe immutable {@code Set} containing the specified array elements. - */ - @SafeVarargs - public static Set setOf(T... elements) { - if (elements == null || elements.length == 0) { - return java.util.Collections.emptySet(); - } - Set set = new LinkedHashSet<>(Arrays.asList(elements)); - return immutable(set); - } - - /** - * Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableList(List)} so both classes - * don't need to be imported. - * - * @param m map to wrap in an immutable/unmodifiable collection - * @param map key type - * @param map value type - * @return an immutable wrapper for {@code m}. - * @since 0.12.0 - */ - public static Map immutable(Map m) { - return m != null ? java.util.Collections.unmodifiableMap(m) : null; - } - - /** - * Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableSet(Set)} so both classes don't - * need to be imported. - * - * @param set set to wrap in an immutable Set - * @param set element type - * @return an immutable wrapper for {@code set} - */ - public static Set immutable(Set set) { - return set != null ? java.util.Collections.unmodifiableSet(set) : null; - } - - /** - * Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableList(List)} so both classes - * don't need to be imported. - * - * @param list list to wrap in an immutable List - * @param list element type - * @return an immutable wrapper for {@code list} - */ - public static List immutable(List list) { - return list != null ? java.util.Collections.unmodifiableList(list) : null; - } - - /** - * Null-safe factory method that returns an immutable/unmodifiable view of the specified collection instance. - * Works for {@link List}, {@link Set} and {@link Collection} arguments. - * - * @param c collection to wrap in an immutable/unmodifiable collection - * @param type of collection - * @param type of elements in the collection - * @return an immutable wrapper for {@code l}. - * @since 0.12.0 - */ - @SuppressWarnings("unchecked") - public static > C immutable(C c) { - if (c == null) { - return null; - } else if (c instanceof Set) { - return (C) java.util.Collections.unmodifiableSet((Set) c); - } else if (c instanceof List) { - return (C) java.util.Collections.unmodifiableList((List) c); - } else { - return (C) java.util.Collections.unmodifiableCollection(c); - } - } - - /** - * Returns a non-null set, either {@code s} if it is not null, or {@link #emptySet()} otherwise. - * - * @param s the set to check for null - * @param type of elements in the set - * @return a non-null set, either {@code s} if it is not null, or {@link #emptySet()} otherwise. - * @since 0.12.0 - */ - public static Set nullSafe(Set s) { - return s == null ? Collections.emptySet() : s; - } - - /** - * Returns a non-null collection, either {@code c} if it is not null, or {@link #emptyList()} otherwise. - * - * @param c the collection to check for null - * @param type of elements in the collection - * @return a non-null collection, either {@code c} if it is not null, or {@link #emptyList()} otherwise. - * @since 0.12.0 - */ - public static Collection nullSafe(Collection c) { - return c == null ? Collections.emptyList() : c; - } - - /** - * Return true if the supplied Collection is null - * or empty. Otherwise, return false. - * - * @param collection the Collection to check - * @return whether the given Collection is empty - */ - public static boolean isEmpty(Collection collection) { - return size(collection) == 0; - } - - /** - * Returns the collection's size or {@code 0} if the collection is {@code null}. - * - * @param collection the collection to check. - * @return the collection's size or {@code 0} if the collection is {@code null}. - * @since 0.9.2 - */ - public static int size(Collection collection) { - return collection == null ? 0 : collection.size(); - } - - /** - * Returns the map's size or {@code 0} if the map is {@code null}. - * - * @param map the map to check - * @return the map's size or {@code 0} if the map is {@code null}. - * @since 0.9.2 - */ - public static int size(Map map) { - return map == null ? 0 : map.size(); - } - - /** - * Return true if the supplied Map is null - * or empty. Otherwise, return false. - * - * @param map the Map to check - * @return whether the given Map is empty - */ - public static boolean isEmpty(Map map) { - return size(map) == 0; - } - - /** - * Convert the supplied array into a List. A primitive array gets - * converted into a List of the appropriate wrapper type. - *

A null source value will be converted to an - * empty List. - * - * @param source the (potentially primitive) array - * @return the converted List result - * @see Objects#toObjectArray(Object) - */ - public static List arrayToList(Object source) { - return Arrays.asList(Objects.toObjectArray(source)); - } - - /** - * Concatenate the specified set with the specified array elements, resulting in a new {@link LinkedHashSet} with - * the array elements appended to the end of the existing Set. - * - * @param c the set to append to - * @param elements the array elements to append to the end of the set - * @param set element type - * @return a new {@link LinkedHashSet} with the array elements appended to the end of the original set. - */ - @SafeVarargs - public static Set concat(Set c, T... elements) { - int size = Math.max(1, Collections.size(c) + io.jsonwebtoken.lang.Arrays.length(elements)); - Set set = new LinkedHashSet<>(size); - set.addAll(c); - java.util.Collections.addAll(set, elements); - return immutable(set); - } - - /** - * Merge the given array into the given Collection. - * - * @param array the array to merge (may be null) - * @param collection the target Collection to merge the array into - */ - @SuppressWarnings("unchecked") - public static void mergeArrayIntoCollection(Object array, Collection collection) { - if (collection == null) { - throw new IllegalArgumentException("Collection must not be null"); - } - Object[] arr = Objects.toObjectArray(array); - java.util.Collections.addAll(collection, arr); - } - - /** - * Merge the given Properties instance into the given Map, - * copying all properties (key-value pairs) over. - *

Uses Properties.propertyNames() to even catch - * default properties linked into the original Properties instance. - * - * @param props the Properties instance to merge (may be null) - * @param map the target Map to merge the properties into - */ - @SuppressWarnings("unchecked") - public static void mergePropertiesIntoMap(Properties props, Map map) { - if (map == null) { - throw new IllegalArgumentException("Map must not be null"); - } - if (props != null) { - for (Enumeration en = props.propertyNames(); en.hasMoreElements(); ) { - String key = (String) en.nextElement(); - Object value = props.getProperty(key); - if (value == null) { - // Potentially a non-String value... - value = props.get(key); - } - map.put(key, value); - } - } - } - - - /** - * Check whether the given Iterator contains the given element. - * - * @param iterator the Iterator to check - * @param element the element to look for - * @return true if found, false else - */ - public static boolean contains(Iterator iterator, Object element) { - if (iterator != null) { - while (iterator.hasNext()) { - Object candidate = iterator.next(); - if (Objects.nullSafeEquals(candidate, element)) { - return true; - } - } - } - return false; - } - - /** - * Check whether the given Enumeration contains the given element. - * - * @param enumeration the Enumeration to check - * @param element the element to look for - * @return true if found, false else - */ - public static boolean contains(Enumeration enumeration, Object element) { - if (enumeration != null) { - while (enumeration.hasMoreElements()) { - Object candidate = enumeration.nextElement(); - if (Objects.nullSafeEquals(candidate, element)) { - return true; - } - } - } - return false; - } - - /** - * Check whether the given Collection contains the given element instance. - *

Enforces the given instance to be present, rather than returning - * true for an equal element as well. - * - * @param collection the Collection to check - * @param element the element to look for - * @return true if found, false else - */ - public static boolean containsInstance(Collection collection, Object element) { - if (collection != null) { - for (Object candidate : collection) { - if (candidate == element) { - return true; - } - } - } - return false; - } - - /** - * Return true if any element in 'candidates' is - * contained in 'source'; otherwise returns false. - * - * @param source the source Collection - * @param candidates the candidates to search for - * @return whether any of the candidates has been found - */ - public static boolean containsAny(Collection source, Collection candidates) { - if (isEmpty(source) || isEmpty(candidates)) { - return false; - } - for (Object candidate : candidates) { - if (source.contains(candidate)) { - return true; - } - } - return false; - } - - /** - * Return the first element in 'candidates' that is contained in - * 'source'. If no element in 'candidates' is present in - * 'source' returns null. Iteration order is - * {@link Collection} implementation specific. - * - * @param source the source Collection - * @param candidates the candidates to search for - * @return the first present object, or null if not found - */ - public static Object findFirstMatch(Collection source, Collection candidates) { - if (isEmpty(source) || isEmpty(candidates)) { - return null; - } - for (Object candidate : candidates) { - if (source.contains(candidate)) { - return candidate; - } - } - return null; - } - - /** - * Find a single value of the given type in the given Collection. - * - * @param collection the Collection to search - * @param type the type to look for - * @param the generic type parameter for {@code type} - * @return a value of the given type found if there is a clear match, - * or null if none or more than one such value found - */ - @SuppressWarnings("unchecked") - public static T findValueOfType(Collection collection, Class type) { - if (isEmpty(collection)) { - return null; - } - T value = null; - for (Object element : collection) { - if (type == null || type.isInstance(element)) { - if (value != null) { - // More than one value found... no clear single value. - return null; - } - value = (T) element; - } - } - return value; - } - - /** - * Find a single value of one of the given types in the given Collection: - * searching the Collection for a value of the first type, then - * searching for a value of the second type, etc. - * - * @param collection the collection to search - * @param types the types to look for, in prioritized order - * @return a value of one of the given types found if there is a clear match, - * or null if none or more than one such value found - */ - public static Object findValueOfType(Collection collection, Class[] types) { - if (isEmpty(collection) || Objects.isEmpty(types)) { - return null; - } - for (Class type : types) { - Object value = findValueOfType(collection, type); - if (value != null) { - return value; - } - } - return null; - } - - /** - * Determine whether the given Collection only contains a single unique object. - * - * @param collection the Collection to check - * @return true if the collection contains a single reference or - * multiple references to the same instance, false else - */ - public static boolean hasUniqueObject(Collection collection) { - if (isEmpty(collection)) { - return false; - } - boolean hasCandidate = false; - Object candidate = null; - for (Object elem : collection) { - if (!hasCandidate) { - hasCandidate = true; - candidate = elem; - } else if (candidate != elem) { - return false; - } - } - return true; - } - - /** - * Find the common element type of the given Collection, if any. - * - * @param collection the Collection to check - * @return the common element type, or null if no clear - * common type has been found (or the collection was empty) - */ - public static Class findCommonElementType(Collection collection) { - if (isEmpty(collection)) { - return null; - } - Class candidate = null; - for (Object val : collection) { - if (val != null) { - if (candidate == null) { - candidate = val.getClass(); - } else if (candidate != val.getClass()) { - return null; - } - } - } - return candidate; - } - - /** - * Marshal the elements from the given enumeration into an array of the given type. - * Enumeration elements must be assignable to the type of the given array. The array - * returned will be a different instance than the array given. - * - * @param enumeration the collection to convert to an array - * @param array an array instance that matches the type of array to return - * @param the element type of the array that will be created - * @param the element type contained within the enumeration. - * @return a new array of type {@code A} that contains the elements in the specified {@code enumeration}. - */ - public static A[] toArray(Enumeration enumeration, A[] array) { - ArrayList elements = new ArrayList<>(); - while (enumeration.hasMoreElements()) { - elements.add(enumeration.nextElement()); - } - return elements.toArray(array); - } - - /** - * Adapt an enumeration to an iterator. - * - * @param enumeration the enumeration - * @param the type of elements in the enumeration - * @return the iterator - */ - public static Iterator toIterator(Enumeration enumeration) { - return new EnumerationIterator<>(enumeration); - } - - /** - * Iterator wrapping an Enumeration. - */ - private static class EnumerationIterator implements Iterator { - - private final Enumeration enumeration; - - public EnumerationIterator(Enumeration enumeration) { - this.enumeration = enumeration; - } - - public boolean hasNext() { - return this.enumeration.hasMoreElements(); - } - - public E next() { - return this.enumeration.nextElement(); - } - - public void remove() throws UnsupportedOperationException { - throw new UnsupportedOperationException("Not supported"); - } - } -} - diff --git a/io/jsonwebtoken/lang/Conjunctor.java b/io/jsonwebtoken/lang/Conjunctor.java deleted file mode 100644 index c604752..0000000 --- a/io/jsonwebtoken/lang/Conjunctor.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -/** - * A {@code Conjunctor} supplies a joined object. It is typically used for nested builders to return - * to the source/original builder. - * - * @param the type of joined object to return. - * @since 0.12.0 - */ -public interface Conjunctor { - - /** - * Returns the joined object. - * - * @return the joined object. - */ - T and(); -} diff --git a/io/jsonwebtoken/lang/DateFormats.java b/io/jsonwebtoken/lang/DateFormats.java deleted file mode 100644 index 6a3b501..0000000 --- a/io/jsonwebtoken/lang/DateFormats.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; - -/** - * Utility methods to format and parse date strings. - * - * @since 0.10.0 - */ -public final class DateFormats { - - private DateFormats() { - } // prevent instantiation - - private static final String ISO_8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; - - private static final String ISO_8601_MILLIS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; - - private static final ThreadLocal ISO_8601 = new ThreadLocal() { - @Override - protected DateFormat initialValue() { - SimpleDateFormat format = new SimpleDateFormat(ISO_8601_PATTERN); - format.setTimeZone(TimeZone.getTimeZone("UTC")); - return format; - } - }; - - private static final ThreadLocal ISO_8601_MILLIS = new ThreadLocal() { - @Override - protected DateFormat initialValue() { - SimpleDateFormat format = new SimpleDateFormat(ISO_8601_MILLIS_PATTERN); - format.setTimeZone(TimeZone.getTimeZone("UTC")); - return format; - } - }; - - /** - * Return an ISO-8601-formatted string with millisecond precision representing the - * specified {@code date}. - * - * @param date the date for which to create an ISO-8601-formatted string - * @return the date represented as an ISO-8601-formatted string with millisecond precision. - */ - public static String formatIso8601(Date date) { - return formatIso8601(date, true); - } - - /** - * Returns an ISO-8601-formatted string with optional millisecond precision for the specified - * {@code date}. - * - * @param date the date for which to create an ISO-8601-formatted string - * @param includeMillis whether to include millisecond notation within the string. - * @return the date represented as an ISO-8601-formatted string with optional millisecond precision. - */ - public static String formatIso8601(Date date, boolean includeMillis) { - if (includeMillis) { - return ISO_8601_MILLIS.get().format(date); - } - return ISO_8601.get().format(date); - } - - /** - * Parse the specified ISO-8601-formatted date string and return the corresponding {@link Date} instance. The - * date string may optionally contain millisecond notation, and those milliseconds will be represented accordingly. - * - * @param s the ISO-8601-formatted string to parse - * @return the string's corresponding {@link Date} instance. - * @throws ParseException if the specified date string is not a validly-formatted ISO-8601 string. - */ - public static Date parseIso8601Date(String s) throws ParseException { - Assert.notNull(s, "String argument cannot be null."); - if (s.lastIndexOf('.') > -1) { //assume ISO-8601 with milliseconds - return ISO_8601_MILLIS.get().parse(s); - } else { //assume ISO-8601 without millis: - return ISO_8601.get().parse(s); - } - } -} diff --git a/io/jsonwebtoken/lang/InstantiationException.java b/io/jsonwebtoken/lang/InstantiationException.java deleted file mode 100644 index d6b414d..0000000 --- a/io/jsonwebtoken/lang/InstantiationException.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -/** - * {@link RuntimeException} equivalent of {@link java.lang.InstantiationException}. - * - * @since 0.1 - */ -public class InstantiationException extends RuntimeException { - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public InstantiationException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/lang/MapMutator.java b/io/jsonwebtoken/lang/MapMutator.java deleted file mode 100644 index 5133b30..0000000 --- a/io/jsonwebtoken/lang/MapMutator.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -import java.util.Map; - -/** - * Mutation (modifications) to a {@link Map} instance while also supporting method chaining. The Map interface's - * {@link Map#put(Object, Object)}, {@link Map#remove(Object)}, {@link Map#putAll(Map)}, and {@link Map#clear()} - * mutation methods do not support method chaining, so this interface enables that behavior. - * - * @param map key type - * @param map value type - * @param the mutator subtype, for method chaining - * @since 0.12.0 - */ -public interface MapMutator> { - - /** - * Removes the map entry with the specified key. - *

This method is the same as {@link Map#remove Map.remove}, but instead returns the mutator instance for - * method chaining.

- * - * @param key the key for the map entry to remove. - * @return the mutator/builder for method chaining. - */ - T delete(K key); - - /** - * Removes all entries from the map. The map will be empty after this call returns. - *

This method is the same as {@link Map#clear Map.clear}, but instead returns the mutator instance for - * method chaining.

- * - * @return the mutator/builder for method chaining. - */ - T empty(); - - /** - * Sets the specified key/value pair in the map, overwriting any existing entry with the same key. - * A {@code null} or empty value will remove the entry from the map entirely. - * - *

This method is the same as {@link Map#put Map.put}, but instead returns the mutator instance for - * method chaining.

- * - * @param key the map key - * @param value the value to set for the specified header parameter name - * @return the mutator/builder for method chaining. - */ - T add(K key, V value); - - /** - * Sets the specified key/value pairs in the map, overwriting any existing entries with the same keys. - * If any pair has a {@code null} or empty value, that pair will be removed from the map entirely. - * - *

This method is the same as {@link Map#putAll Map.putAll}, but instead returns the mutator instance for - * method chaining.

- * - * @param m the map to add - * @return the mutator/builder for method chaining. - */ - T add(Map m); -} diff --git a/io/jsonwebtoken/lang/Maps.java b/io/jsonwebtoken/lang/Maps.java deleted file mode 100644 index aef613a..0000000 --- a/io/jsonwebtoken/lang/Maps.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2019 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * Utility class to help with the manipulation of working with Maps. - * - * @since 0.11.0 - */ -public final class Maps { - - private Maps() { - } //prevent instantiation - - /** - * Creates a new map builder with a single entry. - *

Typical usage:

{@code
-     * Map result = Maps.of("key1", value1)
-     *     .and("key2", value2)
-     *     // ...
-     *     .build();
-     * }
- * - * @param key the key of an map entry to be added - * @param value the value of map entry to be added - * @param the maps key type - * @param the maps value type - * @return a new map builder with a single entry. - */ - public static MapBuilder of(K key, V value) { - return new HashMapBuilder().and(key, value); - } - - /** - * Utility Builder class for fluently building maps: - *

Typical usage:

{@code
-     * Map result = Maps.of("key1", value1)
-     *     .and("key2", value2)
-     *     // ...
-     *     .build();
-     * }
- * - * @param the maps key type - * @param the maps value type - */ - public interface MapBuilder extends Builder> { - /** - * Add a new entry to this map builder - * - * @param key the key of an map entry to be added - * @param value the value of map entry to be added - * @return the current MapBuilder to allow for method chaining. - */ - MapBuilder and(K key, V value); - - /** - * Returns the resulting Map object from this MapBuilder. - * - * @return the resulting Map object from this MapBuilder. - */ - Map build(); - } - - private static class HashMapBuilder implements MapBuilder { - - private final Map data = new HashMap<>(); - - public MapBuilder and(K key, V value) { - data.put(key, value); - return this; - } - - public Map build() { - return Collections.unmodifiableMap(data); - } - } -} diff --git a/io/jsonwebtoken/lang/NestedCollection.java b/io/jsonwebtoken/lang/NestedCollection.java deleted file mode 100644 index 2fac66c..0000000 --- a/io/jsonwebtoken/lang/NestedCollection.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -/** - * A {@link CollectionMutator} that can return access to its parent via the {@link Conjunctor#and() and()} method for - * continued configuration. For example: - *
- * builder
- *     .aNestedCollection()// etc...
- *     .and() // return parent
- * // resume parent configuration...
- * - * @param the type of elements in the collection - * @param

the parent to return - * @since 0.12.0 - */ -public interface NestedCollection extends CollectionMutator>, Conjunctor

{ -} diff --git a/io/jsonwebtoken/lang/Objects.java b/io/jsonwebtoken/lang/Objects.java deleted file mode 100644 index 4284713..0000000 --- a/io/jsonwebtoken/lang/Objects.java +++ /dev/null @@ -1,1031 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -import java.io.Closeable; -import java.io.Flushable; -import java.io.IOException; -import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.Collection; -import java.util.Map; - -/** - * Utility methods for working with object instances to reduce pattern repetition and otherwise - * increased cyclomatic complexity. - */ -public final class Objects { - - private Objects() { - } //prevent instantiation - - private static final int INITIAL_HASH = 7; - private static final int MULTIPLIER = 31; - - private static final String EMPTY_STRING = ""; - private static final String NULL_STRING = "null"; - private static final String ARRAY_START = "{"; - private static final String ARRAY_END = "}"; - private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END; - private static final String ARRAY_ELEMENT_SEPARATOR = ", "; - - /** - * Return whether the given throwable is a checked exception: - * that is, neither a RuntimeException nor an Error. - * - * @param ex the throwable to check - * @return whether the throwable is a checked exception - * @see java.lang.Exception - * @see java.lang.RuntimeException - * @see java.lang.Error - */ - public static boolean isCheckedException(Throwable ex) { - return !(ex instanceof RuntimeException || ex instanceof Error); - } - - /** - * Check whether the given exception is compatible with the exceptions - * declared in a throws clause. - * - * @param ex the exception to checked - * @param declaredExceptions the exceptions declared in the throws clause - * @return whether the given exception is compatible - */ - public static boolean isCompatibleWithThrowsClause(Throwable ex, Class[] declaredExceptions) { - if (!isCheckedException(ex)) { - return true; - } - if (declaredExceptions != null) { - int i = 0; - while (i < declaredExceptions.length) { - if (declaredExceptions[i].isAssignableFrom(ex.getClass())) { - return true; - } - i++; - } - } - return false; - } - - /** - * Returns {@code true} if the specified argument is an Object or primitive array, {@code false} otherwise. - * - * @param obj the object instance to check - * @return {@code true} if the specified argument is an Object or primitive array, {@code false} otherwise. - */ - public static boolean isArray(Object obj) { - return (obj != null && obj.getClass().isArray()); - } - - /** - * Returns {@code true} if the specified argument: - *

    - *
  1. is {@code null}, or
  2. - *
  3. is a CharSequence and {@link Strings#hasText(CharSequence)} is {@code false}, or
  4. - *
  5. is a Collection or Map with zero size, or
  6. - *
  7. is an empty array
  8. - *
- *

or {@code false} otherwise.

- * - * @param v object to check - * @return {@code true} if the specified argument is empty, {@code false} otherwise. - * @since 0.12.0 - */ - public static boolean isEmpty(Object v) { - return v == null || - (v instanceof CharSequence && !Strings.hasText((CharSequence) v)) || - (v instanceof Collection && Collections.isEmpty((Collection) v)) || - (v instanceof Map && Collections.isEmpty((Map) v)) || - (v.getClass().isArray() && Array.getLength(v) == 0); - } - - /** - * {@code true} if the specified array is null or zero length, {@code false} if populated. - * - * @param array the array to check - * @return {@code true} if the specified array is null or zero length, {@code false} if populated. - */ - public static boolean isEmpty(Object[] array) { - return (array == null || array.length == 0); - } - - /** - * Returns {@code true} if the specified byte array is null or of zero length, {@code false} if populated. - * - * @param array the byte array to check - * @return {@code true} if the specified byte array is null or of zero length, {@code false} if populated. - */ - public static boolean isEmpty(byte[] array) { - return array == null || array.length == 0; - } - - /** - * Returns {@code true} if the specified character array is null or of zero length, {@code false} otherwise. - * - * @param chars the character array to check - * @return {@code true} if the specified character array is null or of zero length, {@code false} otherwise. - */ - public static boolean isEmpty(char[] chars) { - return chars == null || chars.length == 0; - } - - /** - * Check whether the given array contains the given element. - * - * @param array the array to check (may be null, - * in which case the return value will always be false) - * @param element the element to check for - * @return whether the element has been found in the given array - */ - public static boolean containsElement(Object[] array, Object element) { - if (array == null) { - return false; - } - for (Object arrayEle : array) { - if (nullSafeEquals(arrayEle, element)) { - return true; - } - } - return false; - } - - /** - * Check whether the given array of enum constants contains a constant with the given name, - * ignoring case when determining a match. - * - * @param enumValues the enum values to check, typically the product of a call to MyEnum.values() - * @param constant the constant name to find (must not be null or empty string) - * @return whether the constant has been found in the given array - */ - public static boolean containsConstant(Enum[] enumValues, String constant) { - return containsConstant(enumValues, constant, false); - } - - /** - * Check whether the given array of enum constants contains a constant with the given name. - * - * @param enumValues the enum values to check, typically the product of a call to MyEnum.values() - * @param constant the constant name to find (must not be null or empty string) - * @param caseSensitive whether case is significant in determining a match - * @return whether the constant has been found in the given array - */ - public static boolean containsConstant(Enum[] enumValues, String constant, boolean caseSensitive) { - for (Enum candidate : enumValues) { - if (caseSensitive ? - candidate.toString().equals(constant) : - candidate.toString().equalsIgnoreCase(constant)) { - return true; - } - } - return false; - } - - /** - * Case insensitive alternative to {@link Enum#valueOf(Class, String)}. - * - * @param the concrete Enum type - * @param enumValues the array of all Enum constants in question, usually per Enum.values() - * @param constant the constant to get the enum value of - * @return the enum constant of the specified enum type with the specified case-insensitive name - * @throws IllegalArgumentException if the given constant is not found in the given array - * of enum values. Use {@link #containsConstant(Enum[], String)} as a guard to - * avoid this exception. - */ - public static > E caseInsensitiveValueOf(E[] enumValues, String constant) { - for (E candidate : enumValues) { - if (candidate.toString().equalsIgnoreCase(constant)) { - return candidate; - } - } - throw new IllegalArgumentException( - String.format("constant [%s] does not exist in enum type %s", - constant, enumValues.getClass().getComponentType().getName())); - } - - /** - * Append the given object to the given array, returning a new array - * consisting of the input array contents plus the given object. - * - * @param array the array to append to (can be null) - * @param
the type of each element in the specified {@code array} - * @param obj the object to append - * @param the type of the specified object, which must be equal to or extend the <A> type. - * @return the new array (of the same component type; never null) - */ - public static A[] addObjectToArray(A[] array, O obj) { - Class compType = Object.class; - if (array != null) { - compType = array.getClass().getComponentType(); - } else if (obj != null) { - compType = obj.getClass(); - } - int newArrLength = (array != null ? array.length + 1 : 1); - @SuppressWarnings("unchecked") - A[] newArr = (A[]) Array.newInstance(compType, newArrLength); - if (array != null) { - System.arraycopy(array, 0, newArr, 0, array.length); - } - newArr[newArr.length - 1] = obj; - return newArr; - } - - /** - * Convert the given array (which may be a primitive array) to an - * object array (if necessary of primitive wrapper objects). - *

A null source value will be converted to an - * empty Object array. - * - * @param source the (potentially primitive) array - * @return the corresponding object array (never null) - * @throws IllegalArgumentException if the parameter is not an array - */ - public static Object[] toObjectArray(Object source) { - if (source instanceof Object[]) { - return (Object[]) source; - } - if (source == null) { - return new Object[0]; - } - if (!source.getClass().isArray()) { - throw new IllegalArgumentException("Source is not an array: " + source); - } - int length = Array.getLength(source); - if (length == 0) { - return new Object[0]; - } - Class wrapperType = Array.get(source, 0).getClass(); - Object[] newArray = (Object[]) Array.newInstance(wrapperType, length); - for (int i = 0; i < length; i++) { - newArray[i] = Array.get(source, i); - } - return newArray; - } - - - //--------------------------------------------------------------------- - // Convenience methods for content-based equality/hash-code handling - //--------------------------------------------------------------------- - - /** - * Determine if the given objects are equal, returning true - * if both are null or false if only one is - * null. - *

Compares arrays with Arrays.equals, performing an equality - * check based on the array elements rather than the array reference. - * - * @param o1 first Object to compare - * @param o2 second Object to compare - * @return whether the given objects are equal - * @see java.util.Arrays#equals - */ - public static boolean nullSafeEquals(Object o1, Object o2) { - if (o1 == o2) { - return true; - } - if (o1 == null || o2 == null) { - return false; - } - if (o1.equals(o2)) { - return true; - } - if (o1.getClass().isArray() && o2.getClass().isArray()) { - if (o1 instanceof Object[] && o2 instanceof Object[]) { - return Arrays.equals((Object[]) o1, (Object[]) o2); - } - if (o1 instanceof boolean[] && o2 instanceof boolean[]) { - return Arrays.equals((boolean[]) o1, (boolean[]) o2); - } - if (o1 instanceof byte[] && o2 instanceof byte[]) { - return Arrays.equals((byte[]) o1, (byte[]) o2); - } - if (o1 instanceof char[] && o2 instanceof char[]) { - return Arrays.equals((char[]) o1, (char[]) o2); - } - if (o1 instanceof double[] && o2 instanceof double[]) { - return Arrays.equals((double[]) o1, (double[]) o2); - } - if (o1 instanceof float[] && o2 instanceof float[]) { - return Arrays.equals((float[]) o1, (float[]) o2); - } - if (o1 instanceof int[] && o2 instanceof int[]) { - return Arrays.equals((int[]) o1, (int[]) o2); - } - if (o1 instanceof long[] && o2 instanceof long[]) { - return Arrays.equals((long[]) o1, (long[]) o2); - } - if (o1 instanceof short[] && o2 instanceof short[]) { - return Arrays.equals((short[]) o1, (short[]) o2); - } - } - return false; - } - - /** - * Return as hash code for the given object; typically the value of - * {@link Object#hashCode()}. If the object is an array, - * this method will delegate to any of the nullSafeHashCode - * methods for arrays in this class. If the object is null, - * this method returns 0. - * - * @param obj the object to use for obtaining a hashcode - * @return the object's hashcode, which could be 0 if the object is null. - * @see #nullSafeHashCode(Object[]) - * @see #nullSafeHashCode(boolean[]) - * @see #nullSafeHashCode(byte[]) - * @see #nullSafeHashCode(char[]) - * @see #nullSafeHashCode(double[]) - * @see #nullSafeHashCode(float[]) - * @see #nullSafeHashCode(int[]) - * @see #nullSafeHashCode(long[]) - * @see #nullSafeHashCode(short[]) - */ - public static int nullSafeHashCode(Object obj) { - if (obj == null) { - return 0; - } - if (obj.getClass().isArray()) { - if (obj instanceof Object[]) { - return nullSafeHashCode((Object[]) obj); - } - if (obj instanceof boolean[]) { - return nullSafeHashCode((boolean[]) obj); - } - if (obj instanceof byte[]) { - return nullSafeHashCode((byte[]) obj); - } - if (obj instanceof char[]) { - return nullSafeHashCode((char[]) obj); - } - if (obj instanceof double[]) { - return nullSafeHashCode((double[]) obj); - } - if (obj instanceof float[]) { - return nullSafeHashCode((float[]) obj); - } - if (obj instanceof int[]) { - return nullSafeHashCode((int[]) obj); - } - if (obj instanceof long[]) { - return nullSafeHashCode((long[]) obj); - } - if (obj instanceof short[]) { - return nullSafeHashCode((short[]) obj); - } - } - return obj.hashCode(); - } - - /** - * Return a hash code based on the contents of the specified array. - * If array is null, this method returns 0. - * - * @param array the array to obtain a hashcode - * @return the array's hashcode, which could be 0 if the array is null. - */ - public static int nullSafeHashCode(Object... array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - int arraySize = array.length; - for (int i = 0; i < arraySize; i++) { - hash = MULTIPLIER * hash + nullSafeHashCode(array[i]); - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If array is null, this method returns 0. - * - * @param array the boolean array to obtain a hashcode - * @return the boolean array's hashcode, which could be 0 if the array is null. - */ - public static int nullSafeHashCode(boolean[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - int arraySize = array.length; - for (int i = 0; i < arraySize; i++) { - hash = MULTIPLIER * hash + hashCode(array[i]); - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If array is null, this method returns 0. - * - * @param array the byte array to obtain a hashcode - * @return the byte array's hashcode, which could be 0 if the array is null. - */ - public static int nullSafeHashCode(byte[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - int arraySize = array.length; - for (int i = 0; i < arraySize; i++) { - hash = MULTIPLIER * hash + array[i]; - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If array is null, this method returns 0. - * - * @param array the char array to obtain a hashcode - * @return the char array's hashcode, which could be 0 if the array is null. - */ - public static int nullSafeHashCode(char[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - int arraySize = array.length; - for (int i = 0; i < arraySize; i++) { - hash = MULTIPLIER * hash + array[i]; - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If array is null, this method returns 0. - * - * @param array the double array to obtain a hashcode - * @return the double array's hashcode, which could be 0 if the array is null. - */ - public static int nullSafeHashCode(double[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - int arraySize = array.length; - for (int i = 0; i < arraySize; i++) { - hash = MULTIPLIER * hash + hashCode(array[i]); - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If array is null, this method returns 0. - * - * @param array the float array to obtain a hashcode - * @return the float array's hashcode, which could be 0 if the array is null. - */ - public static int nullSafeHashCode(float[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - int arraySize = array.length; - for (int i = 0; i < arraySize; i++) { - hash = MULTIPLIER * hash + hashCode(array[i]); - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If array is null, this method returns 0. - * - * @param array the int array to obtain a hashcode - * @return the int array's hashcode, which could be 0 if the array is null. - */ - public static int nullSafeHashCode(int[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - int arraySize = array.length; - for (int i = 0; i < arraySize; i++) { - hash = MULTIPLIER * hash + array[i]; - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If array is null, this method returns 0. - * - * @param array the long array to obtain a hashcode - * @return the long array's hashcode, which could be 0 if the array is null. - */ - public static int nullSafeHashCode(long[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - int arraySize = array.length; - for (int i = 0; i < arraySize; i++) { - hash = MULTIPLIER * hash + hashCode(array[i]); - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If array is null, this method returns 0. - * - * @param array the short array to obtain a hashcode - * @return the short array's hashcode, which could be 0 if the array is null. - */ - public static int nullSafeHashCode(short[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - int arraySize = array.length; - for (int i = 0; i < arraySize; i++) { - hash = MULTIPLIER * hash + array[i]; - } - return hash; - } - - /** - * Return the same value as {@link Boolean#hashCode()}. - * - * @param bool the boolean to get a hashcode - * @return the same value as {@link Boolean#hashCode()}. - * @see Boolean#hashCode() - */ - public static int hashCode(boolean bool) { - return bool ? 1231 : 1237; - } - - /** - * Return the same value as {@link Double#hashCode()}. - * - * @param dbl the double to get a hashcode - * @return the same value as {@link Double#hashCode()}. - * @see Double#hashCode() - */ - public static int hashCode(double dbl) { - long bits = Double.doubleToLongBits(dbl); - return hashCode(bits); - } - - /** - * Return the same value as {@link Float#hashCode()}. - * - * @param flt the float to get a hashcode - * @return the same value as {@link Float#hashCode()}. - * @see Float#hashCode() - */ - public static int hashCode(float flt) { - return Float.floatToIntBits(flt); - } - - /** - * Return the same value as {@link Long#hashCode()}. - * - * @param lng the long to get a hashcode - * @return the same value as {@link Long#hashCode()}. - * @see Long#hashCode() - */ - public static int hashCode(long lng) { - return (int) (lng ^ (lng >>> 32)); - } - - - //--------------------------------------------------------------------- - // Convenience methods for toString output - //--------------------------------------------------------------------- - - /** - * Return a String representation of an object's overall identity. - * - * @param obj the object (which may be null). - * @return the object's identity as String representation, or an empty String if the object was null. - */ - public static String identityToString(Object obj) { - if (obj == null) { - return EMPTY_STRING; - } - return obj.getClass().getName() + "@" + getIdentityHexString(obj); - } - - /** - * Return a hex String form of an object's identity hash code. - * - * @param obj the object - * @return the object's identity code in hex notation - */ - public static String getIdentityHexString(Object obj) { - return Integer.toHexString(System.identityHashCode(obj)); - } - - /** - * Return a content-based String representation if obj is - * not null; otherwise returns an empty String. - *

Differs from {@link #nullSafeToString(Object)} in that it returns - * an empty String rather than "null" for a null value. - * - * @param obj the object to build a display String for - * @return a display String representation of obj - * @see #nullSafeToString(Object) - */ - public static String getDisplayString(Object obj) { - if (obj == null) { - return EMPTY_STRING; - } - return nullSafeToString(obj); - } - - /** - * Determine the class name for the given object. - *

Returns "null" if obj is null. - * - * @param obj the object to introspect (may be null) - * @return the corresponding class name - */ - public static String nullSafeClassName(Object obj) { - return (obj != null ? obj.getClass().getName() : NULL_STRING); - } - - /** - * Return a String representation of the specified Object. - *

Builds a String representation of the contents in case of an array. - * Returns "null" if obj is null. - * - * @param obj the object to build a String representation for - * @return a String representation of obj - */ - public static String nullSafeToString(Object obj) { - if (obj == null) { - return NULL_STRING; - } - if (obj instanceof String) { - return (String) obj; - } - if (obj instanceof Object[]) { - return nullSafeToString((Object[]) obj); - } - if (obj instanceof boolean[]) { - return nullSafeToString((boolean[]) obj); - } - if (obj instanceof byte[]) { - return nullSafeToString((byte[]) obj); - } - if (obj instanceof char[]) { - return nullSafeToString((char[]) obj); - } - if (obj instanceof double[]) { - return nullSafeToString((double[]) obj); - } - if (obj instanceof float[]) { - return nullSafeToString((float[]) obj); - } - if (obj instanceof int[]) { - return nullSafeToString((int[]) obj); - } - if (obj instanceof long[]) { - return nullSafeToString((long[]) obj); - } - if (obj instanceof short[]) { - return nullSafeToString((short[]) obj); - } - String str = obj.toString(); - return (str != null ? str : EMPTY_STRING); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ("{}"). Adjacent elements are separated - * by the characters ", " (a comma followed by a space). Returns - * "null" if array is null. - * - * @param array the array to build a String representation for - * @return a String representation of array - */ - public static String nullSafeToString(Object[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - sb.append(String.valueOf(array[i])); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ("{}"). Adjacent elements are separated - * by the characters ", " (a comma followed by a space). Returns - * "null" if array is null. - * - * @param array the array to build a String representation for - * @return a String representation of array - */ - public static String nullSafeToString(boolean[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - - sb.append(array[i]); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ("{}"). Adjacent elements are separated - * by the characters ", " (a comma followed by a space). Returns - * "null" if array is null. - * - * @param array the array to build a String representation for - * @return a String representation of array - */ - public static String nullSafeToString(byte[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - sb.append(array[i]); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ("{}"). Adjacent elements are separated - * by the characters ", " (a comma followed by a space). Returns - * "null" if array is null. - * - * @param array the array to build a String representation for - * @return a String representation of array - */ - public static String nullSafeToString(char[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - sb.append("'").append(array[i]).append("'"); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ("{}"). Adjacent elements are separated - * by the characters ", " (a comma followed by a space). Returns - * "null" if array is null. - * - * @param array the array to build a String representation for - * @return a String representation of array - */ - public static String nullSafeToString(double[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - - sb.append(array[i]); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ("{}"). Adjacent elements are separated - * by the characters ", " (a comma followed by a space). Returns - * "null" if array is null. - * - * @param array the array to build a String representation for - * @return a String representation of array - */ - public static String nullSafeToString(float[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - - sb.append(array[i]); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ("{}"). Adjacent elements are separated - * by the characters ", " (a comma followed by a space). Returns - * "null" if array is null. - * - * @param array the array to build a String representation for - * @return a String representation of array - */ - public static String nullSafeToString(int[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - sb.append(array[i]); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ("{}"). Adjacent elements are separated - * by the characters ", " (a comma followed by a space). Returns - * "null" if array is null. - * - * @param array the array to build a String representation for - * @return a String representation of array - */ - public static String nullSafeToString(long[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - sb.append(array[i]); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ("{}"). Adjacent elements are separated - * by the characters ", " (a comma followed by a space). Returns - * "null" if array is null. - * - * @param array the array to build a String representation for - * @return a String representation of array - */ - public static String nullSafeToString(short[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - sb.append(array[i]); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Iterate over the specified {@link Closeable} instances, invoking - * {@link Closeable#close()} on each one, ignoring any potential {@link IOException}s. - * - * @param closeables the closeables to close. - */ - public static void nullSafeClose(Closeable... closeables) { - if (closeables == null) { - return; - } - - for (Closeable closeable : closeables) { - if (closeable != null) { - try { - closeable.close(); - } catch (IOException e) { - //Ignore the exception during close. - } - } - } - } - - /** - * Iterate over the specified {@link Flushable} instances, invoking - * {@link Flushable#flush()} on each one, ignoring any potential {@link IOException}s. - * - * @param flushables the flushables to flush. - * @since 0.12.0 - */ - public static void nullSafeFlush(Flushable... flushables) { - if (flushables == null) return; - for (Flushable flushable : flushables) { - if (flushable != null) { - try { - flushable.flush(); - } catch (IOException ignored) { - } - } - } - } -} diff --git a/io/jsonwebtoken/lang/Registry.java b/io/jsonwebtoken/lang/Registry.java deleted file mode 100644 index de5849e..0000000 --- a/io/jsonwebtoken/lang/Registry.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright © 2020 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -import java.util.Map; - -/** - * An immutable (read-only) repository of key-value pairs. In addition to {@link Map} read methods, this interface also - * provides guaranteed/expected lookup via the {@link #forKey(Object)} method. - * - *

Immutability

- * - *

Registries are immutable and cannot be changed. {@code Registry} extends the - * {@link Map} interface purely out of convenience: to allow easy key/value - * pair access and iteration, and other conveniences provided by the Map interface, as well as for seamless use with - * existing Map-based APIs. Attempting to call any of - * the {@link Map} interface's mutation methods however (such as {@link Map#put(Object, Object) put}, - * {@link Map#remove(Object) remove}, {@link Map#clear() clear}, etc) will throw an - * {@link UnsupportedOperationException}.

- * - * @param key type - * @param value type - * @since 0.12.0 - */ -public interface Registry extends Map { - - /** - * Returns the value assigned the specified key or throws an {@code IllegalArgumentException} if there is no - * associated value. If a value is not required, consider using the {@link #get(Object)} method instead. - * - * @param key the registry key assigned to the required value - * @return the value assigned the specified key - * @throws IllegalArgumentException if there is no value assigned the specified key - * @see #get(Object) - */ - V forKey(K key) throws IllegalArgumentException; - -} diff --git a/io/jsonwebtoken/lang/RuntimeEnvironment.java b/io/jsonwebtoken/lang/RuntimeEnvironment.java deleted file mode 100644 index 885cef8..0000000 --- a/io/jsonwebtoken/lang/RuntimeEnvironment.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -import java.security.Provider; -import java.security.Security; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * No longer used by JJWT. Will be removed before the 1.0 final release. - * - * @deprecated since 0.12.0. will be removed before the 1.0 final release. - */ -@Deprecated -public final class RuntimeEnvironment { - - private RuntimeEnvironment() { - } //prevent instantiation - - private static final String BC_PROVIDER_CLASS_NAME = "org.bouncycastle.jce.provider.BouncyCastleProvider"; - - private static final AtomicBoolean bcLoaded = new AtomicBoolean(false); - - /** - * {@code true} if BouncyCastle is in the runtime classpath, {@code false} otherwise. - * - * @deprecated since 0.12.0. will be removed before the 1.0 final release. - */ - @Deprecated - public static final boolean BOUNCY_CASTLE_AVAILABLE = Classes.isAvailable(BC_PROVIDER_CLASS_NAME); - - /** - * Register BouncyCastle as a JCA provider in the system's {@link Security#getProviders() Security Providers} list - * if BouncyCastle is in the runtime classpath. - * - * @deprecated since 0.12.0. will be removed before the 1.0 final release. - */ - @Deprecated - public static void enableBouncyCastleIfPossible() { - - if (!BOUNCY_CASTLE_AVAILABLE || bcLoaded.get()) { - return; - } - - try { - Class clazz = Classes.forName(BC_PROVIDER_CLASS_NAME); - - //check to see if the user has already registered the BC provider: - - Provider[] providers = Security.getProviders(); - - for (Provider provider : providers) { - if (clazz.isInstance(provider)) { - bcLoaded.set(true); - return; - } - } - - //bc provider not enabled - add it: - Provider provider = Classes.newInstance(clazz); - Security.addProvider(provider); - bcLoaded.set(true); - - } catch (UnknownClassException e) { - //not available - } - } - - static { - enableBouncyCastleIfPossible(); - } - -} diff --git a/io/jsonwebtoken/lang/Strings.java b/io/jsonwebtoken/lang/Strings.java deleted file mode 100644 index ac66ef1..0000000 --- a/io/jsonwebtoken/lang/Strings.java +++ /dev/null @@ -1,1371 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Properties; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.TreeSet; - -/** - * Utility methods for working with Strings to reduce pattern repetition and otherwise - * increased cyclomatic complexity. - */ -public final class Strings { - - /** - * Empty String, equal to "". - */ - public static final String EMPTY = ""; - - private static final CharBuffer EMPTY_BUF = CharBuffer.wrap(EMPTY); - - private static final String FOLDER_SEPARATOR = "/"; - - private static final String WINDOWS_FOLDER_SEPARATOR = "\\"; - - private static final String TOP_PATH = ".."; - - private static final String CURRENT_PATH = "."; - - private static final char EXTENSION_SEPARATOR = '.'; - - /** - * Convenience alias for {@link StandardCharsets#UTF_8}. - */ - public static final Charset UTF_8 = StandardCharsets.UTF_8; - - private Strings() { - } //prevent instantiation - - //--------------------------------------------------------------------- - // General convenience methods for working with Strings - //--------------------------------------------------------------------- - - /** - * Check that the given CharSequence is neither null nor of length 0. - * Note: Will return true for a CharSequence that purely consists of whitespace. - *
-     * Strings.hasLength(null) = false
-     * Strings.hasLength("") = false
-     * Strings.hasLength(" ") = true
-     * Strings.hasLength("Hello") = true
-     * 
- * - * @param str the CharSequence to check (may be null) - * @return true if the CharSequence is not null and has length - * @see #hasText(String) - */ - public static boolean hasLength(CharSequence str) { - return (str != null && str.length() > 0); - } - - /** - * Check that the given String is neither null nor of length 0. - * Note: Will return true for a String that purely consists of whitespace. - * - * @param str the String to check (may be null) - * @return true if the String is not null and has length - * @see #hasLength(CharSequence) - */ - public static boolean hasLength(String str) { - return hasLength((CharSequence) str); - } - - /** - * Check whether the given CharSequence has actual text. - * More specifically, returns true if the string not null, - * its length is greater than 0, and it contains at least one non-whitespace character. - *
-     * Strings.hasText(null) = false
-     * Strings.hasText("") = false
-     * Strings.hasText(" ") = false
-     * Strings.hasText("12345") = true
-     * Strings.hasText(" 12345 ") = true
-     * 
- * - * @param str the CharSequence to check (may be null) - * @return true if the CharSequence is not null, - * its length is greater than 0, and it does not contain whitespace only - * @see java.lang.Character#isWhitespace - */ - public static boolean hasText(CharSequence str) { - if (!hasLength(str)) { - return false; - } - int strLen = str.length(); - for (int i = 0; i < strLen; i++) { - if (!Character.isWhitespace(str.charAt(i))) { - return true; - } - } - return false; - } - - /** - * Check whether the given String has actual text. - * More specifically, returns true if the string not null, - * its length is greater than 0, and it contains at least one non-whitespace character. - * - * @param str the String to check (may be null) - * @return true if the String is not null, its length is - * greater than 0, and it does not contain whitespace only - * @see #hasText(CharSequence) - */ - public static boolean hasText(String str) { - return hasText((CharSequence) str); - } - - /** - * Check whether the given CharSequence contains any whitespace characters. - * - * @param str the CharSequence to check (may be null) - * @return true if the CharSequence is not empty and - * contains at least 1 whitespace character - * @see java.lang.Character#isWhitespace - */ - public static boolean containsWhitespace(CharSequence str) { - if (!hasLength(str)) { - return false; - } - int strLen = str.length(); - for (int i = 0; i < strLen; i++) { - if (Character.isWhitespace(str.charAt(i))) { - return true; - } - } - return false; - } - - /** - * Check whether the given String contains any whitespace characters. - * - * @param str the String to check (may be null) - * @return true if the String is not empty and - * contains at least 1 whitespace character - * @see #containsWhitespace(CharSequence) - */ - public static boolean containsWhitespace(String str) { - return containsWhitespace((CharSequence) str); - } - - /** - * Trim leading and trailing whitespace from the given String. - * - * @param str the String to check - * @return the trimmed String - * @see java.lang.Character#isWhitespace - */ - public static String trimWhitespace(String str) { - return (String) trimWhitespace((CharSequence) str); - } - - - private static CharSequence trimWhitespace(CharSequence str) { - if (!hasLength(str)) { - return str; - } - final int length = str.length(); - - int start = 0; - while (start < length && Character.isWhitespace(str.charAt(start))) { - start++; - } - - int end = length; - while (start < length && Character.isWhitespace(str.charAt(end - 1))) { - end--; - } - - return ((start > 0) || (end < length)) ? str.subSequence(start, end) : str; - } - - /** - * Returns the specified string without leading or trailing whitespace, or {@code null} if there are no remaining - * characters. - * - * @param str the string to clean - * @return the specified string without leading or trailing whitespace, or {@code null} if there are no remaining - * characters. - */ - public static String clean(String str) { - CharSequence result = clean((CharSequence) str); - - return result != null ? result.toString() : null; - } - - /** - * Returns the specified {@code CharSequence} without leading or trailing whitespace, or {@code null} if there are - * no remaining characters. - * - * @param str the {@code CharSequence} to clean - * @return the specified string without leading or trailing whitespace, or {@code null} if there are no remaining - * characters. - */ - public static CharSequence clean(CharSequence str) { - str = trimWhitespace(str); - if (!hasLength(str)) { - return null; - } - return str; - } - - /** - * Returns the specified string's UTF-8 bytes, or {@code null} if the string is {@code null}. - * - * @param s the string to obtain UTF-8 bytes - * @return the specified string's UTF-8 bytes, or {@code null} if the string is {@code null}. - * @since 0.12.0 - */ - public static byte[] utf8(CharSequence s) { - if (s == null) return null; - CharBuffer cb = s instanceof CharBuffer ? (CharBuffer) s : CharBuffer.wrap(s); - cb.mark(); - ByteBuffer buf = UTF_8.encode(cb); - byte[] bytes = new byte[buf.remaining()]; - buf.get(bytes); - cb.reset(); - return bytes; - } - - /** - * Returns {@code new String(utf8Bytes, StandardCharsets.UTF_8)}. - * - * @param utf8Bytes UTF-8 bytes to use with the {@code String} constructor. - * @return {@code new String(utf8Bytes, StandardCharsets.UTF_8)}. - * @since 0.12.0 - */ - public static String utf8(byte[] utf8Bytes) { - return new String(utf8Bytes, UTF_8); - } - - /** - * Returns {@code new String(asciiBytes, StandardCharsets.US_ASCII)}. - * - * @param asciiBytes US_ASCII bytes to use with the {@code String} constructor. - * @return {@code new String(asciiBytes, StandardCharsets.US_ASCII)}. - * @since 0.12.0 - */ - public static String ascii(byte[] asciiBytes) { - return new String(asciiBytes, StandardCharsets.US_ASCII); - } - - /** - * Returns the {@link StandardCharsets#US_ASCII US_ASCII}-encoded bytes of the specified {@code CharSequence}. - * - * @param s the {@code CharSequence} to encode to {@code US_ASCII}. - * @return the {@link StandardCharsets#US_ASCII US_ASCII}-encoded bytes of the specified {@code CharSequence}. - */ - public static byte[] ascii(CharSequence s) { - byte[] bytes = null; - if (s != null) { - CharBuffer cb = s instanceof CharBuffer ? (CharBuffer) s : CharBuffer.wrap(s); - ByteBuffer buf = StandardCharsets.US_ASCII.encode(cb); - bytes = new byte[buf.remaining()]; - buf.get(bytes); - } - return bytes; - } - - /** - * Returns a {@code CharBuffer} that wraps {@code seq}, or an empty buffer if {@code seq} is null. If - * {@code seq} is already a {@code CharBuffer}, it is returned unmodified. - * - * @param seq the {@code CharSequence} to wrap. - * @return a {@code CharBuffer} that wraps {@code seq}, or an empty buffer if {@code seq} is null. - */ - public static CharBuffer wrap(CharSequence seq) { - if (!hasLength(seq)) return EMPTY_BUF; - if (seq instanceof CharBuffer) return (CharBuffer) seq; - return CharBuffer.wrap(seq); - } - - /** - * Returns a String representation (1s and 0s) of the specified byte. - * - * @param b the byte to represent as 1s and 0s. - * @return a String representation (1s and 0s) of the specified byte. - */ - public static String toBinary(byte b) { - String bString = Integer.toBinaryString(b & 0xFF); - return String.format("%8s", bString).replace((char) Character.SPACE_SEPARATOR, '0'); - } - - /** - * Returns a String representation (1s and 0s) of the specified byte array. - * - * @param bytes the bytes to represent as 1s and 0s. - * @return a String representation (1s and 0s) of the specified byte array. - */ - public static String toBinary(byte[] bytes) { - StringBuilder sb = new StringBuilder(19); //16 characters + 3 space characters - for (byte b : bytes) { - if (sb.length() > 0) { - sb.append((char) Character.SPACE_SEPARATOR); - } - String val = toBinary(b); - sb.append(val); - } - return sb.toString(); - } - - /** - * Returns a hexadecimal String representation of the specified byte array. - * - * @param bytes the bytes to represent as a hexidecimal string. - * @return a hexadecimal String representation of the specified byte array. - */ - public static String toHex(byte[] bytes) { - StringBuilder result = new StringBuilder(); - for (byte temp : bytes) { - if (result.length() > 0) { - result.append((char) Character.SPACE_SEPARATOR); - } - result.append(String.format("%02x", temp)); - } - return result.toString(); - } - - /** - * Trim all whitespace from the given String: - * leading, trailing, and intermediate characters. - * - * @param str the String to check - * @return the trimmed String - * @see java.lang.Character#isWhitespace - */ - public static String trimAllWhitespace(String str) { - if (!hasLength(str)) { - return str; - } - StringBuilder sb = new StringBuilder(str); - int index = 0; - while (sb.length() > index) { - if (Character.isWhitespace(sb.charAt(index))) { - sb.deleteCharAt(index); - } else { - index++; - } - } - return sb.toString(); - } - - /** - * Trim leading whitespace from the given String. - * - * @param str the String to check - * @return the trimmed String - * @see java.lang.Character#isWhitespace - */ - public static String trimLeadingWhitespace(String str) { - if (!hasLength(str)) { - return str; - } - StringBuilder sb = new StringBuilder(str); - while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) { - sb.deleteCharAt(0); - } - return sb.toString(); - } - - /** - * Trim trailing whitespace from the given String. - * - * @param str the String to check - * @return the trimmed String - * @see java.lang.Character#isWhitespace - */ - public static String trimTrailingWhitespace(String str) { - if (!hasLength(str)) { - return str; - } - StringBuilder sb = new StringBuilder(str); - while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) { - sb.deleteCharAt(sb.length() - 1); - } - return sb.toString(); - } - - /** - * Trim all occurrences of the supplied leading character from the given String. - * - * @param str the String to check - * @param leadingCharacter the leading character to be trimmed - * @return the trimmed String - */ - public static String trimLeadingCharacter(String str, char leadingCharacter) { - if (!hasLength(str)) { - return str; - } - StringBuilder sb = new StringBuilder(str); - while (sb.length() > 0 && sb.charAt(0) == leadingCharacter) { - sb.deleteCharAt(0); - } - return sb.toString(); - } - - /** - * Trim all occurrences of the supplied trailing character from the given String. - * - * @param str the String to check - * @param trailingCharacter the trailing character to be trimmed - * @return the trimmed String - */ - public static String trimTrailingCharacter(String str, char trailingCharacter) { - if (!hasLength(str)) { - return str; - } - StringBuilder sb = new StringBuilder(str); - while (sb.length() > 0 && sb.charAt(sb.length() - 1) == trailingCharacter) { - sb.deleteCharAt(sb.length() - 1); - } - return sb.toString(); - } - - - /** - * Returns {@code true} if the given string starts with the specified case-insensitive prefix, {@code false} otherwise. - * - * @param str the String to check - * @param prefix the prefix to look for - * @return {@code true} if the given string starts with the specified case-insensitive prefix, {@code false} otherwise. - * @see java.lang.String#startsWith - */ - public static boolean startsWithIgnoreCase(String str, String prefix) { - if (str == null || prefix == null) { - return false; - } - if (str.length() < prefix.length()) { - return false; - } - if (str.startsWith(prefix)) { - return true; - } - String lcStr = str.substring(0, prefix.length()).toLowerCase(); - String lcPrefix = prefix.toLowerCase(); - return lcStr.equals(lcPrefix); - } - - /** - * Returns {@code true} if the given string ends with the specified case-insensitive suffix, {@code false} otherwise. - * - * @param str the String to check - * @param suffix the suffix to look for - * @return {@code true} if the given string ends with the specified case-insensitive suffix, {@code false} otherwise. - * @see java.lang.String#endsWith - */ - public static boolean endsWithIgnoreCase(String str, String suffix) { - if (str == null || suffix == null) { - return false; - } - if (str.endsWith(suffix)) { - return true; - } - if (str.length() < suffix.length()) { - return false; - } - - String lcStr = str.substring(str.length() - suffix.length()).toLowerCase(); - String lcSuffix = suffix.toLowerCase(); - return lcStr.equals(lcSuffix); - } - - /** - * Returns {@code true} if the given string matches the given substring at the given index, {@code false} otherwise. - * - * @param str the original string (or StringBuilder) - * @param index the index in the original string to start matching against - * @param substring the substring to match at the given index - * @return {@code true} if the given string matches the given substring at the given index, {@code false} otherwise. - */ - public static boolean substringMatch(CharSequence str, int index, CharSequence substring) { - for (int j = 0; j < substring.length(); j++) { - int i = index + j; - if (i >= str.length() || str.charAt(i) != substring.charAt(j)) { - return false; - } - } - return true; - } - - /** - * Returns the number of occurrences the substring {@code sub} appears in string {@code str}. - * - * @param str string to search in. Return 0 if this is null. - * @param sub string to search for. Return 0 if this is null. - * @return the number of occurrences the substring {@code sub} appears in string {@code str}. - */ - public static int countOccurrencesOf(String str, String sub) { - if (str == null || sub == null || str.length() == 0 || sub.length() == 0) { - return 0; - } - int count = 0; - int pos = 0; - int idx; - while ((idx = str.indexOf(sub, pos)) != -1) { - ++count; - pos = idx + sub.length(); - } - return count; - } - - /** - * Replace all occurrences of a substring within a string with - * another string. - * - * @param inString String to examine - * @param oldPattern String to replace - * @param newPattern String to insert - * @return a String with the replacements - */ - public static String replace(String inString, String oldPattern, String newPattern) { - if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) { - return inString; - } - StringBuilder sb = new StringBuilder(); - int pos = 0; // our position in the old string - int index = inString.indexOf(oldPattern); - // the index of an occurrence we've found, or -1 - int patLen = oldPattern.length(); - while (index >= 0) { - sb.append(inString.substring(pos, index)); - sb.append(newPattern); - pos = index + patLen; - index = inString.indexOf(oldPattern, pos); - } - sb.append(inString.substring(pos)); - // remember to append any characters to the right of a match - return sb.toString(); - } - - /** - * Delete all occurrences of the given substring. - * - * @param inString the original String - * @param pattern the pattern to delete all occurrences of - * @return the resulting String - */ - public static String delete(String inString, String pattern) { - return replace(inString, pattern, ""); - } - - /** - * Delete any character in a given String. - * - * @param inString the original String - * @param charsToDelete a set of characters to delete. - * E.g. "az\n" will delete 'a's, 'z's and new lines. - * @return the resulting String - */ - public static String deleteAny(String inString, String charsToDelete) { - if (!hasLength(inString) || !hasLength(charsToDelete)) { - return inString; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < inString.length(); i++) { - char c = inString.charAt(i); - if (charsToDelete.indexOf(c) == -1) { - sb.append(c); - } - } - return sb.toString(); - } - - - //--------------------------------------------------------------------- - // Convenience methods for working with formatted Strings - //--------------------------------------------------------------------- - - /** - * Quote the given String with single quotes. - * - * @param str the input String (e.g. "myString") - * @return the quoted String (e.g. "'myString'"), - * or null if the input was null - */ - public static String quote(String str) { - return (str != null ? "'" + str + "'" : null); - } - - /** - * Turn the given Object into a String with single quotes - * if it is a String; keeping the Object as-is else. - * - * @param obj the input Object (e.g. "myString") - * @return the quoted String (e.g. "'myString'"), - * or the input object as-is if not a String - */ - public static Object quoteIfString(Object obj) { - return (obj instanceof String ? quote((String) obj) : obj); - } - - /** - * Unqualify a string qualified by a '.' dot character. For example, - * "this.name.is.qualified", returns "qualified". - * - * @param qualifiedName the qualified name - * @return an unqualified string by stripping all previous text before (and including) the last period character. - */ - public static String unqualify(String qualifiedName) { - return unqualify(qualifiedName, '.'); - } - - /** - * Unqualify a string qualified by a separator character. For example, - * "this:name:is:qualified" returns "qualified" if using a ':' separator. - * - * @param qualifiedName the qualified name - * @param separator the separator - * @return an unqualified string by stripping all previous text before and including the last {@code separator} character. - */ - public static String unqualify(String qualifiedName, char separator) { - return qualifiedName.substring(qualifiedName.lastIndexOf(separator) + 1); - } - - /** - * Capitalize a String, changing the first letter to - * upper case as per {@link Character#toUpperCase(char)}. - * No other letters are changed. - * - * @param str the String to capitalize, may be null - * @return the capitalized String, null if null - */ - public static String capitalize(String str) { - return changeFirstCharacterCase(str, true); - } - - /** - * Uncapitalize a String, changing the first letter to - * lower case as per {@link Character#toLowerCase(char)}. - * No other letters are changed. - * - * @param str the String to uncapitalize, may be null - * @return the uncapitalized String, null if null - */ - public static String uncapitalize(String str) { - return changeFirstCharacterCase(str, false); - } - - private static String changeFirstCharacterCase(String str, boolean capitalize) { - if (str == null || str.length() == 0) { - return str; - } - StringBuilder sb = new StringBuilder(str.length()); - if (capitalize) { - sb.append(Character.toUpperCase(str.charAt(0))); - } else { - sb.append(Character.toLowerCase(str.charAt(0))); - } - sb.append(str.substring(1)); - return sb.toString(); - } - - /** - * Extract the filename from the given path, - * e.g. "mypath/myfile.txt" -> "myfile.txt". - * - * @param path the file path (may be null) - * @return the extracted filename, or null if none - */ - public static String getFilename(String path) { - if (path == null) { - return null; - } - int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); - return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path); - } - - /** - * Extract the filename extension from the given path, - * e.g. "mypath/myfile.txt" -> "txt". - * - * @param path the file path (may be null) - * @return the extracted filename extension, or null if none - */ - public static String getFilenameExtension(String path) { - if (path == null) { - return null; - } - int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR); - if (extIndex == -1) { - return null; - } - int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR); - if (folderIndex > extIndex) { - return null; - } - return path.substring(extIndex + 1); - } - - /** - * Strip the filename extension from the given path, - * e.g. "mypath/myfile.txt" -> "mypath/myfile". - * - * @param path the file path (may be null) - * @return the path with stripped filename extension, - * or null if none - */ - public static String stripFilenameExtension(String path) { - if (path == null) { - return null; - } - int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR); - if (extIndex == -1) { - return path; - } - int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR); - if (folderIndex > extIndex) { - return path; - } - return path.substring(0, extIndex); - } - - /** - * Apply the given relative path to the given path, - * assuming standard Java folder separation (i.e. "/" separators). - * - * @param path the path to start from (usually a full file path) - * @param relativePath the relative path to apply - * (relative to the full file path above) - * @return the full file path that results from applying the relative path - */ - public static String applyRelativePath(String path, String relativePath) { - int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); - if (separatorIndex != -1) { - String newPath = path.substring(0, separatorIndex); - if (!relativePath.startsWith(FOLDER_SEPARATOR)) { - newPath += FOLDER_SEPARATOR; - } - return newPath + relativePath; - } else { - return relativePath; - } - } - - /** - * Normalize the path by suppressing sequences like "path/.." and - * inner simple dots. - *

The result is convenient for path comparison. For other uses, - * notice that Windows separators ("\") are replaced by simple slashes. - * - * @param path the original path - * @return the normalized path - */ - public static String cleanPath(String path) { - if (path == null) { - return null; - } - String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR); - - // Strip prefix from path to analyze, to not treat it as part of the - // first path element. This is necessary to correctly parse paths like - // "file:core/../core/io/Resource.class", where the ".." should just - // strip the first "core" directory while keeping the "file:" prefix. - int prefixIndex = pathToUse.indexOf(":"); - String prefix = ""; - if (prefixIndex != -1) { - prefix = pathToUse.substring(0, prefixIndex + 1); - pathToUse = pathToUse.substring(prefixIndex + 1); - } - if (pathToUse.startsWith(FOLDER_SEPARATOR)) { - prefix = prefix + FOLDER_SEPARATOR; - pathToUse = pathToUse.substring(1); - } - - String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR); - List pathElements = new LinkedList(); - int tops = 0; - - for (int i = pathArray.length - 1; i >= 0; i--) { - String element = pathArray[i]; - if (CURRENT_PATH.equals(element)) { - // Points to current directory - drop it. - } else if (TOP_PATH.equals(element)) { - // Registering top path found. - tops++; - } else { - if (tops > 0) { - // Merging path element with element corresponding to top path. - tops--; - } else { - // Normal path element found. - pathElements.add(0, element); - } - } - } - - // Remaining top paths need to be retained. - for (int i = 0; i < tops; i++) { - pathElements.add(0, TOP_PATH); - } - - return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR); - } - - /** - * Compare two paths after normalization of them. - * - * @param path1 first path for comparison - * @param path2 second path for comparison - * @return whether the two paths are equivalent after normalization - */ - public static boolean pathEquals(String path1, String path2) { - return cleanPath(path1).equals(cleanPath(path2)); - } - - /** - * Parse the given localeString value into a {@link java.util.Locale}. - *

This is the inverse operation of {@link java.util.Locale#toString Locale's toString}. - * - * @param localeString the locale string, following Locale's - * toString() format ("en", "en_UK", etc); - * also accepts spaces as separators, as an alternative to underscores - * @return a corresponding Locale instance - */ - public static Locale parseLocaleString(String localeString) { - String[] parts = tokenizeToStringArray(localeString, "_ ", false, false); - String language = (parts.length > 0 ? parts[0] : ""); - String country = (parts.length > 1 ? parts[1] : ""); - validateLocalePart(language); - validateLocalePart(country); - String variant = ""; - if (parts.length >= 2) { - // There is definitely a variant, and it is everything after the country - // code sans the separator between the country code and the variant. - int endIndexOfCountryCode = localeString.indexOf(country) + country.length(); - // Strip off any leading '_' and whitespace, what's left is the variant. - variant = trimLeadingWhitespace(localeString.substring(endIndexOfCountryCode)); - if (variant.startsWith("_")) { - variant = trimLeadingCharacter(variant, '_'); - } - } - return (language.length() > 0 ? new Locale(language, country, variant) : null); - } - - private static void validateLocalePart(String localePart) { - for (int i = 0; i < localePart.length(); i++) { - char ch = localePart.charAt(i); - if (ch != '_' && ch != ' ' && !Character.isLetterOrDigit(ch)) { - throw new IllegalArgumentException("Locale part \"" + localePart + "\" contains invalid characters"); - } - } - } - - /** - * Determine the RFC 3066 compliant language tag, - * as used for the HTTP "Accept-Language" header. - * - * @param locale the Locale to transform to a language tag - * @return the RFC 3066 compliant language tag as String - */ - public static String toLanguageTag(Locale locale) { - return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : ""); - } - - - //--------------------------------------------------------------------- - // Convenience methods for working with String arrays - //--------------------------------------------------------------------- - - /** - * Append the given String to the given String array, returning a new array - * consisting of the input array contents plus the given String. - * - * @param array the array to append to (can be null) - * @param str the String to append - * @return the new array (never null) - */ - public static String[] addStringToArray(String[] array, String str) { - if (Objects.isEmpty(array)) { - return new String[]{str}; - } - String[] newArr = new String[array.length + 1]; - System.arraycopy(array, 0, newArr, 0, array.length); - newArr[array.length] = str; - return newArr; - } - - /** - * Concatenate the given String arrays into one, - * with overlapping array elements included twice. - *

The order of elements in the original arrays is preserved. - * - * @param array1 the first array (can be null) - * @param array2 the second array (can be null) - * @return the new array (null if both given arrays were null) - */ - public static String[] concatenateStringArrays(String[] array1, String[] array2) { - if (Objects.isEmpty(array1)) { - return array2; - } - if (Objects.isEmpty(array2)) { - return array1; - } - String[] newArr = new String[array1.length + array2.length]; - System.arraycopy(array1, 0, newArr, 0, array1.length); - System.arraycopy(array2, 0, newArr, array1.length, array2.length); - return newArr; - } - - /** - * Merge the given String arrays into one, with overlapping - * array elements only included once. - *

The order of elements in the original arrays is preserved - * (with the exception of overlapping elements, which are only - * included on their first occurrence). - * - * @param array1 the first array (can be null) - * @param array2 the second array (can be null) - * @return the new array (null if both given arrays were null) - */ - public static String[] mergeStringArrays(String[] array1, String[] array2) { - if (Objects.isEmpty(array1)) { - return array2; - } - if (Objects.isEmpty(array2)) { - return array1; - } - List result = new ArrayList(); - result.addAll(Arrays.asList(array1)); - for (String str : array2) { - if (!result.contains(str)) { - result.add(str); - } - } - return toStringArray(result); - } - - /** - * Turn given source String array into sorted array. - * - * @param array the source array - * @return the sorted array (never null) - */ - public static String[] sortStringArray(String[] array) { - if (Objects.isEmpty(array)) { - return new String[0]; - } - Arrays.sort(array); - return array; - } - - /** - * Copy the given Collection into a String array. - * The Collection must contain String elements only. - * - * @param collection the Collection to copy - * @return the String array (null if the passed-in - * Collection was null) - */ - public static String[] toStringArray(Collection collection) { - if (collection == null) { - return null; - } - return collection.toArray(new String[collection.size()]); - } - - /** - * Copy the given Enumeration into a String array. - * The Enumeration must contain String elements only. - * - * @param enumeration the Enumeration to copy - * @return the String array (null if the passed-in - * Enumeration was null) - */ - public static String[] toStringArray(Enumeration enumeration) { - if (enumeration == null) { - return null; - } - List list = java.util.Collections.list(enumeration); - return list.toArray(new String[list.size()]); - } - - /** - * Trim the elements of the given String array, - * calling String.trim() on each of them. - * - * @param array the original String array - * @return the resulting array (of the same size) with trimmed elements - */ - public static String[] trimArrayElements(String[] array) { - if (Objects.isEmpty(array)) { - return new String[0]; - } - String[] result = new String[array.length]; - for (int i = 0; i < array.length; i++) { - String element = array[i]; - result[i] = (element != null ? element.trim() : null); - } - return result; - } - - /** - * Remove duplicate Strings from the given array. - * Also sorts the array, as it uses a TreeSet. - * - * @param array the String array - * @return an array without duplicates, in natural sort order - */ - public static String[] removeDuplicateStrings(String[] array) { - if (Objects.isEmpty(array)) { - return array; - } - Set set = new TreeSet(); - for (String element : array) { - set.add(element); - } - return toStringArray(set); - } - - /** - * Split a String at the first occurrence of the delimiter. - * Does not include the delimiter in the result. - * - * @param toSplit the string to split - * @param delimiter to split the string up with - * @return a two element array with index 0 being before the delimiter, and - * index 1 being after the delimiter (neither element includes the delimiter); - * or null if the delimiter wasn't found in the given input String - */ - public static String[] split(String toSplit, String delimiter) { - if (!hasLength(toSplit) || !hasLength(delimiter)) { - return null; - } - int offset = toSplit.indexOf(delimiter); - if (offset < 0) { - return null; - } - String beforeDelimiter = toSplit.substring(0, offset); - String afterDelimiter = toSplit.substring(offset + delimiter.length()); - return new String[]{beforeDelimiter, afterDelimiter}; - } - - /** - * Take an array Strings and split each element based on the given delimiter. - * A Properties instance is then generated, with the left of the - * delimiter providing the key, and the right of the delimiter providing the value. - *

Will trim both the key and value before adding them to the - * Properties instance. - * - * @param array the array to process - * @param delimiter to split each element using (typically the equals symbol) - * @return a Properties instance representing the array contents, - * or null if the array to process was null or empty - */ - public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter) { - return splitArrayElementsIntoProperties(array, delimiter, null); - } - - /** - * Take an array Strings and split each element based on the given delimiter. - * A Properties instance is then generated, with the left of the - * delimiter providing the key, and the right of the delimiter providing the value. - *

Will trim both the key and value before adding them to the - * Properties instance. - * - * @param array the array to process - * @param delimiter to split each element using (typically the equals symbol) - * @param charsToDelete one or more characters to remove from each element - * prior to attempting the split operation (typically the quotation mark - * symbol), or null if no removal should occur - * @return a Properties instance representing the array contents, - * or null if the array to process was null or empty - */ - public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter, String charsToDelete) { - - if (Objects.isEmpty(array)) { - return null; - } - Properties result = new Properties(); - for (String element : array) { - if (charsToDelete != null) { - element = deleteAny(element, charsToDelete); - } - String[] splittedElement = split(element, delimiter); - if (splittedElement == null) { - continue; - } - result.setProperty(splittedElement[0].trim(), splittedElement[1].trim()); - } - return result; - } - - /** - * Tokenize the given String into a String array via a StringTokenizer. - * Trims tokens and omits empty tokens. - *

The given delimiters string is supposed to consist of any number of - * delimiter characters. Each of those characters can be used to separate - * tokens. A delimiter is always a single character; for multi-character - * delimiters, consider using delimitedListToStringArray - * - * @param str the String to tokenize - * @param delimiters the delimiter characters, assembled as String - * (each of those characters is individually considered as delimiter). - * @return an array of the tokens - * @see java.util.StringTokenizer - * @see java.lang.String#trim() - * @see #delimitedListToStringArray - */ - public static String[] tokenizeToStringArray(String str, String delimiters) { - return tokenizeToStringArray(str, delimiters, true, true); - } - - /** - * Tokenize the given String into a String array via a StringTokenizer. - *

The given delimiters string is supposed to consist of any number of - * delimiter characters. Each of those characters can be used to separate - * tokens. A delimiter is always a single character; for multi-character - * delimiters, consider using delimitedListToStringArray - * - * @param str the String to tokenize - * @param delimiters the delimiter characters, assembled as String - * (each of those characters is individually considered as delimiter) - * @param trimTokens trim the tokens via String's trim - * @param ignoreEmptyTokens omit empty tokens from the result array - * (only applies to tokens that are empty after trimming; StringTokenizer - * will not consider subsequent delimiters as token in the first place). - * @return an array of the tokens (null if the input String - * was null) - * @see java.util.StringTokenizer - * @see java.lang.String#trim() - * @see #delimitedListToStringArray - */ - public static String[] tokenizeToStringArray(String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { - - if (str == null) { - return null; - } - StringTokenizer st = new StringTokenizer(str, delimiters); - List tokens = new ArrayList(); - while (st.hasMoreTokens()) { - String token = st.nextToken(); - if (trimTokens) { - token = token.trim(); - } - if (!ignoreEmptyTokens || token.length() > 0) { - tokens.add(token); - } - } - return toStringArray(tokens); - } - - /** - * Take a String which is a delimited list and convert it to a String array. - *

A single delimiter can consists of more than one character: It will still - * be considered as single delimiter string, rather than as bunch of potential - * delimiter characters - in contrast to tokenizeToStringArray. - * - * @param str the input String - * @param delimiter the delimiter between elements (this is a single delimiter, - * rather than a bunch individual delimiter characters) - * @return an array of the tokens in the list - * @see #tokenizeToStringArray - */ - public static String[] delimitedListToStringArray(String str, String delimiter) { - return delimitedListToStringArray(str, delimiter, null); - } - - /** - * Take a String which is a delimited list and convert it to a String array. - *

A single delimiter can consists of more than one character: It will still - * be considered as single delimiter string, rather than as bunch of potential - * delimiter characters - in contrast to tokenizeToStringArray. - * - * @param str the input String - * @param delimiter the delimiter between elements (this is a single delimiter, - * rather than a bunch individual delimiter characters) - * @param charsToDelete a set of characters to delete. Useful for deleting unwanted - * line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a String. - * @return an array of the tokens in the list - * @see #tokenizeToStringArray - */ - public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) { - if (str == null) { - return new String[0]; - } - if (delimiter == null) { - return new String[]{str}; - } - List result = new ArrayList(); - if ("".equals(delimiter)) { - for (int i = 0; i < str.length(); i++) { - result.add(deleteAny(str.substring(i, i + 1), charsToDelete)); - } - } else { - int pos = 0; - int delPos; - while ((delPos = str.indexOf(delimiter, pos)) != -1) { - result.add(deleteAny(str.substring(pos, delPos), charsToDelete)); - pos = delPos + delimiter.length(); - } - if (str.length() > 0 && pos <= str.length()) { - // Add rest of String, but not in case of empty input. - result.add(deleteAny(str.substring(pos), charsToDelete)); - } - } - return toStringArray(result); - } - - /** - * Convert a CSV list into an array of Strings. - * - * @param str the input String - * @return an array of Strings, or the empty array in case of empty input - */ - public static String[] commaDelimitedListToStringArray(String str) { - return delimitedListToStringArray(str, ","); - } - - /** - * Convenience method to convert a CSV string list to a set. - * Note that this will suppress duplicates. - * - * @param str the input String - * @return a Set of String entries in the list - */ - public static Set commaDelimitedListToSet(String str) { - Set set = new TreeSet(); - String[] tokens = commaDelimitedListToStringArray(str); - for (String token : tokens) { - set.add(token); - } - return set; - } - - /** - * Convenience method to return a Collection as a delimited (e.g. CSV) - * String. E.g. useful for toString() implementations. - * - * @param coll the Collection to display - * @param delim the delimiter to use (probably a ",") - * @param prefix the String to start each element with - * @param suffix the String to end each element with - * @return the delimited String - */ - public static String collectionToDelimitedString(Collection coll, String delim, String prefix, String suffix) { - if (Collections.isEmpty(coll)) { - return ""; - } - StringBuilder sb = new StringBuilder(); - Iterator it = coll.iterator(); - while (it.hasNext()) { - sb.append(prefix).append(it.next()).append(suffix); - if (it.hasNext()) { - sb.append(delim); - } - } - return sb.toString(); - } - - /** - * Convenience method to return a Collection as a delimited (e.g. CSV) - * String. E.g. useful for toString() implementations. - * - * @param coll the Collection to display - * @param delim the delimiter to use (probably a ",") - * @return the delimited String - */ - public static String collectionToDelimitedString(Collection coll, String delim) { - return collectionToDelimitedString(coll, delim, "", ""); - } - - /** - * Convenience method to return a Collection as a CSV String. - * E.g. useful for toString() implementations. - * - * @param coll the Collection to display - * @return the delimited String - */ - public static String collectionToCommaDelimitedString(Collection coll) { - return collectionToDelimitedString(coll, ","); - } - - /** - * Convenience method to return a String array as a delimited (e.g. CSV) - * String. E.g. useful for toString() implementations. - * - * @param arr the array to display - * @param delim the delimiter to use (probably a ",") - * @return the delimited String - */ - public static String arrayToDelimitedString(Object[] arr, String delim) { - if (Objects.isEmpty(arr)) { - return ""; - } - if (arr.length == 1) { - return Objects.nullSafeToString(arr[0]); - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < arr.length; i++) { - if (i > 0) { - sb.append(delim); - } - sb.append(arr[i]); - } - return sb.toString(); - } - - /** - * Convenience method to return a String array as a CSV String. - * E.g. useful for toString() implementations. - * - * @param arr the array to display - * @return the delimited String - */ - public static String arrayToCommaDelimitedString(Object[] arr) { - return arrayToDelimitedString(arr, ","); - } - - /** - * Appends a space character (' ') if the argument is not empty, otherwise does nothing. This method - * can be thought of as "non-empty space". Using this method allows reduction of this: - *

-     * if (sb.length != 0) {
-     *     sb.append(' ');
-     * }
-     * sb.append(nextWord);
- *

To this:

- *
-     * nespace(sb).append(nextWord);
- * - * @param sb the string builder to append a space to if non-empty - * @return the string builder argument for method chaining. - * @since 0.12.0 - */ - public static StringBuilder nespace(StringBuilder sb) { - if (sb == null) { - return null; - } - if (sb.length() != 0) { - sb.append(' '); - } - return sb; - } - -} - diff --git a/io/jsonwebtoken/lang/Supplier.java b/io/jsonwebtoken/lang/Supplier.java deleted file mode 100644 index 7a94e59..0000000 --- a/io/jsonwebtoken/lang/Supplier.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2022 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -/** - * Represents a supplier of results. - * - *

There is no requirement that a new or distinct result be returned each time the supplier is invoked.

- * - *

This interface is the equivalent of a JDK 8 {@code java.util.function.Supplier}, backported for JJWT's use in - * JDK 7 environments.

- * - * @param the type of object returned by this supplier - * @since 0.12.0 - */ -public interface Supplier { - - /** - * Returns a result. - * - * @return a result. - */ - T get(); -} diff --git a/io/jsonwebtoken/lang/UnknownClassException.java b/io/jsonwebtoken/lang/UnknownClassException.java deleted file mode 100644 index 07b44d9..0000000 --- a/io/jsonwebtoken/lang/UnknownClassException.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.lang; - -/** - * A RuntimeException equivalent of the JDK's - * ClassNotFoundException, to maintain a RuntimeException paradigm. - * - * @since 0.1 - */ -public class UnknownClassException extends RuntimeException { - - /* - /** - * Creates a new UnknownClassException. - * - public UnknownClassException() { - super(); - }*/ - - /** - * Constructs a new UnknownClassException. - * - * @param message the reason for the exception - */ - public UnknownClassException(String message) { - super(message); - } - - /* - * Constructs a new UnknownClassException. - * - * @param cause the underlying Throwable that caused this exception to be thrown. - * - public UnknownClassException(Throwable cause) { - super(cause); - } - */ - - /** - * Constructs a new UnknownClassException. - * - * @param message the reason for the exception - * @param cause the underlying Throwable that caused this exception to be thrown. - */ - public UnknownClassException(String message, Throwable cause) { - // TODO: remove in v1.0, this constructor is only exposed to allow for backward compatible behavior - super(message, cause); - } - -} \ No newline at end of file diff --git a/io/jsonwebtoken/security/AeadAlgorithm.java b/io/jsonwebtoken/security/AeadAlgorithm.java deleted file mode 100644 index 7e82d85..0000000 --- a/io/jsonwebtoken/security/AeadAlgorithm.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.Identifiable; -import io.jsonwebtoken.Jwts; - -import javax.crypto.SecretKey; -import java.io.OutputStream; - -/** - * A cryptographic algorithm that performs - *
Authenticated encryption with additional data. - * Per JWE RFC 7516, Section 4.1.2, all JWEs - * MUST use an AEAD algorithm to encrypt or decrypt the JWE payload/content. Consequently, all - * JWA "enc" algorithms are AEAD - * algorithms, and they are accessible as concrete instances via {@link Jwts.ENC}. - * - *

"enc" identifier

- * - *

{@code AeadAlgorithm} extends {@code Identifiable}: the value returned from {@link Identifiable#getId() getId()} - * will be used as the JWE "enc" protected header value.

- * - *

Key Strength

- * - *

Encryption strength is in part attributed to how difficult it is to discover the encryption key. As such, - * cryptographic algorithms often require keys of a minimum length to ensure the keys are difficult to discover - * and the algorithm's security properties are maintained.

- * - *

The {@code AeadAlgorithm} interface extends the {@link KeyLengthSupplier} interface to represent the length - * in bits a key must have to be used with its implementation. If you do not want to worry about lengths and - * parameters of keys required for an algorithm, it is often easier to automatically generate a key that adheres - * to the algorithms requirements, as discussed below.

- * - *

Key Generation

- * - *

{@code AeadAlgorithm} extends {@link KeyBuilderSupplier} to enable {@link SecretKey} generation. Each AEAD - * algorithm instance will return a {@link KeyBuilder} that ensures any created keys will have a sufficient length - * and algorithm parameters required by that algorithm. For example:

- * - *

- *     SecretKey key = aeadAlgorithm.key().build();
- * 
- * - *

The resulting {@code key} is guaranteed to have the correct algorithm parameters and strength/length necessary for - * that exact {@code aeadAlgorithm} instance.

- * - * @see Jwts.ENC - * @see Identifiable#getId() - * @see KeyLengthSupplier - * @see KeyBuilderSupplier - * @see KeyBuilder - * @since 0.12.0 - */ -public interface AeadAlgorithm extends Identifiable, KeyLengthSupplier, KeyBuilderSupplier { - - /** - * Encrypts plaintext and signs any {@link AeadRequest#getAssociatedData() associated data}, placing the resulting - * ciphertext, initialization vector and authentication tag in the provided {@code result}. - * - * @param req the encryption request representing the plaintext to be encrypted, any additional - * integrity-protected data and the encryption key. - * @param res the result to write ciphertext, initialization vector and AAD authentication tag (aka digest) - * @throws SecurityException if there is an encryption problem or AAD authenticity cannot be guaranteed. - */ - void encrypt(AeadRequest req, AeadResult res) throws SecurityException; - - /** - * Decrypts ciphertext and authenticates any {@link DecryptAeadRequest#getAssociatedData() associated data}, - * writing the decrypted plaintext to the provided {@code out}put stream. - * - * @param request the decryption request representing the ciphertext to be decrypted, any additional - * integrity-protected data, authentication tag, initialization vector, and decryption key - * @param out the OutputStream for writing decrypted plaintext - * @throws SecurityException if there is a decryption problem or authenticity assertions fail. - */ - void decrypt(DecryptAeadRequest request, OutputStream out) throws SecurityException; -} diff --git a/io/jsonwebtoken/security/AeadRequest.java b/io/jsonwebtoken/security/AeadRequest.java deleted file mode 100644 index 8287869..0000000 --- a/io/jsonwebtoken/security/AeadRequest.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import javax.crypto.SecretKey; -import java.io.InputStream; - -/** - * A request to an {@link AeadAlgorithm} to perform authenticated encryption with a supplied symmetric - * {@link SecretKey}, allowing for additional data to be authenticated and integrity-protected. - * - * @see SecureRequest - * @see AssociatedDataSupplier - * @since 0.12.0 - */ -public interface AeadRequest extends SecureRequest, AssociatedDataSupplier { -} diff --git a/io/jsonwebtoken/security/AeadResult.java b/io/jsonwebtoken/security/AeadResult.java deleted file mode 100644 index c8734b5..0000000 --- a/io/jsonwebtoken/security/AeadResult.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.io.OutputStream; - -/** - * The result of authenticated encryption, providing access to the ciphertext {@link #getOutputStream() output stream} - * and resulting {@link #setTag(byte[]) AAD tag} and {@link #setIv(byte[]) initialization vector}. - * The AAD tag and initialization vector must be supplied with the ciphertext to decrypt. - * - * @since 0.12.0 - */ -public interface AeadResult { - - /** - * Returns the {@code OutputStream} the AeadAlgorithm will use to write the resulting ciphertext during - * encryption or plaintext during decryption. - * - * @return the {@code OutputStream} the AeadAlgorithm will use to write the resulting ciphertext during - * encryption or plaintext during decryption. - */ - OutputStream getOutputStream(); - - /** - * Sets the AEAD authentication tag. - * - * @param tag the AEAD authentication tag. - * @return the AeadResult for method chaining. - */ - AeadResult setTag(byte[] tag); - - /** - * Sets the initialization vector used during encryption. - * - * @param iv the initialization vector used during encryption. - * @return the AeadResult for method chaining. - */ - AeadResult setIv(byte[] iv); -} diff --git a/io/jsonwebtoken/security/AssociatedDataSupplier.java b/io/jsonwebtoken/security/AssociatedDataSupplier.java deleted file mode 100644 index 4f5cd37..0000000 --- a/io/jsonwebtoken/security/AssociatedDataSupplier.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.io.InputStream; - -/** - * Provides any "associated data" that must be integrity protected (but not encrypted) when performing - * AEAD encryption or decryption. - * - * @see #getAssociatedData() - * @since 0.12.0 - */ -public interface AssociatedDataSupplier { - - /** - * Returns any data that must be integrity protected (but not encrypted) when performing - * AEAD encryption or decryption, or - * {@code null} if no additional data must be integrity protected. - * - * @return any data that must be integrity protected (but not encrypted) when performing - * AEAD encryption or decryption, or - * {@code null} if no additional data must be integrity protected. - */ - InputStream getAssociatedData(); -} diff --git a/io/jsonwebtoken/security/AsymmetricJwk.java b/io/jsonwebtoken/security/AsymmetricJwk.java deleted file mode 100644 index b69db54..0000000 --- a/io/jsonwebtoken/security/AsymmetricJwk.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.Key; - -/** - * JWK representation of an asymmetric (public or private) cryptographic key. - * - * @param the type of {@link java.security.PublicKey} or {@link java.security.PrivateKey} represented by this JWK. - * @since 0.12.0 - */ -public interface AsymmetricJwk extends Jwk, X509Accessor { - - /** - * Returns the JWK - * {@code use} (Public Key Use) - * parameter value or {@code null} if not present. {@code use} values are CaSe-SeNsItIvE. - * - *

The JWK specification defines the - * following {@code use} values:

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
JWK Key Use Values
ValueKey Use
{@code sig}signature
{@code enc}encryption
- * - *

Other values MAY be used. For best interoperability with other applications however, it is - * recommended to use only the values above.

- * - *

When a key is used to wrap another key and a public key use designation for the first key is desired, the - * {@code enc} (encryption) key use value is used, since key wrapping is a kind of encryption. The - * {@code enc} value is also to be used for public keys used for key agreement operations.

- * - *

Public Key Use vs Key Operations

- * - *

Per - * JWK RFC 7517, Section 4.3, last paragraph, - * the {@code use} (Public Key Use) and {@link #getOperations() key_ops (Key Operations)} members - * SHOULD NOT be used together; however, if both are used, the information they convey MUST be - * consistent. Applications should specify which of these members they use, if either is to be used by the - * application.

- * - * @return the JWK {@code use} value or {@code null} if not present. - */ - String getPublicKeyUse(); -} diff --git a/io/jsonwebtoken/security/AsymmetricJwkBuilder.java b/io/jsonwebtoken/security/AsymmetricJwkBuilder.java deleted file mode 100644 index fe3ce7e..0000000 --- a/io/jsonwebtoken/security/AsymmetricJwkBuilder.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.Key; - -/** - * A {@link JwkBuilder} that builds asymmetric (public or private) JWKs. - * - * @param the type of Java key provided by the JWK. - * @param the type of asymmetric JWK created - * @param the type of the builder, for subtype method chaining - * @since 0.12.0 - */ -public interface AsymmetricJwkBuilder, T extends AsymmetricJwkBuilder> - extends JwkBuilder, X509Builder { - - /** - * Sets the JWK - * {@code use} (Public Key Use) - * parameter value. {@code use} values are CaSe-SeNsItIvE. A {@code null} value will remove the property - * from the JWK. - * - *

The JWK specification defines the - * following {@code use} values:

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
JWK Key Use Values
ValueKey Use
{@code sig}signature
{@code enc}encryption
- * - *

Other values MAY be used. For best interoperability with other applications however, it is - * recommended to use only the values above.

- * - *

When a key is used to wrap another key and a public key use designation for the first key is desired, the - * {@code enc} (encryption) key use value is used, since key wrapping is a kind of encryption. The - * {@code enc} value is also to be used for public keys used for key agreement operations.

- * - *

Public Key Use vs Key Operations

- * - *

Per - * JWK RFC 7517, Section 4.3, last paragraph, - * the use (Public Key Use) and {@link #operations() key_ops (Key Operations)} members - * SHOULD NOT be used together; however, if both are used, the information they convey MUST be - * consistent. Applications should specify which of these members they use, if either is to be used by the - * application.

- * - * @param use the JWK {@code use} value. - * @return the builder for method chaining. - * @throws IllegalArgumentException if the {@code use} value is {@code null} or empty. - */ - T publicKeyUse(String use) throws IllegalArgumentException; -} diff --git a/io/jsonwebtoken/security/Curve.java b/io/jsonwebtoken/security/Curve.java deleted file mode 100644 index 2cc1f42..0000000 --- a/io/jsonwebtoken/security/Curve.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright © 2022 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.Identifiable; - -/** - * A cryptographic Elliptic Curve for use with digital signature or key agreement algorithms. - * - *

Curve Identifier

- * - *

This interface extends {@link Identifiable}; the value returned from {@link #getId()} will - * be used as the JWK - * crv value.

- * - *

KeyPair Generation

- * - *

A secure-random KeyPair of sufficient strength on the curve may be obtained with its {@link #keyPair()} builder.

- * - *

Standard Implementations

- * - *

Constants for all JWA standard Curves are available via the {@link Jwks.CRV} registry.

- * - * @see Jwks.CRV - * @since 0.12.0 - */ -public interface Curve extends Identifiable, KeyPairBuilderSupplier { -} diff --git a/io/jsonwebtoken/security/DecryptAeadRequest.java b/io/jsonwebtoken/security/DecryptAeadRequest.java deleted file mode 100644 index 5faf1f6..0000000 --- a/io/jsonwebtoken/security/DecryptAeadRequest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import javax.crypto.SecretKey; - -/** - * A request to an {@link AeadAlgorithm} to decrypt ciphertext and perform integrity-protection with a supplied - * decryption {@link SecretKey}. Extends both {@link IvSupplier} and {@link DigestSupplier} to - * ensure the respective required IV and AAD tag returned from an {@link AeadResult} are available for decryption. - * - * @since 0.12.0 - */ -public interface DecryptAeadRequest extends AeadRequest, IvSupplier, DigestSupplier { -} diff --git a/io/jsonwebtoken/security/DecryptionKeyRequest.java b/io/jsonwebtoken/security/DecryptionKeyRequest.java deleted file mode 100644 index 893cad9..0000000 --- a/io/jsonwebtoken/security/DecryptionKeyRequest.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.Key; - -/** - * A {@link KeyRequest} to obtain a decryption key that will be used to decrypt a JWE using an {@link AeadAlgorithm}. - * The AEAD algorithm used for decryption is accessible via {@link #getEncryptionAlgorithm()}. - * - *

The key used to perform cryptographic operations, for example a direct shared key, or a - * JWE "key decryption key" will be accessible via {@link #getKey()}. This is always required and - * never {@code null}.

- * - *

Any encrypted key material (what the JWE specification calls the - * JWE Encrypted Key) will - * be accessible via {@link #getPayload()}. If present, the {@link KeyAlgorithm} will decrypt it to obtain the resulting - * Content Encryption Key (CEK). - * This may be empty however depending on which {@link KeyAlgorithm} was used during JWE encryption.

- * - *

Finally, any public information necessary by the called {@link KeyAlgorithm} to decrypt any - * {@code JWE Encrypted Key} (such as an initialization vector, authentication tag, ephemeral key, etc) is expected - * to be available in the JWE protected header, accessible via {@link #getHeader()}.

- * - * @param the type of {@link Key} used during the request to obtain the resulting decryption key. - * @since 0.12.0 - */ -public interface DecryptionKeyRequest extends SecureRequest, KeyRequest { -} diff --git a/io/jsonwebtoken/security/DigestAlgorithm.java b/io/jsonwebtoken/security/DigestAlgorithm.java deleted file mode 100644 index daeaa6a..0000000 --- a/io/jsonwebtoken/security/DigestAlgorithm.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright © 2022 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.Identifiable; -import io.jsonwebtoken.lang.Registry; - -import javax.crypto.SecretKey; -import java.io.InputStream; -import java.security.PrivateKey; -import java.security.PublicKey; - -/** - * A {@code DigestAlgorithm} is a - * Cryptographic Hash Function - * that computes and verifies cryptographic digests. There are three types of {@code DigestAlgorithm}s represented - * by subtypes, and RFC-standard implementations are available as constants in {@link Registry} singletons: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Types of {@code DigestAlgorithm}s
SubtypeStandard Implementation RegistrySecurity Model
{@link HashAlgorithm}{@link Jwks.HASH}Unsecured (unkeyed), does not require a key to compute or verify digests.
{@link MacAlgorithm}{@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}Requires a {@link SecretKey} to both compute and verify digests (aka - * "Message Authentication Codes").
{@link SignatureAlgorithm}{@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}Requires a {@link PrivateKey} to compute and {@link PublicKey} to verify digests - * (aka "Digital Signatures").
- * - *

Standard Identifier

- * - *

{@code DigestAlgorithm} extends {@link Identifiable}: the value returned from - * {@link Identifiable#getId() getId()} will be used as the JWT standard identifier where required.

- * - *

For example, - * when a {@link MacAlgorithm} or {@link SignatureAlgorithm} is used to secure a JWS, the value returned from - * {@code algorithm.getId()} will be used as the JWS "alg" protected header value. Or when a - * {@link HashAlgorithm} is used to compute a {@link JwkThumbprint}, it's {@code algorithm.getId()} value will be - * used within the thumbprint's {@link JwkThumbprint#toURI() URI} per JWT RFC requirements.

- * - * @param the type of {@link Request} used when computing a digest. - * @param the type of {@link VerifyDigestRequest} used when verifying a digest. - * @see Jwks.HASH - * @see io.jsonwebtoken.Jwts.SIG Jwts.SIG - * @since 0.12.0 - */ -public interface DigestAlgorithm, V extends VerifyDigestRequest> extends Identifiable { - - /** - * Returns a cryptographic digest of the request {@link Request#getPayload() payload}. - * - * @param request the request containing the data to be hashed, mac'd or signed. - * @return a cryptographic digest of the request {@link Request#getPayload() payload}. - * @throws SecurityException if there is invalid key input or a problem during digest creation. - */ - byte[] digest(R request) throws SecurityException; - - /** - * Returns {@code true} if the provided {@link VerifyDigestRequest#getDigest() digest} matches the expected value - * for the given {@link VerifyDigestRequest#getPayload() payload}, {@code false} otherwise. - * - * @param request the request containing the {@link VerifyDigestRequest#getDigest() digest} to verify for the - * associated {@link VerifyDigestRequest#getPayload() payload}. - * @return {@code true} if the provided {@link VerifyDigestRequest#getDigest() digest} matches the expected value - * for the given {@link VerifyDigestRequest#getPayload() payload}, {@code false} otherwise. - * @throws SecurityException if there is an invalid key input or a problem that won't allow digest verification. - */ - boolean verify(V request) throws SecurityException; -} diff --git a/io/jsonwebtoken/security/DigestSupplier.java b/io/jsonwebtoken/security/DigestSupplier.java deleted file mode 100644 index 4c697d9..0000000 --- a/io/jsonwebtoken/security/DigestSupplier.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -/** - * A {@code DigestSupplier} provides access to the result of a cryptographic digest algorithm, such as a - * Message Digest, MAC, Signature, or Authentication Tag. - * - * @since 0.12.0 - */ -public interface DigestSupplier { - - /** - * Returns a cryptographic digest result, such as a Message Digest, MAC, Signature, or Authentication Tag - * depending on the cryptographic algorithm that produced it. - * - * @return a cryptographic digest result, such as a Message Digest, MAC, Signature, or Authentication Tag - * * depending on the cryptographic algorithm that produced it. - */ - byte[] getDigest(); - -} diff --git a/io/jsonwebtoken/security/DynamicJwkBuilder.java b/io/jsonwebtoken/security/DynamicJwkBuilder.java deleted file mode 100644 index afc9094..0000000 --- a/io/jsonwebtoken/security/DynamicJwkBuilder.java +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import javax.crypto.SecretKey; -import java.security.Key; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.X509Certificate; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.util.List; - -/** - * A {@link JwkBuilder} that coerces to a more type-specific builder based on the {@link Key} that will be - * represented as a JWK. - * - * @param the type of Java {@link Key} represented by the created {@link Jwk}. - * @param the type of {@link Jwk} created by the builder - * @since 0.12.0 - */ -public interface DynamicJwkBuilder> extends JwkBuilder> { - - /** - * Ensures the builder will create a {@link PublicJwk} for the specified Java {@link X509Certificate} chain. - * The first {@code X509Certificate} in the chain (at array index 0) MUST contain a {@link PublicKey} - * instance when calling the certificate's {@link X509Certificate#getPublicKey() getPublicKey()} method. - * - *

This method is provided for congruence with the other {@code chain} methods and is expected to be used when - * the calling code has a variable {@code PublicKey} reference. Based on the argument type, it will - * delegate to one of the following methods if possible: - *

    - *
  • {@link #rsaChain(List)}
  • - *
  • {@link #ecChain(List)}
  • - *
  • {@link #octetChain(List)}
  • - *
- * - *

If the specified {@code chain} argument is not capable of being supported by one of those methods, an - * {@link UnsupportedKeyException} will be thrown.

- * - *

Type Parameters

- * - *

In addition to the public key type A, the public key's associated private key type - * B is parameterized as well. This ensures that any subsequent call to the builder's - * {@link PublicJwkBuilder#privateKey(PrivateKey) privateKey} method will be type-safe. For example:

- * - *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>chain(edECPublicKeyX509CertificateChain)
-     *     .privateKey(aPrivateKey) // <-- must be an EdECPrivateKey instance
-     *     ... etc ...
-     *     .build();
- * - * @param the type of {@link PublicKey} provided by the created public JWK. - * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce a - * {@link PrivateJwk} if desired. - * @param chain the {@link X509Certificate} chain to inspect to find the {@link PublicKey} to represent as a - * {@link PublicJwk}. - * @return the builder coerced as a {@link PublicJwkBuilder} for continued method chaining. - * @throws UnsupportedKeyException if the specified key is not a supported type and cannot be used to delegate to - * other {@code key} methods. - * @see PublicJwk - * @see PrivateJwk - */ - PublicJwkBuilder chain(List chain) - throws UnsupportedKeyException; - - /** - * Ensures the builder will create a {@link SecretJwk} for the specified Java {@link SecretKey}. - * - * @param key the {@link SecretKey} to represent as a {@link SecretJwk}. - * @return the builder coerced as a {@link SecretJwkBuilder}. - */ - SecretJwkBuilder key(SecretKey key); - - /** - * Ensures the builder will create an {@link RsaPublicJwk} for the specified Java {@link RSAPublicKey}. - * - * @param key the {@link RSAPublicKey} to represent as a {@link RsaPublicJwk}. - * @return the builder coerced as an {@link RsaPublicJwkBuilder}. - */ - RsaPublicJwkBuilder key(RSAPublicKey key); - - /** - * Ensures the builder will create an {@link RsaPrivateJwk} for the specified Java {@link RSAPrivateKey}. If - * possible, it is recommended to also call the resulting builder's - * {@link RsaPrivateJwkBuilder#publicKey(PublicKey) publicKey} method with the private key's matching - * {@link PublicKey} for better performance. See the - * {@link RsaPrivateJwkBuilder#publicKey(PublicKey) publicKey} and {@link PrivateJwk} JavaDoc for more - * information. - * - * @param key the {@link RSAPublicKey} to represent as a {@link RsaPublicJwk}. - * @return the builder coerced as an {@link RsaPrivateJwkBuilder}. - */ - RsaPrivateJwkBuilder key(RSAPrivateKey key); - - /** - * Ensures the builder will create an {@link EcPublicJwk} for the specified Java {@link ECPublicKey}. - * - * @param key the {@link ECPublicKey} to represent as a {@link EcPublicJwk}. - * @return the builder coerced as an {@link EcPublicJwkBuilder}. - */ - EcPublicJwkBuilder key(ECPublicKey key); - - /** - * Ensures the builder will create an {@link EcPrivateJwk} for the specified Java {@link ECPrivateKey}. If - * possible, it is recommended to also call the resulting builder's - * {@link EcPrivateJwkBuilder#publicKey(PublicKey) publicKey} method with the private key's matching - * {@link PublicKey} for better performance. See the - * {@link EcPrivateJwkBuilder#publicKey(PublicKey) publicKey} and {@link PrivateJwk} JavaDoc for more - * information. - * - * @param key the {@link ECPublicKey} to represent as an {@link EcPublicJwk}. - * @return the builder coerced as a {@link EcPrivateJwkBuilder}. - */ - EcPrivateJwkBuilder key(ECPrivateKey key); - - /** - * Ensures the builder will create a {@link PublicJwk} for the specified Java {@link PublicKey} argument. This - * method is provided for congruence with the other {@code key} methods and is expected to be used when - * the calling code has an untyped {@code PublicKey} reference. Based on the argument type, it will delegate to one - * of the following methods if possible: - *
    - *
  • {@link #key(RSAPublicKey)}
  • - *
  • {@link #key(ECPublicKey)}
  • - *
  • {@link #octetKey(PublicKey)}
  • - *
- * - *

If the specified {@code key} argument is not capable of being supported by one of those methods, an - * {@link UnsupportedKeyException} will be thrown.

- * - *

Type Parameters

- * - *

In addition to the public key type A, the public key's associated private key type - * B is parameterized as well. This ensures that any subsequent call to the builder's - * {@link PublicJwkBuilder#privateKey(PrivateKey) privateKey} method will be type-safe. For example:

- * - *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPublicKey)
-     *     .privateKey(aPrivateKey) // <-- must be an EdECPrivateKey instance
-     *     ... etc ...
-     *     .build();
- * - * @param
the type of {@link PublicKey} provided by the created public JWK. - * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce a - * {@link PrivateJwk} if desired. - * @param key the {@link PublicKey} to represent as a {@link PublicJwk}. - * @return the builder coerced as a {@link PublicJwkBuilder} for continued method chaining. - * @throws UnsupportedKeyException if the specified key is not a supported type and cannot be used to delegate to - * other {@code key} methods. - * @see PublicJwk - * @see PrivateJwk - */ - PublicJwkBuilder key(A key) throws UnsupportedKeyException; - - /** - * Ensures the builder will create a {@link PrivateJwk} for the specified Java {@link PrivateKey} argument. This - * method is provided for congruence with the other {@code key} methods and is expected to be used when - * the calling code has an untyped {@code PrivateKey} reference. Based on the argument type, it will delegate to one - * of the following methods if possible: - *
    - *
  • {@link #key(RSAPrivateKey)}
  • - *
  • {@link #key(ECPrivateKey)}
  • - *
  • {@link #octetKey(PrivateKey)}
  • - *
- * - *

If the specified {@code key} argument is not capable of being supported by one of those methods, an - * {@link UnsupportedKeyException} will be thrown.

- * - *

Type Parameters

- * - *

In addition to the private key type B, the private key's associated public key type - * A is parameterized as well. This ensures that any subsequent call to the builder's - * {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} method will be type-safe. For example:

- * - *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPrivateKey)
-     *     .publicKey(aPublicKey) // <-- must be an EdECPublicKey instance
-     *     ... etc ...
-     *     .build();
- * - * @param
the type of {@link PublicKey} paired with the {@code key} argument to produce the {@link PrivateJwk}. - * @param the type of the {@link PrivateKey} argument. - * @param key the {@link PrivateKey} to represent as a {@link PrivateJwk}. - * @return the builder coerced as a {@link PrivateJwkBuilder} for continued method chaining. - * @throws UnsupportedKeyException if the specified key is not a supported type and cannot be used to delegate to - * other {@code key} methods. - * @see PublicJwk - * @see PrivateJwk - */ - PrivateJwkBuilder key(B key) throws UnsupportedKeyException; - - /** - * Ensures the builder will create a {@link PrivateJwk} for the specified Java {@link KeyPair} argument. This - * method is provided for congruence with the other {@code keyPair} methods and is expected to be used when - * the calling code has a variable {@code PrivateKey} reference. Based on the argument's {@code PrivateKey} type, - * it will delegate to one of the following methods if possible: - *
    - *
  • {@link #key(RSAPrivateKey)}
  • - *
  • {@link #key(ECPrivateKey)}
  • - *
  • {@link #octetKey(PrivateKey)}
  • - *
- *

and automatically set the resulting builder's {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} with - * the pair's {@code PublicKey}.

- * - *

If the specified {@code key} argument is not capable of being supported by one of those methods, an - * {@link UnsupportedKeyException} will be thrown.

- * - *

Type Parameters

- * - *

In addition to the private key type B, the private key's associated public key type - * A is parameterized as well. This ensures that any subsequent call to the builder's - * {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} method will be type-safe. For example:

- * - *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>keyPair(anEdECKeyPair)
-     *     .publicKey(aPublicKey) // <-- must be an EdECPublicKey instance
-     *     ... etc ...
-     *     .build();
- * - * @param
the {@code keyPair} argument's {@link PublicKey} type - * @param the {@code keyPair} argument's {@link PrivateKey} type - * @param keyPair the {@code KeyPair} containing the public and private key - * @return the builder coerced as a {@link PrivateJwkBuilder} for continued method chaining. - * @throws UnsupportedKeyException if the specified {@code KeyPair}'s keys are not supported and cannot be used to - * delegate to other {@code key} methods. - * @see PublicJwk - * @see PrivateJwk - */ - PrivateJwkBuilder keyPair(KeyPair keyPair) - throws UnsupportedKeyException; - - /** - * Ensures the builder will create an {@link OctetPublicJwk} for the specified Edwards-curve {@code PublicKey} - * argument. The {@code PublicKey} must be an instance of one of the following: - * - * - *

Type Parameters

- * - *

In addition to the public key type A, the public key's associated private key type - * B is parameterized as well. This ensures that any subsequent call to the builder's - * {@link PublicJwkBuilder#privateKey(PrivateKey) privateKey} method will be type-safe. For example:

- * - *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPublicKey)
-     *     .privateKey(aPrivateKey) // <-- must be an EdECPrivateKey instance
-     *     ... etc ...
-     *     .build();
- * - * @param the type of Edwards-curve {@link PublicKey} provided by the created public JWK. - * @param the type of Edwards-curve {@link PrivateKey} that may be paired with the {@link PublicKey} to produce - * an {@link OctetPrivateJwk} if desired. - * @param key the Edwards-curve {@link PublicKey} to represent as an {@link OctetPublicJwk}. - * @return the builder coerced as a {@link OctetPublicJwkBuilder} for continued method chaining. - * @throws UnsupportedKeyException if the specified key is not a supported Edwards-curve key. - * @see java.security.interfaces.XECPublicKey - * @see java.security.interfaces.EdECPublicKey - */ - OctetPublicJwkBuilder octetKey(A key); - - /** - * Ensures the builder will create an {@link OctetPrivateJwk} for the specified Edwards-curve {@code PrivateKey} - * argument. The {@code PrivateKey} must be an instance of one of the following: - * - * - *

Type Parameters

- * - *

In addition to the private key type B, the private key's associated public key type - * A is parameterized as well. This ensures that any subsequent call to the builder's - * {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} method will be type-safe. For example:

- * - *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPrivateKey)
-     *     .publicKey(aPublicKey) // <-- must be an EdECPublicKey instance
-     *     ... etc ...
-     *     .build();
- * - * @param the type of the Edwards-curve {@link PrivateKey} argument. - * @param the type of Edwards-curve {@link PublicKey} paired with the {@code key} argument to produce the - * {@link OctetPrivateJwk}. - * @param key the Edwards-curve {@link PrivateKey} to represent as an {@link OctetPrivateJwk}. - * @return the builder coerced as an {@link OctetPrivateJwkBuilder} for continued method chaining. - * @throws UnsupportedKeyException if the specified key is not a supported Edwards-curve key. - * @see java.security.interfaces.XECPrivateKey - * @see java.security.interfaces.EdECPrivateKey - */ - OctetPrivateJwkBuilder octetKey(A key); - - /** - * Ensures the builder will create an {@link OctetPublicJwk} for the specified Java {@link X509Certificate} chain. - * The first {@code X509Certificate} in the chain (at list index 0) MUST - * {@link X509Certificate#getPublicKey() contain} an Edwards-curve public key as defined by - * {@link #octetKey(PublicKey)}. - * - * @param the type of Edwards-curve {@link PublicKey} contained in the first {@code X509Certificate}. - * @param the type of Edwards-curve {@link PrivateKey} that may be paired with the {@link PublicKey} to produce - * an {@link OctetPrivateJwk} if desired. - * @param chain the {@link X509Certificate} chain to inspect to find the Edwards-curve {@code PublicKey} to - * represent as an {@link OctetPublicJwk}. - * @return the builder coerced as an {@link OctetPublicJwkBuilder} for continued method chaining. - */ - OctetPublicJwkBuilder octetChain(List chain); - - /** - * Ensures the builder will create an {@link OctetPrivateJwk} for the specified Java Edwards-curve - * {@link KeyPair}. The pair's {@link KeyPair#getPublic() public key} MUST be an - * Edwards-curve public key as defined by {@link #octetKey(PublicKey)}. The pair's - * {@link KeyPair#getPrivate() private key} MUST be an Edwards-curve private key as defined by - * {@link #octetKey(PrivateKey)}. - * - * @param the type of Edwards-curve {@link PublicKey} contained in the key pair. - * @param the type of the Edwards-curve {@link PrivateKey} contained in the key pair. - * @param keyPair the Edwards-curve {@link KeyPair} to represent as an {@link OctetPrivateJwk}. - * @return the builder coerced as an {@link OctetPrivateJwkBuilder} for continued method chaining. - * @throws IllegalArgumentException if the {@code keyPair} does not contain Edwards-curve public and private key - * instances. - */ - OctetPrivateJwkBuilder octetKeyPair(KeyPair keyPair); - - /** - * Ensures the builder will create an {@link EcPublicJwk} for the specified Java {@link X509Certificate} chain. - * The first {@code X509Certificate} in the chain (at list index 0) MUST contain an {@link ECPublicKey} - * instance when calling the certificate's {@link X509Certificate#getPublicKey() getPublicKey()} method. - * - * @param chain the {@link X509Certificate} chain to inspect to find the {@link ECPublicKey} to represent as a - * {@link EcPublicJwk}. - * @return the builder coerced as an {@link EcPublicJwkBuilder}. - */ - EcPublicJwkBuilder ecChain(List chain); - - /** - * Ensures the builder will create an {@link EcPrivateJwk} for the specified Java Elliptic Curve - * {@link KeyPair}. The pair's {@link KeyPair#getPublic() public key} MUST be an - * {@link ECPublicKey} instance. The pair's {@link KeyPair#getPrivate() private key} MUST be an - * {@link ECPrivateKey} instance. - * - * @param keyPair the EC {@link KeyPair} to represent as an {@link EcPrivateJwk}. - * @return the builder coerced as an {@link EcPrivateJwkBuilder}. - * @throws IllegalArgumentException if the {@code keyPair} does not contain {@link ECPublicKey} and - * {@link ECPrivateKey} instances. - */ - EcPrivateJwkBuilder ecKeyPair(KeyPair keyPair) throws IllegalArgumentException; - - /** - * Ensures the builder will create an {@link RsaPublicJwk} for the specified Java {@link X509Certificate} chain. - * The first {@code X509Certificate} in the chain (at list index 0) MUST contain an {@link RSAPublicKey} - * instance when calling the certificate's {@link X509Certificate#getPublicKey() getPublicKey()} method. - * - * @param chain the {@link X509Certificate} chain to inspect to find the {@link RSAPublicKey} to represent as a - * {@link RsaPublicJwk}. - * @return the builder coerced as an {@link RsaPublicJwkBuilder}. - */ - RsaPublicJwkBuilder rsaChain(List chain); - - /** - * Ensures the builder will create an {@link RsaPrivateJwk} for the specified Java RSA - * {@link KeyPair}. The pair's {@link KeyPair#getPublic() public key} MUST be an - * {@link RSAPublicKey} instance. The pair's {@link KeyPair#getPrivate() private key} MUST be an - * {@link RSAPrivateKey} instance. - * - * @param keyPair the RSA {@link KeyPair} to represent as an {@link RsaPrivateJwk}. - * @return the builder coerced as an {@link RsaPrivateJwkBuilder}. - * @throws IllegalArgumentException if the {@code keyPair} does not contain {@link RSAPublicKey} and - * {@link RSAPrivateKey} instances. - */ - RsaPrivateJwkBuilder rsaKeyPair(KeyPair keyPair) throws IllegalArgumentException; -} diff --git a/io/jsonwebtoken/security/EcPrivateJwk.java b/io/jsonwebtoken/security/EcPrivateJwk.java deleted file mode 100644 index b746646..0000000 --- a/io/jsonwebtoken/security/EcPrivateJwk.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; - -/** - * JWK representation of an {@link ECPrivateKey} as defined by the JWA (RFC 7518) specification sections on - * Parameters for Elliptic Curve Keys and - * Parameters for Elliptic Curve Private Keys. - * - *

Note that the various EC-specific properties are not available as separate dedicated getter methods, as most Java - * applications should rarely, if ever, need to access these individual key properties since they typically represent - * internal key material and/or serialization details. If you need to access these key properties, it is usually - * recommended to obtain the corresponding {@link ECPrivateKey} instance returned by {@link #toKey()} and - * query that instead.

- * - *

Even so, because these properties exist and are readable by nature of every JWK being a - * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method - * using an appropriate JWK parameter id, for example:

- *
- * jwk.get("x");
- * jwk.get("y");
- * // ... etc ...
- * - * @since 0.12.0 - */ -public interface EcPrivateJwk extends PrivateJwk { -} diff --git a/io/jsonwebtoken/security/EcPrivateJwkBuilder.java b/io/jsonwebtoken/security/EcPrivateJwkBuilder.java deleted file mode 100644 index f92e6e4..0000000 --- a/io/jsonwebtoken/security/EcPrivateJwkBuilder.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; - -/** - * A {@link PrivateJwkBuilder} that creates {@link EcPrivateJwk}s. - * - * @since 0.12.0 - */ -public interface EcPrivateJwkBuilder extends PrivateJwkBuilder { -} diff --git a/io/jsonwebtoken/security/EcPublicJwk.java b/io/jsonwebtoken/security/EcPublicJwk.java deleted file mode 100644 index 898fc6f..0000000 --- a/io/jsonwebtoken/security/EcPublicJwk.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.interfaces.ECPublicKey; - -/** - * JWK representation of an {@link ECPublicKey} as defined by the JWA (RFC 7518) specification sections on - * Parameters for Elliptic Curve Keys and - * Parameters for Elliptic Curve Public Keys. - * - *

Note that the various EC-specific properties are not available as separate dedicated getter methods, as most Java - * applications should rarely, if ever, need to access these individual key properties since they typically represent - * internal key material and/or serialization details. If you need to access these key properties, it is usually - * recommended to obtain the corresponding {@link ECPublicKey} instance returned by {@link #toKey()} and - * query that instead.

- * - *

Even so, because these properties exist and are readable by nature of every JWK being a - * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method - * using an appropriate JWK parameter id, for example:

- *
- * jwk.get("x");
- * jwk.get("y");
- * // ... etc ...
- * - * @since 0.12.0 - */ -public interface EcPublicJwk extends PublicJwk { -} diff --git a/io/jsonwebtoken/security/EcPublicJwkBuilder.java b/io/jsonwebtoken/security/EcPublicJwkBuilder.java deleted file mode 100644 index b3ed2ce..0000000 --- a/io/jsonwebtoken/security/EcPublicJwkBuilder.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; - -/** - * A {@link PublicJwkBuilder} that creates {@link EcPublicJwk}s. - * - * @since 0.12.0 - */ -public interface EcPublicJwkBuilder extends PublicJwkBuilder { -} diff --git a/io/jsonwebtoken/security/HashAlgorithm.java b/io/jsonwebtoken/security/HashAlgorithm.java deleted file mode 100644 index 3bc4ec4..0000000 --- a/io/jsonwebtoken/security/HashAlgorithm.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright © 2022 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.Identifiable; - -import java.io.InputStream; - -/** - * A {@link DigestAlgorithm} that computes and verifies digests without the use of a cryptographic key, such as for - * thumbprints and digital fingerprints. - * - *

Standard Identifier

- * - *

{@code HashAlgorithm} extends {@link Identifiable}: the value returned from - * {@link Identifiable#getId() getId()} in all JWT standard hash algorithms will return one of the - * "{@code Hash Name String}" values defined in the IANA - * Named Information Hash - * Algorithm Registry. This is to ensure the correct algorithm ID is used within other JWT-standard identifiers, - * such as within JWK Thumbprint URIs.

- * - *

IANA Standard Implementations

- * - *

Constant definitions and utility methods for common (but not all) - * IANA Hash - * Algorithms are available via {@link Jwks.HASH}.

- * - * @see Jwks.HASH - * @since 0.12.0 - */ -public interface HashAlgorithm extends DigestAlgorithm, VerifyDigestRequest> { -} diff --git a/io/jsonwebtoken/security/InvalidKeyException.java b/io/jsonwebtoken/security/InvalidKeyException.java deleted file mode 100644 index 659c89d..0000000 --- a/io/jsonwebtoken/security/InvalidKeyException.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -/** - * A {@code KeyException} thrown when encountering a key that is not suitable for the required functionality, or - * when attempting to use a Key in an incorrect or prohibited manner. - * - * @since 0.10.0 - */ -public class InvalidKeyException extends KeyException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public InvalidKeyException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - * @since 0.12.0 - */ - public InvalidKeyException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/security/IvSupplier.java b/io/jsonwebtoken/security/IvSupplier.java deleted file mode 100644 index f1cc3d5..0000000 --- a/io/jsonwebtoken/security/IvSupplier.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -/** - * An {@code IvSupplier} provides access to the secure-random Initialization Vector used during - * encryption, which must in turn be presented for use during decryption. To maintain the security integrity of cryptographic - * algorithms, a new secure-random Initialization Vector MUST be generated for every individual - * encryption attempt. - * - * @since 0.12.0 - */ -public interface IvSupplier { - - /** - * Returns the secure-random Initialization Vector used during encryption, which must in turn be presented for - * use during decryption. - * - * @return the secure-random Initialization Vector used during encryption, which must in turn be presented for - * use during decryption. - */ - byte[] getIv(); -} diff --git a/io/jsonwebtoken/security/Jwk.java b/io/jsonwebtoken/security/Jwk.java deleted file mode 100644 index fef6420..0000000 --- a/io/jsonwebtoken/security/Jwk.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.Identifiable; -import io.jsonwebtoken.lang.Supplier; - -import java.security.Key; -import java.util.Map; -import java.util.Set; - -/** - * A JWK is an immutable set of name/value pairs that represent a cryptographic key as defined by - * RFC 7517: JSON Web Key (JWK). The {@code Jwk} - * interface represents properties common to all JWKs. Subtypes will have additional properties specific to - * different types of cryptographic keys (e.g. Secret, Asymmetric, RSA, Elliptic Curve, etc). - * - *

Immutability

- * - *

JWKs are immutable and cannot be changed after they are created. {@code Jwk} extends the - * {@link Map} interface purely out of convenience: to allow easy marshalling to JSON as well as name/value - * pair access and key/value iteration, and other conveniences provided by the Map interface. Attempting to call any of - * the {@link Map} interface's mutation methods however (such as {@link Map#put(Object, Object) put}, - * {@link Map#remove(Object) remove}, {@link Map#clear() clear}, etc) will throw an - * {@link UnsupportedOperationException}.

- * - *

Identification

- * - *

{@code Jwk} extends {@link Identifiable} to support the - * JWK {@code kid} parameter. Calling - * {@link #getId() aJwk.getId()} is the type-safe idiomatic approach to the alternative equivalent of - * {@code aJwk.get("kid")}. Either approach will return an id if one was originally set on the JWK, or {@code null} if - * an id does not exist.

- * - *

Private and Secret Value Safety

- * - *

JWKs often represent secret or private key data which should never be exposed publicly, nor mistakenly printed - * to application logs or {@code System.out.println} calls. As a result, all JJWT JWK - * private or secret values are 'wrapped' in a {@link io.jsonwebtoken.lang.Supplier Supplier} instance to ensure - * any attempt to call {@link String#toString() toString()} on the value will print a redacted value instead of an - * actual private or secret value.

- * - *

For example, a {@link SecretJwk} will have an internal "{@code k}" member whose value reflects raw - * key material that should always be kept secret. If the following is called:

- *
- * System.out.println(aSecretJwk.get("k"));
- *

You would see the following:

- *
- * <redacted>
- *

instead of the actual/raw {@code k} value.

- * - *

Similarly, if attempting to print the entire JWK:

- *
- * System.out.println(aSecretJwk);
- *

You would see the following substring in the output:

- *
- * k=<redacted>
- *

instead of the actual/raw {@code k} value.

- * - *

Finally, because all private or secret values are wrapped as {@link io.jsonwebtoken.lang.Supplier} - * instances, if you really wanted the real internal value, you could just call the supplier's - * {@link Supplier#get() get()} method:

- *
- * String k = ((Supplier<String>)aSecretJwk.get("k")).get();
- *

but BE CAREFUL: obtaining the raw value in your application code exposes greater security - * risk - you must ensure to keep that value safe and out of console or log output. It is almost always better to - * interact with the JWK's {@link #toKey() toKey()} instance directly instead of accessing - * JWK internal serialization parameters.

- * - * @param The type of Java {@link Key} represented by this JWK - * @since 0.12.0 - */ -public interface Jwk extends Identifiable, Map { - - /** - * Returns the JWK - * {@code alg} (Algorithm) value - * or {@code null} if not present. - * - * @return the JWK {@code alg} value or {@code null} if not present. - */ - String getAlgorithm(); - - /** - * Returns the JWK {@code key_ops} - * (Key Operations) parameter values or {@code null} if not present. All JWK standard Key Operations are - * available via the {@link Jwks.OP} registry, but other (custom) values MAY be present in the returned - * set. - * - * @return the JWK {@code key_ops} value or {@code null} if not present. - * @see key_ops(Key Operations) Parameter - */ - Set getOperations(); - - /** - * Returns the required JWK - * {@code kty} (Key Type) - * parameter value. A value is required and may not be {@code null}. - * - *

The JWA specification defines the - * following {@code kty} values:

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
JWK Key Types
ValueKey Type
{@code EC}Elliptic Curve [DSS]
{@code RSA}RSA [RFC 3447]
{@code oct}Octet sequence (used to represent symmetric keys)
{@code OKP}Octet Key Pair (used to represent Edwards - * Elliptic Curve keys)
- * - * @return the JWK {@code kty} (Key Type) value. - */ - String getType(); - - /** - * Computes and returns the canonical JWK Thumbprint of this - * JWK using the {@code SHA-256} hash algorithm. This is a convenience method that delegates to - * {@link #thumbprint(HashAlgorithm)} with a {@code SHA-256} {@link HashAlgorithm} instance. - * - * @return the canonical JWK Thumbprint of this - * JWK using the {@code SHA-256} hash algorithm. - * @see #thumbprint(HashAlgorithm) - */ - JwkThumbprint thumbprint(); - - /** - * Computes and returns the canonical JWK Thumbprint of this - * JWK using the specified hash algorithm. - * - * @param alg the hash algorithm to use to compute the digest of the canonical JWK Thumbprint JSON form of this JWK. - * @return the canonical JWK Thumbprint of this - * JWK using the specified hash algorithm. - */ - JwkThumbprint thumbprint(HashAlgorithm alg); - - /** - * Represents the JWK as its corresponding Java {@link Key} instance for use with Java cryptographic - * APIs. - * - * @return the JWK's corresponding Java {@link Key} instance for use with Java cryptographic APIs. - */ - K toKey(); -} diff --git a/io/jsonwebtoken/security/JwkBuilder.java b/io/jsonwebtoken/security/JwkBuilder.java deleted file mode 100644 index add7be0..0000000 --- a/io/jsonwebtoken/security/JwkBuilder.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.lang.Conjunctor; -import io.jsonwebtoken.lang.MapMutator; -import io.jsonwebtoken.lang.NestedCollection; - -import java.security.Key; - -/** - * A {@link SecurityBuilder} that produces a JWK. A JWK is an immutable set of name/value pairs that represent a - * cryptographic key as defined by - * RFC 7517: JSON Web Key (JWK). - * The {@code JwkBuilder} interface represents common JWK properties that may be specified for any type of JWK. - * Builder subtypes support additional JWK properties specific to different types of cryptographic keys - * (e.g. Secret, Asymmetric, RSA, Elliptic Curve, etc). - * - * @param the type of Java {@link Key} represented by the constructed JWK. - * @param the type of {@link Jwk} created by the builder - * @param the type of the builder, for subtype method chaining - * @see SecretJwkBuilder - * @see RsaPublicJwkBuilder - * @see RsaPrivateJwkBuilder - * @see EcPublicJwkBuilder - * @see EcPrivateJwkBuilder - * @see OctetPublicJwkBuilder - * @see OctetPrivateJwkBuilder - * @since 0.12.0 - */ -public interface JwkBuilder, T extends JwkBuilder> - extends MapMutator, SecurityBuilder, KeyOperationPolicied { - - /** - * Sets the JWK {@code alg} (Algorithm) - * Parameter. - * - *

The {@code alg} (algorithm) parameter identifies the algorithm intended for use with the key. The - * value specified should either be one of the values in the IANA - * JSON Web Signature and Encryption - * Algorithms registry or be a value that contains a {@code Collision-Resistant Name}. The {@code alg} - * must be a CaSe-SeNsItIvE ASCII string.

- * - * @param alg the JWK {@code alg} value. - * @return the builder for method chaining. - * @throws IllegalArgumentException if {@code alg} is {@code null} or empty. - */ - T algorithm(String alg) throws IllegalArgumentException; - - /** - * Sets the JWK {@code kid} (Key ID) - * Parameter. - * - *

The {@code kid} (key ID) parameter is used to match a specific key. This is used, for instance, - * to choose among a set of keys within a {@code JWK Set} during key rollover. The structure of the - * {@code kid} value is unspecified. When {@code kid} values are used within a JWK Set, different keys - * within the {@code JWK Set} SHOULD use distinct {@code kid} values. (One example in which - * different keys might use the same {@code kid} value is if they have different {@code kty} (key type) - * values but are considered to be equivalent alternatives by the application using them.)

- * - *

The {@code kid} value is a CaSe-SeNsItIvE string, and it is optional. When used with JWS or JWE, - * the {@code kid} value is used to match a JWS or JWE {@code kid} Header Parameter value.

- * - * @param kid the JWK {@code kid} value. - * @return the builder for method chaining. - * @throws IllegalArgumentException if the argument is {@code null} or empty. - */ - T id(String kid) throws IllegalArgumentException; - - /** - * Sets the JWK's {@link #id(String) kid} value to be the Base64URL-encoding of its {@code SHA-256} - * {@link Jwk#thumbprint(HashAlgorithm) thumbprint}. That is, the constructed JWK's {@code kid} value will equal - * jwk.{@link Jwk#thumbprint(HashAlgorithm) thumbprint}({@link Jwks.HASH}.{@link Jwks.HASH#SHA256 SHA256}).{@link JwkThumbprint#toString() toString()}. - * - *

This is a convenience method that delegates to {@link #idFromThumbprint(HashAlgorithm)} using - * {@link Jwks.HASH}{@code .}{@link Jwks.HASH#SHA256 SHA256}.

- * - * @return the builder for method chaining. - */ - T idFromThumbprint(); - - /** - * Sets the JWK's {@link #id(String) kid} value to be the Base64URL-encoding of its - * {@link Jwk#thumbprint(HashAlgorithm) thumbprint} using the specified {@link HashAlgorithm}. That is, the - * constructed JWK's {@code kid} value will equal - * {@link Jwk#thumbprint(HashAlgorithm) thumbprint}(alg).{@link JwkThumbprint#toString() toString()}. - * - * @param alg the hash algorithm to use to compute the thumbprint. - * @return the builder for method chaining. - * @see Jwks.HASH - */ - T idFromThumbprint(HashAlgorithm alg); - - /** - * Configures the key operations for which - * the key is intended to be used. When finished, use the collection's {@link Conjunctor#and() and()} method to - * return to the JWK builder, for example: - *
-     * jwkBuilder.operations().add(aKeyOperation).{@link Conjunctor#and() and()} // etc...
- * - *

The {@code add()} method(s) will throw an {@link IllegalArgumentException} if any of the specified - * {@code KeyOperation}s are not permitted by the JWK's - * {@link #operationPolicy(KeyOperationPolicy) operationPolicy}. See that documentation for more - * information on security vulnerabilities when using the same key with multiple algorithms.

- * - *

Standard {@code KeyOperation}s and Overrides

- * - *

All RFC-standard JWK Key Operations in the {@link Jwks.OP} registry are supported via the builder's default - * {@link #operationPolicy(KeyOperationPolicy) operationPolicy}, but other (custom) values - * MAY be specified (for example, using a {@link Jwks.OP#builder()}).

- * - *

If the {@code JwkBuilder} is being used to rebuild or parse an existing JWK however, any custom operations - * should be enabled by configuring an {@link #operationPolicy(KeyOperationPolicy) operationPolicy} - * that includes the custom values (e.g. via - * {@link Jwks.OP#policy()}.{@link KeyOperationPolicyBuilder#add(KeyOperation) add(customKeyOperation)}).

- * - *

For best interoperability with other applications however, it is recommended to use only the {@link Jwks.OP} - * constants.

- * - * @return the {@link NestedCollection} to use for {@code key_ops} configuration. - * @see Jwks.OP - * @see RFC 7517: key_ops (Key Operations) Parameter - */ - NestedCollection operations(); -} diff --git a/io/jsonwebtoken/security/JwkParserBuilder.java b/io/jsonwebtoken/security/JwkParserBuilder.java deleted file mode 100644 index 9c66db7..0000000 --- a/io/jsonwebtoken/security/JwkParserBuilder.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2022 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.io.Parser; -import io.jsonwebtoken.io.ParserBuilder; - -/** - * A builder to construct a {@link Parser} that can parse {@link Jwk}s. - * Example usage: - *
- * Jwk<?> jwk = Jwks.parser()
- *         .provider(aJcaProvider)     // optional
- *         .deserializer(deserializer) // optional
- *         .operationPolicy(policy)    // optional
- *         .build()
- *         .parse(jwkString);
- * - * @since 0.12.0 - */ -public interface JwkParserBuilder extends ParserBuilder, JwkParserBuilder>, KeyOperationPolicied { -} diff --git a/io/jsonwebtoken/security/JwkSet.java b/io/jsonwebtoken/security/JwkSet.java deleted file mode 100644 index c8b3ea5..0000000 --- a/io/jsonwebtoken/security/JwkSet.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.util.Map; -import java.util.Set; - -/** - * A JWK Set is an immutable JSON Object that represents a Set of {@link Jwk}s as defined by - * RFC 7517 JWK Set Format. Per that specification, - * any number of name/value pairs may be present in a {@code JwkSet}, but only a non-empty {@link #getKeys() keys} - * set MUST be present. - * - *

Immutability

- * - *

JWK Sets are immutable and cannot be changed after they are created. {@code JwkSet} extends the - * {@link Map} interface purely out of convenience: to allow easy marshalling to JSON as well as name/value - * pair access and key/value iteration, and other conveniences provided by the Map interface. Attempting to call any of - * the {@link Map} interface's mutation methods however (such as {@link Map#put(Object, Object) put}, - * {@link Map#remove(Object) remove}, {@link Map#clear() clear}, etc) will throw an - * {@link UnsupportedOperationException}.

- * - * @since 0.12.0 - */ -public interface JwkSet extends Map, Iterable> { - - /** - * Returns the non-null, non-empty set of JWKs contained within the {@code JwkSet}. - * - * @return the non-null, non-empty set of JWKs contained within the {@code JwkSet}. - */ - Set> getKeys(); - -} diff --git a/io/jsonwebtoken/security/JwkSetBuilder.java b/io/jsonwebtoken/security/JwkSetBuilder.java deleted file mode 100644 index 987f436..0000000 --- a/io/jsonwebtoken/security/JwkSetBuilder.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.lang.MapMutator; - -import java.security.Provider; -import java.util.Collection; - -/** - * A builder that produces {@link JwkSet}s containing {@link Jwk}s. {@code Jwk}s with any key - * {@link Jwk#getOperations() operations} will be validated by - * the {@link #operationPolicy(KeyOperationPolicy) operationPolicy} first before being added. - * - * @see #operationPolicy(KeyOperationPolicy) - * @see #provider(Provider) - * @since 0.12.0 - */ -public interface JwkSetBuilder extends MapMutator, - SecurityBuilder, KeyOperationPolicied { - - /** - * Appends the specified {@code jwk} to the set. If the {@code jwk} has any key - * {@link Jwk#getOperations() operations}, it will be validated with the - * {@link #operationPolicy(KeyOperationPolicy) operationPolicy} first before being added. - * - * @param jwk the jwk to add to the JWK Set. A {@code null} {@code jwk} is ignored. - * @return the builder for method chaining - */ - JwkSetBuilder add(Jwk jwk); - - /** - * Appends the specified {@code Jwk} collection to the JWK Set. If any {@code Jwk} in the collection has - * any key {@link Jwk#getOperations() operations}, it will be validated with the - * {@link #operationPolicy(KeyOperationPolicy) operationPolicy} first before being added. - * - * @param c the collection of {@code Jwk}s to add to the JWK Set. A {@code null} or empty collection is ignored. - * @return the builder for method chaining - */ - JwkSetBuilder add(Collection> c); - - /** - * Sets the {@code JwkSet} {@code keys} parameter value; per standard Java setter idioms, this is a - * full replacement operation, removing any previous keys from the set. A {@code null} or empty - * collection removes all keys from the set. - * - * @param c the (possibly null or empty) collection of {@code Jwk}s to set as the JWK set {@code keys} parameter - * value. - * @return the builder for method chaining - */ - JwkSetBuilder keys(Collection> c); - -} diff --git a/io/jsonwebtoken/security/JwkSetParserBuilder.java b/io/jsonwebtoken/security/JwkSetParserBuilder.java deleted file mode 100644 index d3fcb75..0000000 --- a/io/jsonwebtoken/security/JwkSetParserBuilder.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.io.Parser; -import io.jsonwebtoken.io.ParserBuilder; - -/** - * A builder to construct a {@link Parser} that can parse {@link JwkSet}s. - * Example usage: - *
- * JwkSet jwkSet = Jwks.setParser()
- *         .provider(aJcaProvider)      // optional
- *         .json(deserializer)          // optional
- *         .operationPolicy(policy)     // optional
- *         .ignoreUnsupported(aBoolean) // optional
- *         .build()
- *         .parse(jwkSetString);
- * - * @since 0.12.0 - */ -public interface JwkSetParserBuilder extends ParserBuilder, KeyOperationPolicied { - - /** - * Sets whether the parser should ignore any encountered JWK it does not support, either because the JWK has an - * unrecognized {@link Jwk#getType() key type} or the JWK was malformed (missing required parameters, etc). - * The default value is {@code true} per - * RFC 7517, Section 5, last paragraph: - *
-     *    Implementations SHOULD ignore JWKs within a JWK Set that use "kty"
-     *    (key type) values that are not understood by them, that are missing
-     *    required members, or for which values are out of the supported
-     *    ranges.
-     * 
- * - *

This value may be set to {@code false} for applications that prefer stricter parsing constraints - * and wish to react to any {@link MalformedKeyException}s or {@link UnsupportedKeyException}s that could - * occur.

- * - * @param ignore whether to ignore unsupported or malformed JWKs encountered during parsing. - * @return the builder for method chaining. - */ - JwkSetParserBuilder ignoreUnsupported(boolean ignore); -} diff --git a/io/jsonwebtoken/security/JwkThumbprint.java b/io/jsonwebtoken/security/JwkThumbprint.java deleted file mode 100644 index 1c20343..0000000 --- a/io/jsonwebtoken/security/JwkThumbprint.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright © 2022 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.net.URI; - -/** - * A canonical cryptographic digest of a JWK as defined by the - * JSON Web Key (JWK) Thumbprint specification. - * - * @since 0.12.0 - */ -public interface JwkThumbprint { - - /** - * Returns the {@link HashAlgorithm} used to compute the thumbprint. - * - * @return the {@link HashAlgorithm} used to compute the thumbprint. - */ - HashAlgorithm getHashAlgorithm(); - - /** - * Returns the actual thumbprint (aka digest) byte array value. - * - * @return the actual thumbprint (aka digest) byte array value. - */ - byte[] toByteArray(); - - /** - * Returns the canonical URI representation of this thumbprint as defined by the - * JWK Thumbprint URI specification. - * - * @return a canonical JWK Thumbprint URI - */ - URI toURI(); - - /** - * Returns the {@link #toByteArray()} value as a Base64URL-encoded string. - */ - String toString(); -} diff --git a/io/jsonwebtoken/security/Jwks.java b/io/jsonwebtoken/security/Jwks.java deleted file mode 100644 index ee8a164..0000000 --- a/io/jsonwebtoken/security/Jwks.java +++ /dev/null @@ -1,482 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.Identifiable; -import io.jsonwebtoken.io.Parser; -import io.jsonwebtoken.lang.Classes; -import io.jsonwebtoken.lang.Registry; - -/** - * Utility methods for creating - * JWKs (JSON Web Keys) with a type-safe builder. - * - *

Standard JWK Thumbprint Algorithm References

- *

Standard IANA Hash - * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid - * JWK Thumbprint URIs - * are available via the {@link Jwks.HASH} registry constants to allow for easy code-completion in IDEs. For example, when - * typing:

- *
- * Jwks.{@link Jwks.HASH HASH}.// press hotkeys to suggest individual hash algorithms or utility methods
- * - * @see #builder() - * @since 0.12.0 - */ -public final class Jwks { - - private Jwks() { - } //prevent instantiation - - private static final String JWKS_BRIDGE_FQCN = "io.jsonwebtoken.impl.security.JwksBridge"; - private static final String BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultDynamicJwkBuilder"; - private static final String PARSER_BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultJwkParserBuilder"; - private static final String SET_BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultJwkSetBuilder"; - private static final String SET_PARSER_BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultJwkSetParserBuilder"; - - /** - * Return a new JWK builder instance, allowing for type-safe JWK builder coercion based on a specified key or key pair. - * - * @return a new JWK builder instance, allowing for type-safe JWK builder coercion based on a specified key or key pair. - */ - public static DynamicJwkBuilder builder() { - return Classes.newInstance(BUILDER_FQCN); - } - - /** - * Returns a new builder used to create {@link Parser}s that parse JSON into {@link Jwk} instances. For example: - *
-     * Jwk<?> jwk = Jwks.parser()
-     *         //.provider(aJcaProvider)     // optional
-     *         //.deserializer(deserializer) // optional
-     *         //.operationPolicy(policy)    // optional
-     *         .build()
-     *         .parse(jwkString);
- * - * @return a new builder used to create {@link Parser}s that parse JSON into {@link Jwk} instances. - */ - public static JwkParserBuilder parser() { - return Classes.newInstance(PARSER_BUILDER_FQCN); - } - - /** - * Return a new builder used to create {@link JwkSet}s. For example: - *
-     * JwkSet jwkSet = Jwks.set()
-     *     //.provider(aJcaProvider)     // optional
-     *     //.operationPolicy(policy)    // optional
-     *     .add(aJwk)                    // appends a key
-     *     .add(aCollection)             // appends multiple keys
-     *     //.keys(allJwks)              // sets/replaces all keys
-     *     .build()
-     * 
- * - * @return a new builder used to create {@link JwkSet}s - */ - public static JwkSetBuilder set() { - return Classes.newInstance(SET_BUILDER_FQCN); - } - - /** - * Returns a new builder used to create {@link Parser}s that parse JSON into {@link JwkSet} instances. For example: - *
-     * JwkSet jwkSet = Jwks.setParser()
-     *         //.provider(aJcaProvider)     // optional
-     *         //.deserializer(deserializer) // optional
-     *         //.operationPolicy(policy)    // optional
-     *         .build()
-     *         .parse(jwkSetString);
- * - * @return a new builder used to create {@link Parser}s that parse JSON into {@link JwkSet} instances. - */ - public static JwkSetParserBuilder setParser() { - return Classes.newInstance(SET_PARSER_BUILDER_FQCN); - } - - /** - * Converts the specified {@link PublicJwk} into JSON. Because {@link PublicJwk}s do not contain secret or private - * key material, they are safe to be printed to application logs or {@code System.out}. - * - * @param publicJwk the {@code PublicJwk} to convert to JSON - * @return the JWK's canonical JSON value - */ - public static String json(PublicJwk publicJwk) { - return UNSAFE_JSON(publicJwk); // safe by nature of it being a Public JWK - } - - /** - * WARNING - UNSAFE OPERATION - RETURN VALUES CONTAIN RAW KEY MATERIAL, DO NOT LOG OR PRINT TO SYSTEM.OUT. - * Converts the specified JWK into JSON, including raw key material. If the specified JWK - * is a {@link SecretJwk} or a {@link PrivateJwk}, be very careful with the return value, ensuring it is not - * printed to application logs or system.out. - * - * @param jwk the JWK to convert to JSON - * @return the JWK's canonical JSON value - */ - public static String UNSAFE_JSON(Jwk jwk) { - return Classes.invokeStatic(JWKS_BRIDGE_FQCN, "UNSAFE_JSON", new Class[]{Jwk.class}, jwk); - } - - /** - * Constants for all standard JWK - * crv (Curve) parameter values - * defined in the JSON Web Key Elliptic - * Curve Registry (including its - * Edwards Elliptic Curve additions). - * Each standard algorithm is available as a ({@code public static final}) constant for direct type-safe - * reference in application code. For example: - *
-     * Jwks.CRV.P256.keyPair().build();
- *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

- * - * @see #get() - * @since 0.12.0 - */ - public static final class CRV { - - private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardCurves"; - private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); - - /** - * Returns a registry of all standard Elliptic Curves in the {@code JSON Web Key Elliptic Curve Registry} - * defined by RFC 7518, Section 7.6 - * (for Weierstrass Elliptic Curves) and - * RFC 8037, Section 5 (for Edwards Elliptic Curves). - * - * @return a registry of all standard Elliptic Curves in the {@code JSON Web Key Elliptic Curve Registry}. - */ - public static Registry get() { - return REGISTRY; - } - - /** - * {@code P-256} Elliptic Curve defined by - * RFC 7518, Section 6.2.1.1 - * using the native Java JCA {@code secp256r1} algorithm. - * - * @see Java Security Standard Algorithm Names - */ - public static final Curve P256 = get().forKey("P-256"); - - /** - * {@code P-384} Elliptic Curve defined by - * RFC 7518, Section 6.2.1.1 - * using the native Java JCA {@code secp384r1} algorithm. - * - * @see Java Security Standard Algorithm Names - */ - public static final Curve P384 = get().forKey("P-384"); - - /** - * {@code P-521} Elliptic Curve defined by - * RFC 7518, Section 6.2.1.1 - * using the native Java JCA {@code secp521r1} algorithm. - * - * @see Java Security Standard Algorithm Names - */ - public static final Curve P521 = get().forKey("P-521"); - - /** - * {@code Ed25519} Elliptic Curve defined by - * RFC 8037, Section 3.1 - * using the native Java JCA {@code Ed25519}1 algorithm. - * - *

1 Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath. If on Java 14 or earlier, BouncyCastle will be used automatically if found in the runtime - * classpath.

- * - * @see Java Security Standard Algorithm Names - */ - public static final Curve Ed25519 = get().forKey("Ed25519"); - - /** - * {@code Ed448} Elliptic Curve defined by - * RFC 8037, Section 3.1 - * using the native Java JCA {@code Ed448}1 algorithm. - * - *

1 Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath. If on Java 14 or earlier, BouncyCastle will be used automatically if found in the runtime - * classpath.

- * - * @see Java Security Standard Algorithm Names - */ - public static final Curve Ed448 = get().forKey("Ed448"); - - /** - * {@code X25519} Elliptic Curve defined by - * RFC 8037, Section 3.2 - * using the native Java JCA {@code X25519}1 algorithm. - * - *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime - * classpath.

- * - * @see Java Security Standard Algorithm Names - */ - public static final Curve X25519 = get().forKey("X25519"); - - /** - * {@code X448} Elliptic Curve defined by - * RFC 8037, Section 3.2 - * using the native Java JCA {@code X448}1 algorithm. - * - *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime - * classpath.

- * - * @see Java Security Standard Algorithm Names - */ - public static final Curve X448 = get().forKey("X448"); - - //prevent instantiation - private CRV() { - } - } - - /** - * Various (but not all) - * IANA Hash - * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid - * JWK Thumbprint URIs. - * Each algorithm is made available as a ({@code public static final}) constant for direct type-safe - * reference in application code. For example: - *
-     * Jwks.{@link Jwks#builder}()
-     *     // ... etc ...
-     *     .{@link JwkBuilder#idFromThumbprint(HashAlgorithm) idFromThumbprint}(Jwts.HASH.{@link Jwks.HASH#SHA256 SHA256}) // <---
-     *     .build()
- *

or

- *
-     * HashAlgorithm hashAlg = Jwks.HASH.{@link Jwks.HASH#SHA256 SHA256};
-     * {@link JwkThumbprint} thumbprint = aJwk.{@link Jwk#thumbprint(HashAlgorithm) thumbprint}(hashAlg);
-     * String rfcMandatoryPrefix = "urn:ietf:params:oauth:jwk-thumbprint:" + hashAlg.getId();
-     * assert thumbprint.toURI().toString().startsWith(rfcMandatoryPrefix);
-     * 
- *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

- * - * @see #get() - * @since 0.12.0 - */ - public static final class HASH { - - private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardHashAlgorithms"; - private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); - - /** - * Returns a registry of various (but not all) - * IANA Hash - * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid - * JWK Thumbprint URIs. - * - * @return a registry of various (but not all) - * IANA Hash - * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid - * JWK Thumbprint URIs. - */ - public static Registry get() { - return REGISTRY; - } - - /** - * IANA - * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") - * value of {@code sha-256}. It is a {@code HashAlgorithm} alias for the native - * Java JCA {@code SHA-256} {@code MessageDigest} algorithm. - */ - public static final HashAlgorithm SHA256 = get().forKey("sha-256"); - - /** - * IANA - * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") - * value of {@code sha-384}. It is a {@code HashAlgorithm} alias for the native - * Java JCA {@code SHA-384} {@code MessageDigest} algorithm. - */ - public static final HashAlgorithm SHA384 = get().forKey("sha-384"); - - /** - * IANA - * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") - * value of {@code sha-512}. It is a {@code HashAlgorithm} alias for the native - * Java JCA {@code SHA-512} {@code MessageDigest} algorithm. - */ - public static final HashAlgorithm SHA512 = get().forKey("sha-512"); - - /** - * IANA - * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") - * value of {@code sha3-256}. It is a {@code HashAlgorithm} alias for the native - * Java JCA {@code SHA3-256} {@code MessageDigest} algorithm. - *

This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath.

- */ - public static final HashAlgorithm SHA3_256 = get().forKey("sha3-256"); - - /** - * IANA - * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") - * value of {@code sha3-384}. It is a {@code HashAlgorithm} alias for the native - * Java JCA {@code SHA3-384} {@code MessageDigest} algorithm. - *

This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath.

- */ - public static final HashAlgorithm SHA3_384 = get().forKey("sha3-384"); - - /** - * IANA - * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") - * value of {@code sha3-512}. It is a {@code HashAlgorithm} alias for the native - * Java JCA {@code SHA3-512} {@code MessageDigest} algorithm. - *

This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath.

- */ - public static final HashAlgorithm SHA3_512 = get().forKey("sha3-512"); - - //prevent instantiation - private HASH() { - } - } - - /** - * Constants for all standard JWK - * key_ops (Key Operations) parameter values - * defined in the JSON Web Key Operations - * Registry. Each standard key operation is available as a ({@code public static final}) constant for - * direct type-safe reference in application code. For example: - *
-     * Jwks.builder()
-     *     .operations(Jwks.OP.SIGN)
-     *     // ... etc ...
-     *     .build();
- *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

- * - * @see #get() - * @since 0.12.0 - */ - public static final class OP { - - private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardKeyOperations"; - private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); - - private static final String BUILDER_CLASSNAME = "io.jsonwebtoken.impl.security.DefaultKeyOperationBuilder"; - - - private static final String POLICY_BUILDER_CLASSNAME = - "io.jsonwebtoken.impl.security.DefaultKeyOperationPolicyBuilder"; - - /** - * Creates a new {@link KeyOperationBuilder} for creating custom {@link KeyOperation} instances. - * - * @return a new {@link KeyOperationBuilder} for creating custom {@link KeyOperation} instances. - */ - public static KeyOperationBuilder builder() { - return Classes.newInstance(BUILDER_CLASSNAME); - } - - /** - * Creates a new {@link KeyOperationPolicyBuilder} for creating custom {@link KeyOperationPolicy} instances. - * - * @return a new {@link KeyOperationPolicyBuilder} for creating custom {@link KeyOperationPolicy} instances. - */ - public static KeyOperationPolicyBuilder policy() { - return Classes.newInstance(POLICY_BUILDER_CLASSNAME); - } - - /** - * Returns a registry of all standard Key Operations in the {@code JSON Web Key Operations Registry} - * defined by RFC 7517, Section 8.3. - * - * @return a registry of all standard Key Operations in the {@code JSON Web Key Operations Registry}. - */ - public static Registry get() { - return REGISTRY; - } - - /** - * {@code sign} operation indicating a key is intended to be used to compute digital signatures or - * MACs. It's related operation is {@link #VERIFY}. - * - * @see #VERIFY - * @see Key Operation Registry Contents - */ - public static final KeyOperation SIGN = get().forKey("sign"); - - /** - * {@code verify} operation indicating a key is intended to be used to verify digital signatures or - * MACs. It's related operation is {@link #SIGN}. - * - * @see #SIGN - * @see Key Operation Registry Contents - */ - public static final KeyOperation VERIFY = get().forKey("verify"); - - /** - * {@code encrypt} operation indicating a key is intended to be used to encrypt content. It's - * related operation is {@link #DECRYPT}. - * - * @see #DECRYPT - * @see Key Operation Registry Contents - */ - public static final KeyOperation ENCRYPT = get().forKey("encrypt"); - - /** - * {@code decrypt} operation indicating a key is intended to be used to decrypt content. It's - * related operation is {@link #ENCRYPT}. - * - * @see #ENCRYPT - * @see Key Operation Registry Contents - */ - public static final KeyOperation DECRYPT = get().forKey("decrypt"); - - /** - * {@code wrapKey} operation indicating a key is intended to be used to encrypt another key. It's - * related operation is {@link #UNWRAP_KEY}. - * - * @see #UNWRAP_KEY - * @see Key Operation Registry Contents - */ - public static final KeyOperation WRAP_KEY = get().forKey("wrapKey"); - - /** - * {@code unwrapKey} operation indicating a key is intended to be used to decrypt another key and validate - * decryption, if applicable. It's related operation is - * {@link #WRAP_KEY}. - * - * @see #WRAP_KEY - * @see Key Operation Registry Contents - */ - public static final KeyOperation UNWRAP_KEY = get().forKey("unwrapKey"); - - /** - * {@code deriveKey} operation indicating a key is intended to be used to derive another key. It does not have - * a related operation. - * - * @see Key Operation Registry Contents - */ - public static final KeyOperation DERIVE_KEY = get().forKey("deriveKey"); - - /** - * {@code deriveBits} operation indicating a key is intended to be used to derive bits that are not to be - * used as key. It does not have a related operation. - * - * @see Key Operation Registry Contents - */ - public static final KeyOperation DERIVE_BITS = get().forKey("deriveBits"); - - //prevent instantiation - private OP() { - } - } -} diff --git a/io/jsonwebtoken/security/KeyAlgorithm.java b/io/jsonwebtoken/security/KeyAlgorithm.java deleted file mode 100644 index e3cd13c..0000000 --- a/io/jsonwebtoken/security/KeyAlgorithm.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.Identifiable; -import io.jsonwebtoken.Jwts; - -import javax.crypto.SecretKey; -import java.security.Key; - -/** - * A {@code KeyAlgorithm} produces the {@link SecretKey} used to encrypt or decrypt a JWE. The {@code KeyAlgorithm} - * used for a particular JWE is {@link #getId() identified} in the JWE's - * {@code alg} header. The {@code KeyAlgorithm} - * interface is JJWT's idiomatic approach to the JWE specification's - * {@code Key Management Mode} concept. - * - *

All standard Key Algorithms are defined in - * JWA (RFC 7518), Section 4.1, - * and they are all available as concrete instances via {@link Jwts.KEY}.

- * - *

"alg" identifier

- * - *

{@code KeyAlgorithm} extends {@code Identifiable}: the value returned from - * {@link Identifiable#getId() keyAlgorithm.getId()} will be used as the - * JWE "alg" protected header value.

- * - * @param The type of key to use to obtain the AEAD encryption key - * @param The type of key to use to obtain the AEAD decryption key - * @see Jwts.KEY - * @see RFC 7561, Section 2: JWE Key (Management) Algorithms - * @since 0.12.0 - */ -@SuppressWarnings("JavadocLinkAsPlainText") -public interface KeyAlgorithm extends Identifiable { - - /** - * Return the {@link SecretKey} that should be used to encrypt a JWE via the request's specified - * {@link KeyRequest#getEncryptionAlgorithm() AeadAlgorithm}. The encryption key will - * be available via the result's {@link KeyResult#getKey() result.getKey()} method. - * - *

If the key algorithm uses key encryption or key agreement to produce an encrypted key value that must be - * included in the JWE, the encrypted key ciphertext will be available via the result's - * {@link KeyResult#getPayload() result.getPayload()} method. If the key algorithm does not produce encrypted - * key ciphertext, {@link KeyResult#getPayload() result.getPayload()} will be a non-null empty byte array.

- * - * @param request the {@code KeyRequest} containing information necessary to produce a {@code SecretKey} for - * {@link AeadAlgorithm AEAD} encryption. - * @return the {@link SecretKey} that should be used to encrypt a JWE via the request's specified - * {@link KeyRequest#getEncryptionAlgorithm() AeadAlgorithm}, along with any optional encrypted key ciphertext. - * @throws SecurityException if there is a problem obtaining or encrypting the AEAD {@code SecretKey}. - */ - KeyResult getEncryptionKey(KeyRequest request) throws SecurityException; - - /** - * Return the {@link SecretKey} that should be used to decrypt a JWE via the request's specified - * {@link DecryptionKeyRequest#getEncryptionAlgorithm() AeadAlgorithm}. - * - *

If the key algorithm used key encryption or key agreement to produce an encrypted key value, the encrypted - * key ciphertext will be available via the request's {@link DecryptionKeyRequest#getPayload() result.getPayload()} - * method. If the key algorithm did not produce encrypted key ciphertext, - * {@link DecryptionKeyRequest#getPayload() request.getPayload()} will return a non-null empty byte array.

- * - * @param request the {@code DecryptionKeyRequest} containing information necessary to obtain a - * {@code SecretKey} for {@link AeadAlgorithm AEAD} decryption. - * @return the {@link SecretKey} that should be used to decrypt a JWE via the request's specified - * {@link DecryptionKeyRequest#getEncryptionAlgorithm() AeadAlgorithm}. - * @throws SecurityException if there is a problem obtaining or decrypting the AEAD {@code SecretKey}. - */ - SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException; -} diff --git a/io/jsonwebtoken/security/KeyBuilder.java b/io/jsonwebtoken/security/KeyBuilder.java deleted file mode 100644 index 9de8f00..0000000 --- a/io/jsonwebtoken/security/KeyBuilder.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import javax.crypto.SecretKey; -import java.security.Key; - -/** - * A {@code KeyBuilder} produces new {@link Key}s suitable for use with an associated cryptographic algorithm. - * A new {@link Key} is created each time the builder's {@link #build()} method is called. - * - *

{@code KeyBuilder}s are provided by components that implement the {@link KeyBuilderSupplier} interface, - * ensuring the resulting {@link SecretKey}s are compatible with their associated cryptographic algorithm.

- * - * @param the type of key to build - * @param the type of the builder, for subtype method chaining - * @see KeyBuilderSupplier - * @since 0.12.0 - */ -public interface KeyBuilder> extends SecurityBuilder { -} diff --git a/io/jsonwebtoken/security/KeyBuilderSupplier.java b/io/jsonwebtoken/security/KeyBuilderSupplier.java deleted file mode 100644 index d556112..0000000 --- a/io/jsonwebtoken/security/KeyBuilderSupplier.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.Key; - -/** - * Interface implemented by components that support building/creating new {@link Key}s suitable for use with - * their associated cryptographic algorithm implementation. - * - * @param type of {@link Key} created by the builder - * @param type of builder to create each time {@link #key()} is called. - * @see #key() - * @see KeyBuilder - * @since 0.12.0 - */ -public interface KeyBuilderSupplier> { - - /** - * Returns a new {@link KeyBuilder} instance that will produce new secure-random keys with a length sufficient - * to be used by the component's associated cryptographic algorithm. - * - * @return a new {@link KeyBuilder} instance that will produce new secure-random keys with a length sufficient - * to be used by the component's associated cryptographic algorithm. - */ - B key(); -} diff --git a/io/jsonwebtoken/security/KeyException.java b/io/jsonwebtoken/security/KeyException.java deleted file mode 100644 index 4deb866..0000000 --- a/io/jsonwebtoken/security/KeyException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -/** - * General-purpose exception when encountering a problem with a cryptographic {@link java.security.Key} - * or {@link Jwk}. - * - * @since 0.10.0 - */ -public class KeyException extends SecurityException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public KeyException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param msg the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public KeyException(String msg, Throwable cause) { - super(msg, cause); - } -} diff --git a/io/jsonwebtoken/security/KeyLengthSupplier.java b/io/jsonwebtoken/security/KeyLengthSupplier.java deleted file mode 100644 index f550dcf..0000000 --- a/io/jsonwebtoken/security/KeyLengthSupplier.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -/** - * Provides access to the required length in bits (not bytes) of keys usable with the associated algorithm. - * - * @since 0.12.0 - */ -public interface KeyLengthSupplier { - - /** - * Returns the required length in bits (not bytes) of keys usable with the associated algorithm. - * - * @return the required length in bits (not bytes) of keys usable with the associated algorithm. - */ - int getKeyBitLength(); -} diff --git a/io/jsonwebtoken/security/KeyOperation.java b/io/jsonwebtoken/security/KeyOperation.java deleted file mode 100644 index 925a3a0..0000000 --- a/io/jsonwebtoken/security/KeyOperation.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.Identifiable; - -/** - * A {@code KeyOperation} identifies a behavior for which a key may be used. Key validation - * algorithms may inspect a key's operations and reject the key if it is being used in a manner inconsistent - * with its indicated operations. - * - *

KeyOperation Identifier

- * - *

This interface extends {@link Identifiable}; the value returned from {@link #getId()} is a - * CaSe-SeNsItIvE value that uniquely identifies the operation among other KeyOperation instances.

- * - * @see JWK key_ops (Key Operations) Parameter - * @see JSON Web Key Operations Registry - * @since 0.12.0 - */ -public interface KeyOperation extends Identifiable { - - /** - * Returns a brief description of the key operation behavior. - * - * @return a brief description of the key operation behavior. - */ - String getDescription(); - - /** - * Returns {@code true} if the specified {@code operation} is an acceptable use case for the key already assigned - * this operation, {@code false} otherwise. As described in the - * JWK key_ops (Key Operations) Parameter - * specification, Key validation algorithms will likely reject keys with inconsistent or unrelated operations - * because of the security vulnerabilities that could occur otherwise. - * - * @param operation the key operation to check if it is related to (consistent or compatible with) this operation. - * @return {@code true} if the specified {@code operation} is an acceptable use case for the key already assigned - * this operation, {@code false} otherwise. - */ - boolean isRelated(KeyOperation operation); -} diff --git a/io/jsonwebtoken/security/KeyOperationBuilder.java b/io/jsonwebtoken/security/KeyOperationBuilder.java deleted file mode 100644 index 9d41477..0000000 --- a/io/jsonwebtoken/security/KeyOperationBuilder.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.lang.Builder; - -/** - * A {@code KeyOperationBuilder} produces {@link KeyOperation} instances that may be added to a JWK's - * {@link JwkBuilder#operations() key operations} parameter. This is primarily only useful for creating - * custom (non-standard) {@code KeyOperation}s for use with a custom {@link KeyOperationPolicy}, as all standard ones - * are available already via the {@link Jwks.OP} registry singleton. - * - * @see Jwks.OP#builder() - * @see Jwks.OP#policy() - * @see JwkBuilder#operationPolicy(KeyOperationPolicy) - * @since 0.12.0 - */ -public interface KeyOperationBuilder extends Builder { - - /** - * Sets the CaSe-SeNsItIvE {@link KeyOperation#getId() id} expected to be unique compared to all other - * {@code KeyOperation}s. - * - * @param id the key operation id - * @return the builder for method chaining - */ - KeyOperationBuilder id(String id); - - /** - * Sets the key operation {@link KeyOperation#getDescription() description}. - * - * @param description the key operation description - * @return the builder for method chaining - */ - KeyOperationBuilder description(String description); - - /** - * Indicates that the {@code KeyOperation} with the given {@link KeyOperation#getId() id} is cryptographically - * related (and complementary) to this one, and may be specified together in a JWK's - * {@link Jwk#getOperations() operations} set. - * - *

More concretely, calling this method will ensure the following:

- *
-     *     KeyOperation built = Jwks.operation()/*...*/.related(otherId).build();
-     *     KeyOperation other = getKeyOperation(otherId);
-     *     assert built.isRelated(other);
- * - *

A {@link JwkBuilder}'s key operation {@link JwkBuilder#operationPolicy(KeyOperationPolicy) policy} is likely - * to {@link KeyOperationPolicyBuilder#unrelated() reject} any unrelated operations specified - * together due to the potential security vulnerabilities that could occur.

- * - *

This method may be called multiple times to add/append a related {@code id} to the constructed - * {@code KeyOperation}'s total set of related ids.

- * - * @param id the id of a KeyOperation that will be considered cryptographically related to this one. - * @return the builder for method chaining. - * @see JwkBuilder#operationPolicy(KeyOperationPolicy) - */ - KeyOperationBuilder related(String id); -} diff --git a/io/jsonwebtoken/security/KeyOperationPolicied.java b/io/jsonwebtoken/security/KeyOperationPolicied.java deleted file mode 100644 index 49e8938..0000000 --- a/io/jsonwebtoken/security/KeyOperationPolicied.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -/** - * A marker interface that indicates the implementing instance supports the ability to configure a - * {@link KeyOperationPolicy} used to validate JWK instances. - * - * @param the implementing instance for method chaining - */ -public interface KeyOperationPolicied> { - - /** - * Sets the key operation policy that determines which {@link KeyOperation}s may be assigned to a - * JWK. Unless overridden by this method, the default RFC-recommended policy is used where: - *
    - *
  • All {@link Jwks.OP RFC-standard key operations} are supported.
  • - *
  • Multiple unrelated operations may not be assigned to the JWK per the - * RFC 7517, Section 4.3 recommendation: - *
    -     * Multiple unrelated key operations SHOULD NOT be specified for a key
    -     * because of the potential vulnerabilities associated with using the
    -     * same key with multiple algorithms.  Thus, the combinations "{@link Jwks.OP#SIGN sign}"
    -     * with "{@link Jwks.OP#VERIFY verify}", "{@link Jwks.OP#ENCRYPT encrypt}" with "{@link Jwks.OP#DECRYPT decrypt}", and "{@link Jwks.OP#WRAP_KEY wrapKey}" with
    -     * "{@link Jwks.OP#UNWRAP_KEY unwrapKey}" are permitted, but other combinations SHOULD NOT be used.
    - *
  • - *
- * - *

If you wish to enable a different policy, perhaps to support additional custom {@code KeyOperation} values, - * one can be created by using the {@link Jwks.OP#policy()} builder, or by implementing the - * {@link KeyOperationPolicy} interface directly.

- * - * @param policy the policy that determines which {@link KeyOperation}s may be assigned to a JWK. - * @return the builder for method chaining. - * @throws IllegalArgumentException if {@code policy} is null - */ - T operationPolicy(KeyOperationPolicy policy) throws IllegalArgumentException; -} diff --git a/io/jsonwebtoken/security/KeyOperationPolicy.java b/io/jsonwebtoken/security/KeyOperationPolicy.java deleted file mode 100644 index 60389a2..0000000 --- a/io/jsonwebtoken/security/KeyOperationPolicy.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.util.Collection; - -/** - * A key operation policy determines which {@link KeyOperation}s may be assigned to a JWK. - * - * @see JwkBuilder#operationPolicy(KeyOperationPolicy) - * @since 0.12.0 - */ -public interface KeyOperationPolicy { - - /** - * Returns all supported {@code KeyOperation}s that may be assigned to a JWK. - * - * @return all supported {@code KeyOperation}s that may be assigned to a JWK. - */ - Collection getOperations(); - - /** - * Returns quietly if all of the specified key operations are allowed to be assigned to a JWK, - * or throws an {@link IllegalArgumentException} otherwise. - * - * @param ops the operations to validate - */ - @SuppressWarnings("GrazieInspection") - void validate(Collection ops) throws IllegalArgumentException; -} diff --git a/io/jsonwebtoken/security/KeyOperationPolicyBuilder.java b/io/jsonwebtoken/security/KeyOperationPolicyBuilder.java deleted file mode 100644 index 548a1a9..0000000 --- a/io/jsonwebtoken/security/KeyOperationPolicyBuilder.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.Identifiable; -import io.jsonwebtoken.lang.Builder; -import io.jsonwebtoken.lang.CollectionMutator; - -import java.util.Collection; - - -/** - * A {@code KeyOperationPolicyBuilder} produces a {@link KeyOperationPolicy} that determines - * which {@link KeyOperation}s may be assigned to a JWK. Custom {@code KeyOperation}s (such as those created by a - * {@link Jwks.OP#builder()}) may be added to a policy via the {@link #add(KeyOperation)} or {@link #add(Collection)} - * methods. - * - * @see Jwks.OP#policy() - * @see JwkBuilder#operationPolicy(KeyOperationPolicy) - * @see Jwks.OP#builder() - * @since 0.12.0 - */ -public interface KeyOperationPolicyBuilder extends CollectionMutator, - Builder { - - /** - * Allows a JWK to have unrelated {@link KeyOperation}s in its {@code key_ops} parameter values. Be careful - * when calling this method - one should fully understand the security implications of using the same key - * with multiple algorithms in your application. - *

If this method is not called, unrelated key operations are disabled by default per the recommendations in - * RFC 7517, Section 4.3:

- *
-     * Multiple unrelated key operations SHOULD NOT be specified for a key
-     * because of the potential vulnerabilities associated with using the
-     * same key with multiple algorithms.
- * - * @return the builder for method chaining - * @see "key_ops" (Key Operations) - * Parameter - */ - KeyOperationPolicyBuilder unrelated(); - - /** - * Adds the specified key operation to the policy's total set of supported key operations - * used to validate a key's intended usage, replacing any existing one with an identical (CaSe-SeNsItIvE) - * {@link Identifiable#getId() id}. - * - *

Standard {@code KeyOperation}s and Overrides

- * - *

The RFC standard {@link Jwks.OP} key operations are supported by default and do not need - * to be added via this method, but beware: If the {@code op} argument has a JWK standard - * {@link Identifiable#getId() id}, it will replace the JJWT standard operation implementation. - * This is to allow application developers to favor their own implementations over JJWT's default implementations - * if necessary (for example, to support legacy or custom behavior).

- * - *

If a custom {@code KeyOperation} is desired, one may be easily created with a {@link Jwks.OP#builder()}.

- * - * @param op a key operation to add to the policy's total set of supported operations, replacing any - * existing one with the same exact (CaSe-SeNsItIvE) {@link KeyOperation#getId() id}. - * @return the builder for method chaining. - * @see Jwks.OP - * @see Jwks.OP#builder() - * @see JwkBuilder#operationPolicy(KeyOperationPolicy) - * @see JwkBuilder#operations() - */ - @Override - // for better JavaDoc - KeyOperationPolicyBuilder add(KeyOperation op); - - /** - * Adds the specified key operations to the policy's total set of supported key operations - * used to validate a key's intended usage, replacing any existing ones with identical - * {@link Identifiable#getId() id}s. - * - *

There may be only one registered {@code KeyOperation} per CaSe-SeNsItIvE {@code id}, and the - * {@code ops} collection is added in iteration order; if a duplicate id is found when iterating the {@code ops} - * collection, the later operation will evict any existing operation with the same {@code id}.

- * - *

Standard {@code KeyOperation}s and Overrides

- * - *

The RFC standard {@link Jwks.OP} key operations are supported by default and do not need - * to be added via this method, but beware: any operation in the {@code ops} argument with a - * JWK standard {@link Identifiable#getId() id} will replace the JJWT standard operation implementation. - * This is to allow application developers to favor their own implementations over JJWT's default implementations - * if necessary (for example, to support legacy or custom behavior).

- * - *

If custom {@code KeyOperation}s are desired, they may be easily created with a {@link Jwks.OP#builder()}.

- * - * @param ops collection of key operations to add to the policy's total set of supported operations, replacing any - * existing ones with the same exact (CaSe-SeNsItIvE) {@link KeyOperation#getId() id}s. - * @return the builder for method chaining. - * @see Jwks.OP - * @see Jwks.OP#builder() - * @see JwkBuilder#operationPolicy(KeyOperationPolicy) - * @see JwkBuilder#operations() - */ - @Override - // for better JavaDoc - KeyOperationPolicyBuilder add(Collection ops); - -} diff --git a/io/jsonwebtoken/security/KeyPair.java b/io/jsonwebtoken/security/KeyPair.java deleted file mode 100644 index edd2bd1..0000000 --- a/io/jsonwebtoken/security/KeyPair.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.PrivateKey; -import java.security.PublicKey; - -/** - * Generics-capable and type-safe alternative to {@link java.security.KeyPair}. Instances may be - * converted to {@link java.security.KeyPair} if desired via {@link #toJavaKeyPair()}. - * - * @param The type of {@link PublicKey} in the key pair. - * @param The type of {@link PrivateKey} in the key pair. - * @since 0.12.0 - */ -public interface KeyPair { - - /** - * Returns the pair's public key. - * - * @return the pair's public key. - */ - A getPublic(); - - /** - * Returns the pair's private key. - * - * @return the pair's private key. - */ - B getPrivate(); - - /** - * Returns this instance as a {@link java.security.KeyPair} instance. - * - * @return this instance as a {@link java.security.KeyPair} instance. - */ - java.security.KeyPair toJavaKeyPair(); -} diff --git a/io/jsonwebtoken/security/KeyPairBuilder.java b/io/jsonwebtoken/security/KeyPairBuilder.java deleted file mode 100644 index f6db1b2..0000000 --- a/io/jsonwebtoken/security/KeyPairBuilder.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.KeyPair; - -/** - * A {@code KeyPairBuilder} produces new {@link KeyPair}s suitable for use with an associated cryptographic algorithm. - * A new {@link KeyPair} is created each time the builder's {@link #build()} method is called. - * - *

{@code KeyPairBuilder}s are provided by components that implement the {@link KeyPairBuilderSupplier} interface, - * ensuring the resulting {@link KeyPair}s are compatible with their associated cryptographic algorithm.

- * - * @see KeyPairBuilderSupplier - * @since 0.12.0 - */ -public interface KeyPairBuilder extends SecurityBuilder { -} diff --git a/io/jsonwebtoken/security/KeyPairBuilderSupplier.java b/io/jsonwebtoken/security/KeyPairBuilderSupplier.java deleted file mode 100644 index 98d42ea..0000000 --- a/io/jsonwebtoken/security/KeyPairBuilderSupplier.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.KeyPair; - -/** - * Interface implemented by components that support building/creating new {@link KeyPair}s suitable for use with their - * associated cryptographic algorithm implementation. - * - * @see #keyPair() - * @see KeyPairBuilder - * @since 0.12.0 - */ -public interface KeyPairBuilderSupplier { - - /** - * Returns a new {@link KeyPairBuilder} that will create new secure-random {@link KeyPair}s with a length and - * parameters sufficient for use with the component's associated cryptographic algorithm. - * - * @return a new {@link KeyPairBuilder} that will create new secure-random {@link KeyPair}s with a length and - * parameters sufficient for use with the component's associated cryptographic algorithm. - */ - KeyPairBuilder keyPair(); -} diff --git a/io/jsonwebtoken/security/KeyRequest.java b/io/jsonwebtoken/security/KeyRequest.java deleted file mode 100644 index ffe2206..0000000 --- a/io/jsonwebtoken/security/KeyRequest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.JweHeader; - -/** - * A request to a {@link KeyAlgorithm} to obtain the key necessary for AEAD encryption or decryption. The exact - * {@link AeadAlgorithm} that will be used is accessible via {@link #getEncryptionAlgorithm()}. - * - *

Encryption Requests

- *

For an encryption key request, {@link #getPayload()} will return - * the encryption key to use. Additionally, any public information specific to the called - * {@link KeyAlgorithm} implementation that is required to be transmitted in the JWE (such as an initialization vector, - * authentication tag or ephemeral key, etc) may be added to the JWE protected header, accessible via - * {@link #getHeader()}. Although the JWE header is checked for authenticity and integrity, it itself is - * not encrypted, so {@link KeyAlgorithm}s should never place any secret or private information in the - * header.

- * - *

Decryption Requests

- *

For a decryption request, the {@code KeyRequest} instance will be - * a {@link DecryptionKeyRequest} instance, {@link #getPayload()} will return the encrypted key ciphertext (a - * byte array), and the decryption key will be available via {@link DecryptionKeyRequest#getKey()}. Additionally, - * any public information necessary by the called {@link KeyAlgorithm} (such as an initialization vector, - * authentication tag, ephemeral key, etc) is expected to be available in the JWE protected header, accessible - * via {@link #getHeader()}.

- * - * @param the type of object relevant during key algorithm cryptographic operations. - * @see DecryptionKeyRequest - * @since 0.12.0 - */ -public interface KeyRequest extends Request { - - /** - * Returns the {@link AeadAlgorithm} that will be called for encryption or decryption after processing the - * {@code KeyRequest}. {@link KeyAlgorithm} implementations that generate an ephemeral {@code SecretKey} to use - * as what the
JWE specification calls a - * "Content Encryption Key (CEK)" should call the {@code AeadAlgorithm}'s - * {@link AeadAlgorithm#key() key()} builder to create a key suitable for that exact {@code AeadAlgorithm}. - * - * @return the {@link AeadAlgorithm} that will be called for encryption or decryption after processing the - * {@code KeyRequest}. - */ - AeadAlgorithm getEncryptionAlgorithm(); - - /** - * Returns the {@link JweHeader} that will be used to construct the final JWE header, available for - * reading or writing any {@link KeyAlgorithm}-specific information. - * - *

For an encryption key request, any public information specific to the called {@code KeyAlgorithm} - * implementation that is required to be transmitted in the JWE (such as an initialization vector, - * authentication tag or ephemeral key, etc) is expected to be added to this header. Although the header is - * checked for authenticity and integrity, it itself is not encrypted, so - * {@link KeyAlgorithm}s should never place any secret or private information in the header.

- * - *

For a decryption request, any public information necessary by the called {@link KeyAlgorithm} - * (such as an initialization vector, authentication tag, ephemeral key, etc) is expected to be available in - * this header.

- * - * @return the {@link JweHeader} that will be used to construct the final JWE header, available for - * reading or writing any {@link KeyAlgorithm}-specific information. - */ - JweHeader getHeader(); -} diff --git a/io/jsonwebtoken/security/KeyResult.java b/io/jsonwebtoken/security/KeyResult.java deleted file mode 100644 index 753909d..0000000 --- a/io/jsonwebtoken/security/KeyResult.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import javax.crypto.SecretKey; - -/** - * The result of a {@link KeyAlgorithm} encryption key request, containing the resulting - * {@code JWE encrypted key} and {@code JWE Content Encryption Key (CEK)}, concepts defined in - * JWE Terminology. - * - *

The result {@link #getPayload() payload} is the {@code JWE encrypted key}, which will be Base64URL-encoded - * and embedded in the resulting compact JWE string.

- * - *

The result {@link #getKey() key} is the {@code JWE Content Encryption Key (CEK)} which will be used to encrypt - * the JWE.

- * - * @since 0.12.0 - */ -public interface KeyResult extends Message, KeySupplier { -} diff --git a/io/jsonwebtoken/security/KeySupplier.java b/io/jsonwebtoken/security/KeySupplier.java deleted file mode 100644 index 2026b25..0000000 --- a/io/jsonwebtoken/security/KeySupplier.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.Key; - -/** - * Provides access to a cryptographic {@link Key} necessary for signing, wrapping, encryption or decryption algorithms. - * - * @param the type of key provided by this supplier. - * @since 0.12.0 - */ -public interface KeySupplier { - - /** - * Returns the key to use for signing, wrapping, encryption or decryption depending on the type of operation. - * - * @return the key to use for signing, wrapping, encryption or decryption depending on the type of operation. - */ - K getKey(); -} diff --git a/io/jsonwebtoken/security/Keys.java b/io/jsonwebtoken/security/Keys.java deleted file mode 100644 index 9ae54e7..0000000 --- a/io/jsonwebtoken/security/Keys.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.lang.Assert; -import io.jsonwebtoken.lang.Classes; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.Provider; -import java.security.PublicKey; - -/** - * Utility class for securely generating {@link SecretKey}s and {@link KeyPair}s. - * - * @since 0.10.0 - */ -public final class Keys { - - private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.KeysBridge"; - private static final Class BRIDGE_CLASS = Classes.forName(BRIDGE_CLASSNAME); - private static final Class[] FOR_PASSWORD_ARG_TYPES = new Class[]{char[].class}; - private static final Class[] SECRET_BUILDER_ARG_TYPES = new Class[]{SecretKey.class}; - private static final Class[] PRIVATE_BUILDER_ARG_TYPES = new Class[]{PrivateKey.class}; - - private static T invokeStatic(String method, Class[] argTypes, Object... args) { - return Classes.invokeStatic(BRIDGE_CLASS, method, argTypes, args); - } - - //prevent instantiation - private Keys() { - } - - /** - * Creates a new SecretKey instance for use with HMAC-SHA algorithms based on the specified key byte array. - * - * @param bytes the key byte array - * @return a new SecretKey instance for use with HMAC-SHA algorithms based on the specified key byte array. - * @throws WeakKeyException if the key byte array length is less than 256 bits (32 bytes) as mandated by the - * JWT JWA Specification - * (RFC 7518, Section 3.2) - */ - public static SecretKey hmacShaKeyFor(byte[] bytes) throws WeakKeyException { - - if (bytes == null) { - throw new InvalidKeyException("SecretKey byte array cannot be null."); - } - - int bitLength = bytes.length * 8; - - //Purposefully ordered higher to lower to ensure the strongest key possible can be generated. - if (bitLength >= 512) { - return new SecretKeySpec(bytes, "HmacSHA512"); - } else if (bitLength >= 384) { - return new SecretKeySpec(bytes, "HmacSHA384"); - } else if (bitLength >= 256) { - return new SecretKeySpec(bytes, "HmacSHA256"); - } - - String msg = "The specified key byte array is " + bitLength + " bits which " + - "is not secure enough for any JWT HMAC-SHA algorithm. The JWT " + - "JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a " + - "size >= 256 bits (the key size must be greater than or equal to the hash " + - "output size). Consider using the Jwts.SIG.HS256.key() builder (or HS384.key() " + - "or HS512.key()) to create a key guaranteed to be secure enough for your preferred HMAC-SHA " + - "algorithm. See https://tools.ietf.org/html/rfc7518#section-3.2 for more information."; - throw new WeakKeyException(msg); - } - - /** - *

Deprecation Notice

- * - *

As of JJWT 0.12.0, symmetric (secret) key algorithm instances can generate a key of suitable - * length for that specific algorithm by calling their {@code key()} builder method directly. For example:

- * - *

-     * {@link Jwts.SIG#HS256}.key().build();
-     * {@link Jwts.SIG#HS384}.key().build();
-     * {@link Jwts.SIG#HS512}.key().build();
-     * 
- * - *

Call those methods as needed instead of this static {@code secretKeyFor} helper method - the returned - * {@link KeyBuilder} allows callers to specify a preferred Provider or SecureRandom on the builder if - * desired, whereas this {@code secretKeyFor} method does not. Consequently this helper method will be removed - * before the 1.0 release.

- * - *

Previous Documentation

- * - *

Returns a new {@link SecretKey} with a key length suitable for use with the specified {@link SignatureAlgorithm}.

- * - *

JWA Specification (RFC 7518), Section 3.2 - * requires minimum key lengths to be used for each respective Signature Algorithm. This method returns a - * secure-random generated SecretKey that adheres to the required minimum key length. The lengths are:

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
JWA HMAC-SHA Key Length Requirements
AlgorithmKey Length
HS256256 bits (32 bytes)
HS384384 bits (48 bytes)
HS512512 bits (64 bytes)
- * - * @param alg the {@code SignatureAlgorithm} to inspect to determine which key length to use. - * @return a new {@link SecretKey} instance suitable for use with the specified {@link SignatureAlgorithm}. - * @throws IllegalArgumentException for any input value other than {@link io.jsonwebtoken.SignatureAlgorithm#HS256}, - * {@link io.jsonwebtoken.SignatureAlgorithm#HS384}, or {@link io.jsonwebtoken.SignatureAlgorithm#HS512} - * @deprecated since 0.12.0. Use your preferred {@link MacAlgorithm} instance's - * {@link MacAlgorithm#key() key()} builder method directly. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - public static SecretKey secretKeyFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException { - Assert.notNull(alg, "SignatureAlgorithm cannot be null."); - SecureDigestAlgorithm salg = Jwts.SIG.get().get(alg.name()); - if (!(salg instanceof MacAlgorithm)) { - String msg = "The " + alg.name() + " algorithm does not support shared secret keys."; - throw new IllegalArgumentException(msg); - } - return ((MacAlgorithm) salg).key().build(); - } - - /** - *

Deprecation Notice

- * - *

As of JJWT 0.12.0, asymmetric key algorithm instances can generate KeyPairs of suitable strength - * for that specific algorithm by calling their {@code keyPair()} builder method directly. For example:

- * - *
-     * Jwts.SIG.{@link Jwts.SIG#RS256 RS256}.keyPair().build();
-     * Jwts.SIG.{@link Jwts.SIG#RS384 RS384}.keyPair().build();
-     * Jwts.SIG.{@link Jwts.SIG#RS512 RS512}.keyPair().build();
-     * ... etc ...
-     * Jwts.SIG.{@link Jwts.SIG#ES512 ES512}.keyPair().build();
- * - *

Call those methods as needed instead of this static {@code keyPairFor} helper method - the returned - * {@link KeyPairBuilder} allows callers to specify a preferred Provider or SecureRandom on the builder if - * desired, whereas this {@code keyPairFor} method does not. Consequently this helper method will be removed - * before the 1.0 release.

- * - *

Previous Documentation

- * - *

Returns a new {@link KeyPair} suitable for use with the specified asymmetric algorithm.

- * - *

If the {@code alg} argument is an RSA algorithm, a KeyPair is generated based on the following:

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Generated RSA Key Sizes
JWA AlgorithmKey Size
RS2562048 bits
PS2562048 bits
RS3843072 bits
PS3843072 bits
RS5124096 bits
PS5124096 bits
- * - *

If the {@code alg} argument is an Elliptic Curve algorithm, a KeyPair is generated based on the following:

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Generated Elliptic Curve Key Parameters
JWA AlgorithmKey SizeJWA Curve NameASN1 OID Curve Name
ES256256 bits{@code P-256}{@code secp256r1}
ES384384 bits{@code P-384}{@code secp384r1}
ES512521 bits{@code P-521}{@code secp521r1}
- * - * @param alg the {@code SignatureAlgorithm} to inspect to determine which asymmetric algorithm to use. - * @return a new {@link KeyPair} suitable for use with the specified asymmetric algorithm. - * @throws IllegalArgumentException if {@code alg} is not an asymmetric algorithm - * @deprecated since 0.12.0 in favor of your preferred - * {@link io.jsonwebtoken.security.SignatureAlgorithm} instance's - * {@link SignatureAlgorithm#keyPair() keyPair()} builder method directly. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - public static KeyPair keyPairFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException { - Assert.notNull(alg, "SignatureAlgorithm cannot be null."); - SecureDigestAlgorithm salg = Jwts.SIG.get().get(alg.name()); - if (!(salg instanceof SignatureAlgorithm)) { - String msg = "The " + alg.name() + " algorithm does not support Key Pairs."; - throw new IllegalArgumentException(msg); - } - SignatureAlgorithm asalg = ((SignatureAlgorithm) salg); - return asalg.keyPair().build(); - } - - /** - * Returns a new {@link Password} instance suitable for use with password-based key derivation algorithms. - * - *

Usage Note: Using {@code Password}s outside of key derivation contexts will likely - * fail. See the {@link Password} JavaDoc for more, and also note the Password Safety section below.

- * - *

Password Safety

- * - *

Instances returned by this method use a clone of the specified {@code password} character array - * argument - changes to the argument array will NOT be reflected in the returned key, and vice versa. If you wish - * to clear a {@code Password} instance to ensure it is no longer usable, call its {@link Password#destroy()} - * method will clear/overwrite its internal cloned char array. Also note that each subsequent call to - * {@link Password#toCharArray()} will also return a new clone of the underlying password character array per - * standard JCE key behavior.

- * - * @param password the raw password character array to clone for use with password-based key derivation algorithms. - * @return a new {@link Password} instance that wraps a new clone of the specified {@code password} character array. - * @see Password#toCharArray() - * @since 0.12.0 - */ - public static Password password(char[] password) { - return invokeStatic("password", FOR_PASSWORD_ARG_TYPES, new Object[]{password}); - } - - /** - * Returns a {@code SecretKeyBuilder} that produces the specified key, allowing association with a - * {@link SecretKeyBuilder#provider(Provider) provider} that must be used with the key during cryptographic - * operations. For example: - * - *
-     * SecretKey key = Keys.builder(key).provider(mandatoryProvider).build();
- * - *

Cryptographic algorithm implementations can inspect the resulting {@code key} instance and obtain its - * mandatory {@code Provider} if necessary.

- * - *

This method is primarily only useful for keys that cannot expose key material, such as PKCS11 or HSM - * (Hardware Security Module) keys, and require a specific {@code Provider} to be used during cryptographic - * operations.

- * - * @param key the secret key to use for cryptographic operations, potentially associated with a configured - * {@link Provider} - * @return a new {@code SecretKeyBuilder} that produces the specified key, potentially associated with any - * specified provider. - * @since 0.12.0 - */ - public static SecretKeyBuilder builder(SecretKey key) { - Assert.notNull(key, "SecretKey cannot be null."); - return invokeStatic("builder", SECRET_BUILDER_ARG_TYPES, key); - } - - /** - * Returns a {@code PrivateKeyBuilder} that produces the specified key, allowing association with a - * {@link PrivateKeyBuilder#publicKey(PublicKey) publicKey} to obtain public key data if necessary, or a - * {@link SecretKeyBuilder#provider(Provider) provider} that must be used with the key during cryptographic - * operations. For example: - * - *
-     * PrivateKey key = Keys.builder(privateKey).publicKey(publicKey).provider(mandatoryProvider).build();
- * - *

Cryptographic algorithm implementations can inspect the resulting {@code key} instance and obtain its - * mandatory {@code Provider} or {@code PublicKey} if necessary.

- * - *

This method is primarily only useful for keys that cannot expose key material, such as PKCS11 or HSM - * (Hardware Security Module) keys, and require a specific {@code Provider} or public key data to be used - * during cryptographic operations.

- * - * @param key the private key to use for cryptographic operations, potentially associated with a configured - * {@link Provider} or {@link PublicKey}. - * @return a new {@code PrivateKeyBuilder} that produces the specified private key, potentially associated with any - * specified provider or {@code PublicKey} - * @since 0.12.0 - */ - public static PrivateKeyBuilder builder(PrivateKey key) { - Assert.notNull(key, "PrivateKey cannot be null."); - return invokeStatic("builder", PRIVATE_BUILDER_ARG_TYPES, key); - } -} diff --git a/io/jsonwebtoken/security/MacAlgorithm.java b/io/jsonwebtoken/security/MacAlgorithm.java deleted file mode 100644 index e34d927..0000000 --- a/io/jsonwebtoken/security/MacAlgorithm.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright © 2022 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.Identifiable; - -import javax.crypto.SecretKey; - -/** - * A {@link SecureDigestAlgorithm} that uses symmetric {@link SecretKey}s to both compute and verify digests as - * message authentication codes (MACs). - * - *

Standard Identifier

- * - *

{@code MacAlgorithm} extends {@link Identifiable}: when a {@code MacAlgorithm} is used to compute the MAC of a - * JWS, the value returned from {@link Identifiable#getId() macAlgorithm.getId()} will be set as the JWS - * "alg" protected header value.

- * - *

Key Strength

- * - *

MAC algorithm strength is in part attributed to how difficult it is to discover the secret key. - * As such, MAC algorithms usually require keys of a minimum length to ensure the keys are difficult to discover - * and the algorithm's security properties are maintained.

- * - *

The {@code MacAlgorithm} interface extends the {@link KeyLengthSupplier} interface to represent - * the length in bits (not bytes) a key must have to be used with its implementation. If you do not want to - * worry about lengths and parameters of keys required for an algorithm, it is often easier to automatically generate - * a key that adheres to the algorithms requirements, as discussed below.

- * - *

Key Generation

- * - *

{@code MacAlgorithm} extends {@link KeyBuilderSupplier} to enable {@link SecretKey} generation. - * Each {@code MacAlgorithm} algorithm instance will return a {@link KeyBuilder} that ensures any created keys will - * have a sufficient length and any algorithm parameters required by that algorithm. For example:

- * - *
- * SecretKey key = macAlgorithm.key().build();
- * - *

The resulting {@code key} is guaranteed to have the correct algorithm parameters and strength/length necessary for - * that exact {@code MacAlgorithm} instance.

- * - *

JWA Standard Implementations

- * - *

Constant definitions and utility methods for all JWA (RFC 7518) standard MAC algorithms are - * available via {@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}.

- * - * @see io.jsonwebtoken.Jwts.SIG Jwts.SIG - * @since 0.12.0 - */ -public interface MacAlgorithm extends SecureDigestAlgorithm, - KeyBuilderSupplier, KeyLengthSupplier { -} diff --git a/io/jsonwebtoken/security/MalformedKeyException.java b/io/jsonwebtoken/security/MalformedKeyException.java deleted file mode 100644 index 59c8016..0000000 --- a/io/jsonwebtoken/security/MalformedKeyException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -/** - * Exception thrown when encountering a key or key material that is incomplete or improperly configured or - * formatted and cannot be used as expected. - * - * @since 0.12.0 - */ -public class MalformedKeyException extends InvalidKeyException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public MalformedKeyException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param msg the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public MalformedKeyException(String msg, Throwable cause) { - super(msg, cause); - } -} diff --git a/io/jsonwebtoken/security/MalformedKeySetException.java b/io/jsonwebtoken/security/MalformedKeySetException.java deleted file mode 100644 index aa268d7..0000000 --- a/io/jsonwebtoken/security/MalformedKeySetException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -/** - * Exception thrown when encountering a {@link JwkSet} that is incomplete or improperly configured or - * formatted and cannot be used as expected. - * - * @since 0.12.0 - */ -public class MalformedKeySetException extends SecurityException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public MalformedKeySetException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public MalformedKeySetException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/security/Message.java b/io/jsonwebtoken/security/Message.java deleted file mode 100644 index cd5e8df..0000000 --- a/io/jsonwebtoken/security/Message.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.Key; - -/** - * A message contains a {@link #getPayload() payload} used as input to or output from a cryptographic algorithm. - * - * @param The type of payload in the message. - * @since 0.12.0 - */ -public interface Message { - - /** - * Returns the message payload used as input to or output from a cryptographic algorithm. This is almost always - * plaintext used for cryptographic signatures or encryption, or ciphertext for decryption, or a {@link Key} - * instance for wrapping or unwrapping algorithms. - * - * @return the message payload used as input to or output from a cryptographic algorithm. - */ - T getPayload(); //plaintext, ciphertext or Key -} diff --git a/io/jsonwebtoken/security/OctetPrivateJwk.java b/io/jsonwebtoken/security/OctetPrivateJwk.java deleted file mode 100644 index cf9956f..0000000 --- a/io/jsonwebtoken/security/OctetPrivateJwk.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.interfaces.ECPrivateKey; - -/** - * JWK representation of an Edwards Curve - * {@link PrivateKey} as defined by RFC 8037, Section 2: - * Key Type "OKP". - * - *

Unlike the {@link EcPrivateJwk} interface, which only supports - * Weierstrass-form {@link ECPrivateKey}s, - * {@code OctetPrivateJwk} allows for multiple parameterized {@link PrivateKey} types - * because the JDK supports two different types of Edwards Curve private keys:

- * - *

As such, {@code OctetPrivateJwk} is parameterized to support both key types.

- * - *

Earlier JDK Versions

- * - *

Even though {@code XECPrivateKey} and {@code EdECPrivateKey} were introduced in JDK 11 and JDK 15 respectively, - * JJWT supports Octet private JWKs in earlier versions when BouncyCastle is enabled in the application classpath. When - * using earlier JDK versions, the {@code OctetPrivateJwk} instance will need be parameterized with the - * generic {@code PrivateKey} type since the latter key types would not be present. For example:

- *
- * OctetPrivateJwk<PrivateKey> octetPrivateJwk = getKey();
- * - *

OKP-specific Properties

- * - *

Note that the various OKP-specific properties are not available as separate dedicated getter methods, as most Java - * applications should rarely, if ever, need to access these individual key properties since they typically represent - * internal key material and/or serialization details. If you need to access these key properties, it is usually - * recommended to obtain the corresponding {@link PrivateKey} instance returned by {@link #toKey()} and - * query that instead.

- * - *

Even so, because these properties exist and are readable by nature of every JWK being a - * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method - * using an appropriate JWK parameter id, for example:

- *
- * jwk.get("x");
- * jwk.get("d");
- * // ... etc ...
- * - * @param The type of Edwards-curve {@link PrivateKey} represented by this JWK (e.g. XECPrivateKey, EdECPrivateKey, etc). - * @param The type of Edwards-curve {@link PublicKey} represented by the JWK's corresponding - * {@link #toPublicJwk() public JWK}, for example XECPublicKey, EdECPublicKey, etc. - * @since 0.12.0 - */ -public interface OctetPrivateJwk extends PrivateJwk> { -} diff --git a/io/jsonwebtoken/security/OctetPrivateJwkBuilder.java b/io/jsonwebtoken/security/OctetPrivateJwkBuilder.java deleted file mode 100644 index 16ebd88..0000000 --- a/io/jsonwebtoken/security/OctetPrivateJwkBuilder.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.PrivateKey; -import java.security.PublicKey; - -/** - * A {@link PrivateJwkBuilder} that creates {@link OctetPrivateJwk} instances. - * - * @param The type of {@link PrivateKey} represented by the constructed {@link OctetPrivateJwk} instance. - * @param The type of {@link PublicKey} available from the constructed {@link OctetPrivateJwk}'s associated {@link PrivateJwk#toPublicJwk() public JWK} properties. - * @since 0.12.0 - */ -public interface OctetPrivateJwkBuilder extends - PrivateJwkBuilder, OctetPrivateJwk, OctetPrivateJwkBuilder> { -} diff --git a/io/jsonwebtoken/security/OctetPublicJwk.java b/io/jsonwebtoken/security/OctetPublicJwk.java deleted file mode 100644 index 18a0d5d..0000000 --- a/io/jsonwebtoken/security/OctetPublicJwk.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.PublicKey; -import java.security.interfaces.ECPublicKey; - -/** - * JWK representation of an Edwards Curve - * {@link PublicKey} as defined by RFC 8037, Section 2: - * Key Type "OKP". - * - *

Unlike the {@link EcPublicJwk} interface, which only supports - * Weierstrass-form {@link ECPublicKey}s, - * {@code OctetPublicJwk} allows for multiple parameterized {@link PublicKey} types - * because the JDK supports two different types of Edwards Curve public keys:

- * - *

As such, {@code OctetPublicJwk} is parameterized to support both key types.

- * - *

Earlier JDK Versions

- * - *

Even though {@code XECPublicKey} and {@code EdECPublicKey} were introduced in JDK 11 and JDK 15 respectively, - * JJWT supports Octet public JWKs in earlier versions when BouncyCastle is enabled in the application classpath. When - * using earlier JDK versions, the {@code OctetPublicJwk} instance will need be parameterized with the - * generic {@code PublicKey} type since the latter key types would not be present. For example:

- *
OctetPublicJwk<PublicKey> octetPublicJwk = getKey();
- * - *

OKP-specific Properties

- * - *

Note that the various OKP-specific properties are not available as separate dedicated getter methods, as most Java - * applications should rarely, if ever, need to access these individual key properties since they typically represent - * internal key material and/or serialization details. If you need to access these key properties, it is usually - * recommended to obtain the corresponding {@link PublicKey} instance returned by {@link #toKey()} and - * query that instead.

- * - *

Even so, because these properties exist and are readable by nature of every JWK being a - * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method - * using an appropriate JWK parameter id, for example:

- *
- * jwk.get("x");
- * // ... etc ...
- * - * @param The type of Edwards-curve {@link PublicKey} represented by this JWK (e.g. XECPublicKey, EdECPublicKey, etc). - * @since 0.12.0 - */ -public interface OctetPublicJwk extends PublicJwk { -} diff --git a/io/jsonwebtoken/security/OctetPublicJwkBuilder.java b/io/jsonwebtoken/security/OctetPublicJwkBuilder.java deleted file mode 100644 index 4ac24da..0000000 --- a/io/jsonwebtoken/security/OctetPublicJwkBuilder.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2019 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.PrivateKey; -import java.security.PublicKey; - -/** - * A {@link PublicJwkBuilder} that creates {@link OctetPublicJwk} instances. - * - * @param the type of {@link PublicKey} provided by the created {@link OctetPublicJwk} (e.g. XECPublicKey, EdECPublicKey, etc). - * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce an - * {@link OctetPrivateJwk} if desired. For example, XECPrivateKey, EdECPrivateKey, etc. - * @since 0.12.0 - */ -public interface OctetPublicJwkBuilder - extends PublicJwkBuilder, OctetPrivateJwk, OctetPrivateJwkBuilder, OctetPublicJwkBuilder> { -} diff --git a/io/jsonwebtoken/security/Password.java b/io/jsonwebtoken/security/Password.java deleted file mode 100644 index 0972e9b..0000000 --- a/io/jsonwebtoken/security/Password.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import javax.crypto.SecretKey; -import javax.security.auth.Destroyable; - -/** - * A {@code Key} suitable for use with password-based key derivation algorithms. - * - *

Usage Warning

- * - *

Because raw passwords should never be used as direct inputs for cryptographic operations (such as authenticated - * hashing or encryption) - and only for derivation algorithms (like password-based encryption) - {@code Password} - * instances will throw an exception when used in these invalid contexts. Specifically, calling a - * {@code Password}'s {@link Password#getEncoded() getEncoded()} method (as would be done automatically by the - * JCA subsystem during direct cryptographic operations) will throw an - * {@link UnsupportedOperationException UnsupportedOperationException}.

- * - * @see #toCharArray() - * @since 0.12.0 - */ -public interface Password extends SecretKey, Destroyable { - - /** - * Returns a new clone of the underlying password character array for use during derivation algorithms. Like all - * {@code SecretKey} implementations, if you wish to clear the backing password character array for - * safety/security reasons, call the {@link #destroy()} method, ensuring that both the character array is cleared - * and the {@code Password} instance can no longer be used. - * - *

Usage

- * - *

Because a new clone is returned from this method each time it is invoked, it is expected that callers will - * clear the resulting clone from memory as soon as possible to reduce probability of password exposure. For - * example:

- * - *

-     * char[] clonedPassword = aPassword.toCharArray();
-     * try {
-     *     doSomethingWithPassword(clonedPassword);
-     * } finally {
-     *     // guarantee clone is cleared regardless of any Exception thrown:
-     *     java.util.Arrays.fill(clonedPassword, '\u0000');
-     * }
-     * 
- * - * @return a clone of the underlying password character array. - */ - char[] toCharArray(); -} diff --git a/io/jsonwebtoken/security/PrivateJwk.java b/io/jsonwebtoken/security/PrivateJwk.java deleted file mode 100644 index 2eb3bf2..0000000 --- a/io/jsonwebtoken/security/PrivateJwk.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.PrivateKey; -import java.security.PublicKey; - -/** - * JWK representation of a {@link PrivateKey}. - * - *

JWK Private Key vs Java {@code PrivateKey} differences

- * - *

Unlike the Java cryptography APIs, the JWK specification requires all public key and private key - * properties to be contained within every private JWK. As such, a {@code PrivateJwk} indeed represents - * private key values as its name implies, but it is probably more similar to the Java JCA concept of a - * {@link java.security.KeyPair} since it contains everything for both keys.

- * - *

Consequently a {@code PrivateJwk} is capable of providing two additional convenience methods:

- *
    - *
  • {@link #toPublicJwk()} - a method to obtain a {@link PublicJwk} instance that contains only the JWK public - * key properties, and
  • - *
  • {@link #toKeyPair()} - a method to obtain both Java {@link PublicKey} and {@link PrivateKey}s in aggregate - * as a {@link KeyPair} instance if desired.
  • - *
- * - * @param The type of {@link PrivateKey} represented by this JWK - * @param The type of {@link PublicKey} represented by the JWK's corresponding {@link #toPublicJwk() public JWK}. - * @param The type of {@link PublicJwk} reflected by the JWK's public properties. - * @since 0.12.0 - */ -public interface PrivateJwk> extends AsymmetricJwk { - - /** - * Returns the private JWK's corresponding {@link PublicJwk}, containing only the key's public properties. - * - * @return the private JWK's corresponding {@link PublicJwk}, containing only the key's public properties. - */ - M toPublicJwk(); - - /** - * Returns the key's corresponding Java {@link PrivateKey} and {@link PublicKey} in aggregate as a - * type-safe {@link KeyPair} instance. - * - * @return the key's corresponding Java {@link PrivateKey} and {@link PublicKey} in aggregate as a - * type-safe {@link KeyPair} instance. - */ - KeyPair toKeyPair(); -} diff --git a/io/jsonwebtoken/security/PrivateJwkBuilder.java b/io/jsonwebtoken/security/PrivateJwkBuilder.java deleted file mode 100644 index bbd24b8..0000000 --- a/io/jsonwebtoken/security/PrivateJwkBuilder.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.PrivateKey; -import java.security.PublicKey; - -/** - * An {@link AsymmetricJwkBuilder} that creates {@link PrivateJwk} instances. - * - * @param the type of Java {@link PrivateKey} provided by the created private JWK. - * @param the type of Java {@link PublicKey} paired with the private key. - * @param the type of {@link PrivateJwk} created - * @param the type of {@link PublicJwk} paired with the created private JWK. - * @param the type of the builder, for subtype method chaining - * @see #publicKey(PublicKey) - * @since 0.12.0 - */ -public interface PrivateJwkBuilder, M extends PrivateJwk, - T extends PrivateJwkBuilder> extends AsymmetricJwkBuilder { - - /** - * Allows specifying of the {@link PublicKey} associated with the builder's existing {@link PrivateKey}, - * offering a reasonable performance enhancement when building the final private JWK. Application developers - * should prefer to use this method when possible when building private JWKs. - * - *

As discussed in the {@link PrivateJwk} documentation, the JWK and JWA specifications require private JWKs to - * contain both private key and public key data. If a public key is not provided via this - * {@code publicKey} method, the builder implementation must go through the work to derive the - * {@code PublicKey} instance based on the {@code PrivateKey} to obtain the necessary public key information.

- * - *

Calling this method with the {@code PrivateKey}'s matching {@code PublicKey} instance eliminates the need - * for the builder to do that work.

- * - * @param publicKey the {@link PublicKey} that matches the builder's existing {@link PrivateKey}. - * @return the builder for method chaining. - */ - T publicKey(L publicKey); -} diff --git a/io/jsonwebtoken/security/PrivateKeyBuilder.java b/io/jsonwebtoken/security/PrivateKeyBuilder.java deleted file mode 100644 index 5bdee74..0000000 --- a/io/jsonwebtoken/security/PrivateKeyBuilder.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.PrivateKey; -import java.security.Provider; -import java.security.PublicKey; - -/** - * A builder that allows a {@code PrivateKey} to be transparently associated with a {@link #provider(Provider)} or - * {@link #publicKey(PublicKey)} if necessary for algorithms that require them. - * - * @since 0.12.0 - */ -public interface PrivateKeyBuilder extends KeyBuilder { - - /** - * Sets the private key's corresponding {@code PublicKey} so that its public key material will be available to - * algorithms that require it. - * - * @param publicKey the private key's corresponding {@code PublicKey} - * @return the builder for method chaining. - */ - PrivateKeyBuilder publicKey(PublicKey publicKey); -} diff --git a/io/jsonwebtoken/security/PublicJwk.java b/io/jsonwebtoken/security/PublicJwk.java deleted file mode 100644 index 6f1eb20..0000000 --- a/io/jsonwebtoken/security/PublicJwk.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.PublicKey; - -/** - * JWK representation of a {@link PublicKey}. - * - * @param The type of {@link PublicKey} represented by this JWK - * @since 0.12.0 - */ -public interface PublicJwk extends AsymmetricJwk { -} diff --git a/io/jsonwebtoken/security/PublicJwkBuilder.java b/io/jsonwebtoken/security/PublicJwkBuilder.java deleted file mode 100644 index eada333..0000000 --- a/io/jsonwebtoken/security/PublicJwkBuilder.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.PrivateKey; -import java.security.PublicKey; - -/** - * An {@link AsymmetricJwkBuilder} that creates {@link PublicJwk} instances. - * - * @param the type of {@link PublicKey} provided by the created public JWK. - * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce a {@link PrivateJwk} if desired. - * @param the type of {@link PublicJwk} created - * @param the type of {@link PrivateJwk} that matches the created {@link PublicJwk} - * @param

the type of {@link PrivateJwkBuilder} that matches this builder if a {@link PrivateJwk} is desired. - * @param the type of the builder, for subtype method chaining - * @see #privateKey(PrivateKey) - * @since 0.12.0 - */ -public interface PublicJwkBuilder, M extends PrivateJwk, - P extends PrivateJwkBuilder, - T extends PublicJwkBuilder> extends AsymmetricJwkBuilder { - - /** - * Sets the {@link PrivateKey} that pairs with the builder's existing {@link PublicKey}, converting this builder - * into a {@link PrivateJwkBuilder} which will produce a corresponding {@link PrivateJwk} instance. The - * specified {@code privateKey} MUST be the exact private key paired with the builder's public key. - * - * @param privateKey the {@link PrivateKey} that pairs with the builder's existing {@link PublicKey} - * @return the builder coerced as a {@link PrivateJwkBuilder} which will produce a corresponding {@link PrivateJwk}. - */ - P privateKey(L privateKey); -} diff --git a/io/jsonwebtoken/security/Request.java b/io/jsonwebtoken/security/Request.java deleted file mode 100644 index 77e0d32..0000000 --- a/io/jsonwebtoken/security/Request.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.Provider; -import java.security.SecureRandom; - -/** - * A {@code Request} aggregates various parameters that may be used by a particular cryptographic algorithm. It and - * any of its subtypes implemented as a single object submitted to an algorithm effectively reflect the - * Parameter Object design pattern. This - * provides for a much cleaner request/result algorithm API instead of polluting the API with an excessive number of - * overloaded methods that would exist otherwise. - * - *

The {@code Request} interface specifically allows for JCA {@link Provider} and {@link SecureRandom} instances - * to be used during request execution, which allows more flexibility than forcing a single {@code Provider} or - * {@code SecureRandom} for all executions. {@code Request} subtypes provide additional parameters as necessary - * depending on the type of cryptographic algorithm invoked.

- * - * @param the type of payload in the request. - * @see #getProvider() - * @see #getSecureRandom() - * @since 0.12.0 - */ -public interface Request extends Message { - - /** - * Returns the JCA provider that should be used for cryptographic operations during the request or - * {@code null} if the JCA subsystem preferred provider should be used. - * - * @return the JCA provider that should be used for cryptographic operations during the request or - * {@code null} if the JCA subsystem preferred provider should be used. - */ - Provider getProvider(); - - /** - * Returns the {@code SecureRandom} to use when performing cryptographic operations during the request, or - * {@code null} if a default {@link SecureRandom} should be used. - * - * @return the {@code SecureRandom} to use when performing cryptographic operations during the request, or - * {@code null} if a default {@link SecureRandom} should be used. - */ - SecureRandom getSecureRandom(); -} diff --git a/io/jsonwebtoken/security/RsaPrivateJwk.java b/io/jsonwebtoken/security/RsaPrivateJwk.java deleted file mode 100644 index 73d8bb5..0000000 --- a/io/jsonwebtoken/security/RsaPrivateJwk.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; - -/** - * JWK representation of an {@link RSAPrivateKey} as defined by the JWA (RFC 7518) specification sections on - * Parameters for RSA Keys and - * Parameters for RSA Private Keys. - * - *

Note that the various RSA-specific properties are not available as separate dedicated getter methods, as most Java - * applications should rarely, if ever, need to access these individual key properties since they typically represent - * internal key material and/or serialization details. If you need to access these key properties, it is usually - * recommended to obtain the corresponding {@link RSAPrivateKey} instance returned by {@link #toKey()} and - * query that instead.

- * - *

Even so, because these properties exist and are readable by nature of every JWK being a - * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method - * using an appropriate JWK parameter id, for example:

- *
- * jwk.get("n");
- * jwk.get("e");
- * // ... etc ...
- * - * @since 0.12.0 - */ -public interface RsaPrivateJwk extends PrivateJwk { -} diff --git a/io/jsonwebtoken/security/RsaPrivateJwkBuilder.java b/io/jsonwebtoken/security/RsaPrivateJwkBuilder.java deleted file mode 100644 index 136df69..0000000 --- a/io/jsonwebtoken/security/RsaPrivateJwkBuilder.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; - -/** - * A {@link PrivateJwkBuilder} that creates {@link RsaPrivateJwk}s. - * - * @since 0.12.0 - */ -public interface RsaPrivateJwkBuilder extends PrivateJwkBuilder { -} diff --git a/io/jsonwebtoken/security/RsaPublicJwk.java b/io/jsonwebtoken/security/RsaPublicJwk.java deleted file mode 100644 index 06e73f9..0000000 --- a/io/jsonwebtoken/security/RsaPublicJwk.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.interfaces.RSAPublicKey; - -/** - * JWK representation of an {@link RSAPublicKey} as defined by the JWA (RFC 7518) specification sections on - * Parameters for RSA Keys and - * Parameters for RSA Public Keys. - * - *

Note that the various RSA-specific properties are not available as separate dedicated getter methods, as most Java - * applications should rarely, if ever, need to access these individual key properties since they typically represent - * internal key material and/or serialization details. If you need to access these key properties, it is usually - * recommended to obtain the corresponding {@link RSAPublicKey} instance returned by {@link #toKey()} and - * query that instead.

- * - *

Even so, because these properties exist and are readable by nature of every JWK being a - * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method - * using an appropriate JWK parameter id, for example:

- *
- * jwk.get("n");
- * jwk.get("e");
- * // ... etc ...
- * - * @since 0.12.0 - */ -public interface RsaPublicJwk extends PublicJwk { -} diff --git a/io/jsonwebtoken/security/RsaPublicJwkBuilder.java b/io/jsonwebtoken/security/RsaPublicJwkBuilder.java deleted file mode 100644 index b6be07e..0000000 --- a/io/jsonwebtoken/security/RsaPublicJwkBuilder.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; - -/** - * A {@link PublicJwkBuilder} that creates {@link RsaPublicJwk}s. - * - * @since 0.12.0 - */ -public interface RsaPublicJwkBuilder extends PublicJwkBuilder { - -} diff --git a/io/jsonwebtoken/security/SecretJwk.java b/io/jsonwebtoken/security/SecretJwk.java deleted file mode 100644 index d1a3b1b..0000000 --- a/io/jsonwebtoken/security/SecretJwk.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import javax.crypto.SecretKey; - -/** - * JWK representation of a {@link SecretKey} as defined by the JWA (RFC 7518) specification section on - * Parameters for Symmetric Keys. - * - *

Note that the {@code SecretKey}-specific properties are not available as separate dedicated getter methods, as - * most Java applications should rarely, if ever, need to access these individual key properties since they typically - * internal key material and/or serialization details. If you need to access these key properties, it is usually - * recommended to obtain the corresponding {@link SecretKey} instance returned by {@link #toKey()} and - * query that instead.

- * - * @since 0.12.0 - */ -public interface SecretJwk extends Jwk { -} diff --git a/io/jsonwebtoken/security/SecretJwkBuilder.java b/io/jsonwebtoken/security/SecretJwkBuilder.java deleted file mode 100644 index 421b5f5..0000000 --- a/io/jsonwebtoken/security/SecretJwkBuilder.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import javax.crypto.SecretKey; - -/** - * A {@link JwkBuilder} that creates {@link SecretJwk}s. - * - * @since 0.12.0 - */ -public interface SecretJwkBuilder extends JwkBuilder { -} diff --git a/io/jsonwebtoken/security/SecretKeyAlgorithm.java b/io/jsonwebtoken/security/SecretKeyAlgorithm.java deleted file mode 100644 index f54c08a..0000000 --- a/io/jsonwebtoken/security/SecretKeyAlgorithm.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import javax.crypto.SecretKey; - -/** - * A {@link KeyAlgorithm} that uses symmetric {@link SecretKey}s to obtain AEAD encryption and decryption keys. - * - * @since 0.12.0 - */ -public interface SecretKeyAlgorithm extends KeyAlgorithm, KeyBuilderSupplier, KeyLengthSupplier { -} diff --git a/io/jsonwebtoken/security/SecretKeyBuilder.java b/io/jsonwebtoken/security/SecretKeyBuilder.java deleted file mode 100644 index b8219d8..0000000 --- a/io/jsonwebtoken/security/SecretKeyBuilder.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import javax.crypto.SecretKey; - -/** - * A {@link KeyBuilder} that creates new secure-random {@link SecretKey}s with a length sufficient to be used by - * the security algorithm that produced this builder. - * - * @since 0.12.0 - */ -public interface SecretKeyBuilder extends KeyBuilder { -} diff --git a/io/jsonwebtoken/security/SecureDigestAlgorithm.java b/io/jsonwebtoken/security/SecureDigestAlgorithm.java deleted file mode 100644 index fbe671d..0000000 --- a/io/jsonwebtoken/security/SecureDigestAlgorithm.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright © 2022 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.Identifiable; - -import java.io.InputStream; -import java.security.Key; - -/** - * A {@link DigestAlgorithm} that requires a {@link Key} to compute and verify the authenticity of digests using either - * digital signature or - * message - * authentication code algorithms. - * - *

Standard Identifier

- * - *

{@code SecureDigestAlgorithm} extends {@link Identifiable}: when a {@code SecureDigestAlgorithm} is used to - * compute the digital signature or MAC of a JWS, the value returned from - * {@link Identifiable#getId() secureDigestAlgorithm.getId()} will be set as the JWS - * "alg" protected header value.

- * - *

Standard Implementations

- * - *

Constant definitions and utility methods for all JWA (RFC 7518) standard - * Cryptographic Algorithms for Digital Signatures and - * MACs are available via {@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}.

- * - *

"alg" identifier

- * - *

{@code SecureDigestAlgorithm} extends {@link Identifiable}: the value returned from - * {@link Identifiable#getId() getId()} will be used as the JWS "alg" protected header value.

- * - * @param the type of {@link Key} used to create digital signatures or message authentication codes - * @param the type of {@link Key} used to verify digital signatures or message authentication codes - * @see MacAlgorithm - * @see SignatureAlgorithm - * @since 0.12.0 - */ -public interface SecureDigestAlgorithm - extends DigestAlgorithm, VerifySecureDigestRequest> { -} diff --git a/io/jsonwebtoken/security/SecureRequest.java b/io/jsonwebtoken/security/SecureRequest.java deleted file mode 100644 index 4e65c30..0000000 --- a/io/jsonwebtoken/security/SecureRequest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.Key; - -/** - * A request to a cryptographic algorithm requiring a {@link Key}. - * - * @param the type of payload in the request - * @param they type of key used by the algorithm during the request - * @since 0.12.0 - */ -public interface SecureRequest extends Request, KeySupplier { -} diff --git a/io/jsonwebtoken/security/SecurityBuilder.java b/io/jsonwebtoken/security/SecurityBuilder.java deleted file mode 100644 index f233ec3..0000000 --- a/io/jsonwebtoken/security/SecurityBuilder.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.lang.Builder; - -import java.security.Provider; -import java.security.SecureRandom; - -/** - * A Security-specific {@link Builder} that allows configuration of common JCA API parameters that might be used - * during instance creation, such as a {@link java.security.Provider} or {@link java.security.SecureRandom}. - * - * @param The type of object that will be created each time {@link #build()} is invoked. - * @param the type of SecurityBuilder returned for method chaining - * @see #provider(Provider) - * @see #random(SecureRandom) - * @since 0.12.0 - */ -public interface SecurityBuilder> extends Builder { - - /** - * Sets the JCA Security {@link Provider} to use if necessary when calling {@link #build()}. This is an optional - * property - if not specified, the default JCA Provider will be used. - * - * @param provider the JCA Security Provider instance to use if necessary when building the new instance. - * @return the builder for method chaining. - */ - B provider(Provider provider); - - /** - * Sets the {@link SecureRandom} to use if necessary when calling {@link #build()}. This is an optional property - * - if not specified and one is required, a default {@code SecureRandom} will be used. - * - * @param random the {@link SecureRandom} instance to use if necessary when building the new instance. - * @return the builder for method chaining. - */ - B random(SecureRandom random); -} diff --git a/io/jsonwebtoken/security/SecurityException.java b/io/jsonwebtoken/security/SecurityException.java deleted file mode 100644 index 107600d..0000000 --- a/io/jsonwebtoken/security/SecurityException.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.JwtException; - -/** - * A {@code JwtException} attributed to a problem with security-related elements, such as - * cryptographic keys, algorithms, or the underlying Java JCA API. - * - * @since 0.10.0 - */ -public class SecurityException extends JwtException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public SecurityException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public SecurityException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/security/SignatureAlgorithm.java b/io/jsonwebtoken/security/SignatureAlgorithm.java deleted file mode 100644 index 2df975f..0000000 --- a/io/jsonwebtoken/security/SignatureAlgorithm.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.Identifiable; - -import java.security.PrivateKey; -import java.security.PublicKey; - -/** - * A digital signature algorithm computes and - * verifies digests using asymmetric public/private key cryptography. - * - *

Standard Identifier

- * - *

{@code SignatureAlgorithm} extends {@link Identifiable}: when a {@code SignatureAlgorithm} is used to compute - * a JWS digital signature, the value returned from {@link Identifiable#getId() signatureAlgorithm.getId()} will be - * set as the JWS "alg" protected header value.

- * - *

Key Pair Generation

- * - *

{@code SignatureAlgorithm} extends {@link KeyPairBuilderSupplier} to enable - * {@link KeyPair} generation. Each {@code SignatureAlgorithm} instance will return a - * {@link KeyPairBuilder} that ensures any created key pairs will have a sufficient length and algorithm parameters - * required by that algorithm. For example:

- * - *
- * KeyPair pair = signatureAlgorithm.keyPair().build();
- * - *

The resulting {@code pair} is guaranteed to have the correct algorithm parameters and length/strength necessary - * for that exact {@code signatureAlgorithm} instance.

- * - *

JWA Standard Implementations

- * - *

Constant definitions and utility methods for all JWA (RFC 7518) standard signature algorithms are - * available via {@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}.

- * - * @see io.jsonwebtoken.Jwts.SIG Jwts.SIG - * @since 0.12.0 - */ -public interface SignatureAlgorithm extends SecureDigestAlgorithm, KeyPairBuilderSupplier { -} diff --git a/io/jsonwebtoken/security/SignatureException.java b/io/jsonwebtoken/security/SignatureException.java deleted file mode 100644 index ad8a167..0000000 --- a/io/jsonwebtoken/security/SignatureException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -/** - * Exception thrown if there is problem calculating or verifying a digital signature or message authentication code. - * - * @since 0.10.0 - */ -@SuppressWarnings("deprecation") -public class SignatureException extends io.jsonwebtoken.SignatureException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public SignatureException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param message the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public SignatureException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/io/jsonwebtoken/security/UnsupportedKeyException.java b/io/jsonwebtoken/security/UnsupportedKeyException.java deleted file mode 100644 index 7937ee6..0000000 --- a/io/jsonwebtoken/security/UnsupportedKeyException.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -/** - * Exception thrown when encountering a key or key material that is not supported or recognized. - * - * @since 0.12.0 - */ -public class UnsupportedKeyException extends KeyException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public UnsupportedKeyException(String message) { - super(message); - } - - /** - * Creates a new instance with the specified explanation message and underlying cause. - * - * @param msg the message explaining why the exception is thrown. - * @param cause the underlying cause that resulted in this exception being thrown. - */ - public UnsupportedKeyException(String msg, Throwable cause) { - super(msg, cause); - } -} diff --git a/io/jsonwebtoken/security/VerifyDigestRequest.java b/io/jsonwebtoken/security/VerifyDigestRequest.java deleted file mode 100644 index 34fbf16..0000000 --- a/io/jsonwebtoken/security/VerifyDigestRequest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright © 2022 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.io.InputStream; - -/** - * A request to verify a previously-computed cryptographic digest (available via {@link #getDigest()}) against the - * digest to be computed for the specified {@link #getPayload() payload}. - * - *

Secure digest algorithms that use keys to perform - * digital signature or - * message - * authentication code verification will use {@link VerifySecureDigestRequest} instead.

- * - * @see VerifySecureDigestRequest - * @since 0.12.0 - */ -public interface VerifyDigestRequest extends Request, DigestSupplier { -} diff --git a/io/jsonwebtoken/security/VerifySecureDigestRequest.java b/io/jsonwebtoken/security/VerifySecureDigestRequest.java deleted file mode 100644 index a1ddbd5..0000000 --- a/io/jsonwebtoken/security/VerifySecureDigestRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright © 2022 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.io.InputStream; -import java.security.Key; - -/** - * A request to a {@link SecureDigestAlgorithm} to verify a previously-computed - * digital signature or - * message - * authentication code. - * - *

The content to verify will be available via {@link #getPayload()}, the previously-computed signature or MAC will - * be available via {@link #getDigest()}, and the verification key will be available via {@link #getKey()}.

- * - * @param the type of {@link Key} used to verify a digital signature or message authentication code - * @since 0.12.0 - */ -public interface VerifySecureDigestRequest extends SecureRequest, VerifyDigestRequest { -} diff --git a/io/jsonwebtoken/security/WeakKeyException.java b/io/jsonwebtoken/security/WeakKeyException.java deleted file mode 100644 index 8b466d0..0000000 --- a/io/jsonwebtoken/security/WeakKeyException.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -/** - * Exception thrown when encountering a key that is not strong enough (of sufficient length) to be used with - * a particular algorithm or in a particular security context. - * - * @since 0.10.0 - */ -public class WeakKeyException extends InvalidKeyException { - - /** - * Creates a new instance with the specified explanation message. - * - * @param message the message explaining why the exception is thrown. - */ - public WeakKeyException(String message) { - super(message); - } -} diff --git a/io/jsonwebtoken/security/X509Accessor.java b/io/jsonwebtoken/security/X509Accessor.java deleted file mode 100644 index 587e0d0..0000000 --- a/io/jsonwebtoken/security/X509Accessor.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.JweHeader; -import io.jsonwebtoken.JwsHeader; - -import java.net.URI; -import java.security.cert.X509Certificate; -import java.util.List; - -/** - * Accessor methods of X.509-specific properties of a - * {@link io.jsonwebtoken.ProtectedHeader ProtectedHeader} or {@link AsymmetricJwk}, guaranteeing consistent behavior - * across similar but distinct JWT concepts with identical parameter names. - * - * @see io.jsonwebtoken.ProtectedHeader - * @see AsymmetricJwk - * @since 0.12.0 - */ -public interface X509Accessor { - - /** - * Returns the {@code x5u} (X.509 URL) that refers to a resource for the associated X.509 public key certificate - * or certificate chain, or {@code null} if not present. - * - *

When present, the URI MUST refer to a resource for an X.509 public key certificate or certificate - * chain that conforms to RFC 5280 in PEM-encoded form, - * with each certificate delimited as specified in - * Section 6.1 of RFC 4945. - * The key in the first certificate MUST match the public key represented by other members of the - * associated ProtectedHeader or JWK. The protocol used to acquire the resource MUST provide integrity - * protection; an HTTP GET request to retrieve the certificate MUST use - * HTTP over TLS; the identity of the server - * MUST be validated, as per - * Section 6 of RFC 6125.

- * - *
    - *
  • When present in a {@link JwsHeader}, the certificate or first certificate in the chain corresponds - * the public key complement of the private key used to digitally sign the JWS.
  • - *
  • When present in a {@link JweHeader}, the certificate or certificate chain corresponds to the - * public key to which the JWE was encrypted, and may be used to determine the private key needed to - * decrypt the JWE.
  • - *
  • When present in an {@link AsymmetricJwk}, the certificate or first certificate in the chain - * MUST contain the public key represented by the JWK.
  • - *
- * - * @return the {@code x5u} (X.509 URL) that refers to a resource for the associated X.509 public key certificate or - * certificate chain. - * @see JWK {@code x5u} (X.509 URL) Parameter - * @see JWS {@code x5u} (X.509 URL) Header Parameter - * @see JWE {@code x5u} (X.509 URL) Header Parameter - */ - URI getX509Url(); - - /** - * Returns the associated {@code x5c} (X.509 Certificate Chain), or {@code null} if not present. The initial - * certificate MAY be followed by additional certificates, with each subsequent certificate being the - * one used to certify the previous one. - * - *
    - *
  • When present in a {@link JwsHeader}, the first certificate (at list index 0) MUST contain - * the public key complement of the private key used to digitally sign the JWS.
  • - *
  • When present in a {@link JweHeader}, the first certificate (at list index 0) MUST contain - * the public key to which the JWE was encrypted, and may be used to determine the private key needed to - * decrypt the JWE.
  • - *
  • When present in an {@link AsymmetricJwk}, the first certificate (at list index 0) - * MUST contain the public key represented by the JWK.
  • - *
- * - * @return the associated {@code x5c} (X.509 Certificate Chain), or {@code null} if not present. - * @see JWK x5c (X.509 Certificate Chain) Parameter - * @see JWS x5c (X.509 Certificate Chain) Header Parameter - * @see JWE x5c (X.509 Certificate Chain) Header Parameter - */ - List getX509Chain(); - - /** - * Returns the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the - * associated X.509 Certificate, or {@code null} if not present. - * - *

Note that certificate thumbprints are also sometimes known as certificate fingerprints.

- * - *
    - *
  • When present in a {@link JwsHeader}, it is the SHA-1 thumbprint of the X.509 certificate complement - * of the private key used to digitally sign the JWS.
  • - *
  • When present in a {@link JweHeader}, it is the SHA-1 thumbprint of the X.509 Certificate containing - * the public key to which the JWE was encrypted, and may be used to determine the private key - * needed to decrypt the JWE.
  • - *
  • When present in an {@link AsymmetricJwk}, it is the SHA-1 thumbprint of the X.509 certificate - * containing the public key represented by the JWK.
  • - *
- * - * @return the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the - * associated X.509 Certificate, or {@code null} if not present - * @see JWK x5t (X.509 Certificate SHA-1 Thumbprint) Parameter - * @see JWS x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter - * @see JWE x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter - */ - byte[] getX509Sha1Thumbprint(); - - /** - * Returns the {@code x5t#S256} (X.509 Certificate SHA-256 Thumbprint) (a.k.a. digest) of the DER-encoding of the - * associated X.509 Certificate, or {@code null} if not present. - * - *

Note that certificate thumbprints are also sometimes known as certificate fingerprints.

- * - *
    - *
  • When present in a {@link JwsHeader}, it is the SHA-256 thumbprint of the X.509 certificate complement - * of the private key used to digitally sign the JWS.
  • - *
  • When present in a {@link JweHeader}, it is the SHA-256 thumbprint of the X.509 Certificate containing - * the public key to which the JWE was encrypted, and may be used to determine the private key - * needed to decrypt the JWE.
  • - *
  • When present in an {@link AsymmetricJwk}, it is the SHA-256 thumbprint of the X.509 certificate - * containing the public key represented by the JWK.
  • - *
- * - * @return the {@code x5t#S256} (X.509 Certificate SHA-256 Thumbprint) (a.k.a. digest) of the DER-encoding of the - * associated X.509 Certificate, or {@code null} if not present - * @see JWK x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Parameter - * @see JWS x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter - * @see JWE x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter - */ - byte[] getX509Sha256Thumbprint(); -} diff --git a/io/jsonwebtoken/security/X509Builder.java b/io/jsonwebtoken/security/X509Builder.java deleted file mode 100644 index 84315be..0000000 --- a/io/jsonwebtoken/security/X509Builder.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import java.security.cert.X509Certificate; -import java.util.List; - -/** - * Additional X.509-specific builder methods for constructing an associated JWT Header or JWK, enabling method chaining. - * - * @param the mutator subtype, for method chaining - * @since 0.12.0 - */ -public interface X509Builder> extends X509Mutator { - - /** - * If the {@code enable} argument is {@code true}, compute the SHA-1 thumbprint of the first - * {@link X509Certificate} in the configured {@link #x509Chain(List) x509CertificateChain}, and set - * the resulting value as the {@link #x509Sha1Thumbprint(byte[])} parameter. - * - *

If no chain has been configured, or {@code enable} is {@code false}, the builder will not compute nor add a - * {@code x5t} value.

- * - * @param enable whether to compute the SHA-1 thumbprint on the first available X.509 Certificate and set - * the resulting value as the {@code x5t} value. - * @return the builder for method chaining. - */ - T x509Sha1Thumbprint(boolean enable); - - /** - * If the {@code enable} argument is {@code true}, compute the SHA-256 thumbprint of the first - * {@link X509Certificate} in the configured {@link #x509Chain(List) x509CertificateChain}, and set - * the resulting value as the {@link #x509Sha256Thumbprint(byte[])} parameter. - * - *

If no chain has been configured, or {@code enable} is {@code false}, the builder will not compute nor add a - * {@code x5t#S256} value.

- * - * @param enable whether to compute the SHA-256 thumbprint on the first available X.509 Certificate and set - * the resulting value as the {@code x5t#S256} value. - * @return the builder for method chaining. - */ - T x509Sha256Thumbprint(boolean enable); -} diff --git a/io/jsonwebtoken/security/X509Mutator.java b/io/jsonwebtoken/security/X509Mutator.java deleted file mode 100644 index afe50fc..0000000 --- a/io/jsonwebtoken/security/X509Mutator.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.security; - -import io.jsonwebtoken.JweHeader; -import io.jsonwebtoken.JwsHeader; - -import java.net.URI; -import java.security.cert.X509Certificate; -import java.util.List; - -/** - * Mutation (modifications) of X.509-specific properties of an associated JWT Header or JWK, enabling method chaining. - * - * @param the mutator subtype, for method chaining - * @since 0.12.0 - */ -public interface X509Mutator> { - - /** - * Sets the {@code x5u} (X.509 URL) that refers to a resource containing the X.509 public key certificate or - * certificate chain of the associated JWT or JWK. A {@code null} value will remove the property from the JSON map. - * - *

The URI MUST refer to a resource for an X.509 public key certificate or certificate chain that - * conforms to RFC 5280 in PEM-encoded form, with - * each certificate delimited as specified in - * Section 6.1 of RFC 4945. - * The key in the first certificate MUST match the public key represented by other members of the - * associated JWT or JWK. The protocol used to acquire the resource MUST provide integrity protection; - * an HTTP GET request to retrieve the certificate MUST use - * HTTP over TLS; the identity of the server - * MUST be validated, as per - * Section 6 of RFC 6125.

- * - *
    - *
  • When set for a {@link JwsHeader}, the certificate or first certificate in the chain contains - * the public key complement of the private key used to digitally sign the JWS.
  • - *
  • When set for {@link JweHeader}, the certificate or first certificate in the chain contains the - * public key to which the JWE was encrypted, and may be used to determine the private key needed to - * decrypt the JWE.
  • - *
  • When set for an {@link AsymmetricJwk}, the certificate or first certificate in the chain - * MUST contain the public key represented by the JWK.
  • - *
- * - * @param uri the {@code x5u} (X.509 URL) that refers to a resource for the X.509 public key certificate or - * certificate chain associated with the JWT or JWK. - * @return the mutator/builder for method chaining. - * @see JWK x5u (X.509 URL) Parameter - * @see JWS x5u (X.509 URL) Header Parameter - * @see JWE x5u (X.509 URL) Header Parameter - */ - T x509Url(URI uri); - - /** - * Sets the {@code x5c} (X.509 Certificate Chain) of the associated JWT or JWK. A {@code null} value will remove the - * property from the JSON map. The initial certificate MAY be followed by additional certificates, with - * each subsequent certificate being the one used to certify the previous one. - * - *
    - *
  • When set for a {@link JwsHeader}, the first certificate (at list index 0) MUST contain - * the public key complement of the private key used to digitally sign the JWS.
  • - *
  • When set for {@link JweHeader}, the first certificate (at list index 0) MUST contain the - * public key to which the JWE was encrypted, and may be used to determine the private key needed to - * decrypt the JWE.
  • - *
  • When set for an {@link AsymmetricJwk}, the first certificate (at list index 0) MUST contain - * the public key represented by the JWK.
  • - *
- * - * @param chain the {@code x5c} (X.509 Certificate Chain) of the associated JWT or JWK. - * @return the header/builder for method chaining. - * @see JWK x5c (X.509 Certificate Chain) Parameter - * @see JWS x5c (X.509 Certificate Chain) Header Parameter - * @see JWE x5c (X.509 Certificate Chain) Header Parameter - */ - T x509Chain(List chain); - - /** - * Sets the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the - * X.509 Certificate associated with the JWT or JWK. A {@code null} value will remove the - * property from the JSON map. - * - *

Note that certificate thumbprints are also sometimes known as certificate fingerprints.

- * - *
    - *
  • When set for a {@link JwsHeader}, it is the SHA-1 thumbprint of the X.509 certificate complement of - * the private key used to digitally sign the JWS.
  • - *
  • When set for {@link JweHeader}, it is the thumbprint of the X.509 Certificate containing the - * public key to which the JWE was encrypted, and may be used to determine the private key needed to - * decrypt the JWE.
  • - *
  • When set for an {@link AsymmetricJwk}, it is the thumbprint of the X.509 certificate containing the - * public key represented by the JWK.
  • - *
- * - * @param thumbprint the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the - * X.509 Certificate associated with the JWT or JWK - * @return the header for method chaining - * @see JWK x5t (X.509 Certificate SHA-1 Thumbprint) Parameter - * @see JWS x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter - * @see JWE x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter - */ - T x509Sha1Thumbprint(byte[] thumbprint); - - /** - * Sets the {@code x5t#S256} (X.509 Certificate SHA-256 Thumbprint) (a.k.a. digest) of the DER-encoding of the - * X.509 Certificate associated with the JWT or JWK. A {@code null} value will remove the - * property from the JSON map. - * - *

Note that certificate thumbprints are also sometimes known as certificate fingerprints.

- * - *
    - *
  • When set for a {@link JwsHeader}, it is the SHA-256 thumbprint of the X.509 certificate complement - * of the private key used to digitally sign the JWS.
  • - *
  • When set for {@link JweHeader}, it is the SHA-256 thumbprint of the X.509 Certificate containing the - * public key to which the JWE was encrypted, and may be used to determine the private key needed to - * decrypt the JWE.
  • - *
  • When set for a {@link AsymmetricJwk}, it is the SHA-256 thumbprint of the X.509 certificate - * containing the public key represented by the JWK.
  • - *
- * - * @param thumbprint the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the - * X.509 Certificate associated with the JWT or JWK - * @return the header for method chaining - * @see JWK x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Parameter - * @see JWS x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter - * @see JWE x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter - */ - T x509Sha256Thumbprint(byte[] thumbprint); -} From 2d5b8475b9e7b1b9097a557addc040104ac8faa1 Mon Sep 17 00:00:00 2001 From: mmije0ng Date: Thu, 23 Jul 2026 09:41:48 +0900 Subject: [PATCH 51/55] =?UTF-8?q?remove:=20META-INF=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- META-INF/MANIFEST.MF | 3 -- .../io.jsonwebtoken/jjwt-api/pom.properties | 3 -- .../maven/io.jsonwebtoken/jjwt-api/pom.xml | 53 ------------------- 3 files changed, 59 deletions(-) delete mode 100644 META-INF/MANIFEST.MF delete mode 100644 META-INF/maven/io.jsonwebtoken/jjwt-api/pom.properties delete mode 100644 META-INF/maven/io.jsonwebtoken/jjwt-api/pom.xml diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF deleted file mode 100644 index 18bd855..0000000 --- a/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Created-By: Maven Source Plugin 3.2.1 - diff --git a/META-INF/maven/io.jsonwebtoken/jjwt-api/pom.properties b/META-INF/maven/io.jsonwebtoken/jjwt-api/pom.properties deleted file mode 100644 index 0756d00..0000000 --- a/META-INF/maven/io.jsonwebtoken/jjwt-api/pom.properties +++ /dev/null @@ -1,3 +0,0 @@ -artifactId=jjwt-api -groupId=io.jsonwebtoken -version=0.12.6 diff --git a/META-INF/maven/io.jsonwebtoken/jjwt-api/pom.xml b/META-INF/maven/io.jsonwebtoken/jjwt-api/pom.xml deleted file mode 100644 index 9b5ea1e..0000000 --- a/META-INF/maven/io.jsonwebtoken/jjwt-api/pom.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - 4.0.0 - - - io.jsonwebtoken - jjwt-root - 0.12.6 - ../pom.xml - - - jjwt-api - JJWT :: API - jar - - - ${basedir}/.. - - - - - - com.github.siom79.japicmp - japicmp-maven-plugin - - - japicmp - - cmp - - - - - - - - \ No newline at end of file From 49822ccf30448a4c8fd6f1b7b706ba810160e41d Mon Sep 17 00:00:00 2001 From: mmije0ng Date: Thu, 23 Jul 2026 09:42:30 +0900 Subject: [PATCH 52/55] =?UTF-8?q?remove:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- null | 0 ...354\240\225 \354\235\264\354\203\201 \352\260\220\354\247\200" | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 null delete mode 100644 "\352\263\265\354\240\225 \354\235\264\354\203\201 \352\260\220\354\247\200" diff --git a/null b/null deleted file mode 100644 index e69de29..0000000 diff --git "a/\352\263\265\354\240\225 \354\235\264\354\203\201 \352\260\220\354\247\200" "b/\352\263\265\354\240\225 \354\235\264\354\203\201 \352\260\220\354\247\200" deleted file mode 100644 index e69de29..0000000 From 59808b8aa3994cc94318969acbc61fe493990765 Mon Sep 17 00:00:00 2001 From: mmije0ng Date: Thu, 23 Jul 2026 09:46:45 +0900 Subject: [PATCH 53/55] =?UTF-8?q?docs:=20README=20=EC=82=AC=EC=A7=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 635b85d..326d8af 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,49 @@ flowchart LR 현재 Backend 코드에는 `KafkaTemplate` Producer Bean이 정의되어 있지만, 애플리케이션 서비스에서 Kafka로 메시지를 발행하는 호출은 확인되지 않습니다. 따라서 실제 입력 Producer는 외부 제조/분석 서비스로 보는 것이 맞습니다. ## 알림 처리 + + + + + + + + + + + + + + + + + + +
알림 우선순위 구조알림 목록
+ 알림 우선순위 구조 + + 알림 목록 +
알림 조치알림 상세
+ 알림 조치 + + 알림 조치 후 상세 +
알림은 `factory.manufacturing.alert` 토픽을 `AlertEventConsumer`가 소비합니다. `AlertEventSaveService`는 JSON을 정규화하고 중복 이벤트를 걸러낸 뒤 점수를 계산하여 DB에 저장합니다. @@ -88,6 +131,7 @@ sequenceDiagram | 유사 장애 조치 추천 | `GET /api/event/{logNo}/recommendation` | ## AGV 배차 및 운반 +AGV AGV는 분석 결과를 직접 Kafka로 재발행하지 않고, 분석 이벤트를 소비한 뒤 Redis 대기열과 DB 상태를 조합하여 시뮬레이션합니다. @@ -109,7 +153,7 @@ flowchart TD A -- 아니오 --> R[Queue 선두 재적재 후 대기] A -- 예 --> DB[agv_operation을 MOVING으로 변경] DB --> RT[Redis realtime 상태 저장] - RT --> WS[/topic/agv publish] + RT --> WS["/topic/agv publish"] ``` ### Route와 Redis 자료구조 @@ -177,7 +221,7 @@ flowchart LR ALERT -->|일반 backend group
main-*/backend-*| NC[AlertEventConsumer] AC --> RQ[Redis AGV Queue] NC --> DB[MySQL AlertEvent] - NC --> WS[STOMP /topic/alerts] + NC --> WS["STOMP /topic/alerts"] ``` ### Kafka Client 동작 From 2c81916d93a5bb7f754cad9eddfc95ad9278ac94 Mon Sep 17 00:00:00 2001 From: mmije0ng Date: Thu, 23 Jul 2026 09:53:26 +0900 Subject: [PATCH 54/55] test: strengthen not-needed alert action assertions --- src/test/java/com/aims/backend/domain/alert/AlertEventTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/com/aims/backend/domain/alert/AlertEventTest.java b/src/test/java/com/aims/backend/domain/alert/AlertEventTest.java index e041b47..0615681 100644 --- a/src/test/java/com/aims/backend/domain/alert/AlertEventTest.java +++ b/src/test/java/com/aims/backend/domain/alert/AlertEventTest.java @@ -30,6 +30,8 @@ void notNeededActionSetsResolvedAt() { alertEvent.updateAction("user01", AlertActionStatus.NOT_NEEDED, "sensor noise"); assertThat(alertEvent.getActionStatus()).isEqualTo(AlertActionStatus.NOT_NEEDED); + assertThat(alertEvent.getActionBy()).isEqualTo("user01"); + assertThat(alertEvent.getReason()).isEqualTo("sensor noise"); assertThat(alertEvent.getResolvedAt()).isNotNull(); } From bd5f3bccee6343218fbda0ad9116e6d02adc702f Mon Sep 17 00:00:00 2001 From: mmije0ng Date: Thu, 23 Jul 2026 10:43:10 +0900 Subject: [PATCH 55/55] =?UTF-8?q?docs:=20Kafka=20=EB=B0=8F=20Assembly=20Se?= =?UTF-8?q?rvice=20=EC=B2=98=EB=A6=AC=20=EA=B5=AC=EC=A1=B0=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 326d8af..a9c620a 100644 --- a/README.md +++ b/README.md @@ -201,22 +201,23 @@ AGV 상태가 변경될 때 전체 AGV 목록을 `/topic/agv`로 publish합니 | --- | ---: | --- | --- | --- | | `factory.manufacturing.alert` | 2 | `AlertEventConsumer` | `app.kafka.group-id` | 알림 저장 및 WebSocket 전달 | | `factory.manufacturing.analysis` | 2 | `ManufacturingAnalysisConsumer` | `app.kafka.consumer.agv-group-id` | 정상 공정 분석 이벤트 기반 AGV 배차 | -| `factory.manufacturing.raw` | 4 | 현재 Backend Listener 없음 | - | 토픽 설정에 정의된 원천 이벤트 채널 | -| `factory.manufacturing.equipment` | 2 | 현재 Backend Listener 없음 | - | 토픽 설정에 정의된 설비 이벤트 채널 | +| `factory.manufacturing.raw` | 4 | Assembly Service 측 처리 | Assembly Service Group | 제조 원천 이벤트 수집·분석 입력 | +| `factory.manufacturing.equipment` | 2 | Assembly Service 측 처리 | Assembly Service Group | 설비 이벤트 수집·분석 입력 | -`raw`와 `equipment`는 `KafkaCustomProperties`의 기본 토픽 목록에는 있으나 현재 Backend Consumer가 연결되어 있지 않습니다. 토픽 목록에 등록되어 있다는 사실과 실제 소비 중인 토픽을 구분해야 합니다. +`raw`와 `equipment`는 Assembly Service에서 제조 원천·설비 정보를 처리하는 채널입니다. 이 Backend는 해당 원천 이벤트를 직접 소비하기보다, 분석 결과가 발행된 `analysis`와 알림 결과가 발행된 `alert` 토픽을 소비합니다. ```mermaid flowchart LR - subgraph K[Kafka / AWS MSK] + subgraph K["Kafka / AWS MSK"] RAW[factory.manufacturing.raw
4 partitions] ANALYSIS[factory.manufacturing.analysis
2 partitions] ALERT[factory.manufacturing.alert
2 partitions] EQUIP[factory.manufacturing.equipment
2 partitions] end - RAW -. 현재 Backend Listener 없음 .-> B[ AIMS Backend ] - EQUIP -. 현재 Backend Listener 없음 .-> B + AS[Assembly Service] + RAW -->|Assembly Service Group| AS + EQUIP -->|Assembly Service Group| AS ANALYSIS -->|AGV group
main-agv-group*| AC[ManufacturingAnalysisConsumer
concurrency=2] ALERT -->|일반 backend group
main-*/backend-*| NC[AlertEventConsumer] AC --> RQ[Redis AGV Queue] @@ -238,6 +239,9 @@ flowchart LR `AlertEventConsumer`는 `app.kafka.listeners-enabled`가 true일 때만 등록됩니다. 반면 AGV 분석 Consumer는 코드상 `app.kafka.consumer.agv-group-id`를 사용하므로 해당 프로퍼티와 Kafka 접속 정보가 실행 환경에 있어야 합니다. +### 데이터 흐름도 +데이터 기능 흐름도 + ## WebSocket / STOMP STOMP endpoint는 `/ws`와 `/api/ws`이며 SockJS를 지원합니다. 서버 브로커 prefix는 `/topic`입니다.