スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Spring MVC Spring Securityでログイン情報を保存する(クッキー)

以前、Spring Securityを使ったログイン画面の紹介をしたが、その中の「Remember me」(ログイン情報を保存する機能)についてはJSPだけ対応して機能は未実装だったため、今回実装したいと思う。

また、ログイン情報の保存は、クッキー(Cookie)を使用しており、クッキーがどのようなものかについても併せて確認したい。

■Spring Securityを使ったログイン画面に関する過去の記事
Spring MVC Spring Securityを使ってみる

◎動作検証にあたっての各バージョンは以下の通り
  • SpringFramework 3.2.8.RELEASE
  • Java 1.7
  • Tomcat 7.0

1.ログイン画面
Cookie_1.png
「Remember me」にチェックを入れて「Login」ボタンを押下する。


2.security-db.xml(関連箇所のみ掲載)

<sec:http auto-config="true">
<sec:intercept-url pattern="/book/list/**" access="ROLE_ADMIN,ROLE_USER"/>
<sec:intercept-url pattern="/book/edit/**" access="ROLE_ADMIN"/>
<sec:form-login login-page="/login.jsp" default-target-url="/book"
authentication-failure-url="/login.jsp?error=true"/>
<sec:logout logout-url="/logout" logout-success-url="/login.jsp"/>
<sec:remember-me token-validity-seconds="#{60}" />
<sec:access-denied-handler error-page="/error.jsp" />
</sec:http>

7行目の「sec:remember-me」が今回追加したタグとなる。「token-validity-seconds」属性には保存期間を秒単位で設定する。今回はテストのため60秒としている。この設定で、「Remember me」にチェックを入れて「Login」ボタンを押下した場合、60秒間、ログイン情報が保持されるようになる。ログイン情報が保持されている間は、一旦ブラウザを閉じても、再びログインすることなく、一覧画面を表示することができる。ちなみに、保存期間を1時間とする場合は、#{60*60}のような計算式も可能。


2.LogFilter.java

package jp.co.sample.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogFilter implements Filter {

private static Logger logger = LoggerFactory.getLogger(LogFilter.class);

public void init(FilterConfig config) throws ServletException {
}

public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {

logger.info("doFilter start");
//クッキーの情報を表示
Cookie cookies[]=((HttpServletRequest) req).getCookies();
if(cookies!=null){
for(int i=0;i<cookies.length;i++){
logger.info( "Cookie_Name_{}: {}",i+1,cookies[i].getName());
logger.info( "Cookie_Value_{}: {}",i+1,cookies[i].getValue());
}
}

chain.doFilter(req, res);
logger.info("doFilter end");
}

public void destroy() {
}
}

フィルターを使って、クッキーの情報をログ出力するようにしている。


3.動作確認
①ログイン画面「Remember me」にチェックを入れて「Login」ボタンを押下した場合のログ

INFO LogFilter - doFilter start
INFO LogFilter - Cookie_Name_1: JSESSIONID
INFO LogFilter - Cookie_Value_1: 8E825B197D1F205E2B154CE52E77B97D
INFO LogFilter - Cookie_Name_2: SPRING_SECURITY_REMEMBER_ME_COOKIE
INFO LogFilter - Cookie_Value_2: dGVzdGFkbWluOjE0MzAzMTA1MjgyMzc6NTlkYzdkNzM0ZWQwZThlYTZhODA5MzU0ZDY3ZDgzOGM
INFO LogFilter - doFilter end

「JSESSIONID」と「SPRING_SECURITY_REMEMBER_ME_COOKIE」のクッキーが生成されていることが確認できる。
「JSESSIONID」は、クライアントを認識するための、サーブレットの仕様で定められたクッキーとなる。
もうひとつの「SPRING_SECURITY_REMEMBER_ME_COOKIE」が、名前から分かる通り「Remember me」にチェックを入れて生成されたクッキーとなる。(チェックを入れなければ、「SPRING_SECURITY_REMEMBER_ME_COOKIE」は生成されない。)

せっかくなので、クッキーのファイルも確認してみる。

今回は、IE(Internet Explorer)11を使って動作確認している。IE11におけるクッキーの保存先は、「ツール」-「インターネット オプション」-「全般」タブ-「設定」ボタンを押下-「インターネット一時ファイル」タブの現在の場所で確認できる。「ファイルの表示」ボタンを押下すれば、現在の場所で記載されているフォルダ配下のファイルを確認できる。

そして、フィルタリングにより抽出した該当のクッキーが下記となる。

Cookie_5.png

有効期限日時が最終変更日時からちょうど1分(60秒)後となっていることが確認できる。また、クッキーは単なるテキストファイルであることも分かる。ちなみにこのファイルは、ログアウトすると削除される。そのため、有効期限内であっても、ログアウトすると、再度ログイン処理が必要となる。

クッキーは有効期限の違いにより、「ブラウザが開いている間のみ有効」なものと「ブラウザを閉じても有効」なものに分けられる。有効期限が過去やマイナス値などの場合が、前者に該当する。

クッキーの仕組みについても少し触れておくと、クッキーはサーバー側のレスポンスから生成(ソースイメージ:「response.addCookie(cookie);」)して、クライアントへ送信している。そして、クライアントはリクエストのヘッダーにクッキーを付与して、サーバーへ送信している。このやりとりを行うことで値を保持している。ちなみに、値を保持する仕組みとしてカートなどで使用されるHttpSessionは、リクエストから生成(ソースイメージ:「request.getSession();」)され、「session.setAttribute("cart",cart);」のようにして値を常にサーバー側で保持する点が大きく異なる。

試しに、有効期限内にブラウザを閉じ、「http://localhost:8080/SpringWebSample/book/list/page/1」にアクセスしてみる。
Cookie_3.png

再度、ログイン処理することなく、一覧画面が表示された。

また、有効期限(今回は60秒後)を過ぎると、「http://localhost:8080/SpringWebSample/book/list/page/1」にアクセスしても、ログイン画面が表示されることも確認できた。


クッキーがどのような場面でよく使われているのか把握できていなかったので、仕組みも含め、非常に勉強になった。


■過去のSpring関連記事
Spring関連記事 Index

スポンサーサイト

Spring MVC ファイルダウンロードを実装する

今回はファイルダウンロードを実装したいと思う。

ちなみにファイルアップロードの実装については過去の記事で掲載している。

■ファイルアップロードに関する過去の記事
Spring MVC ファイルアップロードを実装する[入力チェック編]

◎動作検証にあたっての各バージョンは以下の通り
  • SpringFramework 3.2.8.RELEASE
  • Java 1.7
  • Tomcat 7.0

1.BookController.java(関連箇所のみ掲載)

package jp.co.sample.book.controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletResponse;
import jp.co.sample.exception.BookException;
import org.apache.commons.io.IOUtils;
//他のimport文省略

@Controller
@RequestMapping("/book")
@SessionAttributes("bookSearchForm")
public class BookController {

//変数定義省略

@RequestMapping(value = "/list/download", method = RequestMethod.GET)
public void downloadFile(HttpServletResponse response) {
logger.info("download start");
//ダウンロードファイルの取得
String fileName = "test.xlsx";
File file = new File("download\\" + fileName);
String savePath = file.getAbsolutePath();

InputStream is = null;
byte[] fileContent = null;
try {
is = new FileInputStream(savePath);
fileContent = IOUtils.toByteArray(is);
} catch (FileNotFoundException e) {
throw new BookException(fileName + " not found");
} catch (IOException e) {
throw new BookException(e);
} finally {
IOUtils.closeQuietly(is);
}

//ファイルの書き込み
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
response.setContentLength(fileContent.length);

OutputStream os = null;
try {
os = response.getOutputStream();
os.write(fileContent);
os.flush();
} catch (IOException e) {
throw new BookException(e);
} finally {
IOUtils.closeQuietly(os);
}
}
//他のメソッド省略
}

22行目のdownloadFileメソッドがファイルダウンロード処理となる。引数には、ファイルへの書き込みに必要なHttpServletResponseを設定しており、responseオブジェクトは自動でバインドされる。

25行目のダウンロードファイル名であるが、「test.xlsx」の通りExcelファイルを対象としている。今回はダウンロードすることが目的のため、A1セルに「ダウンロードサンプル」と入力されただけのファイルであるが、Apache POIを使って一覧情報をExcelファイルに保存することなどを想定している。

26行目のFileオブジェクトのコンストラクタには、相対パスで「download」フォルダ配下に「test.xlsx」ファイルが格納されていることを想定している。絶対パスにした場合、移植性が低下するため、相対パスを使っている。そして、27行目のgetAbsolutePathメソッドで、フルパスを取得するようにしている。

33行目がバイト配列に変換している処理となる。

35行目は「test.xlsx」ファイルが見つからなかったときの例外ハンドリングとなるが、例外メッセージにフルパスが表示されることを回避するため、「fileName + " not found"」として、メッセージにはファイル名のみ表示するようにしている。なお、自作のBookExceptionについては、前回の例外ハンドリングに関する記事を参考にしてほしい。

39、55行目については、IOUtilsクラスを使って、InputStreamをクローズしている。

43行目以降がファイルへの書き込み処理となる。

43~45行目はresponseオブジェクトにファイルダウンロード用の設定をしている。

50行目でOutputStreamに変換したバイト配列を書き込むように命令し、51行目のflushメソッドでバッファに入っている出力バイトをすべて強制的に書き込んでいる。

■例外ハンドリングに関する過去の記事
Spring MVC 例外ハンドリング


2.pageList.jsp(関連箇所のみ掲載)

<body>
<spring:url value="/book/list/download" var="varBookDownloadUrl"/>
<spring:message code="link.download" var="varLinkDownload"/>
<!-- 省略 -->

<div class="row">
<div class="col-sm-6"></div>
<div class="col-sm-6 text-right">
<!-- 1.ROLE_ADMIN と ROLE_USERの両方必要 → testadminのみ表示 -->
<sec:authorize ifAllGranted="ROLE_ADMIN,ROLE_USER">
<a href="${varBookAddUrl}">${varLinkAdd}</a> |
</sec:authorize>
<a href="${varBookDownloadUrl}">${varLinkDownload}</a>
</div>
</div>
<!-- 省略 -->
</body>

ダウンロード用のリンクを用意。なお、本機能は管理者、一般ユーザーのどちらでも使用可能としている。

3.動作確認
①日本語の場合
download_1.png

②英語の場合
download_2.png

③ダウンロードの実行(その1)
download_3.png

ファイルダウンロードのダイアログが表示される。

④ダウンロードの実行(その2)
download_4.png

ファイルダウンロード完了のダイアログが表示される。

⑤ダウンロードファイルが存在しない場合
download_5.png

変換した例外メッセージが表示される。


今回の実装で、ファイルアップロード/ダウンロードの両方ができるようになった。


■過去のSpring関連記事
Spring関連記事 Index

プロフィール

bookmount8

Author:bookmount8
システムエンジニア。サーバーサイドでjavaを扱うことが多い。最近は、ミドルやフロント周りも関心あり。

最新記事
カテゴリ
検索フォーム
最新コメント
月別アーカイブ
これまでの訪問者数
ブロとも申請フォーム

この人とブロともになる

RSSリンクの表示
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。