ホームページ  >  記事  >  Java  >  SpringBoot+SpringSecurity+JWT を使用してシステムの認証と認可を実装する方法

SpringBoot+SpringSecurity+JWT を使用してシステムの認証と認可を実装する方法

WBOY
WBOY転載
2023-05-14 20:16:041754ブラウズ

1. Spring Security の概要

Spring Security は Spring のコア プロジェクトであり、強力で高度にカスタマイズ可能な認証およびアクセス制御フレームワークです。これは、認証および認可機能に加えて一般的な攻撃に対する保護を提供し、Spring ベースのアプリケーションを保護するための事実上の標準となっています。

Spring Boot は自動構成を提供しており、スターター依存関係を導入することで使用できます。
Spring Security の機能の概要:

  • 使いやすく、Spring Boot スターターの依存関係を提供し、Spring Boot プロジェクトとの統合が簡単です。

  • プロフェッショナル。CSRF 保護、クリックジャッキング保護、XSS 保護などを提供し、さまざまなセキュリティ ヘッダー統合 (X-XSS-Protection、X-Frame-Options など) を提供します。

  • パスワード暗号化ストレージ、複数の暗号化アルゴリズムをサポート

  • 非常にスケーラブルでカスタマイズ可能

  • OAuth3 JWT認証サポート

  • ##… …

2. JWT の概要

JWT(Json Web Token ) は、JSON です。ネットワーク アプリケーション環境間でクレームを転送するために実装された、ベースのオープン スタンダード (RFC 7519) トークンは、コンパクトで安全になるように設計されており、分散サイトのシングル サインオン (SSO) シナリオに特に適しています。 JWT クレームは通常、アイデンティティ プロバイダーとサービス プロバイダーの間で認証されたユーザー ID 情報を渡し、リソース サーバーからのリソースの取得を容易にするために使用されます。また、他のビジネス ロジックに必要な追加のクレーム情報 (アクセス許可情報など) を追加することもできます。ユーザーにトークンが付与されると、そのトークンを介してサーバー上のリソースにアクセスできるようになります。

3. Spring Boot は Spring Security を統合します

この記事では、次のように JDK と Spring Boot バージョンの使用を示していることに注意してください:

Spring Boot: 2.7.2
JDK: 11
Spring Boot のバージョンが異なれば構成も異なりますが、原則は同じです。

Spring Boot プロジェクトの pom.xml ファイルに次の依存関係を追加します。

<!-- Spring Security的Spring boot starter,引入后将自动启动Spring Security的自动配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 下面的依赖包含了OAuth3 JWT认证实现 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth3-resource-server</artifactId>
</dependency>

上記の 2 つの依存関係で十分です。

4. JWT 認証を使用するように Spring Security を構成する

注: Spring Boot バージョンが異なれば構成も異なりますが、原理は同じです。スプリングブート: 2.7.2。

主に SecurityFilterBean を生成するための HttpSecurity Bean の設定を行います。 Spring Boot で実行する プロジェクトの Resource ディレクトリに RSA キーのペアを生成します。

次の Web サイトを使用して生成できます: http://tools.jb51.net/password/rsa_encode/,

注: キーの形式は PKCS#8 を使用し、秘密キーのパスワードは空です。

もう 1 つ説明する必要があります。コード内で Spring Boot の値インジェクションを使用しました:


import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth3.jwt.JwtDecoder;
import org.springframework.security.oauth3.jwt.JwtEncoder;
import org.springframework.security.oauth3.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth3.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth3.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth3.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth3.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth3.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.web.SecurityFilterChain;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

/**
 * Spring Security 配置
 *
 * @author cloudgyb
 * @since 2022/7/30 18:31
 */
@Configuration(proxyBeanMethods = false)
@EnableMethodSecurity
public class WebSecurityConfigurer {
    //使用RSA对JWT做签名,所以这里需要一对秘钥。
    //秘钥文件的路径在application.yml文件中做了配置(具体配置在下面)。
    @Value("${jwt.public.key}")
    private RSAPublicKey key; 
    @Value("${jwt.private.key}")
    private RSAPrivateKey priv;

     /**
      * 构建SecurityFilterChain bean
      */
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        //"/login"是系统的登录接口,所以需要匿名可访问
        http.authorizeRequests().antMatchers("/login").anonymous();
        //其他请求都需认证后才能访问
        http.authorizeRequests().anyRequest().authenticated()
                .and()
                
                //采用JWT认证无需session保持,所以禁用掉session管理器
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                //login接口可能来自其他站点,所以对login不做csrf防护
                .csrf((csrf) -> csrf.ignoringAntMatchers("/login"))
                //配置认证方式为JWT,并且配置了一个JWT认证装换器,用于去掉解析权限时的SCOOP_前缀
                .oauth3ResourceServer().jwt().jwtAuthenticationConverter(
                        JwtAuthenticationConverter()
                );
        //配置认证失败或者无权限时的处理器
        http.exceptionHandling((exceptions) -> exceptions
                .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
                .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
        );
         //根据配置生成SecurityFilterChain对象
        return http.build();
    }


    /**
     * JWT解码器,用于认证时的JWT解码 
     */
    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey(this.key).build();
    }
    /**
     * JWT编码器,生成JWT
     */
    @Bean
    JwtEncoder jwtEncoder() {
        JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();
        JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwks);
    }
    
    /**
     * JWT认证解码时,去掉Spring Security对权限附带的默认前缀SCOOP_
     */
    @Bean
    JwtAuthenticationConverter JwtAuthenticationConverter() {
        final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");
        final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }
}
興味がありますか? Spring Boot とは何ですか? yaml ファイル内の文字列に対応するファイルを RSAPublicKey と RSAPrivateKey に変換するにはどうすればよいですか?

実際には、Spring Security が処理を行ってくれました。Spring Security でコンバータ ResourceKeyConverterAdapter を実装するのに役立ちました。より深い理解のために、関連するソース コードを読むことができます。

これまでのところ、私たちのプロジェクトは JWT 認証をサポートしています。
しかし、ユーザーは認証を通過してサーバー リソースにアクセスするには、リクエスト ヘッダーの認可に正規の JWT を含める必要があります。では、ユーザーに正規の JWT を発行するにはどうすればよいでしょうか?
これは非常に簡単で、ログイン インターフェイスを提供し、ユーザーにユーザー名とパスワードを入力させ、照合が成功した後にトークンを発行することができます。


実際には、これは必要ありません。他の方法があります。たとえば、サードパーティのインターフェイスを呼び出すとき、通常のアプローチは、最初にサードパーティに適用することです。承認されればトークンを取得できます。このプロセスは上記のログイン後のトークン発行と同じで、どちらも合法的な手段でトークンを取得します。


5. ログイン インターフェイスの実装

ログイン インターフェイスの目的は 1 つだけで、正規のユーザーにトークンを発行することです。
ログイン API インターフェイス:

jwt:
  private.key: classpath:app.key
  public.key: classpath:app.pub

ログイン ロジックの実装:
@Value("${jwt.public.key}")
 private RSAPublicKey key; 
@Value("${jwt.private.key}")
private RSAPrivateKey priv;

その他の非コア コードはここには掲載されません。コードは github に置きます。詳細については、次のリンクを参照してください。 https://github.com/cloudgyb/spring-security-study-jwt。

6. テスト

postman を使用してテスト:

間違ったパスワードを使用すると、認証が失敗したことを示す 401 Unauthorized ステータス コードが返されます!

正しいユーザー名とパスワードを使用する:


SpringBoot+SpringSecurity+JWT を使用してシステムの認証と認可を実装する方法JWT トークンが返されました。

この時点で、クライアントは有効なトークンを取得し、サーバー上でアクセスできるリソースにアクセスできるようになります。

テスト インターフェイスを作成しました:

@RestController
public class SysLoginController {
    private final SysLoginService sysLoginService;

    public SysLoginController(SysLoginService sysLoginService) {
        this.sysLoginService = sysLoginService;
    }

    @PostMapping("/login")
    public String login(@RequestBody LoginInfo loginInfo) {
        return sysLoginService.login(loginInfo);
    }
}
SpringBoot+SpringSecurity+JWT を使用してシステムの認証と認可を実装する方法このインターフェイスではユーザーに「テスト」権限が必要ですが、ログインしているユーザーにはこの権限がありません (アプリの権限は 1 つだけです)。インターフェイスは次のように呼ばれます:

最初に、前のログイン手順で取得したトークンをトークンに貼り付けます:


我们发送请求得到了403 Forbidden的响应,意思就是我们没有访问权限,此时我们将接口权限改为“app”:

@RestController
public class HelloController {

    @GetMapping("/")
    @PreAuthorize("hasAuthority(&#39;app&#39;)")
    public String hello(Authentication authentication) {
        return "Hello, " + authentication.getName() + "!";
    }
}

重启项目。再次发起请求:

SpringBoot+SpringSecurity+JWT を使用してシステムの認証と認可を実装する方法

以上がSpringBoot+SpringSecurity+JWT を使用してシステムの認証と認可を実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。