記事ディレクトリ
これはこのシリーズの4番目の記事です。一部の友達は前の記事を見つけることができません。Songgeがインデックスを提供します。
- 大きな穴を掘ると、春のセキュリティが始まります!
- Song GeはSpring Securityを案内します。パスワードの復号化方法を尋ねないでください
- Spring Securityでフォームログインをカスタマイズすることを教えます
2日前、小さな友人がWeChatでSonggeに質問しましたが、フロントエンドとバックエンドの開発が分離された後、認証は従来のセッションまたはJWTのようなトークンを使用して解決しましたか?
これは、2つの異なる方向を表しています。
セッションを通じてユーザー認証情報を記録する従来の方法は、ステートフルログインとして理解でき、JWTはステートレスログインを表します。この概念に慣れていない友達がいるかもしれませんので、まず人気の科学のステートフルログインとステートレスログインに行きます。
1.ステートレスログイン
1.1ステートフルとは
ステートフルサービス、つまりサーバーは、クライアントのIDを識別し、ユーザーのIDに従って要求を処理するために、各セッションのクライアント情報を記録する必要があります。典型的な設計は、Tomcatのセッションです。たとえば、ログイン:ユーザーがログインした後、サーバーセッションにユーザーの情報を保存し、ユーザーにCookie値を与え、対応するセッションを記録し、次のリクエストでユーザーはCookie値を保持します(この手順はブラウザーによって自動的に完了します) 、対応するセッションを識別して、ユーザーの情報を見つけることができます。この方法は現在最も便利ですが、次のような欠点もあります。
- サーバーは大量のデータを保存し、サーバーへのプレッシャーを増大させます
- サーバーはユーザーのステータスを保存し、クラスター化されたデプロイメントをサポートしていません
1.2ステートレスとは
マイクロサービスクラスター内の各サービスは、外部へのRESTfulインターフェースを提供します。RESTfulスタイルの最も重要な仕様の1つは、サービスのステートレス性です。
- サーバーはクライアントのリクエスター情報を保存しません
- クライアントの各要求には、クライアントのアイデンティティを識別するための自己記述的な情報が必要です
では、この無国籍のメリットは何でしょうか?
- クライアント要求はサーバー情報に依存しません。複数の要求が同じサーバーにアクセスする必要はありません
- サーバークラスタとステータスはクライアントに対して透過的です
- サーバーは任意に移行およびスケーリングできます(クラスター化された展開が便利です)
- サーバーストレージの負荷を軽減
1.3無国籍を達成する方法
ステートレスログインプロセス:
- まず、クライアントは認証のためにサーバーにアカウント名/パスワードを送信します
- 認証が渡されると、サーバーはユーザー情報を暗号化してトークンにエンコードし、それをクライアントに返します。
- 将来的には、クライアントがリクエストを送信するたびに、認証されたトークンを運ぶ必要があります
- サーバーはクライアントから送信されたトークンを復号化し、それが有効かどうかを判断し、ユーザーのログイン情報を取得します
1.4それぞれの長所と短所
セッションを使用する最大の利点は、利便性です。あまり多くの処理を行う必要はありません。すべてがデフォルトです。このシリーズのソン・ゲの以前の記事もセッションに基づいています。
ただし、セッションを使用する際には別の致命的な問題があります。フロントエンドがAndroid、iOS、アプレットなどの場合、これらのアプリには当然Cookieがありません。セッションを使用する場合は、これらのエンジニアがそれぞれのデバイスに適応する必要があります。通常、これはシミュレーションCookieであり、モバイルアプリがどこにでもある今日、セキュリティ管理はセッションに依存しているだけであり、あまり理想的ではないようです。
現時点では、JWTなどのステートレスログインには利点があり、これらのログインメソッドが使用するトークンは、通常のパラメーターまたはリクエストヘッダーを介して渡すことができます。
ただし、フロントエンドとバックエンドの分離が単なるWebページ+サーバーの場合、ステートレスにログインする必要はなく、セッションに基づいてログインできるため、便利で便利です。
では、言うまでもなく、この記事では最初にセッションベースの認証についてお話しします。JWTにログインした後、詳しく説明します。待てない場合は、Songgeも見てください。 JWTのチュートリアル:Spring SecurityとJwtを組み合わせてステートレスログインを実現する。
2.ログイン操作
では最後の記事、歌のGeと我々はログインが成功するために、共通のログインパラメータの設定の問題を撫で、ログインに失敗し、我々はまた、コールバック関数を残し、この記事では、罰金になりますと言うと、私たちはについて話しませんでした。
2.1フロントエンドとバックエンド間の個別のデータ対話
フロントエンドとバックエンドの分離の開発アーキテクチャでは、フロントエンドとバックエンドの相互作用はJSONを介して実行されます。ログインが成功したか失敗したかにかかわらず、サーバー側のジャンプやクライアント側のジャンプはありません。
ログインが成功すると、サーバーはフロントエンドにログインが成功したことを示すJSONを返します。フロントエンドがそれを受信した後、フロントエンド自体によって決定される表示にジャンプする必要があり、バックエンドとは関係ありません。
ログインが失敗した場合、サーバーはフロントエンドにログイン失敗プロンプトのJSONを返します。フロントエンドがそれを受け取った後、ジャンプの表示はフロントエンド自体によって決定され、バックエンドとは何の関係もありません。
まず、このアイデアを決定します。このアイデアに基づいて、ログイン構成を見てみましょう。
2.2成功したログイン
以前は、次の2つの方法で正常なログイン処理を構成しました。
- defaultSuccessUrl
- successForwardUrl
これらは両方ともジャンプアドレスで構成されており、フロントエンドとバックエンドに関係なく開発に適しています。これら2つのメソッドに加えて、successHandlerという別のニルヴァーナがあります。
successHandlerの機能は非常に強力で、defaultSuccessUrlおよびsuccessForwardUrlの機能も含まれています。見てみましょう:
.successHandler((req, resp, authentication) -> {
Object principal = authentication.getPrincipal();
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write(new ObjectMapper().writeValueAsString(principal));
out.flush();
out.close();
})
successHandlerメソッドのパラメーターはAuthenticationSuccessHandlerオブジェクトです。このオブジェクトに実装するメソッドはonAuthenticationSuccessです。
onAuthenticationSuccessメソッドには、次の3つのパラメーターがあります。
- HttpServletRequest
- HttpServletResponse
- 認証
最初の2つのパラメーターを使用すると、必要に応じてデータを返すことができます。HttpServletRequestを使用してサーバー側のジャンプを実行でき、HttpServletResponseを使用してクライアント側のジャンプを実行できます。もちろん、JSONデータを返すこともできます。
3番目の認証パラメーターは、正常にログインしたユーザー情報を保存します。
設定が完了したら、再度ログインすると、次のように、成功したログインのユーザー情報がJSONを介してフロントエンドに返されていることがわかります。
もちろん、ユーザーのパスワードは消去されています。パスワードを消去する問題、Song Brotherは以前にあなたと共有しました、あなたはこの記事を参照することができます:Spring Securityのログインプロセスを通してあなたを連れて行きます
2.3ログインできませんでした
失敗したログインには、次のような同様のコールバックがあります。
.failureHandler((req, resp, e) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write(e.getMessage());
out.flush();
out.close();
})
失敗のコールバックも3つのパラメーターです。最初の2つは言うまでもなく、3つ目は例外です。ログイン失敗にはさまざまな理由があります。例外はログイン失敗の理由を保存し、JSONを介して返すことができます。フロントエンドへ。
もちろん、マイクロパーソンでは、例外の種類も1つずつ識別されていることは誰もが知っています。例外の種類によって、ユーザーに明確なリマインダーを与えることができます。
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
RespBean respBean = RespBean.error(e.getMessage());
if (e instanceof LockedException) {
respBean.setMsg("账户被锁定,请联系管理员!");
} else if (e instanceof CredentialsExpiredException) {
respBean.setMsg("密码过期,请联系管理员!");
} else if (e instanceof AccountExpiredException) {
respBean.setMsg("账户过期,请联系管理员!");
} else if (e instanceof DisabledException) {
respBean.setMsg("账户被禁用,请联系管理员!");
} else if (e instanceof BadCredentialsException) {
respBean.setMsg("用户名或者密码输入错误,请重新输入!");
}
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
ここで注意すべき点が1つあります。
ユーザーがログインすると、ユーザー名またはパスワードが正しく入力されないことがわかっています。通常、ユーザー名またはパスワードが正しく入力されていないことを示すあいまいなプロンプトのみが表示されます。再入力してください。「ユーザー名が正しく入力されていません」または、「パスワード入力エラー」のような正確なプロンプトですが、その方法を知らない多くの初心者の友人には、システムにリスクをもたらす明確なエラープロンプトが表示される場合があります。
しかし、Spring Securityのようなセキュリティ管理フレームワークを使用すれば、初心者であっても、そのような間違いを犯すことはありません。
Spring Securityでは、ユーザー名の検索の失敗に対応する例外は次のとおりです。
- UsernameNotFoundException
パスワードの一致の失敗に対応する例外は次のとおりです。
- BadCredentialsException
ただし、ログイン失敗のコールバックでは、UsernameNotFoundException例外が常に表示されるわけではなく、ユーザー名またはパスワードが正しく入力されていなくても、スローされる例外はBadCredentialsExceptionです。
これはなぜですか?前の記事で紹介した、Spring Securityのログインプロセスを紹介するSong Ge 。ログインには、ユーザーデータを読み込むという重要なステップがあります。このメソッド(パート)を見てみましょう。
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
try {
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else {
throw notFound;
}
}
}
このコードから、ユーザーを検索するときにUsernameNotFoundExceptionがスローされると、この例外がキャッチされることがわかります。キャプチャ後、hideUserNotFoundExceptionsプロパティの値がtrueの場合、BadCredentialsExceptionがスローされます。これは、UsernameNotFoundException例外を非表示にすることと同じです。デフォルトでは、hideUserNotFoundExceptionsの値はtrueです。
ユーザーやパスワードが間違っていても、BadCredentialsExceptionが発生する理由は誰でもわかるはずです。
通常、この構成を変更する必要はありません。UsernameNotFoundExceptionとBadCredentialsExceptionを区別する必要がある場合は、次の3つの方法があります。
- システムデフォルトの代わりにDaoAuthenticationProviderを定義し、それを定義するときにhideUserNotFoundExceptionsプロパティをfalseに設定します。
- ユーザー名の検索が失敗すると、UsernameNotFoundException例外はスローされませんが、カスタム例外がスローされるため、カスタム例外は非表示にならず、フロントエンドユーザーは、ログイン失敗のコールバックのカスタム例外情報に従ってプロンプトが表示されます。
- ユーザー名の検索が失敗すると、BadCredentialsExceptionが直接スローされますが、例外情報は「ユーザー名が存在しません」です。
3つのアイデアは友達だけが参照できるものであり、特別な状況でない限り、この作品のデフォルトの動作を変更する必要はありません。
これの利点は何ですか?明らかに、開発者はあいまいな例外のヒントを与えることを強いられる可能性があるので、その方法を知らない初心者でもシステムを危険にさらすことはありません。
この構成が完了すると、ログインが成功したか失敗したかに関係なく、バックエンドはJSONをフロントエンドに返すだけです。
3.認定されていない治療計画
非認定についてはどうですか?
一部の友人は、それは簡単ではなく、認証なしでデータにアクセスし、ログインページにリダイレクトするだけであると言いますが、システムのデフォルトの動作は同じです。
ただし、フロントエンドとバックエンドの分離では、このロジックは明らかに問題があります。ユーザーがログインせず、アクセスする前に認証が必要なページにアクセスする場合、現時点では、ユーザーにログインページへのリダイレクトを許可せず、ユーザーにログインプロンプトの後、フロントエンドはプロンプトを受信し、ページにジャンプすることを決定します。
この問題を解決するには、Spring Security AuthenticationEntryPoint
のインターフェースを使用します。インターフェースには実装クラスLoginUrlAuthenticationEntryPoint
がありcommence
ます。次のように、このクラスにはメソッドがあります。
/**
* Performs the redirect (or forward) to the login form URL.
*/
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) {
String redirectUrl = null;
if (useForward) {
if (forceHttps && "http".equals(request.getScheme())) {
redirectUrl = buildHttpsRedirectUrlForRequest(request);
}
if (redirectUrl == null) {
String loginForm = determineUrlToUseForThisRequest(request, response,
authException);
if (logger.isDebugEnabled()) {
logger.debug("Server side forward to: " + loginForm);
}
RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
dispatcher.forward(request, response);
return;
}
}
else {
redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
}
redirectStrategy.sendRedirect(request, response, redirectUrl);
}
まず、このメソッドのコメントから、このメソッドがリダイレクトするか転送するかを決定するために使用されていることがわかります。デバッグトラッキングにより、useForwardの値がデフォルトでfalseであることがわかり、リクエストがリダイレクトに送られます。
次に、問題を解決するための私たちのアイデアは非常に簡単です。このメソッドを直接書き直して、メソッドにJSONを返すだけで、リダイレクト操作を行わなくなります。具体的な構成は次のとおりです。
.csrf().disable().exceptionHandling()
.authenticationEntryPoint((req, resp, authException) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("尚未登录,请先登录");
out.flush();
out.close();
}
);
春のセキュリティの構成に加えて、カスタムでAuthenticationEntryPoint
の処理方法、直接JSONへの適切なプロンプトにメソッドが返します。このように、ユーザーが認証後にアクセスできるリクエストに直接アクセスした場合、リダイレクト操作は発生せず、サーバーはJSONプロンプトをブラウザーに直接送信します。ブラウザーがJSONを受け取った後、何をする必要がありますか? 。
4.ログアウト
最後に、ログアウトの処理ソリューションを見てみましょう。
以前は、以前の構成によると、ログアウトした後、システムは自動的にログインページにジャンプしますが、これも不適切です。フロントエンドとバックエンドの分離プロジェクトの場合は、ログアウトが成功した後にJSONを返すことができます。
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler((req, resp, authentication) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("注销成功");
out.flush();
out.close();
})
.permitAll()
.and()
このようにして、ログアウトが成功した後、フロントエンドはJSONも受信します。
この記事では、友達とのフロントエンドとバックエンドの分離における一般的なJSON相互作用の問題を紹介します。友達が記事を参考にした場合は、クリックして読んでください。