> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 서명된 URL의 입력을 올바르게 다루기

> Laravel 13의 서명된 URL이 보호하는 범위와 서명된 쿼리 값을 query()로 가져와야 하는 이유를 유효 기간이 있는 링크 구현 예제로 설명합니다.

## 이 페이지의 목적

유효 기간이 있는 공유 링크에서 발급 시 정한 조건을 그대로 사용하려면, URL 서명 검증과 더불어 **값을 어디에서 가져올지**도 일관되게 맞춰야 합니다.
이 페이지에서는 보고서 종류와 출력 형식을 URL에 포함하고, 미리보기용 JSON을 반환하는 예제로 설명합니다.

대상은 Laravel 13입니다. URL 생성 전반은 [URL 생성](/ko/urls), 입력 취득의 기본은 [HTTP 요청](/ko/requests)을 참조하세요.

<Warning>
  서명은 요청 전체를 보호하지 않습니다. 폼이나 JSON 본문은 서명 대상이 아닙니다.
  서명된 쿼리 값은 서명 검증 후 `$request->query('format')`처럼 가져오세요.
  `input()`, `all()`, 동적 프로퍼티는 서명되지 않은 본문의 값을 가져올 수 있습니다. GET 요청에서도 이 구분이 필요합니다.
</Warning>

## 서명으로 보호되는 범위

| 요소 | 처리 |
| - | - |
| URL 경로 | 서명 검증 대상 |
| 쿼리 문자열 | 원칙적으로 서명 검증 대상. 검증에서 제외한 파라미터는 제외 |
| 스킴·호스트 | 보통 서명에 포함. 상대 서명에서는 포함하지 않음 |
| 폼·JSON 본문 | 서명 대상 아님 |
| 유효 기간 | `temporarySignedRoute()`가 추가하고, 검증 시 만료 여부를 확인 |

`input()`은 쿼리 문자열뿐 아니라 본문도 다루는 API입니다. Laravel은 이러한 입력을 통합할 때 본문의 값을 쿼리 문자열보다 우선합니다.
반면 `query()`는 쿼리 문자열만 가져옵니다.

서명 검증이 성공했다는 것과, 그 후에 읽은 값이 서명된 값이라는 것은 별개의 확인 사항입니다.

```mermaid theme={null}
flowchart TD
    A["유효 기간이 있는 URL로 접근"] --> B{"signed 미들웨어<br>서명·유효 기간 검증"}
    B -->|무효| C["403 응답"]
    B -->|유효| D["query()로<br>서명된 쿼리 값 취득"]
    D --> E["값의 형식·허용 범위 검증"]
    E --> F["미리보기 반환"]
```

## 유효 기간이 있는 미리보기 링크 구현하기

### 1. 검증할 라우트 정의하기

`routes/web.php`에 서명을 필수로 하는 이름 지정 라우트를 정의합니다.
여기서는 DB나 파일을 조작하지 않고, 받은 조건만 반환합니다.

```php theme={null}
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Validator;

Route::get('/reports/preview', function (Request $request) {
    $options = Validator::make([
        'report' => $request->query('report'),
        'format' => $request->query('format'),
    ], [
        'report' => ['required', 'string', 'in:monthly,annual'],
        'format' => ['required', 'string', 'in:csv,pdf'],
    ])->validate();

    return response()->json($options);
})->middleware('signed')->name('reports.preview');
```

* `signed` 미들웨어가 클로저 실행 전에 서명과 유효 기간을 검증합니다. 무효이면 `403`을 반환합니다.
* `query()`로 처리에 사용할 값을 쿼리 문자열에서 명시적으로 가져옵니다.
* 검증은 가져온 배열에 대해 수행합니다. 서명 검증은 값의 타입이나 업무상 허용 범위를 보장하지 않습니다.

<Info>
  이 예제에서 `Validator::make()`에 전달하는 것은 `query()`로 꺼낸 값뿐입니다.
  서명된 쿼리를 검증하려는 위치에서 이를 `$request->all()`로 바꾸면 본문을 포함한 입력을 다루게 됩니다.
</Info>

### 2. 신뢰할 수 있는 발급 처리에서 URL 생성하기

이름 지정 라우트를 등록한 후, 링크를 발급하는 처리에서 다음 코드를 사용합니다.
`report`와 `format`은 경로의 플레이스홀더가 아니므로 쿼리 문자열에 추가됩니다.

```php theme={null}
use Illuminate\Support\Facades\URL;

$url = URL::temporarySignedRoute(
    'reports.preview',
    now()->addMinutes(30),
    ['report' => 'monthly', 'format' => 'csv'],
);
```

이 URL에는 지정한 조건에 더해 `expires`와 `signature`가 포함됩니다.
`expires`와 `signature`는 Laravel이 사용하는 예약 파라미터이므로 업무용 파라미터 이름으로 전달하지 마세요.

실제 애플리케이션에서 발급 처리를 공개하는 경우, 누구나 임의의 조건으로 링크를 발급할 수 있는 상태로 두지 말고 필요한 [인증](/ko/authentication)이나 [인가](/ko/authorization)를 수행하세요.

### 3. 일반적인 사용 확인하기

발급한 URL을 그대로 열면, 유효 기간 내라면 다음 JSON이 반환됩니다.

```json theme={null}
{
  "report": "monthly",
  "format": "csv"
}
```

링크의 유효 기간이 만료되면 라우트의 `signed` 미들웨어가 `403`을 반환합니다.
서명된 URL은 일회용 토큰이 아닙니다. 이 예제에서는 유효 기간 내의 같은 URL을 재사용할 수 있습니다.
한 번만 사용하게 하려면 서명과는 별도로 사용 완료 상태를 관리해야 합니다.

## 검증에서 제외할 값 구분하기

프런트엔드가 추가하는 페이지 번호 등을 허용하려면 `hasValidSignatureWhileIgnoring(['page'])`로 서명 검증에서 제외할 수 있습니다.
단, 제외한 값은 누구나 변경할 수 있으므로 서명된 조건으로 취급하지 마세요.

예를 들어 표시용 `page`와 발급 시 고정하는 `report`, `format`은 역할이 다릅니다.
대상 데이터, 수신자, 요금 등의 판단에 사용하는 값을 편의성만을 이유로 제외하지 않도록 합니다.

<Warning>
  위 구현 예제는 일반 `signed` 미들웨어를 사용하므로 `page`도 제외하지 않습니다.
  쿼리 파라미터를 추가해 사용하려면 생성 측과 검증 측에서 무엇을 서명 대상으로 할지 먼저 정하세요.
</Warning>

## 상대 서명을 사용하는 경우

도메인을 서명에 포함하지 않는 설계에서는 생성 시의 `absolute: false`와 검증 시의 `signed:relative`를 함께 사용합니다.

```php theme={null}
$url = URL::temporarySignedRoute(
    'reports.preview',
    now()->addMinutes(30),
    ['report' => 'monthly', 'format' => 'csv'],
    absolute: false,
);
```

이 경우 앞의 라우트의 `middleware('signed')`를 `middleware('signed:relative')`로 변경합니다.
상대 서명에서도 본문은 서명되지 않으므로 `query()`로 가져오는 방침은 바뀌지 않습니다.

## 구현 시 체크리스트

* 수신 라우트에서 서명 검증을 필수로 하고 있다.
* 서명된 쿼리 값을 `query()`로 가져오고 있다.
* 값의 형식이나 허용 범위를 별도로 검증하고 있다.
* 서명 검증에서 제외한 값을 발급 시 고정된 조건으로 사용하지 않는다.
* 절대 서명·상대 서명 설정을 생성 측과 검증 측에서 일치시키고 있다.
* URL을 알고 있다는 것을 로그인 사용자의 권한과 동일시하지 않는다.

## 관련 페이지와 1차 정보

* [URL 생성](/ko/urls): 서명된 URL의 API와 무효한 서명에 대한 응답
* [HTTP 요청](/ko/requests): 입력값과 쿼리 문자열 취득
* [검증](/ko/validation): 입력 형식과 허용하는 값의 검증
* [Laravel 공식 문서: Signed URLs](https://github.com/laravel/docs/blob/13.x/urls.md#signed-urls)
* [Laravel 공식 문서: Retrieving input from the query string](https://github.com/laravel/docs/blob/13.x/requests.md#retrieving-input-from-the-query-string)
* [Laravel Framework: UrlGenerator](https://github.com/laravel/framework/blob/13.x/src/Illuminate/Routing/UrlGenerator.php)
* [Laravel Framework: ValidateSignature](https://github.com/laravel/framework/blob/13.x/src/Illuminate/Routing/Middleware/ValidateSignature.php)


## Related topics

- [URL 생성](/ko/urls.md)
- [2026년 7월 Laravel 업데이트](/ko/blog/changelog/202607.md)
- [Labeler](/ko/packages/laravel-bluesky/labeler.md)
- [AI 에이전트는 '올바른' 코드에서 'Laravel다운' 코드로 - Boost Benchmarks의 다음 한 수](/ko/blog/boost-benchmarks-idiomatic-laravel.md)
- [Score와 Note 상세 해설 - VOICEVOX for Laravel](/ko/packages/laravel-voicevox/song-score-note.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.