Spring RESTアプリケーションは最初は基本認証で保護されていました。
次に、後続の要求で使用されるJWT JSON Webトークンを作成するログインコントローラーを追加しました。
次のコードをログインコントローラーからセキュリティフィルターに移動できますか?そうすれば、ログインコントローラーはもう必要ありません。
tokenAuthenticationService.addTokenToResponseHeader(responseHeaders, credentialsResource.getEmail());
または、基本認証を削除できますか?
基本認証とJWTを混在させるのは良い設計ですか?
すべて正常に動作しますが、このセキュリティを最適に設計するために、ここでは少し暗闇に隠れています。
ログイン中およびログイン後のすべての通信で100%TLSを想定-基本認証を介してユーザー名/パスワードで認証し、引き換えにJWTを受信することが有効なユースケースです。これは、almostOAuth 2のフロー( 'password grant'))の正確な動作です。
これは、エンドユーザーが1つのエンドポイントを介して認証されるという考え方です。 /login/token
必要なメカニズムを使用します。応答には、後続のすべてのリクエストで送り返されるJWTが含まれている必要があります。 JWTは、適切なJWT有効期限(exp
)フィールドを持つJWS(つまり、暗号で署名されたJWT)である必要があります。これにより、クライアントはJWTを操作したり、必要以上に長くしたりできなくなります。
X-Auth-Token
ヘッダーも必要ありません。HTTP認証Bearer
スキームは、この正確な使用例のために作成されました。基本的に、Bearer
スキーム名に続く情報のビット検証されるべき所有者の情報。 Authorization
ヘッダーを設定するだけです:
Authorization: Bearer <JWT value here>
しかし、あなたのRESTクライアントが「信頼できない」(JavaScript対応ブラウザなど)の場合、私はそれもしません:JavaScriptを介してアクセス可能なHTTP応答の値-基本的に任意のヘッダー値または応答本文値-MITM XSS攻撃を介して盗聴および傍受される可能性があります。
JWT値を安全な専用のhttp専用のcookie(cookie config:setSecure(true)、setHttpOnly(true))に保存することをお勧めします。これにより、ブラウザは次のことを保証します。
このアプローチは、almostベストプラクティスのセキュリティのために必要なすべてのものです。最後に、サイトへのリクエストを開始する外部ドメインが機能しないことを保証するために、すべてのHTTPリクエストでCSRF保護を確保することです。
これを行う最も簡単な方法は、安全な(ただしHTTPのみではない)Cookieをランダムな値で設定することです。 UUID。
次に、サーバーへのすべてのリクエストで、独自のJavaScriptコードがCookie値を読み取り、これをカスタムヘッダーに設定することを確認します。 X-CSRF-Tokenを使用して、サーバー内のすべてのリクエストでその値を確認します。外部ドメインクライアントは、HTTPオプションリクエストを介して認証を取得しない限り、ドメインへのリクエストにカスタムヘッダーを設定できないため、CSRF攻撃(IFrameなど)の試行は失敗します。
これは、今日のWeb上の信頼できないJavaScriptクライアントが利用できる最高のセキュリティです。 Stormpathは これらのテクニック に関する記事も書いています。
最後に、 Stormpath Java Servlet Pluginはすでにあなたのためにこれをすべて行います(そして追加の自動化されたセキュリティチェックを含む、はるかにクールなもの)、それを書く必要はありません-または、さらに悪いこと-自分でそれを維持します。 HTTP要求認証セクション およびForm/Ajax使用方法を確認する例。
これは、Springでこれを行う方法に関する受け入れられた答えをバックアップするためのコードです... UsernamePasswordAuthenticationFilter
を単に拡張し、Spring Securityに追加します...これはHTTP Basic Authentication + Spring Securityでうまく機能します
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
ApplicationUser creds = new ObjectMapper()
.readValue(req.getInputStream(), ApplicationUser.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
String token = Jwts.builder()
.setSubject(((User) auth.getPrincipal()).getUsername())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
}
}
jWTライブラリを使用:
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
スプリングブート設定クラス
package com.vanitysoft.payit.security.web.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.vanitysoft.payit.util.SecurityConstants;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL).permitAll()
.antMatchers("/user/**").authenticated()
.and()
.httpBasic()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.logout()
.permitAll();
}
}