SprigCloudサービス認証(JWT)を詳細に説明する。

11139 ワード

- JWT
JWT(JSON Web Token)は、ネットワークアプリケーション環境間で声明を転送するために実行されるJSONベースのオープン基準(RFC 7519)であり、コンパクトかつ安全に設計され、特に分散サイトのシングルポイント登録(SSO)シーンに適用される。JWTの声明は、一般的に、認証されたユーザ識別情報をアイデンティティプロバイダとサービスプロバイダの間で伝達するために用いられ、リソースサーバからリソースを取得するのに便利であり、他のいくつかの追加的なサービスロジックに必要な声明情報を追加することができ、このtokenは直接に検証にも使用され、暗号化されてもよい。
-JWTと他の違い
通常、APIを直接暴露するのは危険が大きいです。他のことは言わないで、直接にマシンに攻撃されたら、1つのポットを飲みます。一般的には、APIに対して一定の権限レベルを区分し、ユーザーの認証を行い、認証結果に基づいてユーザーに対応するAPIを開放する。現在、主流の方案は何種類ありますか?
OAuth
OAuth(オープンライセンス)は、ユーザがサードパーティアプリケーションに、あるサービスに格納されているプライベートリソース(写真、ビデオなど)にアクセスすることを可能にするオープンなライセンス基準であり、ユーザ名とパスワードを第三者アプリケーションに提供する必要がない。
OAuthは、ユーザが特定のサービスプロバイダに格納されたデータにアクセスするためのユーザ名とパスワードではなく、トークンを提供することを可能にする。各トークンは、特定のサードパーティシステム(例えば、ビデオ編集サイト)に特定の期間(例えば、次の2時間)内に特定のリソース(例えば、あるアルバムの中のビデオ)にアクセスするように権限を与える。このようにOAuthは、すべてのコンテンツではなく、第三者のウェブサイトにアクセスするようにユーザに権限を与えることができる。
Cookie/Session Auth
Cookie認証機構は、一回の認証要求のためにサービス端末でSessionオブジェクトを作成し、同時にクライアントのブラウザ側でCookieオブジェクトを作成した。クライアントがCookieオブジェクトを持ってきて、サーバ端のsessionオブジェクトとマッチングすることで、状態管理を実現する。デフォルトでは、ブラウザを閉じるとクッキーが削除されます。しかし、cookieのexpire timeを修正することによって、cookieを一定時間有効にすることができます。session方式に基づいて認証すると、サーバーに一定の圧力(メモリ記憶)を与え、拡張しにくくなります。
-JWTのメリット
1.sessionに比べて、サーバに保存する必要がなく、サーバメモリのオーバーヘッドを占有しません。
2.無状態、開拓性が強い:例えば、3台のマシン(A、B、C)がサーバクラスタを構成しています。もしsessionがマシンAに存在すれば、sessionは一つのサーバーにしか保存できません。この時にマシンB、Cにアクセスできません。B、CにこのSessionが保存されていないので、tokenを使ってユーザの合法性を検証できます。そして、いくつかのマシンを追加しても大丈夫です。だから、開拓性がいいという意味です。
3.前後端分離し、クロスドメインアクセスをサポートします。
-JWTの構成

{ "iss": "JWT Builder", 
 "iat": 1416797419, 
 "exp": 1448333419, 
 "aud": "www.battcn.com", 
 "sub": "[email protected]", 
 "GivenName": "Levin", 
 "Surname": "Levin", 
 "Email": "[email protected]", 
 "Role": [ "ADMIN", "MEMBER" ] 
}
  •  iss:当該JWTの発行者は、使用するかどうかはオプションです。
  • sub:JWTが向いているユーザは、使用するかどうかはオプションです。
  • aud:JWTを受信する一方、使用するかどうかはオプションです。
  • exp(expires):いつ期限が切れるか、ここはUnixタイムスタンプです。使うかどうかはオプションです。
  • iat:いつ発行されましたか?
  • nbf(Not Before):現在の時間がnbfの時間より前であれば、Tokenは受け入れられない;普通は少し余裕を残します。例えば、数分間。使用するかどうかはオプションです。

  • 一つのJWTは実際には一つの文字列であり、三つの部分から構成されています。頭、負荷、署名(上の図は順次並べ替えます。)
    JWT Tokenジェネレータ:https://jwt.io/
    -認証

    -ログイン認証
  • クライアントは、POST要求をサーバに送信し、登録処理を行うController層
  • を提出する。
  • 認証サービスを起動してユーザ名パスワード認証を行い、認証が通れば完全なユーザ情報と対応権限情報
  • を返す。
  • は、JWTを利用してユーザ、権限情報、秘密鍵を構築するためのToken
  • を構築する。
  • は、構築されたToken
  • に戻る。

    -認証を要求する
  • クライアントはサーバに要求し、サーバは要求ヘッダ情報を読み出してToken
  • を取得する。
  • Token情報が見つかったら、設定ファイルの署名に従って秘密鍵を暗号化し、JJWT Libを呼び出してToken情報を復号し、復号する。
  • は、復号を完了し、署名を検証した後、Tokenのexp、nbf、audなどの情報を検証する。
  • を全部通過した後、取得したユーザの役割権限情報に基づいて、要求されたリソースの権限論理判断を行う。
  • 権限論理判断が通れば、Resonseオブジェクトによって戻ります。そうでなければHTTP 401に戻ります。
  • 無効なToken

    有効Token

    -JWTの欠点
    長所があれば短所があります。適用するかどうかははっきり考えるべきです。
  • tokenが大きすぎると、より多くの空間を占有しやすくなります。
  • tokenには敏感情報を格納するべきではない。
  • JWTはsessionではありません。tokenをsessionとしないでください。
  • は公布されたトークンを無効にすることができません。すべての認証情報はJWTにあります。サービスの状態がないため、たとえあるJWTが盗まれたと知っていても、無効にすることはできません。JWTが期限が切れる前に、どうすることもできません。
  • はキャッシュのように、すでに公開されたトークンを廃棄できないので、その期限が切れる前に、「期限が切れる」データを我慢するしかないです。
  • -コード(セグメント)
    TokenPropertiesとaplication.yml資源のkeyマッピングは、使いやすいです。
    
    @Configuration
    @ConfigurationProperties(prefix = "battcn.security.token")
    public class TokenProperties {
     /**
     * {@link com.battcn.security.model.token.Token} token     
     */
     private Integer expirationTime;
    
     /**
     *    
     */
     private String issuer;
    
     /**
     *      KEY {@link com.battcn.security.model.token.Token}.
     */
     private String signingKey;
    
     /**
     * {@link com.battcn.security.model.token.Token}       
     */
     private Integer refreshExpTime;
    
     // get set ...
    }
    
    
    Token生成のクラス
    
    @Component
    public class TokenFactory {
    
     private final TokenProperties properties;
    
     @Autowired
     public TokenFactory(TokenProperties properties) {
     this.properties = properties;
     }
    
     /**
     *   JJWT    Token
     * @param context
     * @return
     */
     public AccessToken createAccessToken(UserContext context) {
     Optional.ofNullable(context.getUsername()).orElseThrow(()-> new IllegalArgumentException("Cannot create Token without username"));
     Optional.ofNullable(context.getAuthorities()).orElseThrow(()-> new IllegalArgumentException("User doesn't have any privileges"));
     Claims claims = Jwts.claims().setSubject(context.getUsername());
     claims.put("scopes", context.getAuthorities().stream().map(Object::toString).collect(toList()));
     LocalDateTime currentTime = LocalDateTime.now();
     String token = Jwts.builder()
      .setClaims(claims)
      .setIssuer(properties.getIssuer())
      .setIssuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant()))
      .setExpiration(Date.from(currentTime
      .plusMinutes(properties.getExpirationTime())
      .atZone(ZoneId.systemDefault()).toInstant()))
      .signWith(SignatureAlgorithm.HS512, properties.getSigningKey())
     .compact();
     return new AccessToken(token, claims);
     }
    
     /**
     *       RefreshToken
     * @param userContext
     * @return
     */
     public Token createRefreshToken(UserContext userContext) {
     if (StringUtils.isBlank(userContext.getUsername())) {
      throw new IllegalArgumentException("Cannot create Token without username");
     }
     LocalDateTime currentTime = LocalDateTime.now();
     Claims claims = Jwts.claims().setSubject(userContext.getUsername());
     claims.put("scopes", Arrays.asList(Scopes.REFRESH_TOKEN.authority()));
     String token = Jwts.builder()
      .setClaims(claims)
      .setIssuer(properties.getIssuer())
      .setId(UUID.randomUUID().toString())
      .setIssuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant()))
      .setExpiration(Date.from(currentTime
      .plusMinutes(properties.getRefreshExpTime())
      .atZone(ZoneId.systemDefault()).toInstant()))
      .signWith(SignatureAlgorithm.HS512, properties.getSigningKey())
     .compact();
    
     return new AccessToken(token, claims);
     }
    }
    
    
    設定ファイルにtokenの有効期限を含め、秘密鍵を自動的に拡張することができます。
    
    battcn:
     security:
     token:
     expiration-time: 10 #    1440
     refresh-exp-time: 30 #    2880
     issuer: http://blog.battcn.com
     signing-key: battcn
    WebSecurityConfigはSpring Securityのキーコンフィギュレーションであり、Securrtyでは基本的にフィルタを定義することによって私達が欲しい機能を実現することができます。
    
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
     public static final String TOKEN_HEADER_PARAM = "X-Authorization";
     public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
     public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
     public static final String MANAGE_TOKEN_BASED_AUTH_ENTRY_POINT = "/manage/**";
     public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
    
     @Autowired private RestAuthenticationEntryPoint authenticationEntryPoint;
     @Autowired private AuthenticationSuccessHandler successHandler;
     @Autowired private AuthenticationFailureHandler failureHandler;
     @Autowired private LoginAuthenticationProvider loginAuthenticationProvider;
     @Autowired private TokenAuthenticationProvider tokenAuthenticationProvider;
    
     @Autowired private TokenExtractor tokenExtractor;
    
     @Autowired private AuthenticationManager authenticationManager;
    
     protected LoginProcessingFilter buildLoginProcessingFilter() throws Exception {
     LoginProcessingFilter filter = new LoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler);
     filter.setAuthenticationManager(this.authenticationManager);
     return filter;
     }
    
     protected TokenAuthenticationProcessingFilter buildTokenAuthenticationProcessingFilter() throws Exception {
     List<String> list = Lists.newArrayList(TOKEN_BASED_AUTH_ENTRY_POINT,MANAGE_TOKEN_BASED_AUTH_ENTRY_POINT);
     SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(list);
     TokenAuthenticationProcessingFilter filter = new TokenAuthenticationProcessingFilter(failureHandler, tokenExtractor, matcher);
     filter.setAuthenticationManager(this.authenticationManager);
     return filter;
     }
    
     @Bean
     @Override
     public AuthenticationManager authenticationManagerBean() throws Exception {
     return super.authenticationManagerBean();
     }
    
     @Override
     protected void configure(AuthenticationManagerBuilder auth) {
     auth.authenticationProvider(loginAuthenticationProvider);
     auth.authenticationProvider(tokenAuthenticationProvider);
     }
    
     @Override
     protected void configure(HttpSecurity http) throws Exception {
     http
     .csrf().disable() //       JWT,        csrf 
     .exceptionHandling()
     .authenticationEntryPoint(this.authenticationEntryPoint)
     .and()
      .sessionManagement()
      .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
     .and()
      .authorizeRequests()
      .antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
      .antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
     .and()
      .authorizeRequests()
      .antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points
      .antMatchers(MANAGE_TOKEN_BASED_AUTH_ENTRY_POINT).hasAnyRole(RoleEnum.ADMIN.name())
     .and()
      .addFilterBefore(buildLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
      .addFilterBefore(buildTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
     }
    }
    
    
    -何を言いますか
    JWTコードは簡単にパッケージ化されていますので、内容が多く含まれています。文章の中には主要なセグメントだけを貼り付けて、完全なコードが必要です。直接下のGITから取得できます。
    本章コード(battcn-jwt-service):http://xiazai.jb51.net/201801/yuanma/battcn-cloud_jb 51.rar
    以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。