[Wanzi 長文] SpringBoot は SpringSecurity+JWT+Redis を統合した完全なチュートリアル (Gitee ソース コードが提供されています)

前書き: 最近 SpringSecurity を学習する過程で、私は多くのオンライン チュートリアルを参照し、現在主流のオープン ソース フレームワークも参照したため、私自身のアイデアと組み合わせて、完全な SpringBoot 統合 SpringSecurity+JWT+Redis プロジェクトを 0 から作成しました。 to 1 書いた後、多くのことを得ることができたと感じたので、皆さんと共有するために完全なメモをブログとして書きました。これは比較的完成されたプロジェクトであり、参照と学習のみを目的としています。

目次

1. Spring Security の概要

2、Spring Securityの認証プロセス

3. トラブル記録(重要)

4. プロジェクトのコアコードの説明

4.1. pom 依存関係のインポート

4.2、yml設定ファイル

4.3、エンティティクラス

4.3.1、LoginBody ログイン エンティティ クラス

4.3.2、ロール ロールクラス

4.3.3、ユーザー ユーザークラス

4.3.4、LoginUser ログインユーザー情報

4.4、TokenService サービス クラス

4.4.1. トークンコアコードの生成

4.4.2. トークンを生成するための主要なロジック

4.4.3. トークンコアコードを解析する

4.4.4. リクエストヘッダーに含まれるトークンを取得する

4.4.5. Redis に保存されているトークンキーを取得する

4.4.6. リフレッシュトークンの有効期間

4.4.7. 検証トークンの有効期間

4.4.8. ユーザー識別情報の取得

4.4.9. ユーザー識別情報の削除

4.5、AuthenticationEntryPointImpl設定の認証失敗処理クラス

4.6、JwtAuthenticationTokenFilter 認証フィルター

4.7、FastJsonシリアル化

4.8、カスタム Redis シリアル化 

4.9、Redisツールクラス

4.10、SecurityConfigコア構成クラス

4.11、AuthenticationContextHolder スレッドのローカル ストレージ

4.12. UserServiceImpl クエリユーザーインターフェイス

4.13、PasswordServiceImpl パスワード検証サービス クラス 

4.14、UserDetailsS​​erviceImpl 認証ユーザー サービス クラス

4.15、LoginController ログイン インターフェイス

4.16、LoginServiceImpl ログイン インターフェイスのコア ロジック

4.17、LogoutSuccessHandlerImpl ログアウト コア ロジック 

4.18. @PreAuthorize アノテーション

4.19、HelloController テスト インターフェイス

4.20 概要

5. プロジェクトを実行する 

5.1. ログインの成功

5.2. 未承認のインターフェースへのアクセス

5.3. USER権限が必要なインターフェースへのアクセス

5.4. COMMON 権限が必要なインターフェースへのアクセス

5.5. ログアウト

5.6. アクセス失敗 

5.7. ログインに失敗しました

6.Giteeソースコードアドレス

7. まとめ


1. Spring Security の概要

Spring Security は Spring エコシステムのセキュリティ管理フレームワークであり、Web アプリケーションのセキュリティのための完全なソリューションを提供します。

次のような特徴があります。

1. 包括性: Spring Security は、認証、認可、攻撃防御などのセキュリティ管理のすべての機能を提供します。

2. 拡張性:Spring Securityの機能は、クラスの継承やインターフェースの実装により容易に拡張できます。

3. Spring とのシームレスな統合: Spring フレームワークと完全に統合でき、SpringIoC コンテナーを通じて SpringSecurity コンポーネントを管理できます。

4. 一般的な攻撃の防止: スクリプト インジェクション、セッション固定、SQL インジェクションなどの一般的な Web 攻撃を防止できます。

5. シンプルな構成: Spring Security が提供するセキュリティ機能は、構成ファイルを通じてすぐに適用できます。

Spring Security の主な機能は次のとおりです。

1. 認証: ユーザー ID 情報の正当性を検証します。

2. 権限: ユーザーが操作を実行する権限を持っていることを確認します。

3. 攻撃に対する保護: CSRF、セッション固定、SQL インジェクションなどの攻撃に対する保護。

4. メソッドのセキュリティ: システム メソッドを使用してセキュリティ アクセス制御を実装します。

5. セキュリティ応答ヘッダー: ブラウザーのセキュリティ関連の応答ヘッダーを追加して、セキュリティを向上させます。

要約すると、Spring Security は MVC アプリケーションにとって不可欠なセキュリティ保護フレームワークであり、Java アプリケーションに包括的なセキュリティ サポートを提供します。Spring フレームワークと緊密に統合されており、構成と使用が簡単です。

2、Spring Securityの認証プロセス

Spring Security 認証プロセスにおけるいくつかのコア クラスとその機能は次のとおりです。

1. Authentication : 現在のユーザーの認証情報を示す認証情報インターフェイス。通常はUsernamePasswordAuthenticationTokenを使用して実装されます。

2. AuthenticationManager : 認証マネージャー インターフェイス。認証プロセスの実行には、 authenticate ()メソッドが使用されます。

3. ProviderManager : 複数のAuthenticationProvidersをカプセル化する、認証マネージャー インターフェイスの共通実装

4. AuthenticationProvider : 特定の認証メカニズムを完了する特定の認証プロセッサ

5. UserDetailsS​​ervice : ユーザー名に従ってユーザー情報をロードし、UserDetailsインターフェイスの実装を返します

6. UserDetails : ユーザー情報を含むインターフェイス。フレームはユーザー情報を表します

7. UsernamePasswordAuthenticationFilter : フォーム ログイン認証を処理するフィルター

8. AbstractAuthenticationProcessingFilter :認証処理フィルターの基本クラス。

9. SecurityContextHolder : セキュリティコンテキストコンテナ、Authenticationオブジェクトへのアクセス。 

これは完全な SpringSecurity 認証プロセスです。

1.ユーザーは、認証のためにユーザー名とパスワードをシステムに送信します。

2. AuthenticationFilter はリクエストをインターセプトし、リクエストからユーザー名とパスワードを抽出してUsernamePasswordAuthenticationTokenを構築します

3. AuthenticationFilter はUsernamePasswordAuthenticationTokenをAuthenticationManager転送します

4. AuthenticationManager は、認証に一致するAuthenticationProviderを見つけます

5. AuthenticationProvider は、まずUserDetailsS​​erviceloadUserByUsername()メソッドを呼び出し、ユーザー名に従ってユーザー情報をロードします。

6. UserDetailsS​​ervice は、ユーザー名に従ってデータベースにクエリを実行し、ユーザー情報、権限などを含むUserDetailsオブジェクトを構築します。

7. AuthenticationProvider は、 UserDetailsとユーザーが入力したパスワードを使用して、一致検証を実行します一致すれば検証は成功です。

8.検証が成功すると、AuthenticationProvider は認証されたAuthenticationオブジェクトを構築します

9. AuthenticationProvider はAuthenticationManagerAuthenticationを返します

10. AuthenticationManager は、AuthenticationSecurityContextHolder設定します

11.後続のアクセス制御では、SecurityContextHolder認証情報を使用して、ユーザーの ID と権限を確認します。

12.ログインは成功し、ユーザーはシステムの保護されたリソースにアクセスします。

完全なプロセスを次の図に示します。

3. トラブル記録(重要)

以下を読んで、まずこのエラーを理解してください。

autoType は support.org.springframework.security.core.authority.SimpleGrantedAuthority エラー レコードではありません (プロテストが利用可能)

4. プロジェクトのコアコードの説明

コード量が比較的多いため、プロジェクト全体のキーコードを取り出して個別に説明し、その他のマイナーなコードについては掲載しません。主な理由は、Spring Security の実行プロセスをより多くの人に理解していただくためです。コードを Gitee にオープンソース化し、記事の最後に提供します。

4.1. pom 依存関係のインポート

完全な依存関係が掲載されます。

<dependencies>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- lombok依赖包 -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.16.10</version>
			<scope>provided</scope>
		</dependency>


		<!-- spring security 安全认证 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>

		<!-- 单元测试 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- jwt -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.9.1</version>
		</dependency>

		<!-- redis依赖 对象池 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

		<!-- pool 对象池 -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-pool2</artifactId>
			<version>2.11.1</version>
		</dependency>

		<!-- 常用工具类 -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.9</version>
		</dependency>

		<!-- 阿里JSON解析器 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>2.0.22</version>
		</dependency>

	</dependencies>

4.2、yml設定ファイル

主に Redis 接続とトークン定数を設定します。

spring:
  redis:
    host: localhost
    port: 6379
    database: 0
    password:
    timeout: 10s
    lettuce:
      pool:
        min-idle: 0
        max-idle: 8
        max-active: 8
        max-wait: -1ms

token:
  header: Authorization
  secret: oqwe9sdladwosqwqs
  expireTime: 30

4.3、エンティティクラス

合計 4 つのエンティティ クラスが関係しており、いくつかのキー フィールドが主に設計されていますが、あまり完全ではありません。

4.3.1、LoginBody ログイン エンティティ クラス

このクラスは主に、フロントエンドによって渡されたユーザー名とパスワードを受信し、ログイン情報を確認するために使用されます。

完全なコード:

package com.example.security.domain;

import lombok.Data;

@Data
public class LoginBody
{
    /**
     * 用户名
     */
    private String username;

    /**
     * 用户密码
     */
    private String password;

}

4.3.2、ロール ロールクラス

各ユーザーのロール情報を保存するには、シリアル化インターフェイスを実装する必要があります。

完全なコード:

package com.example.security.domain;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.Serializable;

@Data
@AllArgsConstructor
public class Role implements Serializable {

  /**
   * 角色主键
   */
  private Long id;

  /**
   * 角色名称
   */
  private String name;

}

4.3.3、ユーザー ユーザークラス

主にユーザー情報を保存し、シリアル化インターフェイスを実装する必要があります。

完全なコード:

package com.example.security.domain;

import lombok.Data;

import java.io.Serializable;
import java.util.Set;

@Data
public class User implements Serializable {

    /**
     * 主键
     */
    private String id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 角色集合
     */
    private Set<Role> roles;


}

4.3.4、LoginUser ログインユーザー情報

Spring Security に付属する UserDetails インターフェースを実装し、そのメソッドをすべて実装する必要がありますが、Spring Security では、GrantedAuthority インターフェースを通じてユーザーが所有する権限を表現できます。

方法 説明する
isAccountNonExpired() アカウントの有効期限が切れていますか?
isAccountNonLocked() アカウントはロックされていますか?
isCredentialsNonExpired() 資格情報 (パスワード) の有効期限が切れているかどうか
有効になっています() アカウントは利用可能ですか

これらのメソッドが true を返すのはロジックを簡素化するためであり、対応する状態判定が実装されていない場合はデフォルト設定が true となり、不要な認証/認可の失敗を回避できます。 

完全なコード:

package com.example.security.domain;

import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Set;

@Data
public class LoginUser implements UserDetails {

    public LoginUser(User user,Set<GrantedAuthority> authorities)
    {
        this.user = user;
        this.authorities = authorities;

    }

    /**
     * 用户信息
     */
    private User user;

    /**
     * 权限信息
     */
    private Set<GrantedAuthority> authorities;

    /**
     * token信息
     */
    private String token;

    /**
     * 登录时间
     */
    private Long loginTime;
    /**
     * 过期时间
     */
    private Long expireTime;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

4.4、TokenService サービス クラス

まずいくつかのパラメータを注入します。

キーコード:

@Value("${token.header}")
private String header;

@Value("${token.secret}")
private String secret;

@Value("${token.expireTime}")
private int expireTime;

protected static final long MILLIS_SECOND = 1000;

protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;

4.4.1. トークンコアコードの生成

キーコード:

    private String generateToken(Map<String, Object> claims)
    {
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret).compact();
        return token;
    }

4.4.2. トークンを生成するための主要なロジック

常量LOGIN_TOKEN_KEY:login_tokens:

1. まず、トークン値としてランダムな UUID を生成し、それを LoginUser オブジェクトに設定します。

2. LoginUser のログイン時刻と有効期限 (現在時刻 + 有効期限) を設定します。

3. LoginUser オブジェクトを Redis に保存します。キーは LOGIN_TOKEN_KEY+token で、有効期限のデフォルトは yml で設定された 30 分です。

4. 最後に、generateToken メソッドを呼び出して JWT トークンを生成し、それをクレーム ハッシュ マップ コレクションに渡します。

キーコード:

    public String createToken(LoginUser loginUser)
    {
        String token = UUID.randomUUID().toString();
        loginUser.setToken(token);
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        String userKey = CacheConstants.LOGIN_TOKEN_KEY + token;
        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
        Map<String, Object> claims = new HashMap<>();
        claims.put(Constants.LOGIN_USER_KEY, token);
        return generateToken(claims);
    }

4.4.3. トークンコアコードを解析する

キーコード:

 private Claims parseToken(String token)
    {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }

4.4.4. リクエストヘッダーに含まれるトークンを取得する

定数 TOKEN_PREFIX: "ベアラー"

1. リクエストヘッダーから指定した名前(ymlファイルに設定されているヘッダー)の認可情報を取得します。

2. 取得したトークンが空でなく、指定されたプレフィックス (Constants.TOKEN_PREFIX) で始まるかどうかを確認します。

3. 「はい」の場合は、プレフィックスを削除して、最終的な JWT トークンを取得します。

キーコード:

    private String getToken(HttpServletRequest request)
    {
        String token = request.getHeader(header);
        if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
        {
            token = token.replace(Constants.TOKEN_PREFIX, "");
        }
        return token;
    }

4.4.5. Redis に保存されているトークンキーを取得する

キーコード:

    private String getTokenKey(String uuid)
    {
        return CacheConstants.LOGIN_TOKEN_KEY + uuid;
    }

4.4.6. リフレッシュトークンの有効期間

1. パラメータloginUserは、現在のログインユーザー情報です。

2.loginUser の新しいログイン時刻を現在時刻に設定します。

3. 有効期限を現在時刻 + 有効期限 (yml ファイルで設定された expireTime) として再計算します。

4. ログインユーザーのトークンをキーとして、更新したloginUserをRedisに保存し、有効期限を設定します。

5. これは、トークンの有効期限を更新することに相当します。

キーコード:

    public void refreshToken(LoginUser loginUser)
    {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        // 根据uuid将loginUser缓存
        String userKey = getTokenKey(loginUser.getToken());
        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    }

4.4.7. 検証トークンの有効期間

検証トークンの有効期間は 20 分未満で、キャッシュは自動的に更新されます。

キーコード:

    public void verifyToken(LoginUser loginUser)
    {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
        {
            refreshToken(loginUser);
        }
    }

4.4.8. ユーザー識別情報の取得

1. まず、リクエストから JWT トークンを取得します。

2. トークンが空でない場合は、トークンを解析してクレームを取得します。

3. クレームから対応する uuid を取り出します。

4. uuid をキーとして Redis から LoginUser オブジェクトを取得します。

5. 取得に成功すると、LoginUser オブジェクトが返されます。

6. トークンの解析またはユーザーの取得に失敗した場合は、null を返します。

キーコード:

    public LoginUser getLoginUser(HttpServletRequest request)
    {
        // 获取请求携带的令牌
        String token = getToken(request);
        if (StringUtils.isNotEmpty(token))
        {
            try
            {
                Claims claims = parseToken(token);
                // 解析对应的权限以及用户信息
                String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
                String userKey = getTokenKey(uuid);
                LoginUser user = redisCache.getCacheObject(userKey);
                return user;
            }
            catch (Exception e)
            {
            }
        }
        return null;
    }

4.4.9. ユーザー識別情報の削除

トークンを介して Redis でユーザーの一意の識別子を取得してユーザーのログイン データをクリアすると、次回フィルターを通過するときに、ユーザー情報がないためアクセスが制限されます。

    /**
     * 删除用户身份信息
     */
    public void delLoginUser(String token)
    {
        if (StringUtils.isNotEmpty(token))
        {
            String userKey = getTokenKey(token);
            redisCache.deleteObject(userKey);
        }
    }

4.5、AuthenticationEntryPointImpl設定の認証失敗処理クラス

定数 UNAUTHORIZED: 401

AuthenticationEntryPoint は Spring Security で認証失敗を処理するために使用されるインターフェースで、ログインなしまたはログイン期限切れの場合に使用され、開始メソッドがトリガーされます。

1. メソッド内で、まず応答ステータス コードを 401 Unauthorized に設定します。

2. 次に、StringUtils を使用して、要求されたアクセスのインターフェイス パスと認証失敗のプロンプトを含むエラー メッセージ文字列を生成します。3. 最後に、AjaxResult を使用してステータス コードとエラー情報を結果にカプセル化し、ServletUtils を通じて JSON 形式で応答に書き込みます。

4. AjaxResult は、AJAX リクエスト結果をカプセル化するクラスであり、エラーまたは成功した応答結果を簡単に生成できます。

5. ServletUtils は、文字列データを HttpServletResponse に簡単にレンダリングできるツール クラスです。

したがって、このクラスの機能は、認証失敗時にエラーコードやメッセージを含む結果をJSON形式でフロントエンドに返し、フロントエンドは対応するプロンプトを表示したり、結果に応じた処理を行うことになります。

キーコード:

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint
{
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        int code = HttpStatus.UNAUTHORIZED;
        String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
    }
}

4.6、JwtAuthenticationTokenFilter 認証フィルター

OncePerRequestFilter は SpringSecurity によって提供されるフィルター基本クラスです。主にフィルターがリクエスト内で 1 回だけ実行されるようにするために使用されます。JwtAuthenticationTokenFilter はこの基本クラスを継承し、doFilterInternal メソッドを書き直す必要があります。

1. tokenService を通じてリクエスト ヘッダーから JWT トークンを抽出し、解析して LoginUser オブジェクトを取得します

2. tokenService の verifyToken メソッドを呼び出して、JWT トークンの有効性を検証します。

3. LoginUser オブジェクトを使用して UsernamePasswordAuthenticationToken を構築します

4. リクエストの送信元など、AuthenticationToken の詳細を設定します。

5. 構築された UsernamePasswordAuthenticationToken オブジェクトを SecurityContextHolder の Context に設定します。

6. このようにして、ログインしたユーザーの認証オブジェクトがセキュリティ コンテキストに保存されます。

7. 最後に、フィルター チェーンは doFilter メソッドを逆方向に実行し続けます。

このようにして、トークンの解析と検証がフィルター内で実現され、Authentication オブジェクトがセキュリティ コンテキストに設定されるため、後続のフィルターは
それに基づいてユーザー認証情報を判断できます。

キーコード:

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{

    @Value("${token.header}")
    private String header;

    @Value("${token.secret}")
    private String tokenKey;

    @Resource
    private TokenService tokenService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException
    {

        LoginUser loginUser = tokenService.getLoginUser(request);
        if (loginUser != null)
        {
            tokenService.verifyToken(loginUser);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        chain.doFilter(request, response);
    }
}

 注: 現在ログインしているユーザー情報を取得したい場合は、次の 2 行のコードを通じて取得できます。

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();

4.7、FastJsonシリアル化

package com.example.security.config;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.Charset;

/**
 * Redis使用FastJson序列化
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    public FastJson2JsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            return new byte[0];
        }
        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
    }
}

4.8、カスタム Redis シリアル化 

注: 詳細な説明はこの記事にあります: SpringBoot は RedisTemplate を統合して Redis データベースを詳細に操作します (Gitee ソース コードが提供されています) 

package com.example.security.config;

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * redis配置
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        String[] acceptNames = {"org.springframework.security.core.authority.SimpleGrantedAuthority"};
        GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer(acceptNames);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }

}

4.9、Redisツールクラス

package com.example.redis.utils;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
 
import java.util.*;
import java.util.concurrent.TimeUnit;
 
/**
 * spring redis 工具类
 **/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
    @Autowired
    public RedisTemplate redisTemplate;
 
    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }
 
    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }
 
    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }
 
    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }
 
    /**
     * 获取有效时间
     *
     * @param key Redis键
     * @return 有效时间
     */
    public long getExpire(final String key)
    {
        return redisTemplate.getExpire(key);
    }
 
    /**
     * 判断 key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key)
    {
        return redisTemplate.hasKey(key);
    }
 
    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }
 
    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }
 
    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public boolean deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection) > 0;
    }
 
    /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }
 
    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }
 
    /**
     * 缓存Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }
 
    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }
 
    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }
 
    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }
 
    /**
     * 往Hash中存入数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
    {
        redisTemplate.opsForHash().put(key, hKey, value);
    }
 
    /**
     * 获取Hash中的数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }
 
    /**
     * 获取多个Hash中的数据
     *
     * @param key Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }
 
    /**
     * 删除Hash中的某条数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return 是否成功
     */
    public boolean deleteCacheMapValue(final String key, final String hKey)
    {
        return redisTemplate.opsForHash().delete(key, hKey) > 0;
    }
 
    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
 
    /**
     * 存储有序集合
     * @param key 键
     * @param value 值
     * @param score 排序
     */
    public void zSet(Object key, Object value, double score){
        redisTemplate.opsForZSet().add(key, value, score);
    }
 
    /**
     * 存储值
     * @param key 键
     * @param set 集合
     */
    public void zSet(Object key, Set set){
        redisTemplate.opsForZSet().add(key, set);
    }
 
    /**
     * 获取key指定范围的值
     * @param key 键
     * @param start 开始位置
     * @param end 结束位置
     * @return 返回set
     */
    public Set zGet(Object key, long start, long end){
        Set set = redisTemplate.opsForZSet().range(key, start, end);
        return set;
    }
 
    /**
     * 获取key对应的所有值
     * @param key 键
     * @return 返回set
     */
    public Set zGet(Object key){
        Set set = redisTemplate.opsForZSet().range(key, 0, -1);
        return set;
    }
 
    /**
     * 获取对用数据的大小
     * @param key 键
     * @return 键值大小
     */
    public long zGetSize(Object key){
        Long size = redisTemplate.opsForZSet().size(key);
        return size;
    }
}

4.10、SecurityConfigコア構成クラス

これはコア コードであり、コメントはすべてコード上にあります。詳しくは自分で確認できるため、ここでは詳しく説明しません。

完全なコード:

package com.example.security.config;

import com.example.security.filter.JwtAuthenticationTokenFilter;
import com.example.security.service.serviceImpl.AuthenticationEntryPointImpl;
import com.example.security.service.serviceImpl.LogoutSuccessHandlerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter;

import javax.annotation.Resource;

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private UserDetailsService userDetailsService;

    /**
     * token认证过滤器
     */
    @Resource
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    /**
     * 认证失败处理类
     */
    @Resource
    private AuthenticationEntryPointImpl unauthorizedHandler;

    /**
     * 退出处理类
     */
    @Resource
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception
    {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // CSRF禁用,因为不使用session
                .csrf().disable()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                //允许登录接口匿名访问
                .antMatchers("/login").permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();
        // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加Logout filter
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder()
    {
        return new BCryptPasswordEncoder();
    }


    /**
     * 身份认证接口
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

4.11、AuthenticationContextHolder スレッドのローカル ストレージ

主に次の静的メソッドを提供します。

1. getContext() は、現在のスレッドの Authentication オブジェクトを取得します。

2. setContext (認証コンテキスト) は、現在のスレッドの認証オブジェクトを設定します。

3. clearContext() は、現在のスレッドの Authentication オブジェクトをクリアします。

ThreadLocal を使用してスレッドの分離を維持するため、各スレッドは互いに干渉することなく独自の認証情報を持ちます。

Spring Security では、このクラスを通じて認証情報をさまざまな層で渡すことができます。

キーコード:

public class AuthenticationContextHolder
{
    private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();

    public static Authentication getContext()
    {
        return contextHolder.get();
    }

    public static void setContext(Authentication context)
    {
        contextHolder.set(context);
    }

    public static void clearContext()
    {
        contextHolder.remove();
    }
}

4.12. UserServiceImpl クエリユーザーインターフェイス

ここでは私は怠け者なのでデータベースに接続しませんでしたが、このパスワードは次のコードで暗号化して取得します。

キーコード:

PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encode = passwordEncoder.encode("123456");
System.out.println(encode);

主にデモ用なので、ユーザー情報やロール情報はここに直接書き込まれており、実際の開発ではまだデータベースに接続する必要があります。

キーコード:

@Service
public class UserServiceImpl {

    public User selectUserByUsername(String username){
        User user = new User();
        user.setId(UUID.randomUUID().toString());
        user.setUsername(username);
        user.setPassword("$2a$10$ErrO7WgkEBAWVQwuJtbBve7R2.pSKUrfs7zt8XkASqJKqcetMvAUC");
        Set<Role> roles = new HashSet<>();
        Role role1 = new Role(1L, "ROLE_ADMIN");
        Role role2 = new Role(2L, "ROLE_USER");
        roles.add(role1);
        roles.add(role2);
        user.setRoles(roles);
        return user;
    }
}

ここでは、ユーザーはプレフィックスとしてROLE_を使用する必要があります。具体的な分析については、 3.15 の @PreAuthorize アノテーション分析を参照してください。

4.13、PasswordServiceImpl パスワード検証サービス クラス 

1. validate() メソッドは、ユーザーのパスワードを検証するために使用されます。

2. まず、現在認証されているユーザー名とパスワードを AuthenticationContextHolder から取得します。

3. 次に、matches() メソッドを呼び出してパスワードを確認します。

4.matches() メソッドは、BCryptPasswordEncoder を使用して、保存されている暗号文パスワードの一致検証を実行します。

5. 照合が成功した場合、検証は成功し、失敗した場合、検証は失敗します。

このようにして、Spring Security の AuthenticationContextHolder を通じて現在の認証プリンシパルの情報を取得できます。

パスワード暗号化照合検証と組み合わせることで、パスワード検証をサービスに簡単に実装できます。

キーコード:

@Service
public class PasswordServiceImpl {

    public void validate(User user)
    {
        Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();
        String AuthUsername = usernamePasswordAuthenticationToken.getName();
        String AuthPassword = usernamePasswordAuthenticationToken.getCredentials().toString();

        if (matches(user, AuthPassword)) {
            System.out.println("验证成功!");
        } else {
            System.out.println("验证失败!");
        }
    }

    public boolean matches(User user, String rawPassword)
    {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder.matches(rawPassword, user.getPassword());
    }
}

4.14、UserDetailsS​​erviceImpl 認証ユーザー サービス クラス

1. Spring SecurityのUserDetailsS​​erviceインターフェースを実装します。

UserDetailsS​​ervice は、Spring Security がユーザー情報をロードするために使用するコア インターフェイスです。カスタム実装により、ユーザー情報の読み込みプロセスを柔軟に制御できます。

2. ユーザー名に従ってユーザー情報をロードします。userService を通じてデータベースにクエリを実行し、ユーザー名、パスワード、ロールなどのユーザー情報を含むユーザー オブジェクトを取得します。

3. ユーザーパスワードの検証 パスワード検証にpasswordServiceを使用して、ログインパスワードが正しいかどうかを確認します。

4. ユーザー権限情報を構築し、ユーザーロール情報をGrantedAuthority認可情報収集に変換します。

5. ユーザーオブジェクトの戻り値のカプセル化 ユーザー情報と権限情報を LoginUser オブジェクトにカプセル化し、UserDetails として返します。

6. ログイン検証中にユーザーの詳細を提供する Spring Security は、認証と認可のためのログイン検証中にこのサービスを呼び出してユーザーの詳細を取得します。

キーコード:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Resource
    private PasswordServiceImpl passwordService;

    @Resource
    private UserServiceImpl userService;

    @Override
    public UserDetails loadUserByUsername(String username) {
        User user = userService.selectUserByUsername(username);
        passwordService.validate(user);
        //取出角色和权限信息
        Set<Role> roles = user.getRoles();

        Set<GrantedAuthority> authorities = new HashSet<>();
        for (Role role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return new LoginUser(user,authorities);
    }

}

4.15、LoginController ログイン インターフェイス

すべてのログインはこのインターフェイスを経由する必要があり、ログインが成功すると、システムの保護されたリソースにアクセスするためのトークンがユーザーに返されます。

キーコード: 

@RestController
public class LoginController {

    @Resource
    private LoginServiceImpl loginService;

    @PostMapping("/login")
    public AjaxResult login(@RequestBody LoginBody loginBody)
    {
        AjaxResult ajax = AjaxResult.success();
        // 生成令牌
        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword());
        ajax.put(Constants.TOKEN, token);
        return ajax;
    }


}

4.16、LoginServiceImpl ログイン インターフェイスのコア ロジック

1. ユーザー名とパスワードを含む UsernamePasswordAuthenticationToken を作成します。

2. Spring Securityが提供する認証を格納するホルダーであるAuthenticationContextHolderにauthenticationTokenを設定します。

3. 認証のためにAuthenticationManagerのauthenticateメソッドを呼び出します。このメソッドは、認証の構成に従って、関連する UserDetailsS​​ervice を呼び出します。(以下、認証ユーザ情報サービスクラスのloadUserByUsernameメソッドに続きます)、検証成功後に認証オブジェクトが返却されます。

4. AuthenticationContextHolder をクリアします。

5. 認証オブジェクトからログインユーザー情報 LoginUser を取得します。

6. TokenService を使用して JWT トークンを生成します。

7. JWT トークンを返します。

キーコード:

@Service
public class LoginServiceImpl {

    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private TokenService tokenService;

    public String login(String username, String password)
    {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        AuthenticationContextHolder.setContext(authenticationToken);
        Authentication authentication = authenticationManager.authenticate(authenticationToken);
        AuthenticationContextHolder.clearContext();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        return tokenService.createToken(loginUser);
    }

}

4.17、LogoutSuccessHandlerImpl ログアウト コア ロジック 

LogoutSuccessHandlerImpl は、ユーザーが正常にログアウトした後のロジックを処理するために使用される Spring Security の LogoutSuccessHandler インターフェイスを実装します。

1. TokenService を通じて現在ログインしているユーザー情報 LoginUser を取得します。

2. LoginUser が空でない場合は、TokenService の delLoginUser メソッドを呼び出して、ユーザーのキャッシュ情報を削除します。これには、トークンベースのユーザー認証を処理するためのカスタム TokenService が含まれます。ログイン時にユーザー情報に対応したトークンが生成され、ログアウト時にキャッシュ関係を削除する必要があります。

3. ServletUtils を使用して、成功した終了プロンプトを応答に書き込み、ajax リクエストがプロンプト情報を取得できるようにします。

キーコード:

/**
 * 退出登录
 */
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
    @Autowired
    private TokenService tokenService;

    /**
     * 退出处理
     * @return
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
    {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser))
        {
            // 删除用户缓存记录
            tokenService.delLoginUser(loginUser.getToken());
        }
        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success("退出成功")));
    }
}

4.18. @PreAuthorize アノテーション

この注釈は、指定されたユーザーのみがインターフェースにアクセスできることを示すためにインターフェースに配置されます。

1.インターフェイスで@PreAuthorize("hasRole('USER')")アノテーションを使用すると、内部ソース コードの実行プロセスを分析できます。

まず、上記で USER として設定されたインターフェイス パラメータを取得します

hasAnyAuthorityName メソッドを入力して現在のユーザー ( roleSet コレクション)のすべての権限を取得し、for ループを使用してアノテーションのパラメーター配列を走査します。アノテーションにはUSERが 1 つだけあるため、このサイクルは 1 回だけです。配列の長さは 1 です。 If 現在のインターフェイスの権限情報が現在のユーザーのすべての権限セットに含まれている場合は許可され、そうでない場合は解放されません。 

3. getRoleWithDefaultPrefix メソッドは、現在渡されているロールにデフォルトのROLE_ という接頭辞が付いているかどうかを検出するために使用されます。接頭辞が付いている場合は直接返され、そうでない場合はデフォルトの接頭辞ROLE_が付けられて返されます。

これはこのアノテーションの検証プロセスであり、3.9 でユーザー権限の先頭にROLE_を付ける必要がある理由も説明します。

4.19、HelloController テスト インターフェイス

テスト インターフェイスを 2 つずつ作成します。1 つはログイン後に誰でもアクセスできるインターフェイス、1 つはログイン後に USER 権限が必要なユーザーのみがアクセスできるインターフェイス、もう 1 つはログイン後に USER 権限が必要なユーザーのみがアクセスできるインターフェイスです。共通権限。

キーコード:

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return "Hello World!";
    }

    @GetMapping("/user")
    @PreAuthorize("hasRole('USER')")
    public String user(){
        return "Hello USER!";
    }

    @GetMapping("/common")
    @PreAuthorize("hasRole('COMMON')")
    public String common(){
        return "Hello COMMON!";
    }
}

注: これはパブリック メソッドで変更する必要があります。変更しない場合、@PreAuthorize アノテーションは無効になります。

4.20 概要

最後に、実際のプロジェクトを通じて Spring Security 認証プロセスを要約します。

1. LoginService のログイン メソッドで、認証のエントリ ポイントであるユーザー名とパスワードを含む UsernamePasswordAuthenticationToken を構築します。

2. LoginService は、AuthenticationManager のauthenticate メソッドを呼び出して、認証プロセスを開始します。

3. AuthenticationManager は、認証に一致する AuthenticationProvider を見つけます。

4. AuthenticationProvider は、UserDetailsS​​ervice のloadUserByUsername メソッドを呼び出して、ユーザー情報を読み込みます。ここでは、UserDetailsS​​erviceImpl を通じてユーザーにクエリを実行します。

5. UserDetailsS​​erviceImpl で、ユーザー名に従ってユーザー情報をクエリし、パスワードを検証するために PasswordService を呼び出します。

6. PasswordService は、AuthenticationContextHolder を通じてログイン ユーザー名とパスワードを取得します。次に、データベースに保存されているユーザーのパスワード(エンコードされたもの)と照合し、一致すれば検証は成功します。

7. PasswordService 認証が成功すると、UserDetailsS​​erviceImpl はユーザー名、パスワード、権限情報などのユーザー情報に従って UserDetails オブジェクト (ここでは LoginUser) を構築します。

8. UserDetailsS​​erviceImpl は UserDetails を AuthenticationProvider に返します。

9. AuthenticationProvider は UserDetails を受信すると、検証を完了し、認証された Authentication オブジェクトを生成します。

10. AuthenticationProvider は AuthenticationManager に Authentication を返します。

11. AuthenticationManager は、後続のアクセス制御のために Authentication を SecurityContextHolder に設定します。

12. LoginService は認証された Authentication を取得し、そこから UserDetails を抽出し、JWTtoken を生成して返します。

プロジェクトのロジックと合わせてまとめると、Spring Security の認証プロセスはユーザー情報の取得 -> ユーザー検証 -> UserDetails の構築 -> Authentication の生成に大別できます。UserDetailsS​​erviceとPasswordServiceをカスタマイズしてユーザー認証ロジックを実装しました。 

5. プロジェクトを実行する 

5.1. ログインの成功

ログインのポストリクエストを通じてデータをjson形式で送信します。

ログインが成功し、Token トークンが返されました。

5.2. 未承認のインターフェースへのアクセス

取得したトークンをリクエストヘッダーに設定して、テストインターフェイスにアクセスします。

5.3. USER権限が必要なインターフェースへのアクセス

こんにちはをユーザーに変更します。

このユーザーにはデフォルトで ADMIN と USER の 2 つの権限が設定されているため、アクセスは成功します。 

5.4. COMMON 権限が必要なインターフェースへのアクセス

ユーザーを共通に変更します。

明らかに、403 エラー メッセージが返されました。 

5.5. ログアウト

共通をログアウトに変更します。

正常にサインアウトしました。

5.6. アクセス失敗 

上記でログアウトし、以前のトークンを使用してまだテスト インターフェイスにアクセスできるかどうかを確認します。

システムが 401 エラーを返していることがはっきりとわかります。 

5.7. ログインに失敗しました

上記はすべてログインに成功した例ですが、今回は正常にログインできるかどうかを確認するために、意図的に間違ったパスワードを入力しました。

システムが 401 エラーを返していることがはっきりとわかります。 

6.Giteeソースコードアドレス

このブログで提供されているコードは完全ではないため、研究と参考のために完全なプロジェクトを Code Cloud にオープンソースしました。

プロジェクトのアドレス: SpringBoot は SpringSecurity+JWT+Redis の完全なチュートリアルを統合します

7. まとめ

以上は Spring Security についての個人的な理解と、実際のプロジェクトでのアプリケーション開発方法でしたので、ご不明な点がございましたら、コメント欄にメッセージを残してください。

おすすめ

転載: blog.csdn.net/HJW_233/article/details/131969622