rainit2006 / My_AWS-Cloud

0 stars 0 forks source link

Spring讲解 #25

Open rainit2006 opened 4 years ago

rainit2006 commented 4 years ago

https://app.box.com/s/g51ig6ntwv4pvpypxixsklieoa6oq2an/folder/97611523142

rainit2006 commented 4 years ago

DI: インスタンスを管理する。

  1. インスタンスを生成。  2.インスタンスのライフサイクル管理。

@Autowired を付けると、 DIコンテナからインスタンスを取得してくる。

@Scope:インスタンスのライフサイクルを管理する。

singleton コンテナに対して1つのインスタンスを定義します。(デフォルト) prototype Beanを取得する度に新しいインスタンスを生成します。 request HTTP Request単位でインスタンスを生成します。 session HTTP Session単位でインスタンスを生成します。 globalSession something which is connected to Portlet applications. application サーブレットのコンテキスト単位でインスタンスが生成される。Webアプリケーションの場合のみ使用できる。

rainit2006 commented 4 years ago

SpringSecurity

セキュリティ設定用クラス

セキュリティ設定用クラスには、@EnableWebSecurityを付けます。 また、WebSecurityConfigurerAdapterクラスを継承します。

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}

直リンクを禁止

直リンクを禁止するためには、http.authorizeRequests()にメソッドチェーンでリンク禁止先の条件を追加していきます。メソッドチェーンとは、.(ドット)でメソッドを連続して呼び出すことです。

@Override
    protected void configure(HttpSecurity http) throws Exception {

        // ログイン不要ページの設定
        http
            .authorizeRequests()
                .antMatchers("/webjars/**").permitAll() //webjarsへアクセス許可
                .antMatchers("/css/**").permitAll() //cssへアクセス許可
                .antMatchers("/login").permitAll() //ログインページは直リンクOK
                .antMatchers("/signup").permitAll() //ユーザー登録画面は直リンクOK
                .antMatchers("/admin").hasAuthority("ROLE_ADMIN") //アドミンユーザーに許可
                .anyRequest().authenticated(); //それ以外は直リンク禁止

        //CSRF対策を無効に設定(一時的)
        //http.csrf().disable();
    }

ログイン処理を追加

ログイン処理を追加するには、http.formLogin()にメソッドチェーンで条件を追加していきます。

        //ログイン処理
        http
            .formLogin()
                .loginProcessingUrl("/login") //ログイン処理のパス
                .loginPage("/login") //ログインページの指定
                .failureUrl("/login") //ログイン失敗時の遷移先
                .usernameParameter("userId") //ログインページのユーザーID
                .passwordParameter("password") //ログインページのパスワード
                .defaultSuccessUrl("/home", true); //ログイン成功後の遷移先

次に、login.htmlを修正して、ユーザーID、パスワードの入力エリアにname属性を追加します。 また、ログイン失敗時のメッセージを出すように修正します。

ポイント: ログインエラーメッセージの表示ログインエラーメッセージを表示するためには、th:if属性を使います。

ログイン失敗時のメッセージを日本語にします。

メッセージは、messages.propertiesに設定しておきます。

入力されたユーザーID、パスワードをデータベースに問い合わせする処理を追加します。

  1. ユーザーデータの取得(DB) まずは、SQLを実行できるようにするため、DataSourceをAutowiredします。 これはSpringがBeanを自動で用意しています。
  2. 次に、ユーザーデータを取得するために、SQL文を2つ用意します。
  3. これらのSQL文を、usersByUsernameQueryメソッドとauthoritiesByUsernameQueryメソッドの引数に入れます。これで、入力されたユーザーIDとパスワードを使って、認証処理をSpringが行ってくれます。
// データソース
    @Autowired
    private DataSource dataSource;

// ユーザーIDとパスワードを取得するSQL文
    private static final String USER_SQL = "SELECT"
            + "    user_id,"
            + "    password,"
            + "    true"
            + " FROM"
            + "    m_user"
            + " WHERE"
            + "    user_id = ?";

    // ユーザーのロールを取得するSQL文
    private static final String ROLE_SQL = "SELECT"
            + "    user_id,"
            + "    role"
            + " FROM"
            + "    m_user"
            + " WHERE"
            + "    user_id = ?";

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        // ログイン処理時のユーザー情報を、DBから取得する
        auth.jdbcAuthentication()
                .dataSource(dataSource)
                .usersByUsernameQuery(USER_SQL)
                .authoritiesByUsernameQuery(ROLE_SQL)
                .passwordEncoder(passwordEncoder());
    }

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

パスワードを暗号化したり、復号するインターフェースとして、PasswordEncoderというインターフェースがSpringで用意されています。そのPasswordEncoderを実装した、BCryptPasswordEncoderのインスタンスを返すBean定義をしています。

ログアウト処理

        //ログアウト処理
        http
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) //
                .logoutUrl("/logout") //ログアウトのURL
                .logoutSuccessUrl("/login"); //ログアウト成功後のURL

CSRF对策

<!CSRF対策用トークン>
<inputtype="hidden"
     th:name="${_csrf.parameterName}"
     th:value="${_csrf.token}"/>

上記のコードでは、hiddenでCSRF対策用のトークンをサーバーに送ります。CSRF対策を有効にしている場合、フォームを使っている画面では上記のコードを追加しないといけません。そうしないと、Springが何も応答しなくなります。

th:action属性を使っていると、タイムリーフでは自動でCSRF対策用のトークンを追加してくれます。本書では、学習のためにログイン画面だけaction属性を使っていました。基本的にはaction属性ではなく、th:action属性を使うようにしましょう。

URLでの認可設定

URLでの認可設定には、以下のメソッドを追加 ・antMatchers("<パス>")アクセス制限するパスを設定します。正規表現も使えます。 ・hasAuthority("<ロール>")ロールには、データベースから取得してきたロール名を指定します。

画面表示の認可

画面表示の認可をするためには、まずhtmlタグ内に以下のコードを追加します。 xmlns:sec="http://www.thymeleaf.org/thymeleafextrasspringsecurity4" これは、タイムリーフの拡張ライブラリを使用する、ということを表しています。

そして、権限によって表示・非表示を切り替えたい箇所に、以下のようなコードを追加します。 認可用のコード sec:authorize="hasRole('<権限>')"

<htmlxmlns:th="http://www.thymeleaf.org"xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"xmlns:sec="http://www.thymeleaf.org/thymeleaf-extrass-pringsecurity4">

<!ポイント:画面表示の認可>
<lirole="presentation"sec:authorize="hasRole('ADMIN')">
     <ath:href="@{'/admin'}">アドミン用画面</a>
</li>
rainit2006 commented 4 years ago

RESTFUL

REST用コントローラーREST用コントローラーを作成するには、クラスに@RestControllerを付ける。@RestControllerを付けると、そのクラスのメソッドの戻り値を呼び出し元に返す。 PUTメソッドには、@PutMappingを使う。 DELETEメソッドには、@DeleteMappingを使う。

基本流程: Controller-》Service-》DB

rainit2006 commented 4 years ago

SpringTestとは

SpringTestとは、Springを起動した後にJUnitなどのテストを実行できるフレームワークです。SpringTestを使うには、以下の2つのアノテーションをクラスに付けます @RunWith(SpringRunner.class) @SpringBootTest

リポジトリークラスのテスト

ポイント1: テスト用アノテーションテスト用のクラスには、@RunWith(SpringRunner.class)と@SprintBootTestの2つのアノテーションを付けます。 ・@RunWith(SpringRunner.class) @RunWithアノテーションは、テストをどのクラスで実行するか指定できます。SpringRunnerクラスは、Spring用のJUnitを使えるクラスです。 ・@SprintBootTestこのアノテーションを付けると、SpringBootを起動してからテストを始めてくれます。

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class UserDaoTest {

    @Autowired
    @Qualifier("UserDaoJdbcImpl")
    UserDao dao;

    // カウントメソッドのテスト1
    @Test
    public void countTest1() {

        // カウントメソッドの結果が2件であることをテスト
        assertEquals(dao.count(), 2);
    }

    // カウントメソッドのテスト2
    @Test
    @Sql("/testdata.sql")
    public void countTest2() {

        // カウントメソッドの結果が3件であることをテスト
        assertEquals(dao.count(), 3);
    }
}

補足:staticインポートJUnitを使用する際は、コードの記述量を少なくしたり、可読性を上げるためにstaticインポートがよく使われます。 上記のコードで言うと、1行目のimportstaticorg.junit.Assert.∗;の部分です。 こうすることで、Assert.assertEquals(…)ではなく、assertEquals(…)だけでメソッドを実行できます。 staticインポートはEclipseが自動で付けてくれないため、ご注意ください。 この後のコードでも使用しています。

【実行】Springテストは、JUnitを起動すればテストが開始されます。やり方は、Alt+Shift+Xの後にTを押します。 image

任意のSQL実行後にテスト

ポイント:@Sql @Sqlアノテーションを使用すると、そのSQLを実行した後の状態でテストされます。 ただし、@Sqlに記載されているSQLはそのメソッド内だけで有効です。

  1. テスト用のsqlファイル(testdata.sql)を作成します。 testdata.sqlには、ユーザーマスタに1件のユーザーを登録するSQL文を用意します。
  2. このSQLを実行した後にテストを実行するメソッドを、テスト用クラスに追加します。
    //カウントメソッドのテスト2
    //ポイント:@Sql
    @Test
    @Sql("/testdata.sql")
    public void countTest2(){
    //カウントメソッドの結果が3件であることをテスト
    assertEquals(dao.count(),3);
    }

画面表示内容のテスト

  1. 创建文件:LoginControllerTest.java ポイント1:Springのモック画面表示内容の確認をするためには、モックを使います。ここで、Springが用意しているモックを使います。それを使うと、コントローラークラスのテストを簡単にできます。 Springのモックを使うために、@AutoConfigureMockMvcアノテーションをクラスに付けます。 その後、MockMvcクラスを@Autowiredすれば、Springのモックを使うことができます。

ポイント2:画面表示内容の確認 上記のテストコードでは、Springのモックを使って、ログイン画面のテストを行っています。各コードでは、以下のようなテストをしています。 ・mockMvc.perform(get("/login"))/loginにGETリクエストを送っています。つまり、ログイン画面を取得しています。 ・.andExpect(status().isOk())HTTPリクエストが正常に終了したかどうかをチェックしています。 ・.andExpect(content().string(containsString("ユーザーID"))); ログイン画面のhtmlに「ユーザーID」という文字が含まれているかをチェックしています。

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class LoginControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void ログイン画面表示() throws Exception {

        //画面表示内容の確認
        mockMvc.perform(get("/login"))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("ユーザーID")));
    }
}

ログイン後の画面表示テスト

ログインしなければ表示できない画面についてテストします。 HomeControllerTest.java

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class HomeControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService service;

    @Test
    @WithMockUser
    public void ユーザーリスト画面のユーザー件数のテスト() throws Exception {

        // UserServiceのcountメソッドの戻り値を10に設定
        when(service.count()).thenReturn(10);

        // ユーザーリスト画面のチェック
        mockMvc.perform(get("/userList"))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("合計:10件")));
    }
}

@WithMockUserアノテーションを使うと、ログインした後にしか表示できない画面のテストをすることができます。なお、以下のようにusername,password,roles属性を指定することもできます。 ・ユーザー指定サンプル@WithMockUser(username="satou",roles={"ROLE_ADMIN"})

ポイント2:モックの戻り値設定 Springが用意するMockitoを使って、メソッドの戻り値を任意の値に変更することができます。まずは、モックとして使用するBeanに@MockBeanアノテーションを付けます。 その後、以下のコード部分でメソッドの戻り値を任意の値にしています。 ・when(service.count()).thenReturn(10); これは、service.count()メソッドの戻り値を10にしています。 その後、ユーザー一覧画面の表示内容が変わっていることをテストしています。

@MockBean负责声明这是一个模拟的bean。在进行单元测试时,需要将测试目标的所有依赖bean声明为模拟的bean,这些模拟的bean将被注入测试目标bean。

rainit2006 commented 4 years ago

https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/test-method.html

@WithMockUser The question is "How could we most easily run the test as a specific user?" The answer is to use @WithMockUser. The following test will be run as a user with the username "user", the password "password", and the roles "ROLE_USER".

@Test
@WithMockUser
public void getMessageWithMockUser() {
String message = messageService.getMessage();
...
}

The following test would run with the username "customUser". Again, the user does not need to actually exist.

@Test
@WithMockUser("customUsername")
public void getMessageWithMockUserCustomUsername() {
    String message = messageService.getMessage();
...
}
rainit2006 commented 4 years ago

ポイント1:@Mapper MyBatisでSQLを実行するクラスには@Mapperアノテーションを付けます。

ポイント2:変数の指定 SQLを実行するためには、@Insertや@Selectなどのアノテーションを付けます。 そして、アノテーションの引数にSQL文をセットします。 これが、アノテーションでのMyBatisの使い方になります。 そして、#{<変数名>}と指定することで、SQL文にメソッドの引数をセットできます。

ポイント3:カラム名 Select文を実行して、その戻り値をUserなどの参照型にしているメソッドには、注意が必要です。 それは、テーブルのカラム名と、クラスのフィールド名を一致させなければいけないからです。 ユーザーIDの例で説明すると、以下のようになります。 ・ユーザーID m_userテーブルのカラム名:user_id Userクラスのフィールド名:userId このように、テーブルのカラム名と、クラスのフィールド名が一致していない場合は、SQL文にAS句を使ってカラム名を変更します。 そうすることで、カラム名とフィールド名が一致して、Select結果をUserクラスにセットされます。

rainit2006 commented 4 years ago

MyBatis示例 https://www.youtube.com/watch?v=QR3RnWFZmQ0 (千锋Java教程:33 SpringBoot整合Mybatis注解方式)

创建一个Mapper接口 image

创建一个Service image

再创建一个Controller,实现Restful API相应

SpringApp如下: ![image](https://user-images.githubusercontent.com/12871721/73120825-12c94700-3fb6-11ea-970b-b13c28c0ad85.png)
rainit2006 commented 4 years ago

处理多个数据源: https://www.youtube.com/watch?v=vFg34lZtoGE&list=PLwDQt7s1o9J4AgZVM0hdtNpWiyrKIMHOx&index=35

image

rainit2006 commented 4 years ago

@PathVariable: URLに含まれる動的なパラメータを取得

@RequestMapping(value = "/hello/{userName}/{greeting}", method = RequestMethod.GET)
public String printWelcome(@PathVariable("userName") String userName,
@PathVariable("greeting") String greeting,
ModelMap model) {....}