Spring Security OAuth2.0 認証と認可 --- 基本

1. 基本的な考え方

1.1. 認証とは

モバイル インターネットの時代では、誰もが毎日携帯電話をスワイプしています。一般的に使用されているソフトウェアには、WeChat、Alipay、Toutiao などが含まれます。以下では、認証に関連する基本概念を説明するために WeChat を例として使用します。初めて WeChat にログインするには、WeChat ユーザーとして登録し、アカウント番号とパスワードを入力する必要があり、WeChat にログインするためのアカウントとパスワードを入力するプロセスが認証です。

なぜシステムを認証する必要があるのでしょうか?

認証はシステムの個人データとリソースを保護するために行われ、ユーザーは法的な ID でのみシステムのリソースにアクセスできます。

認証: ユーザー認証は、ユーザーの身元が正当であるかどうかを判断するプロセスです。ユーザーがシステム リソースにアクセスするとき、システムはユーザーの身元情報の検証を要求します。身元が正当である場合にのみ、ユーザーはアクセスを続行できます。違法であるため、アクセスは拒否されます。一般的なユーザー ID 認証方法には、ユーザー名とパスワードによるログイン、QR コード ログイン、SMS ログイン、指紋認証などが含まれます。

1.2、セッションとは

ユーザー認証後は、ユーザーの操作ごとの認証を回避するために、セッション内でユーザーの情報を保証することができます。セッションは、現在のユーザーのログイン状態を維持するためにシステムによって提供されるメカニズムであり、一般的な方法には、セッション ベースの方法とトークン ベースの方法があります。

1.2.1、セッションベースの認証方法

その対話プロセスは、ユーザー認証が成功した後、ユーザー関連データがサーバー側で生成されてセッション (現在のセッション) に保存され、クライアントに送信されたsesssion_id が Cookie に保存されるため、ユーザーはユーザーの法的検証を完了するために、サーバー側にセッション データがあるかどうかをリクエストするときに、クライアントを session_id で検証できます。ユーザーがシステムからログアウトするか、セッションが期限切れになって破棄されると、セッション ID は、サーバー側にセッション データがあるかどうかを確認します。クライアントは無効になります。

ここに画像の説明を挿入

1.2.2. トークンに基づく

その対話プロセスは、ユーザー認証が成功した後、サーバーがトークンを生成してクライアントに送信することです。クライアントはトークンを cookie や localStorage などのストレージに置き、各リクエストでトークンを持ち込むことができます。サーバーはトークンを受け取ります。認証に合格すると、ユーザーの本人確認が可能となります。

ここに画像の説明を挿入

  • セッションベースの認証方法はサーブレット仕様によってカスタマイズされており、サーバーはセッション情報を保存するためにメモリ リソースを占有する必要があり、クライアントは Cookie をサポートする必要があります。
  • トークンベースの方法では、通常、サーバーがトークンを保存する必要がなく、クライアントの保存方法も制限されません。
  • 今日のモバイル インターネット時代では、より多くの種類のクライアントがシステムにアクセスする必要があり、システムはほとんどの場合、フロント エンドとバック エンドに別個のアーキテクチャで実装されているため、トークン ベースのアプローチがより適しています。

1.3. 認可とは何ですか

WeChat を例に挙げると、WeChat に正常にログインすると、ユーザーは赤い封筒の送信、友達サークルの送信、友達の追加などの WeChat の機能を使用できるようになります。銀行カード 赤い封筒を送信できるのはユーザーのみです 赤い封筒を送信する機能とモーメントは WeChat リソース、つまり機能リソースです 赤い封筒を送信する権限を持つユーザーは通常、赤い封筒を送信する機能を使用できます友達を送信する権限を持った人は、友達を送信する機能やサークル機能を利用することができ、ユーザーの権限に応じてユーザーのリソースの使用を制御する処理が認可です。

なぜ認可するのか?

認証はユーザーのアイデンティティの正当性を保証するものであり、認可は個人データをより細かい粒度で分割することです。認可は認証に合格した後に発生し、異なるユーザーが異なるリソースにアクセスするように制御します。

認可: 認可は、ユーザーの権限に従ってリソースへのユーザーのアクセスを制御するユーザー認証のプロセスです。リソースへのアクセス権がある場合は通常どおりアクセスでき、権限がない場合はアクセスを拒否します。

1.4. 認可されたデータモデル

承認方法とは、ユーザーのリソースへのアクセスを制御する方法を意味し、まず承認に関連するデータ モデルを学習する必要があります。

承認は、単純に、誰が、何を (どの) に対してどのように操作するかというものとして理解できます。これには、以下が含まれます。

、つまりサブジェクト (Subject)。サブジェクトは一般にユーザーを指しますが、システム内のリソースにアクセスする必要があるプログラムの場合もあります。

内容、つまり、システム メニュー、ページ、ボタン、コード メソッド、システム製品情報、システム注文情報などのリソース (Resource) です。システムのメニュー、ページ、ボタン、コードメソッドはすべてシステム機能リソースです。Web システムの場合、通常、各機能リソースは URL に対応します。システム製品情報とシステム注文情報はすべてエンティティ リソース (データ リソース) であり、エンティティ リソースが決定されます。リソースタイプとリソースによるインスタンスの構成。たとえば、商品情報がリソースタイプ、商品番号001がリソースインスタンスです。

どのように、権限/権限(Permission)は、リソースを操作するためのユーザーの権限を指定します。ユーザーのクエリ権限、ユーザーの追加権限、コードメソッドの呼び出し権限、番号付けされたユーザーの変更権限など、リソースがなければ権限は意味がありません。 001 など、権限を通じて、ユーザーがどのリソースに対してどのような操作権限を持っているかを知ることができます。

サブジェクト、リソース、権限の関係は次のとおりです。
ここに画像の説明を挿入

サブジェクト、リソース、および権限に関連するデータ モデルは次のとおりです。

  • 件名 (ユーザーID、アカウント、パスワードなど)
  • リソース (リソース ID、リソース名、アクセス アドレス、...)
  • 権限 (権限 ID、権限 ID、権限名、リソース ID、...)
  • ロール (ロール ID、ロール名、...)
  • ロールと権限の関係 (ロール ID、権限 ID、...)
  • プリンシパル (ユーザー) とロール関係 (ユーザー ID、ロール ID など)

サブジェクト (ユーザー)、リソース、権限の関係は次のとおりです。

ここに画像の説明を挿入

通常、エンタープライズ開発では、次のように、リソース テーブルと権限テーブルが 1 つの権限テーブルに結合されます。

  • リソース (リソース ID、リソース名、アクセス アドレス、...)
  • 権限 (権限 ID、権限 ID、権限名、リソース ID、...)

に統合されました:

  • 権限 (権限 ID、権限 ID、権限名、リソース名、リソース アクセス アドレスなど)

変更されたデータ モデル間の関係は次のとおりです。
ここに画像の説明を挿入

1.5、RBAC

認可を取得するにはどうすればよいですか? 業界では通常、RBAC に基づいて認可を実装します。

1.5.1、ロールベースのアクセス制御

RBAC ロールベースのアクセス制御 (Role-Based Access Control) はロールによって許可されます。たとえば、サブジェクトのロールはゼネラル マネージャーであり、エンタープライズ オペレーション レポートと従業員の給与情報をクエリできます。アクセス制御プロセスは次のとおりです。
ここに画像の説明を挿入

上図の判定ロジックによれば、認可コードは以下のように表現できます。

if(主体.hasRole("总经理角色id")){
    
    
	查询工资
}

上図の給与照会に必要なロールが部長と部長に変更された場合、判定ロジックを「ユーザーのロールが部長か部長かを判定する」に変更し、以下のようにコードを修正する必要があります。

if(主体.hasRole("总经理角色id") || 主体.hasRole("部门经理角色id")){
    
    
	查询工资
}

上記の例によれば、ロールの権限を変更する必要がある場合、認可の関連コードを変更する必要があり、システムの拡張性が低いことが分かる。

1.5.2. リソースベースのアクセス制御

RBAC リソースベースのアクセス制御 (リソースベースのアクセス制御) は、リソース (または権限) によって許可されます。たとえば、ユーザーは従業員の給与情報をクエリするには、給与をクエリする権限を持っている必要があります。アクセス制御プロセスは次のとおりです。
ここに画像の説明を挿入

上図の判断によれば、認可コードは次のように表現できます。

if(主体.hasPermission("查询工资权限标识")){
    
    
	查询工资
}

メリット:給与照会の権限識別子を定義するシステムとなっているため、給与照会に必要な役割が部長や部長に変更された場合でも、認可コードを変更する必要がなく、拡張性が高いシステムです。

2. セッションベースの認証方式

2.1. 認証プロセス

セッション認証方式に基づく処理は、ユーザーの認証が成功した後、ユーザー関連データがサーバー側で生成されてセッション (現在のセッション) に保存され、クライアントに送信された session_id が Cookie に保存されます。 、クライアントが session_id を使用してリクエストしたときに、サーバー側にセッション データがあるかどうかを確認して、ユーザーの法的検証を完了できるようにします。ユーザーがシステムからログアウトするか、セッションが期限切れになって破棄されると、クライアントの session_id も無効になります。

次の図は、セッション認証方法のフローチャートです。
ここに画像の説明を挿入

セッションベースの認証機構はサーブレット仕様によりカスタマイズされており、サーブレットコンテナが実装されており、ユーザはHttpSessionの操作メソッドを通じて実現できます。HttpSessionに関する操作APIは以下のとおりです。

方法 意味
HttpSession getSession(Boolean create) 現在の HttpSession オブジェクトを取得します
void setAttribute(文字列名,オブジェクト値) オブジェクトをセッションに保存する
オブジェクト getAttribute(文字列名) セッションからオブジェクトを取得する
void RemoveAttribute(文字列名); セッション内のオブジェクトを削除します
無効無効化() HttpSession を無効にする

2.2. プロジェクトの作成

この事例のプロジェクトはmavenを使用して構築され、SpringMVCとServlet3.0を使用して実装されています。

2.2.1. Maven プロジェクトの作成

Maven プロジェクト security-springmvc を作成する

次のように依存関係を導入します。注意してください。

1. Web プロジェクトであるため、パッケージ化は war に設定されます。
2. tomcat7-maven-plugin プラグインを使用してプロジェクトを実行します。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.test.security</groupId>
    <artifactId>security-springmvc</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>security-springmvc</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>

                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <encoding>utf-8</encoding>
                        <useDefaultDelimiters>true</useDefaultDelimiters>
                        <resources>
                            <resource>
                                <directory>src/main/resources</directory>
                                <filtering>true</filtering>
                                <includes>
                                    <include>**/*</include>
                                </includes>
                            </resource>
                            <resource>
                                <directory>src/main/java</directory>
                                <includes>
                                    <include>**/*.xml</include>
                                </includes>
                            </resource>
                        </resources>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

2.2.2、Springコンテナの設定

config パッケージの下に ApplicationConfig.java を定義します。これは、web.xml の ContextLoaderListener の構成に対応します。

@Configuration
@ComponentScan(basePackages = "com.test.security.springmvc",excludeFilters = {
    
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class ApplicationConfig {
    
    
	//在此配置除了Controller的其它bean,比如:数据库链接池、事务管理器、业务bean等。
}

2.2.3、servletContext配置

このケースでは、web.xml メソッドを使用しない Servlet3.0 を採用し、s に対応する DispatcherServlet 設定に相当する WebConfig.java を config パッケージ配下に定義します。

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.test.security.springmvc",includeFilters = {
    
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
    
    
	//视频解析器
	@Bean
	public InternalResourceViewResolver viewResolver(){
    
    
		InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
		viewResolver.setPrefix("/WEB‐INF/views/");
		viewResolver.setSuffix(".jsp");
		return viewResolver;
	}
}

2.2.4、Springコンテナのロード

init パッケージの下に Spring コンテナ初期化クラス SpringApplicationInitializer を定義します。これは WebApplicationInitializer インターフェイスを実装し、Spring コンテナの起動時に WebApplicationInitializer インターフェイスのすべての実装クラスをロードします。

public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    
	
	@Override
	protected Class<?>[] getRootConfigClasses() {
    
    
		return new Class<?>[] {
    
     ApplicationConfig.class };//指定rootContext的配置类
	}
	
	@Override
	protected Class<?>[] getServletConfigClasses() {
    
    
		return new Class<?>[] {
    
     WebConfig.class }; //指定servletContext的配置类
	}
	
	@Override
	protected String[] getServletMappings() {
    
    
		return new String [] {
    
    "/"};
	}
}

SpringApplicationInitializerはweb.xmlに相当します servlet3.0開発を利用する場合は改めてweb.xmlを定義する必要はありません ApplicationConfig.classは以下の構成のapplication-context.xmlに、WebConfig.classは以下の構成コンテンツの springmvc.xml および web.xml 参照

<web‐app>
	<listener>
		<listener‐class>org.springframework.web.context.ContextLoaderListener</listener‐class>
	</listener>
	<context‐param>
		<param‐name>contextConfigLocation</param‐name>
		<param‐value>/WEB‐INF/application‐context.xml</param‐value>
	</context‐param>
	
	<servlet>
		<servlet‐name>springmvc</servlet‐name>
		<servlet‐class>org.springframework.web.servlet.DispatcherServlet</servlet‐class>
		<init‐param>
			<param‐name>contextConfigLocation</param‐name>
			<param‐value>/WEB‐INF/spring‐mvc.xml</param‐value>
		</init‐param>
		<load‐on‐startup>1</load‐on‐startup>
	</servlet>
	<servlet‐mapping>
		<servlet‐name>springmvc</servlet‐name>
		<url‐pattern>/</url‐pattern>
	</servlet‐mapping>
</web‐app>

2.3. 認証機能の実現

2.3.1. 認証ページ

webapp/WEB-INF/views に認証ページ login.jsp を定義します。このケースは認証プロセスをテストするだけです。ページには CSS スタイルは追加されていません。ページはユーザー名とパスワードを入力することで実装できます。ログインがトリガーされると、フォーム情報が /login に送信され、その内容は次のようになります。

<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" %>
<html>
<head>
    <title>用户登录</title>
</head>
<body>
<form action="login" method="post">
    用户名:<input type="text" name="username"><br>&nbsp;&nbsp;&nbsp;码:
    <input type="password" name="password"><br>
    <input type="submit" value="登录">
</form>
</body>
</html>

WebConfig に次の構成を追加して、login.jsp ページに /directly を誘導します。

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    
    
	registry.addViewController("/").setViewName("login");
}

プロジェクトを開始し、/パス アドレスにアクセスしてテストします。

ここに画像の説明を挿入

2.3.2. 認証インターフェース

ユーザーは認証ページに入り、アカウント番号とパスワードを入力し、クリックしてログインし、ID 認証のために /login を要求します。

(1) 受信したユーザー名とパスワードを検証するために使用される認証インターフェイスを定義し、成功した場合はユーザーの詳細情報を返し、そうでない場合はエラー例外をスローします。

/**
* 认证服务
*/
public interface AuthenticationService {
    
    
	/**
	* 用户认证
	* @param authenticationRequest 用户认证请求
	* @return 认证成功的用户信息
	*/
	UserDto authentication(AuthenticationRequest authenticationRequest);
}

認証リクエストの構造:

@Data
public class AuthenticationRequest {
    
    
	/**
	* 用户名
	*/
	private String username;
	/**
	* 密码
	*/
	private String password;
}

認証が成功した後に返されるユーザーの詳細、つまり現在ログインしているユーザーの情報:

/**
* 当前登录用户信息
*/
@Data
@AllArgsConstructor
public class UserDto {
    
    
	private String id;
	private String username;
	private String password;
	private String fullname;
	private String mobile;
}

(2) ユーザー名に基づいてユーザー情報を検索し、パスワードを検証する認証実装クラス ここでは、2 人のユーザーがシミュレートされています。

@Service
public class AuthenticationServiceImpl implements  AuthenticationService{
    
    
    /**
     * 用户认证,校验用户身份信息是否合法
     *
     * @param authenticationRequest 用户认证请求,账号和密码
     * @return 认证成功的用户信息
     */
    @Override
    public UserDto authentication(AuthenticationRequest authenticationRequest) {
    
    
        //校验参数是否为空
        if(authenticationRequest == null
            || StringUtils.isEmpty(authenticationRequest.getUsername())
            || StringUtils.isEmpty(authenticationRequest.getPassword())){
    
    
            throw new RuntimeException("账号和密码为空");
        }
        //根据账号去查询数据库,这里测试程序采用模拟方法
        UserDto user = getUserDto(authenticationRequest.getUsername());
        //判断用户是否为空
        if(user == null){
    
    
            throw new RuntimeException("查询不到该用户");
        }
        //校验密码
        if(!authenticationRequest.getPassword().equals(user.getPassword())){
    
    
            throw new RuntimeException("账号或密码错误");
        }
        //认证通过,返回用户身份信息
        return user;
    }
    //根据账号查询用户信息
    private UserDto getUserDto(String userName){
    
    
        return userMap.get(userName);
    }
    
    //用户信息
    private Map<String,UserDto> userMap = new HashMap<>();
    {
    
    
        userMap.put("zhangsan",new UserDto("1010","zhangsan","123","张三","133443",authorities1));
        userMap.put("lisi",new UserDto("1011","lisi","456","李四","144553",authorities2));
    }
}

(3) コントローラにログインし、/login リクエストを処理します。認証を完了するために AuthenticationService を呼び出し、ログイン結果のプロンプト メッセージを返します。

@RestController
public class LoginController {
    
    

	@Autowired
	private AuthenticationService authenticationService;
	
	/**
	* 用户登录
	* @param authenticationRequest 登录请求
	* @return
	*/
	@PostMapping(value = "/login",produces = {
    
    "text/plain;charset=UTF‐8"})
	public String login(AuthenticationRequest authenticationRequest){
    
    
		UserDetails userDetails = authenticationService.authentication(authenticationRequest);
		return userDetails.getFullname() + " 登录成功";
	}
}

(4) テスト
プロジェクトを開始し、アクセス/パスアドレスにアクセスしてテストします。

2.4. セッション機能の実現

セッションとは、ユーザーがシステムにログインした後、システムがユーザーのログイン状態を記憶し、ユーザーがシステムからログアウトするまでシステムを操作し続けることができることを意味します。

認証の目的はシステム リソースを保護することであり、リクエストを合法的に傍受するために、リソースにアクセスするたびに、システムはリソースにアクセスしているユーザーを認識する必要があります。したがって、認証が成功した後は、通常、認証に成功したユーザーのユーザー情報がセッションに入れられ、その後のリクエストでは、システムはセッションから現在のユーザーを取得し、この方法でセッションのメカニズムを実装できます。

(1) セッション制御の強化
まず、Sessionにログインユーザー情報を格納するためのキーとしてUserDtoにSESSION_USER_KEYを定義します。

public static final String SESSION_USER_KEY = "_user";

次に、LoginController を変更し、認証が成功したら、現在のセッションにユーザー情報を入力します。また、ユーザーのログアウトメソッドを追加し、ログアウト時にセッションが無効になるように設定します。

@RestController
public class LoginController {
    
    

	@Autowired
	private AuthenticationService authenticationService;
	
	/**
	* 用户登录
	* @param authenticationRequest 登录请求
	* @param session http会话
	* @return
	*/
	@PostMapping(value = "/login",produces = "text/plain;charset=utf‐8")
	public String login(AuthenticationRequest authenticationRequest, HttpSession session){
    
    
		UserDto userDto = authenticationService.authentication(authenticationRequest);
		//用户信息存入session
		session.setAttribute(UserDto.SESSION_USER_KEY,userDto);
		return userDto.getUsername() + "登录成功";
	}
	
	@GetMapping(value = "logout",produces = "text/plain;charset=utf‐8")
	public String logout(HttpSession session){
    
    
		session.invalidate();
		return "退出成功";
	}
}

(2) テストリソースの
追加 LoginController を変更し、現在のセッションから現在のログインユーザーを取得し、プロンプト情報をフォアグラウンドに返すテストリソース 1 を追加します。

/**
* 测试资源1
* @param session
* @return
*/
@GetMapping(value = "/r/r1",produces = {
    
    "text/plain;charset=UTF‐8"})
public String r1(HttpSession session){
    
    
	String fullname = null;
	Object userObj = session.getAttribute(UserDto.SESSION_USER_KEY);
	if(userObj != null){
    
    
		fullname = ((UserDto)userObj).getFullname();
	}else{
    
    
		fullname = "匿名";
	}
	return fullname + " 访问资源1";
}

2.5. 認可機能の実現

これで、ユーザー ID 資格情報とログイン ステータスの検証が完了し、現在ログインしているユーザーの情報 (セッションから取得) を取得する方法もわかりました。次に、ユーザーにシステムへのアクセスを許可する必要があります。つまり、次のことを行う必要があります。 機能:

  • 匿名ユーザー (ログインしていないユーザー) のアクセスのブロック: 匿名ユーザーが特定のリソースにアクセスすることを禁止します。
  • ログイン ユーザーのアクセス インターセプト: ユーザーの権限に従って特定のリソースにアクセスできるかどうかを判断します。

(1) パーミッションデータの追加
この機能を実現するには、UserDtoにログインユーザーのパーミッションを示すパーミッション属性を追加し、同時にUserDtoの構築方法を変更する必要があります。

@Data
@AllArgsConstructor
public class UserDto {
    
    
    public static final String SESSION_USER_KEY = "_user";
    //用户身份信息
    private String id;
    private String username;
    private String password;
    private String fullname;
    private String mobile;
    /**
     * 用户权限
     */
    private Set<String> authorities;
}

そして、AuthenticationServiceImpl でシミュレートされたユーザーの権限を初期化します。そのうち、Zhang San は p1 権限を与え、Li Si は p2 権限を与えました。

//用户信息
private Map<String,UserDto> userMap = new HashMap<>();
{
    
    
    Set<String> authorities1 = new HashSet<>();
    authorities1.add("p1");//这个p1我们人为让它和/r/r1对应
    Set<String> authorities2 = new HashSet<>();
    authorities2.add("p2");//这个p2我们人为让它和/r/r2对应
    userMap.put("zhangsan",new UserDto("1010","zhangsan","123","张三","133443",authorities1));
    userMap.put("lisi",new UserDto("1011","lisi","456","李四","144553",authorities2));
}

(2) テストリソースの追加
異なるユーザーが異なるリソースにアクセスできることを実現したいため、前提として複数のリソースが存在する必要があるため、LoginController にテストリソース 2 を追加します。

/**
* 测试资源2
* @param session
* @return
*/
@GetMapping(value = "/r/r2",produces = {
    
    "text/plain;charset=UTF‐8"})
public String r2(HttpSession session){
    
    
	String fullname = null;
	Object userObj = session.getAttribute(UserDto.SESSION_USER_KEY);
	if(userObj != null){
    
    
		fullname = ((UserDto)userObj).getFullname();
	}else{
    
    
		fullname = "匿名";
	}
	return fullname + " 访问资源2";
}

(3) 認可インターセプタの実現 認可
インターセプトを実現するには、インターセプタパッケージ配下に SimpleAuthenticationInterceptor インターセプタを定義します。
1. ユーザーがログインしているかどうかを確認します
。 2. ユーザーに操作権限があるかどうかを確認します。

@Component
public class SimpleAuthenticationInterceptor implements HandlerInterceptor {
    
    

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        //在这个方法中校验用户请求的url是否在用户的权限范围内
        //取出用户身份信息
        Object object = request.getSession().getAttribute(UserDto.SESSION_USER_KEY);
        if(object == null){
    
    
            //没有认证,提示登录
            writeContent(response,"请登录");
        }
        UserDto userDto = (UserDto) object;
        //请求的url
        String requestURI = request.getRequestURI();
        if( userDto.getAuthorities().contains("p1") && requestURI.contains("/r/r1")){
    
    
            return true;
        }
        if( userDto.getAuthorities().contains("p2") && requestURI.contains("/r/r2")){
    
    
            return true;
        }
        writeContent(response,"没有权限,拒绝访问");
        return false;
    }

    //响应信息给客户端
    private void writeContent(HttpServletResponse response, String msg) throws IOException {
    
    
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print(msg);
        writer.close();
        response.resetBuffer();
    }
}

WebConfig でインターセプターを構成します。/r/** に一致するリソースは保護されたシステム リソースであり、リソースへのアクセス要求は SimpleAuthenticationInterceptor インターセプターに入ります。

@Configuration//就相当于springmvc.xml文件
@EnableWebMvc
@ComponentScan(basePackages = "com.test.security.springmvc"
        ,includeFilters = {
    
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
    
    

    @Autowired
    SimpleAuthenticationInterceptor simpleAuthenticationInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(simpleAuthenticationInterceptor).addPathPatterns("/r/**");
    }
}

(4) テスト
ログインしていない場合は、/r/r1 と /r/r2 の両方で「最初にログインしてください」というプロンプトが表示されます。
Zhang San がログインすると、Zhang San は p1 権限を持っているため、/r/r1 にアクセスできますが、Zhang San は p2 権限を持っていないため、/r/r2 にアクセスすると、「権限が不十分です」というメッセージが表示されます。
Li Si がログインすると、Li Si は p2 権限を持っているため、/r/r2 にアクセスできますが、Li Si は p1 権限を持っていないため、/r/r1 にアクセスすると、「権限が不十分です」というメッセージが表示されます。
テスト結果はすべて予想された結果と一致しています。

2.6 概要

セッションベースの認証方式は一般的な認証方式であり、現在でも多くの方式が採用されています。この節では、ユーザー認証、認可、セッションの機能的意味と実装ルーチン、つまり何をするのかを誰でも理解できるように、Spring mvc 技術を使って簡単に実装します。私は何をすべきか?

正式な運用プロジェクトでは、ある程度の生産性の向上とソフトウェアの標準化を促進できるため、認証・認可機能の実装にサードパーティのセキュリティ フレームワーク (Spring Security、hiro などのセキュリティ フレームワークなど) の使用を検討することがよくあります。これらのフレームワークは、多くの場合、スケーラビリティが非常に包括的であると考えられています。しかし、欠点も非常に明らかです。サポート範囲を改善するために、これらの一般的なコンポーネントには必要のない機能が多数追加され、構造は比較的抽象的になります。十分に理解していない場合は、一度問題が発生すると、その場所を特定するのが困難になります。

3、Spring Security のクイックスタート

3.1. Spring Security の概要

Spring Security は、Spring ベースのエンタープライズ アプリケーション システムに宣言型セキュリティ アクセス制御ソリューションを提供できるセキュリティ フレームワークです。Spring エコシステムのメンバーであるため、Spring エコシステム全体とともに常に改訂およびアップグレードされます。Spring セキュリティを Spring Boot プロジェクトに追加するのは非常に簡単です。Spring Security を使用すると、大量の重複を記述する必要性が軽減されます。エンタープライズ システム セキュリティ制御 コードは機能します。

3.2. プロジェクトの作成

3.2.1. Maven プロジェクトの作成

Maven プロジェクト security-spring-security を作成して、
次の依存関係を導入します。

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring‐security‐web</artifactId>
	<version>5.1.4.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring‐security‐config</artifactId>
	<version>5.1.4.RELEASE</version>
</dependency>

3.2.2、Springコンテナの設定

@Configuration
@ComponentScan(basePackages = "com.test.security.springmvc",excludeFilters = {
    
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value =
Controller.class)})
public class ApplicationConfig {
    
    
	//在此配置除了Controller的其它bean,比如:数据库链接池、事务管理器、业务bean等。
}

3.2.3、サーブレットコンテキストの設定

@Configuration//就相当于springmvc.xml文件
@EnableWebMvc
@ComponentScan(basePackages = "com.test.security.springmvc"
        ,includeFilters = {
    
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
    
    

    //视频解析器
    @Bean
    public InternalResourceViewResolver viewResolver(){
    
    
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/view/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }  
}

3.2.4、Springコンテナのロード

init パッケージの下に Spring コンテナ初期化クラス SpringApplicationInitializer を定義します。これは WebApplicationInitializer インターフェイスを実装し、Spring コンテナの起動時に WebApplicationInitializer インターフェイスのすべての実装クラスをロードします。

public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    

    //spring容器,相当于加载 applicationContext.xml
    @Override
    protected Class<?>[] getRootConfigClasses() {
    
    
        return new Class[]{
    
    ApplicationConfig.class};
    }

    //servletContext,相当于加载springmvc.xml
    @Override
    protected Class<?>[] getServletConfigClasses() {
    
    
        return new Class[]{
    
    WebConfig.class};
    }

    //url-mapping
    @Override
    protected String[] getServletMappings() {
    
    
        return new String[]{
    
    "/"};
    }
}

3.3. 認証

3.3.1. 認証ページ

springSecurity はデフォルトで認証ページを提供するため、追加の開発は必要ありません。

3.3.2、セキュリティ設定

Spring securityでは、ユーザー名とパスワードによるログイン、ログアウト、セッション管理などの認証機能を提供していますが、これらは設定のみで利用できます。

1) config パッケージで WebSecurityConfig を定義します。セキュリティ設定の内容には、ユーザー情報、パスワード エンコーダ、セキュリティ インターセプト メカニズムが含まれます。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
	//配置用户信息服务
	@Bean
	public UserDetailsService userDetailsService() {
    
    
		InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
		manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
		manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
		return manager;
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() {
    
    
		return NoOpPasswordEncoder.getInstance();
	}
	
	//配置安全拦截机制
	@Override
	protected void configure(HttpSecurity http) throws Exception {
    
    
		http.authorizeRequests()
		.antMatchers("/r/**").authenticated()//url匹配/r/**的资源,经过认证后才能访问。
		.anyRequest().permitAll()//其他url完全开放。
		.and()
		.formLogin().successForwardUrl("/login‐success");//支持form表单认证,认证成功后转向/login-success。
	}
}

userDetailsS​​ervice() メソッドでは、Spring コンテナに UserDetailsS​​ervice を返します。Spring Security はこれを使用してユーザー情報を取得します。一時的に InMemoryUserDetailsManager 実装クラスを使用し、その中に 2 人のユーザー (zhangsan と lisi) を作成し、パスワードと権限を設定します。

configure() では、HttpSecurity を通じてセキュリティ インターセプト ルールを設定します。

2) WebSecurityConfig をロードし
、SpringApplicationInitializer の getRootConfigClasses() メソッドを変更して WebSecurityConfig.class を追加します。

@Override
protected Class<?>[] getRootConfigClasses() {
    
    
	return new Class<?>[] {
    
     ApplicationConfig.class, WebSecurityConfig.class};
}

3.3.3、Spring Securityの初期化

  • 現在の環境で Spring または Spring MVC を使用していない場合は、WebSecurityConfig (Spring Security 構成クラス) をスーパークラスに渡して、構成が取得され、Spring コンテキストが作成されていることを確認する必要があります。

  • 逆に、現在の環境ですでに spring が使用されている場合は、既存の springContext に Spring Security を登録する必要があり (前の手順で WebSecurityConfig を rootcontext にロードしました)、このメソッドでは何もできません。

init パッケージで SpringSecurityApplicationInitializer を定義します。

public class SpringSecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
    
    
	public SpringSecurityApplicationInitializer() {
    
    
		//super(WebSecurityConfig.class);
	}
}

3.3.4. デフォルトのルートパスリクエスト

WebConfig.java の /login にジャンプするデフォルトのリクエスト ルート パスを追加します。この URL は Spring セキュリティを提供します。

//默认Url根路径跳转到/login,此url为spring security提供
@Override
public void addViewControllers(ViewControllerRegistry registry) {
    
    
	registry.addViewController("/").setViewName("redirect:/login");
}

3.3.5. 認証成功ページ

セキュリティ構成では、認証が成功すると /login-success にジャンプします。コードは次のとおりです。

//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
	http.authorizeRequests()
	.antMatchers("/r/**").authenticated()
	.anyRequest().permitAll()
	.and()
	.formLogin().successForwardUrl("/login‐success");
}

Spring Security はフォーム認証をサポートしており、認証が成功すると /login-success に変わります。
LoginController で /login-success を定義します。

@RestController
public class LoginController {
    
    

    @RequestMapping(value = "/login-success",produces = {
    
    "text/plain;charset=UTF-8"})
    public String loginSuccess(){
    
    
        return " 登录成功";
    } 
}

3.3.6. テスト

(1) プロジェクトを開始し、http://localhost:8080/security-spring-security/ パス アドレスにアクセスします。

  • このページは、WebConfig の addViewControllers 構成ルールに従って /login にジャンプします。/login は、pring Security によって提供されるログイン ページです。

(2) ログイン

  • 1. 間違ったユーザー名とパスワードを入力してください
  • 2. 正しいユーザー名とパスワードを入力すると、ログインが成功します。

(3) 終了

  • 1. /logout を要求して終了します
  • 2. ログアウトしてリソースにアクセスすると、自動的にログインページにジャンプします

3.4. 認可

認可を実装するには、ユーザーのアクセスを傍受して検証し、ユーザーの権限で指定されたリソースを操作できるかどうかを検証する必要がありますが、Spring Securityではデフォルトで認可の実装方法を提供しています。

LoginController に /r/r1 または /r/r2 を追加します

@RestController
public class LoginController {
    
    

    @RequestMapping(value = "/login-success",produces = {
    
    "text/plain;charset=UTF-8"})
    public String loginSuccess(){
    
    
        return " 登录成功";
    }

    /**
     * 测试资源1
     * @return
     */
    @GetMapping(value = "/r/r1",produces = {
    
    "text/plain;charset=UTF-8"})
    public String r1(){
    
    
        return " 访问资源1";
    }

    /**
     * 测试资源2
     * @return
     */
    @GetMapping(value = "/r/r2",produces = {
    
    "text/plain;charset=UTF-8"})
    public String r2(){
    
    
        return " 访问资源2";
    }
}

セキュリティ構成クラス WebSecurityConfig.java で認可ルールを構成します。

//安全拦截机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
   http.authorizeRequests()
          .antMatchers("/r/r1").hasAuthority("p1")
          .antMatchers("/r/r2").hasAuthority("p2")
          .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
          .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
          .and()
          .formLogin()//允许表单登录
          .successForwardUrl("/login-success");//自定义登录成功的页面地址

}
  • .antMatchers("/r/r1").hasAuthority("p1") は、/r/r1 リソースにアクセスする URL には p1 権限が必要であることを意味します。

  • .antMatchers(“/r/r2”).hasAuthority(“p2”) は、/r/r2 リソースにアクセスする URL には p2 権限が必要であることを意味します。

テスト:
1. ログインに成功します。
2. /r/r1 および /r/r2 にアクセスします。許可があれば正常にアクセスできますが、そうでない場合は 403 (アクセス拒否) が返されます。

3.5 概要

Spring Securityを利用した認証・認可をクイックスタートで実現します Spring Securityはアカウント番号とパスワードによる認証方式を提供します セキュリティ設定によりリクエストの傍受・認可機能を実現します Spring Securityではこれ以外にも様々なことができます

4. Spring Securityアプリケーションの詳細説明

4.1. SpringBoot の統合

4.1.1 Spring Boot の概要

Spring Boot は Spring の迅速な開発フレームワークです。Spring 4.0 の設計に基づいており、Spring Boot を使用して開発すると、面倒なプロジェクトの構築と構成を回避できます。同時に、多数の一般的なフレームワークを統合し、依存パッケージを迅速にインポートし、依存パッケージ間の競合を回避します。基本的に、一般的に使用される開発フレームワーク (MyBatis、Dubbo など)、特に Spring クラウド、Spring mvc、Spring security などの Spring ファミリをサポートします。 Spring Boot 開発を使用すると、生産性が大幅に向上します。 Spring Boot の使用率は非常に高いです。

次に、Spring Boot を使用して Spring Security アプリケーションを開発する方法について説明します。Spring Boot は、Spring Security アプリケーションを開発するための spring-boot-starter-security を提供します。

4.1.2. Maven プロジェクトの作成

1) Maven プロジェクト security-spring-boot を作成します。
2) 次の依存関係を導入します。

<dependencies>
    <!-- 以下是>spring boot依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 以下是>spring security依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>


    <!-- 以下是jsp依赖-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <scope>provided</scope>
    </dependency>
    <!--jsp页面使用jstl标签 -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    <!--用于编译jsp -->
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

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

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
</dependencies>

4.1.3、スプリングコンテナの構成

SpringBoot プロジェクトの起動では、起動クラスが配置されているパッケージ内のすべての Bean が自動的にスキャンされ、Spring コンテナにロードされます。
1) Spring Boot 設定ファイル
リソースの下に application.properties を追加します。内容は次のとおりです。

server.port=8080
server.servlet.context‐path=/security‐springboot
spring.application.name = security‐springboot

2) Spring Boot スタートアップ クラス

@SpringBootApplication
public class SecuritySpringBootApp {
    
    
	public static void main(String[] args) {
    
    
		SpringApplication.run(SecuritySpringBootApp.class, args);
	}
}

4.1.4、サーブレットコンテキストの設定

Spring Boot Starter の自動アセンブリ機構により、ここでは @EnableWebMvc と @ComponentScan を使用する必要はなく、WebConfig は次のようになります。

@Configuration//就相当于springmvc.xml文件
public class WebConfig implements WebMvcConfigurer {
    
    

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/").setViewName("redirect:/login-view");

    }
}

ビデオパーサーは application.properties で構成されます

spring.mvc.view.prefix=/WEB‐INF/views/
spring.mvc.view.suffix=.jsp

4.1.5. セキュリティ設定

Spring Boot Starter の自動アセンブリ機構により、ここで @EnableWebSecurity を使用する必要はなく、WebSecurityConfig の内容は次のとおりです。

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

	//配置用户信息服务
	@Bean
	public UserDetailsService userDetailsService() {
    
    
		InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
		manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
		manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
		return manager;
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() {
    
    
		return NoOpPasswordEncoder.getInstance();
	}
	
	//配置安全拦截机制
	@Override
	protected void configure(HttpSecurity http) throws Exception {
    
    
		http.authorizeRequests()
		.antMatchers("/r/**").authenticated()
		.anyRequest().permitAll()
		.and()
		.formLogin().successForwardUrl("/login‐success");
	}
}

4.1.6. テスト

@RestController
public class LoginController {
    
    

    @RequestMapping(value = "/login-success",produces = {
    
    "text/plain;charset=UTF-8"})
    public String loginSuccess(){
    
    
        return " 登录成功";
    }

    /**
     * 测试资源1
     * @return
     */
    @GetMapping(value = "/r/r1",produces = {
    
    "text/plain;charset=UTF-8"})
    public String r1(){
    
    
        return " 访问资源1";
    }

    /**
     * 测试资源2
     * @return
     */
    @GetMapping(value = "/r/r2",produces = {
    
    "text/plain;charset=UTF-8"})
    public String r2(){
    
    
        return " 访问资源2";
    }
}

(1) プロジェクトを開始し、http://localhost:8080/security-springboot/ パス アドレスにアクセスします。

  • このページは、WebConfig の addViewControllers 構成ルールに従って /login にジャンプします。/login は、pring Security によって提供されるログイン ページです。

(2) ログイン

  • 1. 間違ったユーザー名とパスワードを入力してください
  • 2. 正しいユーザー名とパスワードを入力すると、ログインが成功します。

(3) 終了

  • 1. /logout を要求して終了します
  • 2. ログアウトしてリソースにアクセスすると、自動的にログインページにジャンプします

4.2. 動作原理

4.2.1. 構造の概要

Spring Security が解決する問題はセキュリティ アクセス制御であり、セキュリティ アクセス制御機能は実際にはシステムに入るすべてのリクエストをインターセプトし、各リクエストが期待するリソースにアクセスできるかどうかを検証します。これまでの知識の学習によれば、Filter や AOP などの技術で実現できますが、Spring Security の Web リソースの保護は Filter によって実現されるため、この Filter から始めて Spring Security の原理を徐々に深めていきます。

Spring Security が初期化されると、javax.servlet.Filter を実装する org.springframework.security.web.FilterChainProxy タイプの SpringSecurityFilterChain という名前のサーブレット フィルターが作成されるため、以下に示すように、外部リクエストはこのクラスを通過します。チェーン構造図:

ここに画像の説明を挿入

FilterChainProxy はプロキシです。実際に機能するのは、FilterChainProxy の SecurityFilterChain に含まれるフィルタです。同時に、これらのフィルタは Spring によって Bean として管理されます。これらは Spring Security の中核であり、独自の責任を持ちますが、直接処理することはありませんユーザー認証。ユーザー認証を直接処理するのではなく、 AuthenticationManager と AccessDecisionManagerに渡して処理します。次の図は、FilterChainProxy 関連クラスの UML 図です。

ここに画像の説明を挿入

Spring Security 機能の実現は、主に一連のフィルター チェーンによって完了します。

ここに画像の説明を挿入

以下に、フィルタ チェーン内の主なフィルタとその機能について説明します。

  • SecurityContextPersistenceFilter このフィルターは、インターセプト プロセス全体の入口と出口 (つまり、最初と最後のインターセプター) であり、リクエストの開始時に構成された SecurityContextRepository から SecurityContext を取得し、それを SecurityContextHolder に設定します。リクエストが完了したら、SecurityContextHolder が保持する SecurityContext を構成済みの SecurityContextRepository に保存し、securityContextHolder が保持する SecurityContext をクリアします。

  • UsernamePasswordAuthenticationFilter は、フォーム送信からの認証を処理するために使用されます。フォームには、対応するユーザー名とパスワードを指定する必要があります。また、ログインの成功または失敗後に処理するための AuthenticationSuccessHandler および AuthenticationFailureHandler もあり、要件に応じて変更できます。

  • FilterSecurityInterceptor は Web リソースを保護するために使用され、AccessDecisionManager を使用して現在のユーザーへのアクセスを承認します。これについては以前に詳しく説明しました。

  • ExceptionTranslationFilter は、FilterChain からすべての例外をキャッチして処理できます。ただし、AuthenticationException と AccessDeniedException の 2 種類の例外のみを処理し、他の例外は引き続きスローされます。

4.2.2. 認証プロセス

4.2.2.1. 認証プロセス

ここに画像の説明を挿入

認証プロセスを詳しく見てみましょう。

  • ユーザーによって送信されたユーザー名とパスワードは、SecurityFilterChain の UsernamePasswordAuthenticationFilter フィルターによって取得され、リクエスト Authentication (通常は実装クラス UsernamePasswordAuthenticationToken) としてカプセル化されます。

  • 次に、フィルターは認証のために AuthenticationManager に Authentication を送信します。

  • 認証が成功すると、AuthenticationManager ID マネージャーは、情報 (上記の許可情報、ID 情報、詳細情報を含むが、通常はパスワードは削除されます) が詰め込まれた Authentication インスタンスを返します。

  • SecurityContextHolder セキュリティ コンテキスト コンテナは、SecurityContextHolder.getContext().setAuthentication(…) メソッドを通じて、手順 3 の情報が入力された Authentication をコンテナに設定します。

    • AuthenticationManager インターフェイス (認証マネージャー) は、認証に関連するコア インターフェイスであり、認証を開始するための開始点であることがわかり、その実装クラスは ProviderManager です。Spring Security は複数の認証方法をサポートしているため、ProviderManager は複数の認証方法を格納するリスト リストを維持し、最終的な実際の認証作業は AuthenticationProvider によって行われます。Web フォームの対応する AuthenticationProvider 実装クラスは DaoAuthenticationProvider であり、UserDetails の取得を担当する UserDetailsS​​ervice を内部に保持していることがわかります。最後に、AuthenticationProvider は UserDetails を Authentication に入力します。

認証コア コンポーネント間の一般的な関係は次のとおりです。

ここに画像の説明を挿入

4.2.2.2、認証プロバイダー

前の Spring Security 認証プロセスを通じて、認証マネージャー (AuthenticationManager) が AuthenticationProvider に認証作業の完了を委託することがわかりました。

AuthenticationProvider は次のように定義されるインターフェイスです。

public interface AuthenticationProvider {
    
    
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
	boolean supports(Class<?> var1);
}

authenticate() メソッドは認証の実装プロセスを定義し、そのパラメータはログイン ユーザーによって送信されたユーザーとパスワードを含む Authentication です。戻り値も Authentication です。これは、認証が成功した後にユーザーの権限とその他の情報を再構成した後に生成されます。

Spring Security は、複数の認証メソッドを格納する List リストを維持し、異なる認証メソッドは異なる AuthenticationProvider を使用します。たとえば、ユーザー名とパスワードを使用してログインする場合は AuthenticationProvider1 を使用し、テキスト メッセージを使用してログインする場合は AuthenticationProvider2 を使用するなど、多くの例があります。

各 AuthenticationProvider は、サポートする認証方法を示すために、supports() メソッドを実装する必要があります。たとえば、フォーム認証を使用する場合、Spring Security はリクエストを送信するときに UsernamePasswordAuthenticationToken を生成します。これは、によって送信されたユーザー名とパスワードの情報をカプセル化する認証です。ユーザーです。それに応じて、どの AuthenticationProvider がそれを処理するのでしょうか?

DaoAuthenticationProvider の基本クラス AbstractUserDetailsAuthenticationProvider に次のコードが見つかりました。

public boolean supports(Class<?> authentication) {
    
    
	return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}

つまり、Web フォームがユーザー名とパスワードを送信すると、Spring Security は DaoAuthenticationProvider によって処理されます。

最後に、インターフェイスである Authentication (認証情報) の構造を見てみましょう。先ほど説明した UsernamePasswordAuthenticationToken はその実装の 1 つです。

//Authentication是spring security包中的接口,直接继承自Principal类,而Principal是位于 java.security包中的。它是表示着一个抽象主体身份,任何主体都有一个名称,因此包含一个getName()方法。
public interface Authentication extends Principal, Serializable {
    
     
	//权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系列字符串。
	Collection<? extends GrantedAuthority> getAuthorities(); 
	
	//凭证信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
	Object getCredentials(); 
	
	//细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。
	Object getDetails(); 
	
	//身份信息,大部分情况下返回的是UserDetails接口的实现类,UserDetails代表用户的详细信息,那从Authentication中取出来的UserDetails就是当前登录用户信息,它也是框架中的常用接口之一。
	Object getPrincipal(); 
	
	boolean isAuthenticated();
	
	void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

4.2.2.3、UserDetailsサービス

1) UserDetailsS​​ervice について理解する
これで、DaoAuthenticationProvider が Web フォームの認証ロジックを処理することがわかりました。認証が成功すると、ID 情報 (プリンシパル) を含む認証 (UsernamePasswordAuthenticationToken によって実装) が取得されます。この ID 情報はオブジェクトであり、ほとんどの場合、UserDetails オブジェクトに強制的に組み込むことができます。

DaoAuthenticationProvider には、ユーザー名に基づいてユーザー情報 UserDetails (パスワードを含む) を抽出する役割を担う UserDetailsS​​ervice インスタンスが含まれています。DaoAuthenticationProvider は、UserDetailsS​​ervice によって抽出されたユーザー パスワードが、認証成功の主要な基準としてユーザーによって送信されたパスワードと一致するかどうかを比較します。カスタム UserDetailsS​​ervice は、カスタム認証を定義する Spring Bean として公開されます。

public interface UserDetailsService {
    
    
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

DaoAuthenticationProvider と UserDetailsS​​ervice の役割を混同している人が多いですが、実際、UserDetailsS​​ervice は特定の場所 (通常はデータベース) からユーザー情報をロードすることのみを担当し、それ以上のことは行いません。DaoAuthenticationProvider はより大きな責任を持ち、完全な認証
プロセスを完了し、同時に UserDetails を Authentication に入力します。

UserDetails がユーザー情報であることは上で述べましたが、実際の色を見てみましょう。

public interface UserDetails extends Serializable {
    
    
	Collection<? extends GrantedAuthority> getAuthorities();
	String getPassword();
	String getUsername();
	boolean isAccountNonExpired();
	boolean isAccountNonLocked();
	boolean isCredentialsNonExpired();
	boolean isEnabled();
}

これは認証インターフェイスに非常に似ており、たとえば、両方ともユーザー名と権限を持っています。認証の getCredentials() と UserDetails の getPassword() は、別の方法で処理する必要があります。前者はユーザーによって送信されたパスワード資格情報であり、後者はユーザーによって実際に保存されたパスワードです。認証は実際には 2 つの比較です。Authentication の getAuthorities() は、実際には UserDetails の getAuthorities() を渡すことによって形成されます。認証インターフェースの getDetails() メソッドを覚えていますか? このうち、UserDetails には、AuthenticationProvider 認証後にユーザーの詳細が入力されます。

UserDetailsS​​erviceとUserDetailsを実装することで、ユーザー情報の取得方法とユーザー情報フィールドの拡張が完了します。

Spring Securityが提供するInMemoryUserDetailsManager(メモリ認証)とJdbcUserDetailsManager(jdbc認証)はUserDetailsS​​erviceの実装クラスであり、主な違いはユーザをメモリからロードするかデータベースからロードするかである。

2) テスト

カスタム UserDetailsS​​ervice

@Service
public class SpringDataUserDetailsService implements UserDetailsService {
    
    

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
		//登录账号
		System.out.println("username="+username);
		//根据账号去数据库查询...
		//这里暂时使用静态数据
		UserDetails userDetails = User.withUsername(username).password("123").authorities("p1").build();
		return userDetails;
	}
}

セキュリティ構成クラスの UserDetailsS​​ervice の定義をシールドします。

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

    //定义用户信息服务(查询用户信息)
/*
    @Bean
    public UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
        manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
        return manager;
    }
*/

}

プロジェクトを再起動し、認証を要求すると、SpringDataUserDetailsS​​ervice の loadUserByUsername メソッドが呼び出され、ユーザー情報が照会されます。

4.2.2.4、パスワードエンコーダ

1) PasswordEncoder DaoAuthenticationProvider について理解する
認証プロセッサが UserDetailsS​​ervice を通じて UserDetails を取得した後、それは Authentication リクエストのパスワードとどのように比較されるのでしょうか。

ここで、さまざまな暗号化タイプに適応するために、Spring Security は抽象化を行っています。DaoAuthenticationProvider は、PasswordEncoder インターフェイスのmatches メソッドを通じてパスワード比較を実行します。具体的なパスワード比較の詳細は実装によって異なります。

public interface PasswordEncoder {
    
    

	String encode(CharSequence var1);

	boolean matches(CharSequence var1, String var2);
	
	default boolean upgradeEncoding(String encodedPassword) {
    
    
		return false;
	}
}

Spring Security には、すぐに使用できる多くの組み込みの PasswordEncoder が用意されており、特定の PasswordEncoder を使用するには、次のステートメントを作成するだけで済みます。

@Bean
public PasswordEncoder passwordEncoder() {
    
    
	return NoOpPasswordEncoder.getInstance();
}

NoOpPasswordEncoder は文字列照合方式を採用しており、パスワードの暗号化や比較は行いません。パスワードの比較プロセスは次のとおりです。

  • 1. ユーザーはパスワードを (プレーンテキストで) 入力します。

  • 2. DaoAuthenticationProvider は UserDetails (ユーザーの正しいパスワードを保存する) を取得します。

  • 3. DaoAuthenticationProvider は、PasswordEncoder を使用して、入力されたパスワードと正しいパスワードを検証します。パスワードが一致する場合は検証に合格し、一致しない場合は検証に失敗します。

NoOpPasswordEncoder の検証ルールは、入力されたパスワードと UserDetails の正しいパスワードを比較し、文字列の内容が一致する場合は検証に合格し、一致しない場合は検証に失敗します。

実際のプロジェクトでは、BCryptPasswordEncoder、Pbkdf2PasswordEncoder、SCryptPasswordEncoder などを使用することをお勧めしますので、興味があれば、これらの PasswordEncoder の具体的な実装を確認してください。

2) BCryptPasswordEncoder を使用する
1. BCryptPasswordEncoder を構成して
、セキュリティ構成クラスで定義します。

@Bean
public PasswordEncoder passwordEncoder() {
    
    
	return new BCryptPasswordEncoder();
}

テストでは認証が失敗したことが判明し、次のメッセージが表示されました: エンコードされたパスワードは BCrypt のように見えません。
理由: 元のパスワード (例: 123) は UserDetails に保存されているため、BCrypt 形式ではありません。DaoAuthenticationProvider の 33 行目のコードを追跡して userDetails の内容を確認し、38 行目のコードを追跡して PasswordEncoder の種類を確認します。

2. BCrypt のテスト
次のコードを使用して、BCrypt の暗号化と検証の方法をテストします。
依存関係を追加します。

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring‐boot‐starter‐test</artifactId>
	<scope>test</scope>
</dependency>

テストメソッドを書きます。

@RunWith(SpringRunner.class)
public class TestBCrypt {
    
    

    @Test
    public void testBCrypt(){
    
    

        //对密码进行加密
        String hashpw = BCrypt.hashpw("123", BCrypt.gensalt());
        System.out.println(hashpw);

        //校验密码
        boolean checkpw = BCrypt.checkpw("123", "$2a$10$NlBC84MVb7F95EXYTXwLneXgCca6/GipyWR5NHm8K0203bSQMLpvm");  
        System.out.println(checkpw);
    }
}

3. セキュリティ構成クラスを変更して、
UserDetails の元のパスワードを BCrypt 形式に変更します。

//配置用户信息服务
@Bean
public UserDetailsService userDetailsService() {
    
    
	InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
	manager.createUser(User.withUsername("zhangsan").password("$2a$10$1b5mIkehqv5c4KRrX9bUj.A4Y2hug3IGCnMCL5i4RpQrYV12xNKye").authorities("p1").build());
	return manager;
}

実際のプロジェクトのデータベースに保存されるパスワードは、オリジナルのパスワードではなく、暗号化されたパスワードです。

4.2.3. 認可プロセス

4.2.3.1. 認可プロセス

クイックスタートを通じて、Spring Security が http.authorizeRequests() を通じて Web リクエストを承認および保護できることがわかりました。Spring Security は標準のフィルターを使用して Web リクエストの傍受を確立し、最終的にリソースへの承認されたアクセスを実現します。

Spring Securityの認可プロセスは次のとおりです。

ここに画像の説明を挿入

認可プロセスを分析します。

  • リクエストをインターセプトすると、保護された Web リソースへの認証されたユーザーのアクセスが、SecurityFilterChain の FilterSecurityInterceptor のサブクラスによってインターセプトされます。

  • リソース アクセス ポリシーを取得します。FilterSecurityInterceptor は、現在のリソースにアクセスするために必要なアクセス許可のコレクションを、SecurityMetadataSource のサブクラスである DefaultFilterInvocationSecurityMetadataSource から取得します。

    • SecurityMetadataSource は実際には読み取りアクセス戦略の抽象化であり、読み取りコンテンツは実際に構成したアクセス ルールです。読み取りアクセス戦略は次のとおりです。
//安全拦截机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
   http.authorizeRequests()
          .antMatchers("/r/r1").hasAuthority("p1")
          .antMatchers("/r/r2").hasAuthority("p2")
          .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
          .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
          .and()
          .formLogin()//允许表单登录
          .successForwardUrl("/login-success");//自定义登录成功的页面地址

}
  • 最後に、FilterSecurityInterceptor は AccessDecisionManager を呼び出して承認の決定を行い、決定に合格した場合はリソースへのアクセスが許可され、そうでない場合はアクセスが禁止されます。
    • AccessDecisionManager (アクセス意思決定マネージャー) のコア インターフェイスは次のとおりです。
public interface AccessDecisionManager {
    
    
	/**
	* 通过传递的参数来决定用户是否有访问对应受保护资源的权限
	*/
	void decide(Authentication authentication , Object object, Collection<ConfigAttribute> configAttributes ) throws AccessDeniedException, InsufficientAuthenticationException;
	//略..
}

ここでは、決定のパラメータに焦点を当てます。

  • 認証: リソースへのアクセスを希望する訪問者の ID
  • オブジェクト: アクセスされる保護されたリソース。Web リクエストは FilterInvocation に対応します。
  • configAttributes: SecurityMetadataSource を通じて取得される、保護されたリソースのアクセス ポリシーです。

決定インターフェースは、現在のユーザーが対応する保護されたリソースにアクセスできるかどうかを識別するために使用されます。

4.2.3.2. 認可の決定

AccessDecisionManager は投票を使用して、保護されたリソースにアクセスできるかどうかを判断します。

ここに画像の説明を挿入

上の図からわかるように、AccessDecisionManager に含まれる一連の AccessDecisionVoter は、Authentication が保護されたオブジェクトにアクセスする権利があるかどうかの投票に使用され、AccessDecisionManager は投票結果に基づいて最終決定を行います。

AccessDecisionVoter は 3 つのメソッドを定義するインターフェースであり、具体的な構造は以下のとおりです。

public interface AccessDecisionVoter<S> {
    
    
	int ACCESS_GRANTED = 1;
	int ACCESS_ABSTAIN = 0;
	int ACCESS_DENIED =1;
	
	boolean supports(ConfigAttribute var1);
	boolean supports(Class<?> var1);
	int vote(Authentication var1, S var2, Collection<ConfigAttribute> var3);
}

vote() メソッドの戻り結果は、AccessDecisionVoter で定義された 3 つの定数のいずれかになります。ACCESS_GRANTED は同意、ACCESS_DENIED は拒否、ACCESS_ABSTAIN は棄権を意味します。AccessDecisionVoter が現在の認証に対応する保護オブジェクトにアクセスする権利があるかどうかを判断できない場合、その vote() メソッドの戻り値は ACCESS_ABSTAIN である必要があります。

Spring Security には、次の 3 つの組み込みの投票ベースの AccessDecisionManager 実装クラスがあります。AffirmativeBasedConsensusBased、およびUnanimousBasedです。

AffirmativeBasedのロジックは次のとおりです:
(1) ACCESS_GRANTED に投票する AccessDecisionVoter が存在する限り、ユーザーはアクセスを許可されます; (2
) すべての棄権も承認された場合;
(3) 誰も投票しなかったが、誰かが投票した場合これに対しては、AccessDeniedException がスローされます。

Spring セキュリティはデフォルトで AffirmativeBased を使用します。

ConsensusBasedのロジックは次のとおりです。
(1) 賛成票が反対票よりも多ければ、可決を意味します。
(2) 逆に、否定票が肯定票よりも多い場合は、AccessDeniedException がスローされます。
(3) 承認票が反対票と同じで 0 に等しくなく、allowIfEqualGrantedDeniedDecisions 属性の値が true の場合は、承認されたことを意味します。それ以外の場合は、例外 AccessDeniedException がスローされます
パラメータallowIfEqualGrantedDeniedDecisionsの値のデフォルトはtrueです。
(4) すべての AccessDecisionVoters が棄権した場合、パラメータallowIfAllAbstainDecisionsの値に依存します。値がtrueの場合は合格を意味し、それ以外の場合は例外AccessDeniedExceptionがスローされます。パラメータallowIfAllAbstainDecisionsの値のデフォルトはfalseです。

UnanimousBasedのロジックは他の 2 つの実装とは少し異なり、他の 2 つは投票のために保護オブジェクトのすべての構成属性を一度に AccessDecisionVoter に渡しますが、UnanimousBased は投票のために一度に 1 つの ConfigAttribute だけを AccessDecisionVoter に渡します。これは、AccessDecisionVoter のロジックが、渡された ConfigAttributes の 1 つが一致する限り賛成に投票するが、UnanimousBased での投票結果が必ずしも賛成ではないことも意味します。

具体的には、UnanimousBased のロジックは次のとおりです。
(1) 保護されたオブジェクト構成の特定の ConfigAttribute が AccessDecisionVoter によって反対された場合、AccessDeniedException がスローされます。
(2) 反対票がなく、賛成票がある場合、可決を意味します。
(3) すべて棄権した場合は、パラメータallowIfAllAbstainDecisionsの値に依存し、trueの場合はパスし、falseの場合はAccessDeniedExceptionをスローします。

Spring Security には、RoleVoter、AuthenticatedVoterWebExpressionVoter などの組み込みの Voter 実装クラスもいくつかあります。学習のための情報を自分で確認することができます。

4.3. カスタム認証

Spring Security は非常に優れた認証拡張メソッドを提供します。たとえば、クイックスタートではユーザー情報をメモリに保存します。実際の開発ではユーザー情報は通常データベースにあります。Spring Security はデータベースからユーザー情報を読み取ることができます。Spring Security はさまざまな機能もサポートしています。認可方法の。

4.3.1、カスタムログインページ

クイック スタートでは、ログイン ページがどこから来たのか疑問に思うかもしれません。HTML ファイルや JSP ファイルが提供されていなかったためです。Spring Securityのデフォルト設定ではログインページURLが明示的に設定されていないため、Spring Securityは有効な機能に応じてログインページURLを自動生成し、デフォルトURLを使用してログイン送信内容を処理し、ログイン後にデフォルトURLにジャンプします。 、など。自動生成されたログイン ページは、すぐに起動して実行するのに便利ですが、ほとんどのアプリケーションでは独自のログイン ページを定義する必要があります。

4.3.1.1. 認証ページ

<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" %>
<html>
<head>
    <title>用户登录</title>
</head>
<body>
<form action="login" method="post">
    用户名:<input type="text" name="username"><br>&nbsp;&nbsp;&nbsp;码:
    <input type="password" name="password"><br>
    <input type="submit" value="登录">
</form>
</body>
</html>

4.3.1.2、認証ページの設定

WebConfig.java で認証ページのアドレスを構成します。

//默认Url根路径跳转到/login,此url为spring security提供
@Override
public void addViewControllers(ViewControllerRegistry registry) {
    
    
	registry.addViewController("/").setViewName("redirect:/login‐view");
	registry.addViewController("/login‐view").setViewName("login");
}

4.3.1.3、セキュリティ設定

WebSecurityConfig でテーブルと章のログイン情報を構成します。

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

    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
                .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
                .and()
                .formLogin()//允许表单登录
                .loginPage("/login-view")//指定我们自己的登录页,spring security以重定向方式跳转到/login-view
                .loginProcessingUrl("/login")//指定登录处理的URL,也就是用户名、密码表单提交的目的路径
                .successForwardUrl("/login-success")//指定登录成功后的跳转URL
        		.permitAll();
                
    }
}

4.3.1.4. テスト

アドレス: localbost:8000/security-springboot/login-view
ユーザーが認証されていない場合、システム リソースへのアクセスはログイン ビュー ページにリダイレクトされます。

CSRF (クロスサイト リクエスト フォージェリ) の発生を防ぐために、Spring セキュリティでは get を除くほとんどのメソッドが制限されています。
解決策 1:
シールド CSRF 制御、つまりスプリング セキュリティによって CSRF が制限されなくなりました。
WebSecurityConfig を構成する

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
	http.csrf().disable() //屏蔽CSRF控制,即spring security不再限制CSRF
	...
}

解決策 2:
login.jsp ページにトークンを追加すると、Spring Security がトークンを検証します。トークンが正当な場合は、リクエストを続行できます。
login.jspを変更する

<form action="login" method="post">
	<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
	...
</form>

4.3.2、接続データベース認証

先ほどの例ではユーザー情報をメモリ上に保存していましたが、実際のプロジェクトではデータベースにユーザー情報を保存しますが、この部分ではデータベースからユーザー情報を読み出すことを実現します。認証プロセスに関するこれまでの研究によれば、ユーザー アカウントに応じてデータベースにクエリを実行するには、UserDetailService を再定義するだけで済みます。

4.3.2.1. データベースの作成

user_db データベースを作成する

CREATE DATABASE `user_db` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

t_userテーブルを作成する

CREATE TABLE `t_user` (
	`id` bigint(20) NOT NULL COMMENT '用户id',
	`username` varchar(64) NOT NULL,
	`password` varchar(64) NOT NULL,
	`fullname` varchar(255) NOT NULL COMMENT '用户姓名',
	`mobile` varchar(11) DEFAULT NULL COMMENT '手机号',
	PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC

4.3.2.2、コードの実装

1)
application.properties 構成でdataSource を定義します

spring.datasource.url=jdbc:mysql://localhost:3306/user_db
spring.datasource.username=root
spring.datasource.password=mysql
spring.datasource.driver‐class‐name=com.mysql.jdbc.Driver

2) 依存関係を追加する

<dependency>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

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

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

3) Dao を定義する

@Data
public class UserDto {
    
    
    private String id;
    private String username;
    private String password;
    private String fullname;
    private String mobile;
}

Dao パッケージで UserDao を定義します。

@Repository
public class UserDao {
    
    
	@Autowired
	JdbcTemplate jdbcTemplate;
	
	public UserDto getUserByUsername(String username){
    
    
		String sql ="select id,username,password,fullname from t_user where username = ?";
		List<UserDto> list = jdbcTemplate.query(sql, new Object[]{
    
    username}, new BeanPropertyRowMapper<>(UserDto.class));
		if(list == null && list.size() <= 0){
    
    
			return null;
		}
		return list.get(0);
	}
}

4.3.2.3. UserDetailService の定義

サービス パッケージの下に SpringDataUserDetailsS​​ervice を定義します。

@Service
public class SpringDataUserDetailsService implements UserDetailsService {
    
    
	@Autowired
	UserDao userDao;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
		//登录账号
		System.out.println("username="+username);
		//根据账号去数据库查询...
		UserDto user = userDao.getUserByUsername(username);
		if(user == null){
    
    
			return null;
		}
		//这里暂时使用静态数据
		UserDetails userDetails = User.withUsername(user.getFullname()).password(user.getPassword()).authorities("p1").build();
		return userDetails;
	}
}

4.3.2.4. テスト

アカウント番号とパスワードを入力して、認証とトラッキング コードを要求します。

4.3.2.5. BCryptPasswordEncoder の使用

前述の PasswordEncoder の使用方法に従って、BCryptPasswordEncoder を使用するには、次のタスクを完了する必要があります。
1. セキュリティ構成クラスで BCryptPasswordEncoder を定義します。

@Bean
public PasswordEncoder passwordEncoder() {
    
    
	return new BCryptPasswordEncoder();
}

2. UserDetails のパスワードは BCrypt 形式で保存されます
ユーザー情報はデータベースから照会されるため、データベースのパスワードは BCrypt 形式で保存する必要があります。

4.4. セッション

ユーザーが認証された後、ユーザーの操作ごとの認証を回避するために、ユーザーの情報をセッションに保存できます。Spring セキュリティはセッション管理を提供し、認証が渡された後、ID 情報が SecurityContextHolder コンテキストに配置され、SecurityContext が現在のスレッドにバインドされて、ユーザー ID の取得が容易になります。

4.4.1. ユーザー ID の取得

LoginControllerを記述し、/r/r1と/r/r2のテストリソースを実現し、loginSuccessメソッドを修正します。getUsernameメソッドに注目してください。Spring Securityが現在のログインユーザー情報を取得するメソッドはSecurityContextHolder.getContext()です。 get認証()

@RestController
public class LoginController {
    
    
	/**
	* 用户登录成功
	* @return
	*/
	@RequestMapping(value = "/login‐success",produces = {
    
    "text/plain;charset=UTF‐8"})
	public String loginSuccess(){
    
    
		String username = getUsername()
		return username + " 登录成功";
	}

	/**
	* 获取当前登录用户名
	* @return
	*/
	private String getUsername(){
    
    
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		if(!authentication.isAuthenticated()){
    
    
			return null;
		}
		Object principal = authentication.getPrincipal();
		String username = null;
		if (principal instanceof org.springframework.security.core.userdetails.UserDetails) {
    
    
			username = ((org.springframework.security.core.userdetails.UserDetails)principal).getUsername();
		} else {
    
    
			username = principal.toString();
		}
		return username;
	}
	
	/**
	* 测试资源1
	* @return
	*/
	@GetMapping(value = "/r/r1",produces = {
    
    "text/plain;charset=UTF‐8"})
	public String r1(){
    
    
		String username = getUsername();
		return username + " 访问资源1";
	}

	/**
	* 测试资源2
	* @return
	*/
	@GetMapping(value = "/r/r2",produces = {
    
    "text/plain;charset=UTF‐8"})
	public String r2(){
    
    
		String username = getUsername();
		return username + " 访问资源2";
	}
}

4.4.2、セッション制御

次のオプションを使用して、セッションがいつ作成されるか、および Spring Security がセッションとどのように対話するかを正確に制御できます。

機構 説明
いつも セッションが存在しない場合はセッションを作成します
もし必要なら ログイン時に必要に応じてセッションを作成します (デフォルト)
一度もない Spring Security はセッションを作成しませんが、アプリケーション内の別の場所でセッションが作成された場合、Spring Security はそれを使用します。
ステートレス SpringSecurity はセッションを作成したり、セッションを使用したりすることはありません

このオプションを次の構成で構成します。

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

	@Override
	protected void configure(HttpSecurity http) throws Exception {
    
    
		http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
	}
}

デフォルトでは、Spring Security はログインに成功したユーザーごとに新しいセッション (ifRequired) を作成します。

「never」を選択すると、ログインに成功したユーザーに対してセッションを作成しないように Spring Security に指示しますが、アプリケーションがどこかに新しいセッションを作成した場合、Spring Security はそれを使用します。

ステートレスを使用する場合、Spring Security は正常にログインしたユーザーのセッションを作成せず、アプリケーションは新しいセッションを許可しないことを意味します。また、Cookie が使用されていないことを意味するため、すべてのリクエストで再認証が必要になります。このステートレス アーキテクチャは、REST API およびそのステートレス認証メカニズムとうまく連携します。

セッション タイムアウト 次
のように sevlet コンテナでセッション タイムアウトを設定し、セッションの有効期間を 3600 秒に設定できます。
スプリング ブート設定ファイル:

server.servlet.session.timeout=3600s

セッションがタイムアウトした後、Spring Security を通じてジャンプ パスを設定できます。

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

	@Override
	protected void configure(HttpSecurity http) throws Exception {
    
    
		http.sessionManagement()
			.expiredUrl("/login‐view?error=EXPIRED_SESSION")
			.invalidSessionUrl("/login‐view?error=INVALID_SESSION");
	}
}

expired はセッションの有効期限が切れたことを意味し、invalidSession は受信したセッション ID が無効であることを意味します。

セキュアなセッション Cookie
httpOnly とセキュア タグを使用してセッション Cookie を保護できます。

  • httpOnly: true の場合、ブラウザ スクリプトは Cookie にアクセスできなくなります。

  • secure: true の場合、Cookie は HTTPS 接続経由でのみ送信されます。

スプリングブート設定ファイル:

server.servlet.session.cookie.http‐only=true
server.servlet.session.cookie.secure=true

4.6. 終了

Spring セキュリティはデフォルトでログアウトを実装し、/logout にアクセスします。予想どおり、終了関数も Spring によって実行されます。

WebSecurityConfig の protected void configure(HttpSecurity http) で設定します。

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

    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
                .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
                .and()
                .formLogin()//允许表单登录
                .loginPage("/login-view")//指定我们自己的登录页,spring security以重定向方式跳转到/login-view
                .loginProcessingUrl("/login")//指定登录处理的URL,也就是用户名、密码表单提交的目的路径
                .successForwardUrl("/login-success")//指定登录成功后的跳转URL
                .and()
				.logout()
				.logoutUrl("/logout")
				.logoutSuccessUrl("/login‐view?logout");
                
    }
}

終了操作が起動すると、次のことが起こります。

  • HTTPセッションを無効にする
  • SecurityContextHolder をクリアする
  • /login-view?logout にジャンプします

ただし、ログイン機能の構成と同様に、ログアウト機能をさらにカスタマイズできます。

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

    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
                .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
                .and()
                .formLogin()//允许表单登录
                .loginPage("/login-view")//指定我们自己的登录页,spring security以重定向方式跳转到/login-view
                .loginProcessingUrl("/login")//指定登录处理的URL,也就是用户名、密码表单提交的目的路径
                .successForwardUrl("/login-success")//指定登录成功后的跳转URL
                .and()
				.logout()//提供系统退出支持,使用 WebSecurityConfigurerAdapter 会自动被应用
				.logoutUrl("/logout")//设置触发退出操作的URL (默认是 /logout )
				.logoutSuccessUrl("/login‐view?logout")//退出之后跳转的URL。默认是 /login?logout 
				.logoutSuccessHandler(logoutSuccessHandler) //定制的 LogoutSuccessHandler ,用于实现用户退出成功时的处理。如果指定了这个选项那么logoutSuccessUrl() 的设置会被忽略。
				.addLogoutHandler(logoutHandler) //添加一个 LogoutHandler ,用于实现用户退出时的清理工作.默认 SecurityContextLogoutHandler 会被添加为最后一个 LogoutHandler 
				.invalidateHttpSession(true);//指定是否在退出时让 HttpSession 无效。 默认设置为 true。
                
    }
}

注: GET リクエストでログアウトを有効にしたい場合は、CSRF 攻撃を防ぐために csrf().disable() を閉じる必要があります。CSRF が有効な場合は、post メソッドを使用して /logou をリクエストする必要があります。

logoutHandler :
一般に、LogoutHandler 実装は必要なクリーンアップを実行するために使用されるため、例外をスローすべきではありません。

Spring Security によって提供される実装の一部を次に示します。

  • PersistentTokenBasedRememberMeServices 永続トークンに基づく RememberMe 機能に関連するクリーンアップ

  • TokenBasedRememberMeService トークンベースの RememberMe 機能に関連するクリーンアップ

  • CookieClearingLogoutHandler 終了時の Cookie 関連のクリーニング

  • CsrfLogoutHandler は、終了時に csrfToken を削除する責任があります。

  • SecurityContextLogoutHandler 終了時の SecurityContext 関連のクリーンアップ

チェーン API は、対応する LogoutHandler 実装 (deleteCookies() など) を呼び出すためのショートカットを提供します。

4.7. 認可

4.7.1. 概要

認可方法にはWeb認可メソッド認可が含まれます

  • Web 認証は URL インターセプトによって認証されます
  • メソッド認可はメソッドインターセプトを通じて認可されます
  • これらはすべて、認可決定のために accessDecisionManager を呼び出します。
  • Web 認証の場合、インターセプターは FilterSecurityInterceptor です。
  • メソッド認可の場合、インターセプタは MethodSecurityInterceptor です。
  • Web認可とメソッド認可が同時に可決された場合は、Web認可が実行され、次にメソッド認可が実行され、最終的に判定が可決された場合はリソースへのアクセスが許可され、可決されなかった場合はアクセスが禁止されます。

クラス関係は次のとおりです。

ここに画像の説明を挿入

4.7.2. 環境を準備する

4.7.2.1、データベース環境

t_user データベースに次のテーブルを作成します。

役割テーブル:

CREATE TABLE `t_role` (
	`id` varchar(32) NOT NULL,
	`role_name` varchar(255) DEFAULT NULL,
	`description` varchar(255) DEFAULT NULL,
	`create_time` datetime DEFAULT NULL,
	`update_time` datetime DEFAULT NULL,
	`status` char(1) NOT NULL,
	PRIMARY KEY (`id`),
	UNIQUE KEY `unique_role_name` (`role_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

insert into `t_role`(`id`,`role_name`,`description`,`create_time`,`update_time`,`status`) values('1','管理员',NULL,NULL,NULL,'');

ユーザー役割関係テーブル:

CREATE TABLE `t_user_role` (
	`user_id` varchar(32) NOT NULL,
	`role_id` varchar(32) NOT NULL,
	`create_time` datetime DEFAULT NULL,
	`creator` varchar(255) DEFAULT NULL,
	PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

insert into `t_user_role`(`user_id`,`role_id`,`create_time`,`creator`) values('1','1',NULL,NULL);

権限テーブル:

CREATE TABLE `t_permission` (
	`id` varchar(32) NOT NULL,
	`code` varchar(32) NOT NULL COMMENT '权限标识符',
	`description` varchar(64) DEFAULT NULL COMMENT '描述',
	`url` varchar(128) DEFAULT NULL COMMENT '请求地址',
	PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

insert into `t_permission`(`id`,`code`,`description`,`url`) values ('1','p1','测试资源1','/r/r1'),('2','p3','测试资源2','/r/r2');

ロール権限関係テーブル:

CREATE TABLE `t_role_permission` (
	`role_id` varchar(32) NOT NULL,
	`permission_id` varchar(32) NOT NULL,
	PRIMARY KEY (`role_id`,`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

insert into `t_role_permission`(`role_id`,`permission_id`) values ('1','1'),('1','2');

4.7.2.2. UserDetailService の変更

1. dao インターフェイスを変更し、
UserDao に追加します。

//根据用户id查询用户权限
public List<String> findPermissionsByUserId(String userId){
    
    
    String sql = "SELECT * FROM t_permission WHERE id IN(\n" +
            "\n" +
            "SELECT permission_id FROM t_role_permission WHERE role_id IN(\n" +
            "  SELECT role_id FROM t_user_role WHERE user_id = ? \n" +
            ")\n" +
            ")\n";

    List<PermissionDto> list = jdbcTemplate.query(sql, new Object[]{
    
    userId}, new BeanPropertyRowMapper<>(PermissionDto.class));
    List<String> permissions = new ArrayList<>();
    list.forEach(c -> permissions.add(c.getCode()));
    return permissions;
}

2. UserDetailService を変更して
データベースからの読み取り権限を取得します。

//根据 账号查询用户信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    

    //将来连接数据库根据账号查询用户信息
    UserDto userDto = userDao.getUserByUsername(username);
    if(userDto == null){
    
    
        //如果用户查不到,返回null,由provider来抛出异常
        return null;
    }
    //根据用户的id查询用户的权限
    List<String> permissions = userDao.findPermissionsByUserId(userDto.getId());
    //将permissions转成数组
    String[] permissionArray = new String[permissions.size()];
    permissions.toArray(permissionArray);
    UserDetails userDetails = User.withUsername(userDto.getUsername()).password(userDto.getPassword()).authorities(permissionArray).build();
    return userDetails;
}

4.7.3、Web認証

上記の例では、/r/** 配下の一部のリソースに対する認証インターセプトと簡易認可保護が完了しましたが、柔軟な認可制御を実装したい場合はどうすればよいでしょうか? 次のように複数の子ノードを http.authorizeRequests() に追加して、URL への要件をカスタマイズします。

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
	http
		.authorizeRequests() //http.authorizeRequests() 方法有多个子节点,每个macher按照他们的声明顺序执行。
		.antMatchers("/r/r1").hasAuthority("p1")//指定"/r/r1"URL,拥有p1权限能够访问
		.antMatchers("/r/r2").hasAuthority("p2") //指定"/r/r2"URL,拥有p2权限能够访问
		.antMatchers("/r/r3").access("hasAuthority('p1') and hasAuthority('p2')")//指定了"/r/r3"URL,同时拥有p1和p2权限才能够访问
		.antMatchers("/r/**").authenticated() //指定了除了r1、r2、r3之外"/r/**"资源,同时通过身份认证就能够访问,这里使用SpEL(Spring Expression Language)表达式
		.anyRequest().permitAll() //剩余的尚未匹配的资源,不做保护。
		.and()
		.formLogin()
	// ...
}

知らせ:

ルールの順序は重要です。より具体的なルールを最初に記述する必要があります。/admin
で始まるものはすべて、/admin/login パスも含めて、ADMIN ロールを持つ認証されたユーザーを必要とします (/admin/login はすでに /admin に置き換えられているため) /** ルールが一致するため、2 番目のルールは無視されます)。

.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/admin/login").permitAll()

したがって、ログイン ページのルールは /admin/** ルールの前に置く必要があります。

.antMatchers("/admin/login").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")

URL を保護する一般的な方法は次のとおりです。

  • Authenticated() は URL を保護し、ユーザーのログインを要求します。

  • permitAll() 指定された URL は保護する必要がなく、一般的なアプリケーションと静的リソース ファイルです

  • hasRole(String role) は単一のロールへのアクセスを制限し、ロールは "ROLE_" ずつ増分されるため、"ADMIN" は "ROLE_ADMIN" と比較されます。

  • hasAuthority(String author) はアクセスを単一の権限に制限します

  • hasAnyRole(String...roles) 複数のロールのアクセスを許可します。

  • hasAnyAuthority(String…authority) 複数の権限へのアクセスを許可します。

  • access(String 属性) このメソッドは SpEL 式を使用するため、複雑な制限を作成できます。

  • hasIpAddress(String ipaddressExpression) IP アドレスまたはサブネットを制限します

4.7.4、メソッドの認可

http.authorizeRequests() を使用して Web リソースを認証および保護する方法をマスターしました。Spring Security2.0 からは、サービス層メソッドのセキュリティ サポートがサポートされます。このセクションでは、@PreAuthorize、@PostAuthorize、@Secured の 3 種類のアノテーションを学習します。

任意の @Configuration インスタンスで @EnableGlobalMethodSecurity アノテーションを使用して、アノテーション ベースのセキュリティを有効にすることができます。

以下は Spring Security の @Secured アノテーションを有効にします。

@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
    
    // ...}

(クラスまたはインターフェース上の) メソッドにアノテーションを追加すると、そのメソッドへのアクセスが制限されます。Spring Security のネイティブ アノテーション サポートは、このメソッドの一連の属性を定義します。これらは、実際の決定を行うために AccessDecisionManager に渡されます。

public interface BankService {
    
    
	@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
	public Account readAccount(Long id);
	
	@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
	public Account[] findAccounts();
	
	@Secured("ROLE_TELLER")
	public Account post(Account account, double amount);
}

上記の構成は、readAccount メソッドと findAccounts メソッドに匿名でアクセスできることを示しており、最下層では AffirmativeBased の 23 行目から追跡できる WebExpressionVoter 投票者を使用しています。

post メソッドにはアクセスするために TELLER ロールが必要で、最下層では、RoleVoter 投票者を使用します。

次のコードを使用して、prePost アノテーションのサポートを有効にします。

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
    
    
	// ...
}

対応する Java コードは次のとおりです。

public interface BankService {
    
    
	@PreAuthorize("isAnonymous()")
	public Account readAccount(Long id);
	
	@PreAuthorize("isAnonymous()")
	public Account[] findAccounts();
	
	@PreAuthorize("hasAuthority('p_transfer') and hasAuthority('p_read_account')")
	public Account post(Account account, double amount);
}

上記の構成は、readAccount メソッドと findAccounts メソッドに匿名でアクセスできることを示しています。post メソッドには、アクセスするには p_transfer と p_read_account の両方のアクセス許可が必要です。最下層では WebExpressionVoter 投票者が使用されており、これは AffirmativeBased コードの 23 行目から追跡できます。

5. 分散システム認証方式

5.1. 分散システムとは

ソフトウェア環境と要件が変化するにつれて、ソフトウェア アーキテクチャは単一構造から分散アーキテクチャに進化します。分散アーキテクチャを持つシステムは分散システムと呼ばれます。分散システムの動作は通常、ネットワークに依存します。いくつかのサービスでは、サービスはネットワークを介して相互に対話し、ユーザーのビジネス処理を完了します。現在一般的なマイクロサービス アーキテクチャは、次の図に示すような分散システム アーキテクチャです。

ここに画像の説明を挿入

分散システムの基本的な特性は次のとおりです。

  • 1. 分散: 各部分は独立して展開でき、注文サービス、商品サービスなどのサービス間の対話はネットワークを通じて通信されます。

  • 2. スケーラビリティ: 各部分をクラスタ モードで展開でき、一部のノードに対してハードウェアおよびソフトウェアの拡張を行うことができ、一定のスケーラビリティを備えています。

  • 3. 共有: 各部分は外部サービスを提供するための共有リソースとして機能することができ、複数の部分が共有リソースを操作することもできます。

  • 4. オープン性: 各部分は、必要に応じて共有リソースのアクセス インターフェイスを公開し、サードパーティ システムがアクセスできるようにすることができます。

5.2. 分散認証の要件

分散システムの各サービスには認証と認可の要件があります。各サービスが一連の認証と認可のロジックを実装すると、非常に冗長になります。分散システムの共有特性を考慮すると、独立した認証サービスがシステムの認証と認可を処理する必要があります。 . request; 分散システムのオープン性の特性を考慮して、システムの内部サービスの認証だけでなく、サードパーティ システムの認証も提供します。分散認証の要件は次のように要約されます。

統合された認証と認可

独立した認証サービスを提供し、認証と認可を一元的に処理します。

さまざまなタイプのユーザーやさまざまなタイプのクライアント (Web、H5、APP) に関係なく、それらはすべて一貫した認証、権限、およびセッションのメカニズムを採用して、統一された認証と認可を実現します。

統合を実現するには、認証方法がスケーラブルであり、ユーザー名、パスワード認証、SMS 認証コード、QR コード、顔認識、その他の認証方法などのさまざまな認証要件をサポートし、非常に柔軟に切り替えることができる必要があります。

アプリケーションアクセス認証

拡張機能とオープン機能を提供し、安全なシステム ドッキング メカニズムを提供し、サードパーティ アクセス用にいくつかの API をオープンする必要があります。ワンパーティ アプリケーション (内部システム サービス) とサードパーティ アプリケーション (サードパーティ アプリケーション) はすべて、統一されたメカニズムを使用します。アクセス用に。

5.3. 分散認証方式

5.3.1. タイプ選択分析

1. セッションベースの認証方法
分散環境では、セッションベースの認証が問題となりますが、各アプリケーション サービスはユーザー ID 情報をセッションに保存し、ロード バランシングを通じてローカル リクエストを別のアプリケーション サービスに分散する必要があります。引き継ぐ必要があります。そうでない場合は、再認証されます。

ここに画像の説明を挿入

このとき、一般的な方法は次のとおりです。

  • セッション レプリケーション: 複数のアプリケーション サーバー間でセッションを同期し、セッションの一貫性を保ち、外部に対して透過的に保ちます。

  • セッション固着: ユーザーがクラスター内のサーバーにアクセスするときは、後続のすべてのリクエストがこのマシンに送信されるように指定することが必須です。

  • 集中セッションストレージ: セッションを分散キャッシュに保存し、すべてのサーバーアプリケーションインスタンスが分散キャッシュから均一にセッションにアクセスします。

一般に、セッション認証に基づく認証方法は、サーバー側でセッションをより適切に制御でき、セキュリティが高くなります。しかし、セッションメカニズムは Cookie に基づいており、複雑で多様なモバイルクライアントでは効果的に使用できず、ドメインを越えることもできません。また、システムの拡張に伴い、セッションのコピー、ペースト、保存のフォールトトレランスも必要になります
。改善されました。

2. トークンベースの認証方式
トークンベースの認証方式では、サーバーに認証データを保存する必要がないため、保守が容易で拡張性が高く、クライアントは任意の場所にトークンを保存でき、統一的な認証の仕組みを実現できます。ウェブとアプリの。欠点も明らかで、トークンは自己完結型の情報であるため、一般に大量のデータを持ち、要求されるたびに送信する必要があるため、多くの帯域幅を占有します。さらに、トークン署名検証操作により、CPU に追加の処理負荷がかかります。

ここに画像の説明を挿入

5.3.2. 技術的解決策

選択の分析によると、トークンベースの認証方式を採用することが決定され、その利点は次のとおりです。

  • 1. 統合認証に適したメカニズム クライアント、ワンパーティ アプリケーション、およびサードパーティ アプリケーションはすべて、一貫した認証メカニズムに従います。

  • 2. トークン認証方法は、よりオープンであり、現在普及しているオープン プロトコル Oauth2.0、JWT などを使用できるため、サードパーティ アプリケーションのアクセスに適しています。

  • 3. 一般に、サーバーはセッション情報を保存する必要がないため、サーバーへの負担が軽減されます。

分散システムの認証技術スキームを次の図に示します。ここに画像の説明を挿入

過程説明:

  • (1) ユーザーはアクセス側(アプリケーション)からログインし、アクセス側は OAuth2.0 を採用し、統一認証サービス(UAA)で認証します。

  • (2) 認証サービス (UAA) は、ユーザーの身元が正当であるかどうかを確認し、ユーザーの許可情報を取得するために呼び出します。

  • (3)認証サービス(UAA)は、アクセス者の許可情報を取得し、アクセス者が正当であるかどうかを検証する。

  • (4) ログイン ユーザーとアクセス パーティの両方が正当な場合、認証サービスは jwt トークンを生成し、アクセス パーティに返します。jwt には、ユーザのアクセス許可とアクセス パーティのアクセス許可が含まれます。

  • (5) 続いて、アクセス側は jwt トークンを保持して API ゲートウェイ内のマイクロサービス リソースにアクセスします。

  • (6) API ゲートウェイはトークンを解析し、アクセス者の権限が要求されたマイクロサービスにアクセスできるかどうかを検証します。

  • (7) アクセス側の権限に問題がない場合、API ゲートウェイは解析された平文トークンを元のリクエストのヘッダーに添付し、リクエストをマイクロサービスに転送します。

  • (8) マイクロサービスはリクエストを受信し、平文トークンにはログイン ユーザーの ID と権限情報が含まれます。したがって、後続のマイクロサービスは、それ自体で次の 2 つのことを実行できます。

    • 1: ユーザー認証のインターセプト (現在のユーザーがリソースにアクセスする権利があるかどうかを確認します)
    • 2: ユーザー情報を現在のスレッド コンテキストに保存します (後続のビジネス ロジックがいつでも現在のユーザー情報を取得できるようになります)。

このプロセスに関与する UAA サービスと API ゲートウェイ コンポーネントの責任は次のとおりです。

1) 統​​一認証サービス (UAA)
。OAuth2.0 のアクセス側の認証、ログイン ユーザーの認証、認可、トークンの生成を担い、実際のユーザー認証および認可機能を完了します。

2) API ゲートウェイ API ゲートウェイ
は、システムへの唯一の入り口として、アクセス側にカスタマイズされた API コレクションを提供します。また、認証、監視、負荷分散、キャッシュなどの他の役割も担う場合があります。API ゲートウェイ方式の中核となるのは、すべてのアクセス当事者と消費者が統合ゲートウェイを通じてマイクロサービスにアクセスし、ビジネス以外の機能をすべてゲートウェイ層で処理することです。

おすすめ

転載: blog.csdn.net/shuai_h/article/details/130653929