以前、別のプロジェクトの後端部には、多くのがありますが、戦略上のログが、JWTは、より人気の解決策であると考えられ、紙と皆はこのように前後端の分離を達成し、春のセキュリティとJWTを一緒に使用方法を共有しますときサインオンソリューション。
1ステートレスログイン
1.1ステータスは何ですか?
ステートフルなサービス、すなわち、サーバは、クライアントIDを識別するために、セッションのクライアント情報ごとに記録する必要があり、処理は、Tomcatのセッションのような典型的な設計では、ユーザ識別情報に応じて要求します。例えば、ログインの場合:ユーザのログイン、我々はサーバー側のセッションに保存されているユーザーの情報を入れて、ユーザーにクッキーの値を与え、記録されたセッションに対応し、その後、次の要求は、ユーザーが(自動的にブラウザでこのステップを)クッキーの値を運びます我々は、ユーザーに関する情報を見つけるために、対応するセッションを識別することができるようになります。このように、今、最も便利な、しかし、次のようにいくつかの欠点があります:
- 大量のデータを保存するサーバー、サービス側の圧力を高めます
- サーバーは、ユーザーの状態を保存し、クラスタの展開をサポートしていません。
1.2ステートレスとは何ですか
マイクロサービスは、両方がRESTfulなスタイルのインターフェイスを使用し提供する、各サービスをクラスタ化します。そして、最も重要な仕様のRESTfulなスタイルのものである:ステートレスなサービス、すなわち:
- サーバーは、任意のクライアントの情報要求を格納していません
- 各クライアントは、情報が側でクライアントを識別する、自己記述情報要求を持っている必要があります
そして、これはステートレスそれが良い何ですか?
- サービス側に依存しないクライアント要求の情報は、あなたが同じサーバーに複数の要求へのアクセスを持っている必要はありません。
- クライアントに対して透過Serverのクラスタリングと状態
- サーバーは、いずれかを移行し、延伸することができます(簡単にクラスタ化展開することができます)
- 圧力ストレージサーバを削減
ステートレスを達成するための方法1.3。
ステートレスログインプロセス:
- まず、クライアントは認証のためにサーバにアカウント名/パスワードを送信します
- 認証後、サーバがクライアントに返す、トークンにユーザー情報を暗号化してエンコードされます
- クライアントが要求を送信する各時間の後、我々は、認証トークンを運ぶために必要
- クライアントへのトークンサーバは、妥当性を判断し、ユーザーのログオン情報を得る復号化するために送信します
1.4 JWT
1.4.1はじめに
JWTは、JSONウェブトークンの略で、JSONは許可ステートレス、分散型Webアプリケーションを有効にする、軽量スタイルの認可と認証の仕様です。
規範としてJWTはなく、特定の言語に縛られ、一般的に使用されるJava実装では、次のアドレスで、GitHubの上のオープンソースプロジェクトのjjwt、次のとおりです。https://github.com/jwtk/jjwt
1.4.2 JWTデータ形式
3部からなるJWTデータ:
-
ヘッダー:ヘッド、ヘッドは通常、2つの情報を持っています:
- 宣言された型は、ここにJWTです
- 暗号化アルゴリズム、カスタム
我々は、データの第1の部分を与えるために、Base64Urlコーディングヘッダ(復号可能)であろう。
-
ペイロード:ペイロードは、公式ドキュメント(RFC7519)、情報の7つの例では、有効なデータを、次のとおりです。
- ISS(発行者):発行者を表し、
- EXP(有効期限)は:トークンの有効期限を表し、
- サブ(被写体):テーマ
- AUD(聴衆):対象読者
- NBF(前ではなく):効果的な時間
- IAT(で発行された):時間の問題
- JTI(JWT ID):いいえ
この部分は、データの第2の部分を与えるために、Base64Urlを用いて符号化されます。
- 署名:署名、認証情報が全体のデータです。一緒に秘密鍵暗号化アルゴリズムとサービスとの一般的なデータの最初の2つのステップ、ヘッダ生成によって構成、(端末に格納されているサービスキーは、クライアントに公開されていません)。データの整合性と全体の信頼性を検証するために使用されます。
以下に示すようなデータフォーマットを生成します。
ここで、データスルーことに留意されたい.
データがラップされていない場合に加えて、ラップピクチャのみ便宜上示し、それぞれ前述の三つの部分に対応する離間3つの部分に、。
1.4.3 JWTの対話の流れ
フローチャート:
翻訳の手順:
- 要求の許可、認可サーバへのアプリケーションやクライアント
- 承認を得た後、認証サーバは、アプリケーションへのアクセストークンを返します。
- (例えばAPIなど)、保護されたリソースにアクセスするためのアクセストークンを使用するアプリケーション
JWTは、既に発行されたトークンは、ユーザーのID情報が含まれており、各要求が運ぶので、そのようなサービスでもステートレスRESTfulな仕様とこれに完全に沿って、データベースを照会することなく、ユーザー情報を格納する必要はありません。
1.5 JWT問題
:ログイン状態を維持するために、クライアントによって、JWTは完璧ではない、という問題のいくつかを言って、ここで次のようにまだたとえば、存在をもたらしました
- リニューアル伝統的なクッキー+セッションプログラムのサポート自然な再生を批判されている多くの問題の一つである問題が、しかし、JWTサーバは、ユーザの状態を保存しないため、導入Redisの場合はできますが、問題を更新するために最適なソリューションすることは困難ですこの問題を解決するが、JWTはどちらも魚も鳥になっています。
- ライトオフ問題を、サーバがユーザ情報を格納しないので、このように、期限が切れていないトークンが認証によって発行されたが失敗し、一般的にキャンセル達成し、サーバ秘密の後に秘密のキャンセル、変更を変更することによって達成することができますが、すべての後、キャンセルのない伝統が存在しません便利。
- パスワードのリセット、パスワードリセットトークンはまだ元のシステムにアクセスすることができ、この時間も必須秘密を変更する必要があります。
- 第二の点に基づいて、第三の点は、一般的に異なるユーザが異なる秘密を取得することをお勧めします。
2戦闘
そんなに、その後、私たちはどのように使用することを最終的にこの事を見て言いましたか?
構築するために2.1環境
まず、我々はあなたが春のセキュリティは、作成に依存している追加する必要があり、春のブートプロジェクトを作成し、作成が追加、完了しjjwt
、次のように依存して、完全なpom.xmlファイルを:
<dependency>
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
次のように続いて、プロジェクトの実現UserDetailsインターフェースでシンプルなユーザーオブジェクトを作成します。
public class User implements UserDetails { private String username; private String password; private List<GrantedAuthority> authorities; public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } //省略getter/setter }
これが私たちのユーザーオブジェクトで、次のように最初のバックアップを置き、その後、HelloControllerを作成し、読み取ります。
@RestController
public class HelloController { @GetMapping("/hello") public String hello() { return "hello jwt !"; } @GetMapping("/admin") public String admin() { return "hello admin !"; } }
HelloControllerは、設計された2つのインターフェースがあり、非常にシンプルであり/hello
、ユーザの役割を持つユーザがアクセスすることができるインターフェース、および/admin
インターフェースは、ユーザー管理者の役割でアクセスすることができます。
2.2 JWTフィルタ構成
JWTは、次の二つと関連するフィルタ構成を提供しました。
- フィルターは、ユーザーのログイン、ユーザーログインフィルタはログインが戻ってクライアントにトークンを生成し、成功した場合、ユーザーのログインが成功したかどうかを確認され、ログオンは遠位先端に失敗したログインの失敗です。
- 他の要求が送信されたときに、チェックが成功した場合には、第2のフィルタは、検証トークンフィルタは、要求をできるように進みます。
これらの2つのフィルタが、我々は最初のものを見て、見ていました。
public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter { protected JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) { super(new AntPathRequestMatcher(defaultFilterProcessesUrl)); setAuthenticationManager(authenticationManager); } @Override public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse resp) throws AuthenticationException, IOException, ServletException { User user = new ObjectMapper().readValue(req.getInputStream(), User.class); return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword())); } @Override protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse resp, FilterChain chain, Authentication authResult) throws IOException, ServletException { Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities(); StringBuffer as = new StringBuffer(); for (GrantedAuthority authority : authorities) { as.append(authority.getAuthority()) .append(","); } String jwt = Jwts.builder() .claim("authorities", as)//配置用户角色 .setSubject(authResult.getName()) .setExpiration(new Date(System.currentTimeMillis() + 10 * 60 * 1000)) .signWith(SignatureAlgorithm.HS512,"sang@123") .compact(); resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(jwt)); out.flush(); out.close(); } protected void unsuccessfulAuthentication(HttpServletRequest req, HttpServletResponse resp, AuthenticationException failed) throws IOException, ServletException { resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write("登录失败!"); out.flush(); out.close(); } }
このクラスについては、私は次のように言います:
- カスタムJwtLoginFilterはAbstractAuthenticationProcessingFilterから継承し、デフォルトの方法は、それらの3を達成するために。
- attemptAuthentication方法は、我々はユーザー名とパスワードでログインパラメータから抽出し、その後、自動的にチェックするためにAuthenticationManager.authenticate()メソッドを呼び出します。
- 第二段階認証が成功した場合、それはsuccessfulAuthentication方法でsuccessfulAuthenticationにコールバックし、ユーザの役割がトラバース
,
接続され、その後、コードの順に、Jwtsトークンを生成するように配置された4つの合計を生成するプロセスを使用パラメータ、すなわち、ユーザーの役割、テーマ、有効期限と暗号化アルゴリズムとキー、およびクライアントに生成されたトークンを書き込みます。 - チェックが第2のステップはunsuccessfulAuthentication方法を来る失敗した場合、エラーメッセージをクライアントに返し、この方法でもよいです。
第二のトークンの検証フィルターを見てみましょう:
public class JwtFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; String jwtToken = req.getHeader("authorization"); System.out.println(jwtToken); Claims claims = Jwts.parser().setSigningKey("sang@123").parseClaimsJws(jwtToken.replace("Bearer","")) .getBody(); String username = claims.getSubject();//获取当前登录用户名 List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get("authorities")); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, authorities); SecurityContextHolder.getContext().setAuthentication(token); filterChain.doFilter(req,servletResponse); } }
このフィルタでは、私は次のように言いました:
- 最初のユーザのトークンに対応する値である認証要求ヘッダフィールドから抽出されました。
- 請求オブジェクトに抽出されたトークンの文字列が、現在のユーザ名とユーザの役割、請求項オブジェクトから抽出され、現在のコンテキストにUsernamePasswordAuthenticationTokenを作成し、実行を継続するためのフィルタリングチェーン要求を行います。
だから、2とJWT関連のフィルタの後に設定されていても。
2.3春のセキュリティ設定
次のように次は、春のセキュリティを設定します。
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("admin") .password("123").roles("admin") .and() .withUser("sang") .password("456") .roles("user"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/hello").hasRole("user") .antMatchers("/admin").hasRole("admin") .antMatchers(HttpMethod.POST, "/login").permitAll() .anyRequest().authenticated() .and() .addFilterBefore(new JwtLoginFilter("/login",authenticationManager()),UsernamePasswordAuthenticationFilter.class) .addFilterBefore(new JwtFilter(),UsernamePasswordAuthenticationFilter.class) .csrf().disable(); } }
- シンプルさが、私はパスワードを暗号化するので、NoOpPasswordEncoderの構成例はなかったです。
- 簡潔には、データベースに接続されていない、私は異なる役割を有するメモリ内の2人のユーザー直接、二人のユーザーを構成しました。
- あなたはパスの規則を設定する場合、
/hello
インターフェイスがアクセスするユーザーの役割を持っている必要があり、/admin
インターフェイスは、管理者の役割へのアクセス権を持つPOSTリクエストとである必要があり/login
、他のインタフェースにアクセスするには、あなたが直接通じできるインターフェースを認定しなければなりません。 - 最後の2つのカスタムフィルタの設定とは、CSRF保護を閉鎖します。
2.4テスト
これが完了すると、当社の環境が完全に構築された場合でも、その後、プロジェクトを開始してから、次のようにPOSTMANでテスト:
成功したログオン文字列トークンbase64urlトランスコーディング、三つの部分の合計を介して戻された後を通じて.
分離、我々可能な第1 .
の文字列の復号、すなわちヘッダの前に、次のように
そして2つの.
デコーディング、すなわちペイロードの間の文字:
base64で暗号化方式は、したがって、トークンにユーザーの機密情報に対して推奨されるだけではなく符号化方式ではないので、あなたは、私たちは情報を設定し、見ることができます。
そして、アクセスに行く/hello
次のように、インターフェース、ノート認証モード値ベアラートークン、トークン値だけ取得を:
私たちは、成功した訪問を見ることができます。
プロジェクトアドレスします。https://github.com/11500667/security