Chapter 02

認証(Spring Security)・Flutterスマホアプリ対応

kazpgm
kazpgm
2022.06.14に更新

フロント側を、スマホアプリにするための認証(Spring Security)対応

「kaz_javaSpringBootプログラム自動作成◎自動生成」ツールで作成されたプログラムと当ツールで作成したプログラムを比較しながら説明します。
 比較元詳細は、Zennの本「SpringBoot、Thymeleaf プログラム自動作成(マクロ使用版)」・「Chapters21:ログイン(Spring Security)、トップページ」を見てください。

スマホアプリ追加による修正

スマホアプリからのアクセスにおいて、正常時にはHTMLでなくJSON及び、エラー時にはHTMLでなくレスポンスコードを戻します。そのための修正を行いました。

■WebSecurityConfig.javaを比較


(1)①SimpleAuthenticationEntryPoint.java

未認証のユーザーが認証の必要なAPIにアクセスしたときの処理
 PC・WEB側には、HTTPステータス403を返すだけの処理。結果、自動的に/error/403.htmlが表示される。
 Flutterスマホ側(クッキーにfromFlutterFlgが入っている)には、HTTPステータス401を返すだけの処理。
 補足:403を返すと、/error/403.htmlが戻されてしまうので、401にした。

package com.kaz01u.demo.auth;
・・・途中省略・・・
@Component
//log出力用
@Slf4j
/*
 * 未認証のユーザーが認証の必要なAPIにアクセスしたときの処理
 * 
 * デフォルトや用意されている標準実装クラスは利用せず、
 * HTTPステータス403を返すだけの処理を実装。補足:PC・WEB側は自動的に/error/403.htmlが表示される。
 * 
 */
public class SimpleAuthenticationEntryPoint implements AuthenticationEntryPoint {
 @Override
 public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
  if (response.isCommitted()) {
   log.info("Response has already been committed.");
   return;
  }
  // クッキーにfromFlutterFlgが入っている場合(Flutter画面)
  if (Functions.isFromFlutter(request)) {
    // HTTPステータス401を戻す
    response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
  } else {
   // HTTPステータス403を戻す
   response.sendError(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase());
  }
 }
}

(2)②SimpleAccessDeniedHandler.java

ユーザーは認証済みだが未認可のリソースへアクセスしたときの処理
 PC・WEB側には、HTTPステータス403を返すだけの処理。結果、自動的に/error/403.htmlが表示される。
 Flutterスマホ側(クッキーにfromFlutterFlgが入っている)には、HTTPステータス401を返すだけの処理。
 補足:403を返すと、/error/403.htmlが戻されてしまうので、401にした。

package com.kaz01u.demo.auth;
・・・途中省略・・・
/*
 * ユーザーは認証済みだが未認可のリソースへアクセスしたときの処理
 * 
 * デフォルトや用意されている標準実装クラスは利用せず、
 * HTTPステータス403を返すだけの処理を実装。補足:PC・WEB側は自動的に/error/403.htmlが表示される。
 * 
 */
public class SimpleAccessDeniedHandler implements AccessDeniedHandler {
 @Override
 public void handle(HttpServletRequest request,
        HttpServletResponse response,
        AccessDeniedException exception) throws IOException, ServletException {
  if (response.isCommitted()) {
   log.info("Response has already been committed.");
   return;
  }
   // クッキーにfromFlutterFlgが入っている場合(Flutter画面)
   if (Functions.isFromFlutter(request)) {
  // HTTPステータス401を戻す
  response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
   } else {
    // HTTPステータス403を戻す
   response.sendError(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase());
   }
 }
}

(3)③SimpleAuthenticationSuccessHandler.java

認証が成功した時の処理を実装したハンドラを設定
 PC・WEB側には、/members/indexが表示される。
 Flutterスマホ側(クッキーにfromFlutterFlgが入っている)には、HTTPステータス200を返すだけの処理。

package com.kaz01u.demo.auth;
・・・途中省略・・・
@Component
//log出力用
@Slf4j
/*
 * 認証が成功した時の処理を実装したハンドラを設定
 * 
 * デフォルトや用意されている標準実装クラスは利用せず、HTTPステータス200を返すだけのハンドラを実装
 */
public class SimpleAuthenticationSuccessHandler implements AuthenticationSuccessHandler  {
  @Override
  public void onAuthenticationSuccess(HttpServletRequest request,
           HttpServletResponse response, Authentication authentication) throws IOException {
  if (response.isCommitted()) {
   log.info("Response has already been committed.");
   return;
  }
   // クッキーにfromFlutterFlgが入っている場合(Flutter画面)
   if (Functions.isFromFlutter(request)) {
    response.setStatus(HttpStatus.OK.value());
    clearAuthenticationAttributes(request);
   } else {
  response.setStatus(HttpServletResponse.SC_OK);
  response.sendRedirect("/members/index");
   }
  }

 /**
  * Removes temporary authentication-related data which may have been stored in the
  * session during the authentication process.
  * このメソッドは、SimpleUrlAuthenticationSuccessHandler.javaからコピーしました。
  */
 private void clearAuthenticationAttributes(HttpServletRequest request) {
  HttpSession session = request.getSession(false);
  if (session == null) {
   return;
  }
  session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
 }
}

(4)④SimpleAuthenticationFailureHandler.java

認証が失敗した時の処理を実装したハンドラを設定
 PC・WEB側には、/login?error=trueが表示される。
 Flutterスマホ側(クッキーにfromFlutterFlgが入っている)には、HTTPステータス401を返すだけの処理。

package com.kaz01u.demo.auth;
・・・途中省略・・・

/*
 * 認証が失敗した時の処理を実装したハンドラを設定
 * 
 * デフォルトや用意されている標準実装クラスは利用せず、HTTPステータス401と
 * デフォルトのメッセージを返すだけのハンドラを実装
 */
public class SimpleAuthenticationFailureHandler implements AuthenticationFailureHandler {
 @Override
 public void onAuthenticationFailure(HttpServletRequest request,
          HttpServletResponse response,
          AuthenticationException exception) throws IOException, ServletException {
   // クッキーにfromFlutterFlgが入っている場合(Flutter画面)
   if (Functions.isFromFlutter(request)) {
  // HTTPステータス401を戻す
  response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
   } else {
  response.setStatus(HttpServletResponse.SC_OK);
  response.sendRedirect("/login?error=true");
   }
 }
}

(5)⑤SimpleLogoutSuccessHandler.java

ログアウトが成功した時の処理を実装したハンドラを設定
 PC・WEB側には、/loginが表示される。
 Flutterスマホ側(クッキーにfromFlutterFlgが入っている)には、HTTPステータス200を返すだけの処理。

package com.kaz01u.demo.auth;
・・・途中省略・・・
@Component
//log出力用
@Slf4j
public class SimpleLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler 
       implements LogoutSuccessHandler{
 @Override
 public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
        Authentication auth) throws IOException, ServletException {
  if (auth != null && auth.getDetails() != null) {
   log.info("ログアウト: " + auth.getDetails().toString());
  } else {
   log.info("ログアウト");
  }
   // クッキーにfromFlutterFlgが入っている場合(Flutter画面)
   if (Functions.isFromFlutter(request)) {
  // デフォルト処理
  super.onLogoutSuccess(request, response, auth);
   } else {
  response.setStatus(HttpServletResponse.SC_OK);
  //redirect to login
  response.sendRedirect("/login");
   }
 }
}