CINXE.COM

なぜ我々はsession.cookieを変更しなければならなかったのか - BASEプロダクトチームブログ

<!DOCTYPE html> <html lang="ja" data-admin-domain="//blog.hatena.ne.jp" data-admin-origin="https://blog.hatena.ne.jp" data-author="basedevteam" data-avail-langs="ja en" data-blog="base-developer.hatenablog.com" data-blog-host="base-developer.hatenablog.com" data-blog-is-public="1" data-blog-name="BASEプロダクトチームブログ" data-blog-owner="basedevteam" data-blog-show-ads="" data-blog-show-sleeping-ads="" data-blog-uri="https://devblog.thebase.in/" data-blog-uuid="10328537792370962182" data-blogs-uri-base="https://devblog.thebase.in" data-brand="devblog" data-data-layer="{&quot;hatenablog&quot;:{&quot;admin&quot;:{},&quot;analytics&quot;:{&quot;brand_property_id&quot;:&quot;&quot;,&quot;measurement_id&quot;:&quot;G-W1284X7XBB&quot;,&quot;non_sampling_property_id&quot;:&quot;&quot;,&quot;property_id&quot;:&quot;UA-177755085-1&quot;,&quot;separated_property_id&quot;:&quot;UA-29716941-19&quot;},&quot;blog&quot;:{&quot;blog_id&quot;:&quot;10328537792370962182&quot;,&quot;content_seems_japanese&quot;:&quot;true&quot;,&quot;disable_ads&quot;:&quot;custom_domain&quot;,&quot;enable_ads&quot;:&quot;false&quot;,&quot;enable_keyword_link&quot;:&quot;false&quot;,&quot;entry_show_footer_related_entries&quot;:&quot;false&quot;,&quot;force_pc_view&quot;:&quot;true&quot;,&quot;is_public&quot;:&quot;true&quot;,&quot;is_responsive_view&quot;:&quot;true&quot;,&quot;is_sleeping&quot;:&quot;false&quot;,&quot;lang&quot;:&quot;ja&quot;,&quot;name&quot;:&quot;BASE\u30d7\u30ed\u30c0\u30af\u30c8\u30c1\u30fc\u30e0\u30d6\u30ed\u30b0&quot;,&quot;owner_name&quot;:&quot;basedevteam&quot;,&quot;uri&quot;:&quot;https://devblog.thebase.in/&quot;},&quot;brand&quot;:&quot;devblog&quot;,&quot;page_id&quot;:&quot;entry&quot;,&quot;permalink_entry&quot;:{&quot;author_name&quot;:&quot;yoga819&quot;,&quot;categories&quot;:&quot;&quot;,&quot;character_count&quot;:6542,&quot;date&quot;:&quot;2022-04-13&quot;,&quot;entry_id&quot;:&quot;13574176438082457789&quot;,&quot;first_category&quot;:&quot;&quot;,&quot;hour&quot;:&quot;11&quot;,&quot;title&quot;:&quot;\u306a\u305c\u6211\u3005\u306fsession.cookie\u3092\u5909\u66f4\u3057\u306a\u3051\u308c\u3070\u306a\u3089\u306a\u304b\u3063\u305f\u306e\u304b&quot;,&quot;uri&quot;:&quot;https://devblog.thebase.in/entry/2022/04/13/114714&quot;},&quot;pro&quot;:&quot;pro&quot;,&quot;router_type&quot;:&quot;blogs&quot;}}" data-device="pc" data-dont-recommend-pro="false" data-global-domain="https://hatena.blog" data-globalheader-color="b" data-globalheader-type="pc" data-has-touch-view="1" data-help-url="https://help.hatenablog.com" data-hide-header="1" data-no-suggest-touch-view="1" data-page="entry" data-parts-domain="https://hatenablog-parts.com" data-plus-available="1" data-pro="true" data-router-type="blogs" data-sentry-dsn="https://03a33e4781a24cf2885099fed222b56d@sentry.io/1195218" data-sentry-environment="production" data-sentry-sample-rate="0.1" data-static-domain="https://cdn.blog.st-hatena.com" data-version="c85372bc899dec1e795892b094b326" data-initial-state="{}" > <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#"> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="robots" content="max-image-preview:large" /> <meta charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=7; IE=9; IE=10; IE=11" /> <title>なぜ我々はsession.cookieを変更しなければならなかったのか - BASEプロダクトチームブログ</title> <link rel="canonical" href="https://devblog.thebase.in/entry/2022/04/13/114714"/> <meta itemprop="name" content="なぜ我々はsession.cookieを変更しなければならなかったのか - BASEプロダクトチームブログ"/> <meta itemprop="image" content="https://cdn.image.st-hatena.com/image/scale/703e3ce672a3e25bb59222cc6a16ab5004bda2aa/backend=imagemagick;version=1;width=1300/https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Fy%2Fyoga819%2F20220413%2F20220413093904.png"/> <meta property="og:title" content="なぜ我々はsession.cookieを変更しなければならなかったのか - BASEプロダクトチームブログ"/> <meta property="og:type" content="article"/> <meta property="og:url" content="https://devblog.thebase.in/entry/2022/04/13/114714"/> <meta property="og:image" content="https://cdn.image.st-hatena.com/image/scale/703e3ce672a3e25bb59222cc6a16ab5004bda2aa/backend=imagemagick;version=1;width=1300/https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Fy%2Fyoga819%2F20220413%2F20220413093904.png"/> <meta property="og:image:alt" content="なぜ我々はsession.cookieを変更しなければならなかったのか - BASEプロダクトチームブログ"/> <meta property="og:description" content="はじめに こんにちは。バックエンドエンジニアの小笠原です。 今回は、2022年2月18日から2022年3月4日にかけて発生していたこちらの障害に対し私達開発チームが実施した、session.cookieで定義しているCookieのkey名を変更するという影響範囲の大きい対応について、実施に至るまでの経緯や対応過程についてご紹介したいと思います。 ショップオーナー向けに掲載していたお知らせの内容 背景 全ては iOS14.5から端末識別子の取得に同意が必要になったことから始まった ことの発端は、iOS14.5以降からIDFA(端末ごとに持つ固有識別子)の取得に端末所有者の許可が必要になったことで…" /> <meta property="og:site_name" content="BASEプロダクトチームブログ"/> <meta property="article:published_time" content="2022-04-13T02:47:14Z" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:image" content="https://cdn.image.st-hatena.com/image/scale/703e3ce672a3e25bb59222cc6a16ab5004bda2aa/backend=imagemagick;version=1;width=1300/https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Fy%2Fyoga819%2F20220413%2F20220413093904.png" /> <meta name="twitter:title" content="なぜ我々はsession.cookieを変更しなければならなかったのか - BASEプロダクトチームブログ" /> <meta name="twitter:description" content="はじめに こんにちは。バックエンドエンジニアの小笠原です。 今回は、2022年2月18日から2022年3月4日にかけて発生していたこちらの障害に対し私達開発チームが実施した、session.cookieで定義しているCookieのkey名を変更するという影響範囲の大きい対応について、実施に至るまでの経緯や対応過程につい…" /> <meta name="twitter:app:name:iphone" content="はてなブログアプリ" /> <meta name="twitter:app:id:iphone" content="583299321" /> <meta name="twitter:app:url:iphone" content="hatenablog:///open?uri=https%3A%2F%2Fdevblog.thebase.in%2Fentry%2F2022%2F04%2F13%2F114714" /> <meta name="description" content="はじめに こんにちは。バックエンドエンジニアの小笠原です。 今回は、2022年2月18日から2022年3月4日にかけて発生していたこちらの障害に対し私達開発チームが実施した、session.cookieで定義しているCookieのkey名を変更するという影響範囲の大きい対応について、実施に至るまでの経緯や対応過程についてご紹介したいと思います。 ショップオーナー向けに掲載していたお知らせの内容 背景 全ては iOS14.5から端末識別子の取得に同意が必要になったことから始まった ことの発端は、iOS14.5以降からIDFA(端末ごとに持つ固有識別子)の取得に端末所有者の許可が必要になったことで…" /> <meta name="google-site-verification" content="_FbkQSjQgcZcxJjN06Pre9XjjxZDgyKpxuQLyX7Yq_Y" /> <meta name="keywords" content="テックブログ,BASE,開発,エンジニア,Web" /> <script id="embed-gtm-data-layer-loader" data-data-layer-page-specific="{&quot;hatenablog&quot;:{&quot;blogs_permalink&quot;:{&quot;is_blog_sleeping&quot;:&quot;false&quot;,&quot;entry_afc_issued&quot;:&quot;false&quot;,&quot;has_related_entries_with_elasticsearch&quot;:&quot;false&quot;,&quot;is_author_pro&quot;:&quot;true&quot;,&quot;blog_afc_issued&quot;:&quot;false&quot;}}}" > (function() { function loadDataLayer(elem, attrName) { if (!elem) { return {}; } var json = elem.getAttribute(attrName); if (!json) { return {}; } return JSON.parse(json); } var globalVariables = loadDataLayer( document.documentElement, 'data-data-layer' ); var pageSpecificVariables = loadDataLayer( document.getElementById('embed-gtm-data-layer-loader'), 'data-data-layer-page-specific' ); var variables = [globalVariables, pageSpecificVariables]; if (!window.dataLayer) { window.dataLayer = []; } for (var i = 0; i < variables.length; i++) { window.dataLayer.push(variables[i]); } })(); </script> <!-- Google Tag Manager --> <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-P4CXTW');</script> <!-- End Google Tag Manager --> <link rel="shortcut icon" href="https://devblog.thebase.in/icon/favicon"> <link rel="apple-touch-icon" href="https://devblog.thebase.in/icon/touch"> <link rel="icon" sizes="192x192" href="https://devblog.thebase.in/icon/link"> <link rel="alternate" type="application/atom+xml" title="Atom" href="https://devblog.thebase.in/feed"/> <link rel="alternate" type="application/rss+xml" title="RSS2.0" href="https://devblog.thebase.in/rss"/> <link rel="alternate" type="application/json+oembed" href="https://hatena.blog/oembed?url=https%3A%2F%2Fdevblog.thebase.in%2Fentry%2F2022%2F04%2F13%2F114714&amp;format=json" title="oEmbed Profile of なぜ我々はsession.cookieを変更しなければならなかったのか"/> <link rel="alternate" type="text/xml+oembed" href="https://hatena.blog/oembed?url=https%3A%2F%2Fdevblog.thebase.in%2Fentry%2F2022%2F04%2F13%2F114714&amp;format=xml" title="oEmbed Profile of なぜ我々はsession.cookieを変更しなければならなかったのか"/> <link rel="author" href="http://www.hatena.ne.jp/yoga819/"> <link rel="preload" href="https://cdn-ak.f.st-hatena.com/images/fotolife/i/itsukichitose/20200909/20200909153301.png" as="image"/> <link rel="stylesheet" type="text/css" href="https://cdn.blog.st-hatena.com/css/blog.css?version=c85372bc899dec1e795892b094b326"/> <link rel="stylesheet" type="text/css" href="https://usercss.blog.st-hatena.com/blog_style/10328537792370962182/441da5de1734928a5676e270b5d7cf691bb346e6"/> <script> </script> <style> div#google_afc_user, div.google-afc-user-container, div.google_afc_image, div.google_afc_blocklink { display: block !important; } </style> <script type="application/ld+json">{"@context":"http://schema.org","@type":"Article","dateModified":"2024-07-01T17:54:04+09:00","datePublished":"2022-04-13T11:47:14+09:00","headline":"なぜ我々はsession.cookieを変更しなければならなかったのか","image":["https://cdn-ak.f.st-hatena.com/images/fotolife/y/yoga819/20220413/20220413093904.png"]}</script> </head> <body class="page-entry header-image-only enable-bottom-editarea globalheader-off globalheader-ng-enabled"> <div id="globalheader-container" data-brand="hatenablog" style="display: none" > <iframe id="globalheader" height="37" frameborder="0" allowTransparency="true"></iframe> </div> <div id="container"> <div id="container-inner"> <header id="blog-title" data-brand="hatenablog"> <div id="blog-title-inner" style="background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/i/itsukichitose/20200909/20200909153301.png'); background-position: center 0px;"> <div id="blog-title-content"> <h1 id="title"><a href="https://devblog.thebase.in/">BASEプロダクトチームブログ</a></h1> <h2 id="blog-description">ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。</h2> </div> </div> </header> <div id="content" class="hfeed" > <div id="content-inner"> <div id="wrapper"> <div id="main"> <div id="main-inner"> <!-- google_ad_section_start --> <!-- rakuten_ad_target_begin --> <article class="entry hentry test-hentry js-entry-article date-first autopagerize_page_element chars-6800 words-200 mode-markdown entry-odd" id="entry-13574176438082457789" data-keyword-campaign="" data-uuid="13574176438082457789" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date first"> <a href="https://devblog.thebase.in/archive/2022/04/13" rel="nofollow"> <time datetime="2022-04-13T02:47:14Z" title="2022-04-13T02:47:14Z"> <span class="date-year">2022</span><span class="hyphen">-</span><span class="date-month">04</span><span class="hyphen">-</span><span class="date-day">13</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://devblog.thebase.in/entry/2022/04/13/114714" class="entry-title-link bookmark">なぜ我々はsession.cookieを変更しなければならなかったのか</a> </h1> <div class="customized-header"> <div class="entry-header-html"><div id="oldArticleWarning" style="display: none;" class="notice-warning"></div></div> </div> </header> <div class="entry-content hatenablog-entry"> <h1 id="はじめに">はじめに</h1> <p>こんにちは。バックエンドエンジニアの小笠原です。</p> <p>今回は、2022年2月18日から2022年3月4日にかけて発生していたこちらの障害に対し私達開発チームが実施した、session.cookieで定義しているCookieのkey名を変更するという影響範囲の大きい対応について、実施に至るまでの経緯や対応過程についてご紹介したいと思います。</p> <p><figure class="figure-image figure-image-fotolife" title="ショップオーナー向けに掲載していたお知らせの内容"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yoga819/20220413/20220413093904.png" width="1016" height="731" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ショップオーナー向けに掲載していたお知らせの内容</figcaption></figure></p> <h1 id="背景">背景</h1> <h2 id="全ては-iOS145から端末識別子の取得に同意が必要になったことから始まった">全ては iOS14.5から端末識別子の取得に同意が必要になったことから始まった</h2> <p>ことの発端は、iOS14.5以降からIDFA(端末ごとに持つ固有識別子)の取得に端末所有者の許可が必要になったことでした。</p> <p>この変更は、端末所有者側から見ると情報の活用範囲を自身で管理できることでよりプライバシーに配慮されるようになった良い変更と言えるでしょう。 一方で、広告出稿側から見た場合は拒否をしたユーザーの広告トラッキングが出来なくなることで広告の効果測定が大幅に制限される、という問題が発生してしまいます。</p> <p>この問題に対して、Facebookピクセルという広告効果測定ツールを提供しているMeta社(旧Facebook社)は、広告効果測定の仕様を変更して合算イベント測定による集計を行うことでIDFAの取得を拒否したユーザーについても広告の効果測定ができるように対策を行いました。 BASEにおいてもInstagram広告Appがこの影響を受けるので、何らかの対応を行う必要に迫られました。</p> <p>合算イベント測定に対応する際の詳細については本記事の主題ではないのでここでは省略させていただきますが、結論としてeTLD+1なドメインを認証することで合算イベント測定を使用可能になるということがわかったため、当時開発チームはショップ開設時に選択することができるドメイン群をPublic Suffix List(PSL)に登録するという対応を行っていました。</p> <h2 id="Public-Suffix-ListPSLとは">Public Suffix List(PSL)とは</h2> <p>Public Suffix List(PSL)とは、jpやcomなどのTop Level Domain(TLD)と、co.jpやmeguro.tokyo.jpのような実質的にTLDのように振る舞うことが期待されるeffective Top Level Domain (eTLD)を管理しているリストのことで、GitHub上で管理・運営されています。 このリストに対して必要な情報を添えてPull Requestを送ることで、誰でも任意のドメインの追加を申請することが可能です。</p> <p><a href="https://github.com/publicsuffix/list">https://github.com/publicsuffix/list</a></p> <p>つまり、PSLに任意のドメインを登録することでそのドメインをeTLDとして扱うようにすることができ、これによってショップのURLがeTLD+1と認識されるため、ショップ単位でドメイン認証を行うことで合算イベント測定を使用可能になる、ということです。</p> <p>この対応のため、開発チームはショップ開設時に選択することができる以下のドメイン群をPSLに登録する申請を行いました。</p> <pre class="code" data-lang="" data-unlink>base.ec official.ec buyshop.jp fashionstore.jp handcrafted.jp kawaiishop.jp supersale.jp theshop.jp shopselect.net base.shop</pre> <p>リポジトリのPull Request履歴を確認すると、登録申請をしたのは2021年9月14日で、マージされたのは2021年12月5日だということがわかります。 <a href="https://github.com/publicsuffix/list/pull/1420">https://github.com/publicsuffix/list/pull/1420</a></p> <h2 id="PSLに登録されたドメインにはCookieを保存できない">PSLに登録されたドメインにはCookieを保存できない</h2> <p>ところで、 PSLに登録したドメインにはCookieを保存することができなくなってしまいます。</p> <p>仮にjpのようなTLDに対してCookieを保存可能にしてしまうと、そのドメインを使用している全てのWebサイトでそのCookieを共有できることになってしまいます。TLDは不特定多数の利用者が様々な目的でサブドメインを取得して運用していることが多く、このような広範囲に対してCookieを参照可能な状態にしてしまうことはセキュリティリスクが高いため推奨されるものではありません。</p> <p>そのため、TLDにはCookieを保存できないルールになっています。そして、TLDと同様の振る舞いをするeTLDに対しても同じことが言えるため、TLDと同様にeTLDに対してもCookieを保存できません。</p> <p>つまり、PSLにドメインを登録するということは、そのドメインに対してCookieを保存できなくなる、ということを意味します。</p> <h2 id="PSLへドメインを登録したことによってどのような影響が出てしまったのか">PSLへドメインを登録したことによってどのような影響が出てしまったのか</h2> <p>BASEのショップでも例に漏れずCookieを利用しており、例えば「シークレットECショップへのログイン情報」「カートへ商品を追加する際の商品情報」などはCookieの<code>THEBASE</code> というkey名に保存して管理していました。そして、これらのCookieはショップ毎に割り当てられているサブドメインに対してではなく、前項で紹介したPSLに登録したドメインに対してCookieを保存する処理になっていました。</p> <p>つまり、これらの情報をCookieに保存できなくなったことで「シークレットECにログインできない」「カートへ商品を入れてもカートの中が空のまま」といった不具合が発生していたことが今回の障害の裏側で発生していた事象でした。</p> <h2 id="なぜPSLにドメインを登録してから数ヶ月経過してから問題が顕在化し始めたのか">なぜPSLにドメインを登録してから数ヶ月経過してから問題が顕在化し始めたのか</h2> <p>PSLにドメインを登録したのは2021年12月5日ですが、この障害を開発チームが認識したのは2022年2月19日の段階でした。 なぜおよそ2ヶ月ほど経過するまでこの不具合に気がつくことができなかったのかというと、それはブラウザが最新のPSLを取り込んだタイミングが関係していたようでした。</p> <p>実は、各ブラウザは常に最新のPSLを参照しているわけではなく、任意のアップデートのタイミングでその時点の最新のPSLのスナップショットをビルドに含めて参照しています。 さらに、以下の表のように必ずしもアップデート時に最新のPSLへと更新しているわけではなく、その更新周期には規則性がないこともわかりました。</p> <table> <thead> <tr> <th style="text-align:left;">ブラウザ名</th> <th style="text-align:left;">PSLの更新周期</th> </tr> </thead> <tbody> <tr> <td style="text-align:left;">FireFox</td> <td style="text-align:left;">Firefox96(2022/01/12リリース)時点ではBASEの登録したドメインは含まれておらず、Firefox97(2022/02/08)には含まれていた</td> </tr> <tr> <td style="text-align:left;">Chrome</td> <td style="text-align:left;">chrome97(2022/01/04リリース)時点で 2021/10/27 のPSLを取り込んで以後、更新されていない</td> </tr> </tbody> </table> <p>上記の通り、直近のFirefox97のリリースによってこの障害に遭遇する購入者が徐々に増えてきたのではないか、と推測されました。</p> <h1 id="障害への対応内容">障害への対応内容</h1> <p>base.shopなどのeTLDに対してCookieが保存できないという問題に対して、今回はショップのURLに該当するサブドメインに対してCookieを保存するように変更するという方法を採用しました。</p> <p>これは、ショップ毎にサブドメインを割り当てているBASEの仕組みを考えると、基本的にはショップの中でsessionが保持できれば購入者の買い物体験は阻害されないであろう、という判断によるものです。</p> <p>実現方法として、チームでは以下2点の選択肢が挙がりました。</p> <ul> <li>Cookieのdomain属性でサブドメインを指定する</li> <li>Cookieのdomain属性を指定しない</li> </ul> <p>domain属性を指定しなかった場合は一番狭い範囲に対してCookieが保存されるため、挙動としては「サブドメインに対してCookieを発行するように変更する」という点でどちらの対応を実施しても同じ意味となります。</p> <p>今回はセキュリティの入門書として有名である『体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践』にdomain属性を指定しない状態が最もCookieの送信範囲が狭く安全な状態であるという言及があったことから、後者のdomain属性を指定しないように修正する方針に決まりました。</p> <h1 id="問題点">問題点</h1> <p>さて、前置きが長くなってしまいましたが、ここからが本記事の本題となります。</p> <p>対応方針が決まったところで検証環境で動作確認をしていると「対応後のソースコードでもシークレットECにログインできない」という障害が稀に発生することがありました。</p> <p><figure class="figure-image figure-image-fotolife" title="修正前と修正後の動作確認結果の比較"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yoga819/20220413/20220413100004.png" width="866" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>修正前と修正後の動作確認結果の比較</figcaption></figure></p> <p>この現象は eTLD+1をドメイン属性にもつ <code>THEBASE</code> のCookieと、eTLDをドメイン属性にもつ<code>THEBASE</code> のCookieが同時に送信されているケースで発生していることがわかりました。</p> <p>これは、今まで不具合が発生していたショップ(Cookieが保存できていなかったショップ)では修正後のCookieのみが保存されているために不具合が解消された一方で、今まで正常にログインできていたショップ(Cookieが保存できていたショップ)で新たに不具合が発生するようになった、ということです。</p> <h2 id="そもそもなぜ同じkeyのCookieが二種類できてしまうのか">そもそもなぜ同じkeyのCookieが二種類できてしまうのか</h2> <p><code>THEBASE</code> のCookieは有効期限をセッションに設定していたため、この現象に遭遇した場合はブラウザを再起動すれば古いCookieが削除されて問題を解消することができると予想されました。</p> <p>ところが、実際にブラウザを閉じてショップを開きなおしても、本来であれば消えるはずの前回アクセスした際のCookieが残ったままになってしまっていることが発生していました。</p> <p>実は、この問題はブラウザが「前回開いたサイトを復元する」機能を実現するために、ブラウザを閉じた後も有効期限がセッションになっているCookieを保持し続ける挙動をすることが原因で発生しているらしいことがわかりました。</p> <p>さらに、同名のCookieが存在する場合のCookieの取扱もブラウザによって異なっていることが私たちを混乱させました。</p> <table> <thead> <tr> <th style="text-align:left;">ブラウザ</th> <th style="text-align:left;">Cookieの並び順</th> <th style="text-align:left;">同名のCookieが複数ある時シークレットECにログインできるか</th> </tr> </thead> <tbody> <tr> <td style="text-align:left;">FireFox</td> <td style="text-align:left;">古いCookieが新しいCookieよりも先に並ぶ</td> <td style="text-align:left;">できない</td> </tr> <tr> <td style="text-align:left;">Chrome</td> <td style="text-align:left;">古いCookieが新しいCookieよりも先に並ぶ</td> <td style="text-align:left;">できない</td> </tr> <tr> <td style="text-align:left;">Safari</td> <td style="text-align:left;">新しいCookieが古いCookieよりも先に並ぶ</td> <td style="text-align:left;">できる</td> </tr> </tbody> </table> <p>このように、ブラウザによって挙動が異なっており、いつCookieが削除されるのかがブラウザ依存であるという状態であることから、Cookieのdomain指定方法を変更するだけでは障害から復旧できないことがわかってしまいました。</p> <h1 id="解決案の模索">解決案の模索</h1> <p>この問題に対して、私達のチームでは2つの案について検討しました。一つ目の案はこの現象を許容したままで対応をリリースすること、そして二つ目の案はsession.cookieで定義しているCookieのkey名を変更した上で対応をリリースすること、でした。</p> <p>この2つの案の比較検討と障害の影響範囲の把握のため、開発メンバーで協力してソースコード上でsession.cookieの定義を使用している全ての参照箇所を洗い出しました。</p> <p>以下の表は、この2つの案に対してそれぞれ比較検討した内容を表にまとめたものです。</p> <table> <thead> <tr> <th style="text-align:left;"></th> <th style="text-align:left;">案1:Cookieの重複を許容する</th> <th style="text-align:left;">案2:Cookieのkey名を変更する</th> </tr> </thead> <tbody> <tr> <td style="text-align:left;">影響範囲</td> <td style="text-align:left;">FireFox97とSafari以外のブラウザを使用している購入者</td> <td style="text-align:left;">全ての購入者</td> </tr> <tr> <td style="text-align:left;">メリット</td> <td style="text-align:left;">すぐにリリースできる</td> <td style="text-align:left;">完全に不具合が発生しなくなる</td> </tr> <tr> <td style="text-align:left;">デメリット</td> <td style="text-align:left;">Cookieが二重で登録されてしまった購入者には、ブラウザキャッシュを削除してもらう必要がある</td> <td style="text-align:left;">セッションがリセットされるので、再ログイン等が必要になる</td> </tr> <tr> <td style="text-align:left;">工数</td> <td style="text-align:left;">なし</td> <td style="text-align:left;">リグレッションテストが膨大</td> </tr> <tr> <td style="text-align:left;">影響期間</td> <td style="text-align:left;">ブラウザの旧Cookieが消えるまで(つまりいつ収束するか不明)</td> <td style="text-align:left;">デプロイのタイミングのみ</td> </tr> </tbody> </table> <p>どちらの案でも発生するデメリットとして、デプロイを跨いで購入者が操作した際に以下の影響が出るという問題がありました。</p> <ul> <li>改善リリースデプロイ前に抽選・定期便・コミュニティ限定商品をカートへ追加して未購入状態の場合、改善リリースデプロイ後にはカート内の商品が全て消えてしまう</li> <li>コミュニティ会員ページへログイン済みの状態でも、再度ログインが必要になってしまう</li> <li>シークレットECがかかったショップページへアクセスをしている状態でも、再度PW入力が必要になってしまう</li> <li>デプロイをまたいで購入をしたユーザーの場合、決済が走っているもののCookieを持ち越せないために購入完了画面が表示されないことで、購入に失敗したと誤解をして重複購入してしまう</li> </ul> <p><figure class="figure-image figure-image-fotolife" title="修正対応リリース前後で問題が発生するケース"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yoga819/20220413/20220413101944.png" width="1200" height="761" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>修正対応リリース前後で問題が発生するケース</figcaption></figure></p> <h1 id="そして我々はsessioncookieを修正してCookieのKey名を変えた">そして我々はsession.cookieを修正してCookieのKey名を変えた</h1> <p>最終的に、Cookieのkey名を変更する案2の方がより購入者に優しいだろう、ということで決まりました。</p> <p>一時的な不便を全購入者に要求してしまうことにはなるのですが、購入者に要求する操作としては再ログインやカートへの再度の商品追加など、通常のWebブラウジングの操作の範囲内で対処できるものとなっています。</p> <p>逆に、案1の場合はブラウザのキャッシュを削除するという通常のWebブラウジングでは行わない操作を購入者に要求してしまう上に、BASE以外のサイトのキャッシュも削除してしまうことになります。もちろん特定のサイトのみのCookieを削除することもブラウザの機能としては可能ですが、その操作はさらに難易度の高いものです。</p> <p>また、案1については重複したCookieが削除されるタイミングがブラウザ依存であるため、インシデントの収束タイミングを把握できないという問題点がある以上避けるべきだろう、という意見もありました。</p> <p>そうして、上記のような理由から安全かつ完全な形で障害から復旧させる方法である、Cookieのkey名を変更してからリリースする、という方法を実施する決断を行いました。</p> <p>リリースに当たっては、万全を期すために調査で判明したsession.cookieの定義を使用している処理を全て網羅するテストケースを作成すると共に、QAチームが使用しているリグレッションテスト項目を共有してもらい、購入者の一般的な操作を全て動作確認することでより安全性を高めました。</p> <p>これによってさらに障害の復旧までに時間を要することにはなりますが、より安全かつ完全に対応するためには必要な作業だというのが開発チームの共通認識でした。</p> <h1 id="おわりに">おわりに</h1> <p>今回、障害の発生を認識してから収束するまでの間に2週間という時間がかかってしまった点と、対応の副作用によって購入者様の皆様にご迷惑をおかけしてしまったことは大変申し訳なかったと感じています。</p> <p>サービスを提供していく上で、障害を起こさないように気をつけることは重要なことです。そして、万が一障害が発生してしまった際には、如何に素早く影響を最小限に留めて適切な対処で障害を解決することができるか、という点もまた重要なことです。</p> <p>BASEでは、このようにBASEが提供するサービスを利用してくださる購入者やショップオーナーの皆様のことを第一に考えてサービスを共に発展させていく仲間を募集しております。 カジュアル面談も実施しておりますので、ぜひお気軽にお問い合わせください。</p> <p><a href="https://open.talentio.com/r/1/c/binc/homes/4380">https://open.talentio.com/r/1/c/binc/homes/4380</a></p> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{&quot;area&quot;: &quot;finish_reading&quot;}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="yoga819" >yoga819</span></span> <span class="entry-footer-time"><a href="https://devblog.thebase.in/entry/2022/04/13/114714"><time data-relative datetime="2022-04-13T02:47:14Z" title="2022-04-13T02:47:14Z" class="updated">2022-04-13 11:47</time></a></span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://devblog.thebase.in/entry/2022/04/13/114714" data-hatena-star-title="なぜ我々はsession.cookieを変更しなければならなかったのか" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/devblog.thebase.in/entry/2022/04/13/114714" class="hatena-bookmark-button" data-hatena-bookmark-url="https://devblog.thebase.in/entry/2022/04/13/114714" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://devblog.thebase.in/entry/2022/04/13/114714"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?text=%E3%81%AA%E3%81%9C%E6%88%91%E3%80%85%E3%81%AFsession.cookie%E3%82%92%E5%A4%89%E6%9B%B4%E3%81%97%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%81%AA%E3%82%89%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%AE%E3%81%8B+-+BASE%E3%83%97%E3%83%AD%E3%83%80%E3%82%AF%E3%83%88%E3%83%81%E3%83%BC%E3%83%A0%E3%83%96%E3%83%AD%E3%82%B0&amp;url=https%3A%2F%2Fdevblog.thebase.in%2Fentry%2F2022%2F04%2F13%2F114714" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> <div class="entry-footer-html"><p><iframe src="https://blog.hatena.ne.jp/basedevteam/base-developer.hatenablog.com/subscribe/iframe" allowtransparency="true" frameborder="0" scrolling="no" width="150" height="28"></iframe></p> </div> </div> </footer> </div> </article> <!-- rakuten_ad_target_end --> <!-- google_ad_section_end --> <div class="pager pager-permalink permalink"> <span class="pager-prev"> <a href="https://devblog.thebase.in/entry/phperkaigi2022talk" rel="prev"> <span class="pager-arrow">&laquo; </span> PHPerKaigi2022に4名のメンバーが登壇しま… </a> </span> <span class="pager-next"> <a href="https://devblog.thebase.in/entry/2022/03/28/130016" rel="next"> OpenAPI Generator で API Client と型を… <span class="pager-arrow"> &raquo;</span> </a> </span> </div> </div> </div> <aside id="box1"> <div id="box1-inner"> </div> </aside> </div><!-- #wrapper --> <aside id="box2"> <div id="box2-inner"> <div class="hatena-module hatena-module-html"> <div class="hatena-module-body"> <div class="sw-recruitment-area"> <a href='https://speakerdeck.com/base/for-engineers' target="_blank"> <img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yoshiokachang/20211125/20211125184229.jpg" class="corporate sw-sidenav"> </a> <a href='https://note.com/base_designteam/' target="_blank"> <img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/applepine1125/20230117/20230117092925.png" class="note sw-sidenav"> </a> <a href="https://www.youtube.com/channel/UCPHQqCdsk1ZeU1tCkp-gLdw" target="_blank"> <img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yoshiokachang/20211125/20211125184259.jpg" class="youtube sw-sidenav"> </a> <a href="https://base.connpass.com/event/" target="_blank"> <img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/applepine1125/20221208/20221208144956.png" class="connpass sw-sidenav"> </a> </div> </div> </div> <div class="hatena-module hatena-module-search-box"> <div class="hatena-module-title"> 検索 </div> <div class="hatena-module-body"> <form class="search-form" role="search" action="https://devblog.thebase.in/search" method="get"> <input type="text" name="q" class="search-module-input" value="" placeholder="記事を検索" required> <input type="submit" value="検索" class="search-module-button" /> </form> </div> </div> <div class="hatena-module hatena-module-recent-entries "> <div class="hatena-module-title"> <a href="https://devblog.thebase.in/archive"> 最近の投稿 </a> </div> <div class="hatena-module-body"> <ul class="recent-entries hatena-urllist urllist-with-thumbnails"> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a class="urllist-image-link recent-entries-image-link" href="https://devblog.thebase.in/entry/2025/03/25/180000"> <img alt="プロジェクトのリード経験を学びに変えるために、設計・プロジェクト推進のふりかえりをする勉強会を開催しました" src="https://cdn.image.st-hatena.com/image/square/7226c2204c39f4a6b06ad77c41e91b36bea7e231/backend=imagemagick;height=128;version=1;width=240/https%3A%2F%2Fcdn.user.blog.st-hatena.com%2Fdefault_entry_og_image%2F153078222%2F1614340287711564" class="urllist-image recent-entries-image" title="プロジェクトのリード経験を学びに変えるために、設計・プロジェクト推進のふりかえりをする勉強会を開催しました" width="240" height="128" loading="lazy"> </a> <a href="https://devblog.thebase.in/entry/2025/03/25/180000" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title">プロジェクトのリード経験を学びに変えるために、設計・プロジェクト推進のふりかえりをする勉強会を開催しました</a> <div class="urllist-entry-body recent-entries-entry-body">はじめに こんにちは、BASE BANK Departmentで開発責任者をしている斉藤です。 今回はBASE BANKの開発チー…</div> </div> </li> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a class="urllist-image-link recent-entries-image-link" href="https://devblog.thebase.in/entry/2025/03/17/110000"> <img alt="BASEショップに最適なカートバッジを実装するまでの試行錯誤" src="https://cdn.image.st-hatena.com/image/square/7226c2204c39f4a6b06ad77c41e91b36bea7e231/backend=imagemagick;height=128;version=1;width=240/https%3A%2F%2Fcdn.user.blog.st-hatena.com%2Fdefault_entry_og_image%2F153078222%2F1614340287711564" class="urllist-image recent-entries-image" title="BASEショップに最適なカートバッジを実装するまでの試行錯誤" width="240" height="128" loading="lazy"> </a> <a href="https://devblog.thebase.in/entry/2025/03/17/110000" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title">BASEショップに最適なカートバッジを実装するまでの試行錯誤</a> <div class="urllist-entry-body recent-entries-entry-body">はじめに こんにちは! BASEのカートチームでバックエンドエンジニアをしている、かがの(@ykagano)です…</div> </div> </li> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a class="urllist-image-link recent-entries-image-link" href="https://devblog.thebase.in/entry/2025/03/10/110000"> <img alt="PHPerKaigi 2025 にゴールドスポンサーとして協賛します" src="https://cdn.image.st-hatena.com/image/square/7226c2204c39f4a6b06ad77c41e91b36bea7e231/backend=imagemagick;height=128;version=1;width=240/https%3A%2F%2Fcdn.user.blog.st-hatena.com%2Fdefault_entry_og_image%2F153078222%2F1614340287711564" class="urllist-image recent-entries-image" title="PHPerKaigi 2025 にゴールドスポンサーとして協賛します" width="240" height="128" loading="lazy"> </a> <a href="https://devblog.thebase.in/entry/2025/03/10/110000" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title">PHPerKaigi 2025 にゴールドスポンサーとして協賛します</a> <div class="urllist-entry-body recent-entries-entry-body">2025/3/21(金)- 3/23(日)の3日間、中野セントラルパークカンファレンス &amp; ニコニコ生放送で PHPerKaig…</div> </div> </li> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a class="urllist-image-link recent-entries-image-link" href="https://devblog.thebase.in/entry/phpconnagoya2025"> <img alt="PHPカンファレンス名古屋 2025にBASE BANKのエンジニアが登壇&amp;スポンサーとして協賛しました" src="https://cdn.image.st-hatena.com/image/square/dba7ce32be7928b5a2599fbb4f0040e919aadeaf/backend=imagemagick;height=128;version=1;width=240/https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Fc%2Fcocoeyes02%2F20250228%2F20250228144748.jpg" class="urllist-image recent-entries-image" title="PHPカンファレンス名古屋 2025にBASE BANKのエンジニアが登壇&amp;スポンサーとして協賛しました" width="240" height="128" loading="lazy"> </a> <a href="https://devblog.thebase.in/entry/phpconnagoya2025" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title">PHPカンファレンス名古屋 2025にBASE BANKのエンジニアが登壇&amp;スポンサーとして協賛しました</a> <div class="urllist-entry-body recent-entries-entry-body">はじめに BASE BANK Division で フルサイクルエンジニア をしている02 (@cocoeyes02)です。 2025/02/22…</div> </div> </li> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a href="https://devblog.thebase.in/entry/2025/02/21/102320" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title">Developers Summit 2025に参加・登壇しました</a> <div class="urllist-entry-body recent-entries-entry-body">はじめに BASE の Product Dev Division で Advanced Engineer のプログラミングをするパンダ(@Panda_Pro…</div> </div> </li> </ul> </div> </div> <div class="hatena-module hatena-module-category"> <div class="hatena-module-title"> カテゴリー </div> <div class="hatena-module-body"> <ul class="hatena-urllist"> <li> <a href="https://devblog.thebase.in/archive/category/Web%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E9%96%8B%E7%99%BA" class="category-Webサービス開発"> Webサービス開発 (47) </a> </li> <li> <a href="https://devblog.thebase.in/archive/category/%E3%83%81%E3%83%BC%E3%83%A0%E9%96%8B%E7%99%BA" class="category-チーム開発"> チーム開発 (32) </a> </li> <li> <a href="https://devblog.thebase.in/archive/category/%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89" class="category-フロントエンド"> フロントエンド (34) </a> </li> <li> <a href="https://devblog.thebase.in/archive/category/Go" class="category-Go"> Go (19) </a> </li> <li> <a href="https://devblog.thebase.in/archive/category/%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92" class="category-機械学習"> 機械学習 (21) </a> </li> <li> <a href="https://devblog.thebase.in/archive/category/%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3" class="category-デザイン"> デザイン (35) </a> </li> <li> <a href="https://devblog.thebase.in/archive/category/%E3%83%9E%E3%83%8D%E3%82%B8%E3%83%A1%E3%83%B3%E3%83%88%E3%83%BB%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%83%AA%E3%83%B3%E3%82%B0%E7%B5%84%E7%B9%94" class="category-マネジメント・エンジニアリング組織"> マネジメント・エンジニアリング組織 (25) </a> </li> <li> <a href="https://devblog.thebase.in/archive/category/%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88%E3%83%BB%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B5%E3%83%BC%E3%83%BB%E7%99%BB%E5%A3%87" class="category-イベントレポート・スポンサー・登壇"> イベントレポート・スポンサー・登壇 (97) </a> </li> </ul> </div> </div> <div class="hatena-module hatena-module-archive" data-archive-type="default" data-archive-url="https://devblog.thebase.in/archive"> <div class="hatena-module-title"> <a href="https://devblog.thebase.in/archive">アーカイブ</a> </div> <div class="hatena-module-body"> <ul class="hatena-urllist"> <li class="archive-module-year archive-module-year-hidden" data-year="2025"> <div class="archive-module-button"> <span class="archive-module-hide-button">▼</span> <span class="archive-module-show-button">▶</span> </div> <a href="https://devblog.thebase.in/archive/2025" class="archive-module-year-title archive-module-year-2025"> 2025 </a> <ul class="archive-module-months"> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2025/03" class="archive-module-month-title archive-module-month-2025-3"> 2025 / 3 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2025/02" class="archive-module-month-title archive-module-month-2025-2"> 2025 / 2 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2025/01" class="archive-module-month-title archive-module-month-2025-1"> 2025 / 1 </a> </li> </ul> </li> <li class="archive-module-year archive-module-year-hidden" data-year="2024"> <div class="archive-module-button"> <span class="archive-module-hide-button">▼</span> <span class="archive-module-show-button">▶</span> </div> <a href="https://devblog.thebase.in/archive/2024" class="archive-module-year-title archive-module-year-2024"> 2024 </a> <ul class="archive-module-months"> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2024/12" class="archive-module-month-title archive-module-month-2024-12"> 2024 / 12 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2024/11" class="archive-module-month-title archive-module-month-2024-11"> 2024 / 11 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2024/10" class="archive-module-month-title archive-module-month-2024-10"> 2024 / 10 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2024/08" class="archive-module-month-title archive-module-month-2024-8"> 2024 / 8 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2024/07" class="archive-module-month-title archive-module-month-2024-7"> 2024 / 7 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2024/06" class="archive-module-month-title archive-module-month-2024-6"> 2024 / 6 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2024/05" class="archive-module-month-title archive-module-month-2024-5"> 2024 / 5 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2024/04" class="archive-module-month-title archive-module-month-2024-4"> 2024 / 4 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2024/03" class="archive-module-month-title archive-module-month-2024-3"> 2024 / 3 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2024/02" class="archive-module-month-title archive-module-month-2024-2"> 2024 / 2 </a> </li> </ul> </li> <li class="archive-module-year archive-module-year-hidden" data-year="2023"> <div class="archive-module-button"> <span class="archive-module-hide-button">▼</span> <span class="archive-module-show-button">▶</span> </div> <a href="https://devblog.thebase.in/archive/2023" class="archive-module-year-title archive-module-year-2023"> 2023 </a> <ul class="archive-module-months"> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2023/12" class="archive-module-month-title archive-module-month-2023-12"> 2023 / 12 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2023/11" class="archive-module-month-title archive-module-month-2023-11"> 2023 / 11 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2023/10" class="archive-module-month-title archive-module-month-2023-10"> 2023 / 10 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2023/09" class="archive-module-month-title archive-module-month-2023-9"> 2023 / 9 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2023/08" class="archive-module-month-title archive-module-month-2023-8"> 2023 / 8 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2023/06" class="archive-module-month-title archive-module-month-2023-6"> 2023 / 6 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2023/05" class="archive-module-month-title archive-module-month-2023-5"> 2023 / 5 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2023/04" class="archive-module-month-title archive-module-month-2023-4"> 2023 / 4 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2023/03" class="archive-module-month-title archive-module-month-2023-3"> 2023 / 3 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2023/01" class="archive-module-month-title archive-module-month-2023-1"> 2023 / 1 </a> </li> </ul> </li> <li class="archive-module-year archive-module-year-hidden" data-year="2022"> <div class="archive-module-button"> <span class="archive-module-hide-button">▼</span> <span class="archive-module-show-button">▶</span> </div> <a href="https://devblog.thebase.in/archive/2022" class="archive-module-year-title archive-module-year-2022"> 2022 </a> <ul class="archive-module-months"> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2022/12" class="archive-module-month-title archive-module-month-2022-12"> 2022 / 12 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2022/11" class="archive-module-month-title archive-module-month-2022-11"> 2022 / 11 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2022/10" class="archive-module-month-title archive-module-month-2022-10"> 2022 / 10 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2022/09" class="archive-module-month-title archive-module-month-2022-9"> 2022 / 9 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2022/08" class="archive-module-month-title archive-module-month-2022-8"> 2022 / 8 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2022/07" class="archive-module-month-title archive-module-month-2022-7"> 2022 / 7 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2022/06" class="archive-module-month-title archive-module-month-2022-6"> 2022 / 6 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2022/05" class="archive-module-month-title archive-module-month-2022-5"> 2022 / 5 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2022/04" class="archive-module-month-title archive-module-month-2022-4"> 2022 / 4 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2022/03" class="archive-module-month-title archive-module-month-2022-3"> 2022 / 3 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2022/02" class="archive-module-month-title archive-module-month-2022-2"> 2022 / 2 </a> </li> </ul> </li> <li class="archive-module-year archive-module-year-hidden" data-year="2021"> <div class="archive-module-button"> <span class="archive-module-hide-button">▼</span> <span class="archive-module-show-button">▶</span> </div> <a href="https://devblog.thebase.in/archive/2021" class="archive-module-year-title archive-module-year-2021"> 2021 </a> <ul class="archive-module-months"> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2021/12" class="archive-module-month-title archive-module-month-2021-12"> 2021 / 12 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2021/11" class="archive-module-month-title archive-module-month-2021-11"> 2021 / 11 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2021/10" class="archive-module-month-title archive-module-month-2021-10"> 2021 / 10 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2021/09" class="archive-module-month-title archive-module-month-2021-9"> 2021 / 9 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2021/08" class="archive-module-month-title archive-module-month-2021-8"> 2021 / 8 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2021/07" class="archive-module-month-title archive-module-month-2021-7"> 2021 / 7 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2021/06" class="archive-module-month-title archive-module-month-2021-6"> 2021 / 6 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2021/05" class="archive-module-month-title archive-module-month-2021-5"> 2021 / 5 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2021/04" class="archive-module-month-title archive-module-month-2021-4"> 2021 / 4 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2021/03" class="archive-module-month-title archive-module-month-2021-3"> 2021 / 3 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2021/02" class="archive-module-month-title archive-module-month-2021-2"> 2021 / 2 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2021/01" class="archive-module-month-title archive-module-month-2021-1"> 2021 / 1 </a> </li> </ul> </li> <li class="archive-module-year archive-module-year-hidden" data-year="2020"> <div class="archive-module-button"> <span class="archive-module-hide-button">▼</span> <span class="archive-module-show-button">▶</span> </div> <a href="https://devblog.thebase.in/archive/2020" class="archive-module-year-title archive-module-year-2020"> 2020 </a> <ul class="archive-module-months"> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2020/12" class="archive-module-month-title archive-module-month-2020-12"> 2020 / 12 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2020/11" class="archive-module-month-title archive-module-month-2020-11"> 2020 / 11 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2020/10" class="archive-module-month-title archive-module-month-2020-10"> 2020 / 10 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2020/09" class="archive-module-month-title archive-module-month-2020-9"> 2020 / 9 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2020/08" class="archive-module-month-title archive-module-month-2020-8"> 2020 / 8 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2020/07" class="archive-module-month-title archive-module-month-2020-7"> 2020 / 7 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2020/06" class="archive-module-month-title archive-module-month-2020-6"> 2020 / 6 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2020/05" class="archive-module-month-title archive-module-month-2020-5"> 2020 / 5 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2020/02" class="archive-module-month-title archive-module-month-2020-2"> 2020 / 2 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2020/01" class="archive-module-month-title archive-module-month-2020-1"> 2020 / 1 </a> </li> </ul> </li> <li class="archive-module-year archive-module-year-hidden" data-year="2019"> <div class="archive-module-button"> <span class="archive-module-hide-button">▼</span> <span class="archive-module-show-button">▶</span> </div> <a href="https://devblog.thebase.in/archive/2019" class="archive-module-year-title archive-module-year-2019"> 2019 </a> <ul class="archive-module-months"> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2019/12" class="archive-module-month-title archive-module-month-2019-12"> 2019 / 12 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2019/11" class="archive-module-month-title archive-module-month-2019-11"> 2019 / 11 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2019/10" class="archive-module-month-title archive-module-month-2019-10"> 2019 / 10 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2019/09" class="archive-module-month-title archive-module-month-2019-9"> 2019 / 9 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2019/08" class="archive-module-month-title archive-module-month-2019-8"> 2019 / 8 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2019/07" class="archive-module-month-title archive-module-month-2019-7"> 2019 / 7 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2019/06" class="archive-module-month-title archive-module-month-2019-6"> 2019 / 6 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2019/05" class="archive-module-month-title archive-module-month-2019-5"> 2019 / 5 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2019/04" class="archive-module-month-title archive-module-month-2019-4"> 2019 / 4 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2019/03" class="archive-module-month-title archive-module-month-2019-3"> 2019 / 3 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2019/02" class="archive-module-month-title archive-module-month-2019-2"> 2019 / 2 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2019/01" class="archive-module-month-title archive-module-month-2019-1"> 2019 / 1 </a> </li> </ul> </li> <li class="archive-module-year archive-module-year-hidden" data-year="2018"> <div class="archive-module-button"> <span class="archive-module-hide-button">▼</span> <span class="archive-module-show-button">▶</span> </div> <a href="https://devblog.thebase.in/archive/2018" class="archive-module-year-title archive-module-year-2018"> 2018 </a> <ul class="archive-module-months"> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2018/12" class="archive-module-month-title archive-module-month-2018-12"> 2018 / 12 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2018/11" class="archive-module-month-title archive-module-month-2018-11"> 2018 / 11 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2018/10" class="archive-module-month-title archive-module-month-2018-10"> 2018 / 10 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2018/09" class="archive-module-month-title archive-module-month-2018-9"> 2018 / 9 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2018/08" class="archive-module-month-title archive-module-month-2018-8"> 2018 / 8 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2018/07" class="archive-module-month-title archive-module-month-2018-7"> 2018 / 7 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2018/06" class="archive-module-month-title archive-module-month-2018-6"> 2018 / 6 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2018/05" class="archive-module-month-title archive-module-month-2018-5"> 2018 / 5 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2018/04" class="archive-module-month-title archive-module-month-2018-4"> 2018 / 4 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2018/03" class="archive-module-month-title archive-module-month-2018-3"> 2018 / 3 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2018/02" class="archive-module-month-title archive-module-month-2018-2"> 2018 / 2 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2018/01" class="archive-module-month-title archive-module-month-2018-1"> 2018 / 1 </a> </li> </ul> </li> <li class="archive-module-year archive-module-year-hidden" data-year="2017"> <div class="archive-module-button"> <span class="archive-module-hide-button">▼</span> <span class="archive-module-show-button">▶</span> </div> <a href="https://devblog.thebase.in/archive/2017" class="archive-module-year-title archive-module-year-2017"> 2017 </a> <ul class="archive-module-months"> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2017/12" class="archive-module-month-title archive-module-month-2017-12"> 2017 / 12 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2017/06" class="archive-module-month-title archive-module-month-2017-6"> 2017 / 6 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2017/04" class="archive-module-month-title archive-module-month-2017-4"> 2017 / 4 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2017/03" class="archive-module-month-title archive-module-month-2017-3"> 2017 / 3 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2017/01" class="archive-module-month-title archive-module-month-2017-1"> 2017 / 1 </a> </li> </ul> </li> <li class="archive-module-year archive-module-year-hidden" data-year="2016"> <div class="archive-module-button"> <span class="archive-module-hide-button">▼</span> <span class="archive-module-show-button">▶</span> </div> <a href="https://devblog.thebase.in/archive/2016" class="archive-module-year-title archive-module-year-2016"> 2016 </a> <ul class="archive-module-months"> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2016/10" class="archive-module-month-title archive-module-month-2016-10"> 2016 / 10 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2016/06" class="archive-module-month-title archive-module-month-2016-6"> 2016 / 6 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2016/05" class="archive-module-month-title archive-module-month-2016-5"> 2016 / 5 </a> </li> <li class="archive-module-month"> <a href="https://devblog.thebase.in/archive/2016/04" class="archive-module-month-title archive-module-month-2016-4"> 2016 / 4 </a> </li> </ul> </li> </ul> </div> </div> </div> </aside> </div> </div> <div id="bottom-editarea"> <script type="text/javascript"> // 記事上の警告用divタグを取得 const warning = document.getElementById("oldArticleWarning"); // 記事の最終編集日時を取得(なければ投稿日時) const timeElement = document.querySelector(".entry-date > span > time") || document.querySelector(".entry-date > a > time"); const getYearsElapsed = function(dateString) { const today = new Date(); const startDate = new Date(dateString); let yearsElapsed = today.getFullYear() - startDate.getFullYear(); console.log(yearsElapsed) console.log(startDate) // まだ1年経過していない場合は減算 if ( today.getMonth() < startDate.getMonth() || (today.getMonth() === startDate.getMonth() && today.getDate() < startDate.getDate()) ) { yearsElapsed--; } return yearsElapsed; } if (timeElement) { const elapsedYears = getYearsElapsed(timeElement.dateTime) // 1年以上経過していたら警告を表示 if (elapsedYears > 0) { warning.style.display = "block"; warning.innerHTML = ` <span>この記事の最終編集日時は${elapsedYears}年以上前です。</span><br> 古い情報が含まれている可能性があるため、予めご了承ください。 `; } } </script> </div> </div> </div> <script async src="https://s.hatena.ne.jp/js/widget/star.js"></script> <script> if (typeof window.Hatena === 'undefined') { window.Hatena = {}; } if (!Hatena.hasOwnProperty('Star')) { Hatena.Star = { VERSION: 2, }; } </script> <div id="fb-root"></div> <script>(function(d, s, id) { var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) return; js = d.createElement(s); js.id = id; js.src = "//connect.facebook.net/ja_JP/sdk.js#xfbml=1&appId=719729204785177&version=v17.0"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk'));</script> <div class="quote-box"> <div class="tooltip-quote tooltip-quote-stock"> <i class="blogicon-quote" title="引用をストック"></i> </div> <div class="tooltip-quote tooltip-quote-tweet js-tooltip-quote-tweet"> <a class="js-tweet-quote" target="_blank" data-track-name="quote-tweet" data-track-once> <img src="https://cdn.blog.st-hatena.com/images/admin/quote/quote-x-icon.svg?version=c85372bc899dec1e795892b094b326" title="引用して投稿する" > </a> </div> </div> <div class="quote-stock-panel" id="quote-stock-message-box" style="position: absolute; z-index: 3000"> <div class="message-box" id="quote-stock-succeeded-message" style="display: none"> <p>引用をストックしました</p> <button class="btn btn-primary" id="quote-stock-show-editor-button" data-track-name="curation-quote-edit-button">ストック一覧を見る</button> <button class="btn quote-stock-close-message-button">閉じる</button> </div> <div class="message-box" id="quote-login-required-message" style="display: none"> <p>引用するにはまずログインしてください</p> <button class="btn btn-primary" id="quote-login-button">ログイン</button> <button class="btn quote-stock-close-message-button">閉じる</button> </div> <div class="error-box" id="quote-stock-failed-message" style="display: none"> <p>引用をストックできませんでした。再度お試しください</p> <button class="btn quote-stock-close-message-button">閉じる</button> </div> <div class="error-box" id="unstockable-quote-message-box" style="display: none; position: absolute; z-index: 3000;"> <p>限定公開記事のため引用できません。</p> </div> </div> <script type="x-underscore-template" id="js-requote-button-template"> <div class="requote-button js-requote-button"> <button class="requote-button-btn tipsy-top" title="引用する"><i class="blogicon-quote"></i></button> </div> </script> <div id="hidden-subscribe-button" style="display: none;"> <div class="hatena-follow-button-box btn-subscribe js-hatena-follow-button-box" > <a href="#" class="hatena-follow-button js-hatena-follow-button"> <span class="subscribing"> <span class="foreground">読者です</span> <span class="background">読者をやめる</span> </span> <span class="unsubscribing" data-track-name="profile-widget-subscribe-button" data-track-once> <span class="foreground">読者になる</span> <span class="background">読者になる</span> </span> </a> <div class="subscription-count-box js-subscription-count-box"> <i></i> <u></u> <span class="subscription-count js-subscription-count"> </span> </div> </div> </div> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <script src="https://b.st-hatena.com/js/bookmark_button.js" charset="utf-8" async="async"></script> <script type="text/javascript" src="https://cdn.blog.st-hatena.com/js/external/jquery.min.js?v=1.12.4&amp;version=c85372bc899dec1e795892b094b326"></script> <script src="https://cdn.blog.st-hatena.com/js/texts-ja.js?version=c85372bc899dec1e795892b094b326"></script> <script id="vendors-js" data-env="production" src="https://cdn.blog.st-hatena.com/js/vendors.js?version=c85372bc899dec1e795892b094b326" crossorigin="anonymous"></script> <script id="hatenablog-js" data-env="production" src="https://cdn.blog.st-hatena.com/js/hatenablog.js?version=c85372bc899dec1e795892b094b326" crossorigin="anonymous" data-page-id="entry"></script> <script>Hatena.Diary.GlobalHeader.init()</script> </body> </html>

Pages: 1 2 3 4 5 6 7 8 9 10