Spring Security の戦い (1) - メモリとデータベース モデルに基づく認証と認可

目次

序章

1. Spring Security について知る (エントリーケース)

(1) 新規プロジェクトを作成する

(2) 依存関係の選択

(3) HelloControllerを書く

(4) プロジェクトを開始し、localhost:8080 にアクセスします。

(5) ユーザー名とパスワードをカスタマイズする

 2. フォーム認証

1. カスタムフォームログインページ

2. スプリングセキュリティを設定する

3. プロジェクトを再起動します

編集

3. 認証と認可

1. リソースの準備

(1) 2つの新しいコントローラを作成します

(2) リソース認可の設定

(3) サービスアクセスの再開

 2. メモリベースのマルチユーザー サポート

(1) メモリ内のユーザーを設定する

 (2) アクセステスト

  3. デフォルトのデータベースモデルに基づく認証と認可

(1) データベーステーブルの作成

(2) jdbc 依存関係を導入し、データベースを構成する

(3) Spring Security認可の設定

 4. カスタムデータベースモデルに基づく認証と認可

(1) データベースの準備

 (2) エンティティクラスの書き込み User

(3) カスタム UserDetailsS​​erviceImpl を作成する

(4) ライトマッパー

(5) スプリングセキュリティ構成

(6) テスト


序章

一般的な Web アプリケーションには認証認可が必要です。

認証 : 現在のアクセス システムがこのシステムのユーザーであることを確認し、どのユーザーであるかを確認します

認可 : 認証後、現在のユーザーに操作を実行する権限があるかどうかを判断します。

認証と認可は、セキュリティ フレームワークとしての Spring Security の中核機能でもあります。

(1) フロントエンドとバックエンド分離プロジェクトのログイン認証プロセス:

 (2) Spring Securityの完全なプロセス

Spring Security の原理は実際にはフィルター チェーンであり、これには次のようなさまざまな機能を提供するフィルターが含まれています。

1. Spring Security について知る (エントリーケース)

(1) 新規プロジェクトを作成する

(2) 依存関係の選択

(3) HelloControllerを書く

@RestController
@RequestMapping("/")
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "hello spring security!";
    }
}

(4) プロジェクトを開始し、localhost:8080 にアクセスします。

Spring Security導入後は何も設定しなくても、対応するURLリソースにアクセスする際にHTTP Basic認証が必要となります。

起動後、図に示すように、コンソールに初期パスワードが出力されます。

 localhost:8080 にアクセスし、ログイン情報 (これは HTTP 基本認証) を入力し、ユーザー名userを入力します。

(5) ユーザー名とパスワードをカスタマイズする

設定ファイル application.yml でユーザー名とパスワードを設定し、プロジェクトの再起動後にそのユーザー名とパスワードを使用してログインします。

spring:
  security:
    user:
      name: zy
      password: abc

導入事​​例の概要:

 

認証インターフェイス: その実装クラスは、現在システムにアクセスしているユーザーを表し、ユーザー関連の情報をカプセル化します。

AuthenticationManager インターフェイス: 認証認証方法を定義します。

UserDetailsS​​ervice インターフェイス: ユーザー固有のデータをロードするためのコア インターフェイス。ユーザー名に基づいてユーザー情報をクエリするメソッドを定義します。

UserDetails インターフェイス: 主要なユーザー情報を提供します。ユーザー名に従って UserDetailsS​​ervice を通じて取得および処理されたユーザー情報は、UserDetails オブジェクトにカプセル化されて返される必要があります。次に、この情報を認証オブジェクトにカプセル化します。

ステップ:

(1) ユーザー名とパスワードを送信すると、ユーザー名とパスワードがUsernamePasswordAuthenticationFilterに渡されます。

        UsernamePasswordAuthenticationFilterは Spring Security の非常に重要なフィルターであり、フォームベースの認証の処理を担当します。つまり、ユーザーがユーザー名とパスワードを含むフォームを送信すると、フィルターはリクエストからユーザー名とパスワードを抽出し、認証を実行します。 

(2) UsernamePasswordAuthenticationFilter は、認証のためにauthenticate() メソッドを呼び出します。

        このメソッドが実行されると、リクエスト内のユーザー名とパスワードのパラメーターが取得され、認証のために AuthenticationManager オブジェクトのauthenticate() メソッドが呼び出されます。認証が成功すると、Authentication オブジェクトが作成され、SecurityContextHolder に渡されます。認証に失敗した場合は、AuthenticationException がスローされます。

 (3) AuthenticationManager は検証のために DaoAuthenticationProvider のauthenticate() を呼び出し続けます。

        DaoAuthenticationProvider は、ユーザー名とパスワードの検証プロセスを処理するために使用される AuthenticationProvider の実装クラスです。AuthenticationManager がauthenticate() メソッドを呼び出すと、実際には認証リクエストの処理を DaoAuthenticationProvider に委任します。

(4) DaoAuthenticationProvider は、loadUserByUsername() メソッドも呼び出してユーザーをクエリします。

        DaoAuthenticationProviderのauthenticate()メソッドでは、まずUserDetailsS​​erviceのloadUserByUsername ()メソッドを呼び出してユーザー情報を取得し(実際の状況に応じてメモリ内またはデータベース内で検索されます)、次に取得したユーザー情報とユーザー入力を取得します。パスワードが比較されて、ユーザーが認証されているかどうかが判断されます。したがって、認証プロセスでは、loadUserByUsername() メソッドが非常に重要なリンクとなります。

(5) UserDetailオブジェクトを返す

        loadUserByUsername() メソッドは主に、指定されたユーザー名に従ってユーザー情報をクエリし、UserDetails オブジェクトを返します。この方法の具体的な実装には、データベースまたは他の記憶装置にアクセスしてユーザーの詳細情報を取得することが含まれる場合があります。Spring Securityでは通常、このメソッドはUserDetailsS​​erviceの実装クラスによって行われる。実装クラスでは、通常、ユーザー情報はユーザー名に従って照会され、後続の認証プロセスで使用するために UserDetails オブジェクトにカプセル化されます。UserDetails オブジェクトには、ユーザーの ID 情報、認可情報、およびパスワードや有効かどうかなどのその他の詳細が含まれます。

(6) UserDetail のパスワードと、PasswordEncoder オブジェクトを通じて送信されたパスワードを比較します。

        ユーザーが PasswordEncoder オブジェクトを通じて入力したパスワードを暗号化し、それを UserDetails に保存されている暗号化されたパスワードと比較して、ユーザーの身元を確認します。2 つが一致する場合、ユーザー認証は成功したとみなされます。PasswordEncoder は主に、セキュリティを向上させるためにパスワードを暗号化するために使用されます。

(7) 正しければUserDetailsの権限情報をAuthenticationオブジェクトに設定します

(8) Authenticationオブジェクトを返す

(9) 認証に成功したAuthentication オブジェクトをSecurityContextHolder 次の場所に保存します。

        SecurityContextHolder は、ThreadLocal を使用して認証オブジェクトを保存し、各スレッドが独自の SecurityContext インスタンスを持つようにします。後続のリクエストでは、他のフィルターは SecurityContextHolder.getContext().getAuthentication() メソッドを使用して認証オブジェクトを取得できます。


 2. フォーム認証

1. カスタムフォームログインページ

login.html、静的下に配置

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login Page</title>
    <style>
        /* 样式可以自行修改 */
        body {
            background-color: cadetblue;
        }

        .login-form {
            width: 350px;
            margin: 150px auto;
            background-color: #fff;
            padding: 20px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
        }

        h1 {
            font-size: 24px;
            text-align: center;
            margin-bottom: 30px;
        }

        input[type="text"], input[type="password"] {
            width: 100%;
            padding: 10px;
            margin-bottom: 20px;
            border: 2px solid #ccc;
            border-radius: 4px;
            box-sizing: border-box;
        }

        button {
            background-color: darksalmon;
            color: white;
            padding: 12px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            width: 100%;
        }

        button:hover {
            background-color: #45a049;
        }
    </style>
</head>
<body>
<div class="login-form">
    <h1>Login Page</h1>
    <form th:action="@{/login}" method="post">
        <label for="username">Username</label>
        <input type="text" id="username" name="username" placeholder="Enter username">

        <label for="password">Password</label>
        <input type="password" id="password" name="password" placeholder="Enter password">

        <button type="submit">Login</button>
    </form>
</div>
</body>
</html>

2. スプリングセキュリティを設定する

configure メソッドを書き換えて、HttpSecurityオブジェクトを受け取ります。HttpSecurity には、構成関連のメソッドが多数用意されています。

(1) authorizeRequests() は Spring Security の構成メソッドであり、アクセスを許可する必要があるリクエストを定義するために使用されます。このメソッドは、URL のアクセス許可を構成するために使用される ExpressionInterceptUrlRegistry オブジェクトを返します。

この方法により、次のようなさまざまな方法を使用して URL 認証を構成できます。

  • antMatchers()メソッドは、URL を照合し、必要なアクセス許可を設定するために使用されます。
  • hasRole()およびhasAuthority()メソッドは、必要なロールまたは権限を指定するために使用されます。
  • permitAll()メソッドは、アクセスにアクセス権が必要ないことを指定するために使用されます。

(2)フォームログイン()

formLogin()Spring Security の構成メソッドで、認証にフォーム ログインの使用を指定します。デフォルトでは、認証が行われない場合、Spring Security は自動的にデフォルトのログインページにリダイレクトします。

この方法により、次のように構成できます。

  • loginPage()メソッドは、ログイン ページの URL を指定するために使用されます。
  • loginProcessingUrl()メソッドは、ログイン要求を処理する URL を指定するために使用されます。
  • usernameParameter()およびpasswordParameter()メソッドは、フォーム内のユーザー名とパスワードのパラメータ名を指定するために使用されます。
  • successHandler()およびfailureHandler()メソッドは、ログインの成功および失敗後の処理ロジックを指定するために使用されます。
  • permitAll()メソッドは、ログイン ページへのアクセス許可を指定するために使用されます。

(3)csrf()はSpring Securityにおける設定方法であり、クロスサイトリクエストフォージェリ(Cross-Site Request Forgery、CSRF)保護機能を設定するために使用される。CSRF 攻撃は、攻撃者が何らかの手段でユーザーの認証情報を取得し、その情報を利用して悪意のあるリクエストを送信し、攻撃目的を達成する悪意のある攻撃手法です。

Spring SecurityではCSRF保護がデフォルトで有効になっているため、無効にしたい場合はcsrf().disable()メソッドを使用して無効にすることができます。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
}

3. プロジェクトを再起動します

localhost:8080/hello にアクセスすると、 http://localhost:8080/login.htmlにジャンプします。

3. 認証と認可

1. リソースの準備

(1) 2つの新しいコントローラを作成します

1 つは管理者のみがアクセスでき、もう 1 つは一般ユーザーがアクセスできます。

@RestController
@RequestMapping("/admin/api")
public class AdminController {

    @GetMapping("/hello")
    public String helloAdmin() {
        return "hello Admin!";
    }
}
@RestController
@RequestMapping("/user/api")
public class UserController {

    @GetMapping("/hello")
    public String helloAdmin() {
        return "hello User!";
    }
}

(2) リソース認可の設定

/admin/api/** の下のリソースをリクエストするには、ADMIN ロールがあるかどうかを確認する必要があります

/user/api/** の下のリソースをリクエストするには、USER ロールがあるかどうかを確認する必要があります

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
}

(3) サービスアクセスの再開

http://localhost:8080/hello へのアクセスは 成功しますが、http://localhost:8080/admin/api/hello へのアクセスは拒否されることがわかります (403 エラー コード)。

 2. メモリベースのマルチユーザー サポート

(1) メモリ内のユーザーを設定する

        次の構成の2 つのconfigure(HttpSecurity http) メソッドとconfigure(AuthenticationManagerBuilder auth)メソッドは、WebSecurityConfigurerAdapter の 2 つの主要なメソッドであり、Spring Security の認証と認可を構成するために使用されます。

  configure(HttpSecurity http)このメソッドは、リクエストの認可ルール、つまり、どのリクエストがどのアクセス許可を必要とするかを設定するために使用されます。

  configure(AuthenticationManagerBuilder auth)Method は、認証方法、つまりユーザーの身元を確認する方法を構成するために使用されます。.inMemoryAuthentication()この例では、メソッドを呼び出してメモリ内認証を使用し、次に.withUser()そのメソッドを使用してユーザー名とパスワードを指定し、{noop}プレフィックスを使用してパスワードが暗号化されていないことを示し、最後に.roles()メソッドを使用してユーザーのロールを指定します。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin").password("{noop}abcd").roles("ADMIN")
                .and()
                .withUser("zy").password("{noop}abc").roles("USER");
    }
}

 (2) アクセステスト

ユーザー名 admin とパスワード abcd を使用してログインすると、次のコンテンツが表示されます。

  3. デフォルトのデータベースモデルに基づく認証と認可

(1) データベーステーブルの作成

データベース内に次の 2 人のユーザーが作成されます。

ユーザーに対応するロールは ROLE_USER です。

admin に対応するロールは ROLE_ADMIN です。

CREATE TABLE users (
   username VARCHAR(50) NOT NULL PRIMARY KEY,
   password VARCHAR(100) NOT NULL,
   enabled BOOLEAN NOT NULL
);

CREATE TABLE authorities (
   username VARCHAR(50) NOT NULL,
   authority VARCHAR(50) NOT NULL,
   FOREIGN KEY (username) REFERENCES users(username)
);


INSERT INTO users (username, password, enabled) VALUES ('user', '12345', true);
INSERT INTO users (username, password, enabled) VALUES ('admin', '12345', true);

INSERT INTO authorities (username, authority) VALUES ('user', 'ROLE_USER');
INSERT INTO authorities (username, authority) VALUES ('admin', 'ROLE_ADMIN');

(2) jdbc 依存関係を導入し、データベースを構成する

jdbc と MySQL の依存関係を導入する

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>

 データベース接続を構成します。

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/security-db?useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

(3) Spring Security認可の設定

        ここでは jdbcAuthentication() メソッドを使用して JDBC ベースのユーザー ストレージを有効にし、dataSource() メソッドを使用してデータ ソース (データベースに接続された DataSource) を設定します。

        次に、usersByUsernameQuery() メソッドを使用して、ユーザー名、パスワード、および有効ステータスをクエリするための SQL ステートメントを設定します。このステートメントはユーザーのログイン時に実行され、入力された内容に従ってデータベース内のユーザー情報がクエリされます。ユーザー名、照会されたパスワード、および有効な状態が認証に使用されます。authorityByUsernameQuery() メソッドは、ユーザー ロールをクエリするための SQL ステートメントを設定するために使用されます。最後に、passwordEncoder() メソッドを使用してパスワード暗号化方式を設定し、NoOpPasswordEncoder.getInstance() メソッドを使用してパスワード暗号化を無効にします。つまり、データベースからクエリされたパスワードがプレーンテキストとして直接比較されます。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
                .dataSource(dataSource)
                .usersByUsernameQuery("SELECT username, password, enabled FROM users WHERE username = ?")
                .authoritiesByUsernameQuery("SELECT username, authority FROM authorities WHERE username = ?")
                .passwordEncoder(NoOpPasswordEncoder.getInstance());
    }
}

 4. カスタムデータベースモデルに基づく認証と認可

上記では、2 つの UserDetailsS​​ervice 実装クラス、InMemoryUserDetailsManager と JdbcUserDetailsManager を使用しました。以下では、カスタム データベース モデルとカスタム UserDetails 実装クラスを使用します。

(1) データベースの準備

CREATE TABLE my_users (
  id INT(11) NOT NULL AUTO_INCREMENT,
  username VARCHAR(50) NOT NULL,
  password VARCHAR(100) NOT NULL,
  enabled TINYINT(1) NOT NULL DEFAULT '1',
  roles VARCHAR(200) NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY username_UNIQUE (username)
);

2 つのデータを挿入します。

 (2) エンティティクラスの書き込み User

        このエンティティ クラスはUser前の table と1 対 1 の対応関係を持ちmy_users、各属性はテーブル内のフィールドに対応します。

  • idテーブル内のフィールドに対応しid、各ユーザーを一意に識別するために使用されます。
  • usernameテーブル内のフィールドに対応しusername、ユーザーのログイン名を示します。
  • passwordテーブル内のフィールドに対応しpassword、ユーザーのパスワードを示します。
  • enabledテーブル内のフィールドに対応しenabled、ユーザーが有効にするかどうかを示します。
  • rolesテーブル内のフィールドに対応しroles、ユーザーが所有するロールを示します。

Userこのエンティティ クラスには、ユーザーの権限情報を保存するために使用されるauthorities属性もあり、テーブル内のどのフィールドにも対応しないこと        に注意してください。この属性は、認証・認可用UserDetailsServiceImplクラスのメソッドにユーザーの権限情報として設定されますloadUserByUsernameこの機能を実現するために、Userクラスにもメソッドが定義されています。このメソッドは、フィールドを型のコレクションに解析し、必要に応じて遅延読み込みを実行するgetAuthoritiesために使用されます。rolesList<GrantedAuthority>

@Data
public class User implements UserDetails {

    private Long id;
    private String username;
    private String password;
    private boolean enabled;
    private String roles;
    private List<GrantedAuthority> authorities;


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

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

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

    //用于将 roles 字段解析成 List<GrantedAuthority> 类型的集合
    public List<GrantedAuthority> getAuthorities() {
        if (authorities == null) {
            authorities = new ArrayList<>();
            for (String role : roles.split(",")) {
                authorities.add(new SimpleGrantedAuthority(role.trim()));
            }
        }
        return authorities;
    }
}

(3) カスタム UserDetailsS​​erviceImpl を作成する

        このUserDetailsServiceImplクラスはUserDetailsServiceインターフェースを実装し、ユーザー情報をロードするためのサービスクラスです。loadUserByUsernameこのメソッドでは、UserMapperデータベースから対応するオブジェクトをクエリし、次にUseruser に対応するGrantedAuthorityリストを構築し、最後にUserオブジェクトを返します。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public User loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库中查询用户信息
        User user = userMapper.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }

        // 构建用户权限信息
        List<GrantedAuthority> authorities = user.getAuthorities();
        user.setAuthorities(authorities);
        return user;
    }
}

(4) ライトマッパー

@Mapper
public interface UserMapper {

    @Select("SELECT * FROM my_users WHERE username = #{username}")
    User findByUsername(@Param("username") String username);
}

(5) スプリングセキュリティ構成

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private DataSource dataSource;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
//使用自定义的数据库模型进行认证和授权
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(NoOpPasswordEncoder.getInstance());
    }

}

(6) テスト

ブレークポイントのデバッグでは、管理者ユーザーでログインすると 2 つのロールを取得できることがわかります。

 

おすすめ

転載: blog.csdn.net/weixin_49561506/article/details/130140438