Movatterモバイル変換


[0]ホーム

URL:


LoginSignup
66

Go to list of users who liked

71

Share on X(Twitter)

Share on Facebook

Add to Hatena Bookmark

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AngularJSでSpring Security

Last updated atPosted at 2015-08-15

2017/04/30 追記
現バージョンでは、CookieCsrfTokenRepositoryを利用したほうが簡単に実装できるはずです。
https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-cookie

「はじめてのSpring Boot」が面白く、これはSpring覚えねば!と、いう気分になりWebアプリを実装中。
Spring Securityを使用すると認証周りの実装がとても簡単になるようなので使ってみるかー、SPAでもなんとかなるだろうと手を出したら割と大変でした。
もっと簡単に実現できそうではあるのですが。。
フロントは2.0の移行が大変そうなAngularJSを使用しました。
テンプレートがわかりやすいのでAngular1.x系は残しておいて欲しいなあ。
あと、開発が遅くてイマイチな評価のUI Bootstrapを使ってます。

参考

  • はじめてのSpring Boot
    読むといろいろ作りたくなります。本の薄さと裏腹に内容がとても濃いです。

  • Spring Security and Angular JS
    Spring公式のAngularJSとSpring Securityの組み合わせ例。
    ほぼこれでやりたいことできるのですが、自分の場合、ログイン画面をモーダルで表示したかったので、そこだけ適用できず。

  • Spring Security
    Spring Security公式です。

実装

サーバサイド

SecurityConfigで、静的リソースとログインAPI以外は未認証の場合エラーとします。
POSTがデフォルトでCSRFチェック対象のため、最初のログインのURLをチェック対象外に設定しています。(requireCsrfProtectionMatcherの設定をしないと、ログインのPOSTが403)
チェック対象外ロジックはstackoverflowのここから拝借しました。
csrfTokenRepositoryはSpring Security and Angular JSのコードを使わせてもらっています。
Spring SecurityはデフォルトでCSRFチェックをしていて、AngularJSも$http($resourceでも)でX-XSRF-TOKENヘッダが自動的に付加されることを利用して、ここでCSRFのヘッダ名を指定しています。
AngularJSはCookie(名称:XSRF-TOKEN)からX-XSRF-TOKENトークンをHTTPヘッダにセットする(参考:$http)ので、Spring SecurityがAngularJSに合わせるとこうなるということですね。

SecurityConfing.java
@ConfigurationpublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{RequestMatchercsrfRequestMatcher=newRequestMatcher(){// CSRF対象外URL:privateAntPathRequestMatcher[]requestMatchers={newAntPathRequestMatcher("/api/login")};@Overridepublicbooleanmatches(HttpServletRequestrequest){for(AntPathRequestMatcherrm:requestMatchers){if(rm.matches(request)){returnfalse;}}returntrue;}};http.authorizeRequests().antMatchers("/images/**","/scripts/**","/styles/**","/views/**","/index.html","/api/login").permitAll().anyRequest().authenticated()//上記にマッチしなければ未認証の場合エラー.and().csrf().requireCsrfProtectionMatcher(csrfRequestMatcher).csrfTokenRepository(this.csrfTokenRepository());}privateCsrfTokenRepositorycsrfTokenRepository(){HttpSessionCsrfTokenRepositoryrepository=newHttpSessionCsrfTokenRepository();repository.setHeaderName("X-XSRF-TOKEN");returnrepository;}}

UserDetailsServiceを設定します。

GlobalAuthenticationConfig.java
@ConfigurationpublicclassGlobalAuthenticationConfigextendsGlobalAuthenticationConfigurerAdapter{@Overridepublicvoidinit(AuthenticationManagerBuilderauth)throwsException{auth.userDetailsService(userDetailsService());}@BeanUserDetailsServiceuserDetailsService(){returnnewFooAuthService();}}

ログインのエンドポイントになるRestControllerです。
サービスクラスで認証されたら組み込みのCSRFフィルターから取得したトークンをCookieにセットして、NGなら401を返却しています。

LoginRestController.java
@RestController@RequestMapping("/api/")publicclassLoginRestController{@AutowiredLoginServiceloginService;@RequestMapping(value="login",method=RequestMethod.POST)ResponseEntity<PageDto>login(@RequestBodyLoginDtologinDto,HttpServletRequestrequest,HttpServletResponseresponse){PageDtopageDto=loginService.login(loginDto);if(pageDto.getHeaderDto().isAuth()){CsrfTokencsrf=(CsrfToken)request.getAttribute(CsrfToken.class.getName());if(csrf!=null){Cookiecookie=WebUtils.getCookie(request,"XSRF-TOKEN");Stringtoken=csrf.getToken();Authenticationauthentication=SecurityContextHolder.getContext().getAuthentication();if((cookie==null||token!=null&&!token.equals(cookie.getValue()))&&(authentication!=null&&authentication.isAuthenticated())){cookie=newCookie("XSRF-TOKEN",token);cookie.setPath("/");//適切なスコープでresponse.addCookie(cookie);}}returnnewResponseEntity<>(pageDto,null,HttpStatus.OK);}else{returnnewResponseEntity<>(pageDto,null,HttpStatus.UNAUTHORIZED);}}@RequestMapping(value="logout",method=RequestMethod.POST)voidlogout(HttpServletRequestrequest){try{request.logout();}catch(ServletExceptione){thrownewRuntimeException(e);}}}

認証処理は公式のロジックを拝借してます。
UserDetailsとUserDetailsServiceの実装クラスははじめてのSpring Bootがわかりやすいです。

LoginService.java
@ServicepublicclassLoginService{@AutowiredprotectedMappermapper;@AutowiredprivateAuthenticationManagerauthManager;/**     * ログイン処理を行います.     * @return     */publicPageDtologin(LoginDtologinDto){PageDtopageDto=newPageDto();LoginDtooutLoginDto=mapper.map(loginDto,LoginDto.class);pageDto.setLoginDto(outLoginDto);HeaderDtoheaderDto=newHeaderDto();pageDto.setHeaderDto(headerDto);//Spring Security認証処理AuthenticationauthResult=null;try{Authenticationrequest=newUsernamePasswordAuthenticationToken(loginDto.getMailAddress(),loginDto.getPassword());authResult=authManager.authenticate(request);SecurityContextHolder.getContext().setAuthentication(authResult);FooUserDetailsprincipal=(FooUserDetails)authResult.getPrincipal();headerDto.setNickName(principal.getUserInfo().getNickName());headerDto.setAuth(true);}catch(AuthenticationExceptione){outLoginDto.setMessage("メールアドレスかパスワードがまちがっています。");headerDto.setAuth(false);}returnpageDto;}}

フロントエンド

UI Bootstrap使ってモーダルでログイン画面を表示しています。
index.htmlでログインのhtmlテンプレートを読み込みます。

index.html
<!-- 略 --><divclass="header"ng-controller="HeaderCtrl"><!-- 略 --><divclass="collapse navbar-collapse"id="js-navbar-collapse"><ulclass="nav navbar-nav"><liclass="active"><ahref="#/">Home</a></li><ling-if="!headerDto.auth"><inputtype="button"class="btn btn-info"value="ログイン"ng-click="loginDialog()"/></li><ling-if="headerDto.auth"><inputtype="button"class="btn btn-info"value="ログアウト"ng-click="logout()"/></li><li><divng-if="headerDto.auth">{{headerDto.nickName}}さん</div></li></ul><divng-includesrc="'./views/login.html'"></div></div><!-- 略 --></div><!-- 略 -->

モーダルで実行されるログインテンプレートです。

login.html
<div><scripttype="text/ng-template"id="login.tmpl.html"><divclass="modal-header"><h4>ログイン</h4></div><divclass="modal-body"><span>{{loginDto.message}}</span><br/><label>メールアドレス</label><inputtype="text"class="form-control"ng-model="loginDto.mailAddress"><br/><label>パスワード</label><inputtype="password"class="form-control"ng-model="loginDto.password"><br/><inputtype="button"class="btn btn-info"value="ログイン"ng-click="login()"/><inputtype="button"class="btn btn-default"value="cancel"ng-click="cancel()"/></div></script></div>

LoginRestControllerで定義されたエンドポイントにアクセスするサービスです。
SIerがAngular+Javaで実装するんならRestController(Java)とそれにアクセスするサービス(JavaScript)がExcelで自動生成されたりするんでしょうか。

loginHttp.js
angular.module('fooApp').factory('loginHttp',['$http',function($http){varlogin={login:function(param){return$http.post('/foo/api/login',param);},logout:function(){return$http.post('/foo/api/logout');}}returnlogin;}]);

ヘッダ部分のコントローラで「ログイン」ボタンが押下時にモーダルダイアログを起動させています。

header.js
angular.module('fooApp').controller('HeaderCtrl',['$scope','$modal','loginHttp',function($scope,$modal,loginHttp){$scope.loginDialog=function(){varmodalInstance=$modal.open({size:'sm',templateUrl:'login.tmpl.html',controller:'LoginCtrl'});modalInstance.result.then(function(data){$scope.headerDto=data;});};$scope.logout=function(){loginHttp.logout();$scope.headerDto={};}}]);

ログイン画面のコントローラでログインサービスを実行します。
認証エラーの場合は401が返ってくるのでerror()に入ります。

login.js
angular.module('fooApp').controller('LoginCtrl',['$scope','loginHttp','$modalInstance',function($scope,loginHttp,$modalInstance){$scope.login=function(){loginHttp.login({mailAddress:$scope.loginDto.mailAddress,password:$scope.loginDto.password}).success(function(data){$scope.loginDto=data.loginDto;$modalInstance.close(data.headerDto);}).error(function(data){$scope.loginDto=data.loginDto;})}$scope.cancel=function(){$modalInstance.close();}}]);

まとめ

モーダルのログイン画面でSpring Securityを実装するのは、ちょっと面倒でした。
もっと簡潔に実現できる方法があったら知りたいなあ。。
でも、Spring Securityで実現できるセキュリティ対策をフルスクラッチで作成することと比べたら、はるかに楽かなと思います。
Spring BootでSpring盛り上がっている感じなので、Spring関連の日本語資料いっぱいでるといいですね!

66

Go to list of users who liked

71
4

Go to list of comments

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
66

Go to list of users who liked

71

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?


[8]ページ先頭

©2009-2026 Movatter.jp