#author("2021-07-02T06:05:48+00:00","","")
#author("2021-07-02T07:19:18+00:00","","")
[[第3回]]

* 認証・認可 [#ldb0bb1e]

- テキストはパワーポイントを参照のこと
- 参考  [[SpringBoot/認証・認可]]

** ToDo管理アプリに認証・認可を実装する [#j079ac41]

*** やりたいこと [#e753a6d2]
+ ToDo管理に''メンバーごとのパスワード認証''をかけたい
+ これまで管理者が行っていたメンバー登録は,メンバー自身でやるように変更したい
-- サインアップ: メンバーが自分自身でサービスに登録すること
-- サインイン: メンバーが登録した自身の認証情報でサービスにログインすること
+ 認可の権限は2種類
-- ''メンバー (MEMBER):'' ToDo画面 (/{mid}/todos) で自分のToDoを管理する.
--- 自身のTodDoを閲覧,登録,完了できる.全員のToDo (/{mid}/todos/all) を見ることもできる
--- ただし,''他人のToDo画面は見ることができない''
-- ''管理者 (ADMIN):'' 管理者画面(/admin/register)でユーザの管理を行う
-- 登録済ユーザの一覧,新規登録,削除ができる

** 認証・認可部の実装 [#p48d9fed]
*** 方針 [#va9742c6]
- S1: Member, MemberFormにパスワードトロールを追加
- S2: UserDetailsの実装クラスUserDetailsImplを作る
- S3: MemverService にUserDetailsServiceを継承させ,loadUserByUsername()を実装する
- S4: ToDoAppSecurityConfig を作る

*** 準備 [#zf1b5ff4]
- build.gradle の dependenciesの中に,下記を追記
 implementation 'org.springframework.boot:spring-boot-starter-security'
 implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
 

*** S1: Member, MemberFormにパスワードトロールを追加 [#rf4563b2]
- entity/Member.java
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 @Entity
 public class Member {
     @Id
     String mid;   //メンバーID
     String name;  //名前
     String password; //パスワード(暗号化済)
     String role; //ロール
 }
- dto/MemberForm.java
 @Data
 public class MemberForm {
     @Pattern(regexp ="[a-z0-9_\\-]{4,16}")
     String mid; //メンバーID.英小文字,数字,ハイフン,アンダーバー.4文字以上16文字以下.
     @NotBlank
     @Size(min = 1, max = 32)
     String name; //名前.最大32文字
     @NotBlank
     @Size(min = 8)
     String password; //パスワード
     String role = "MEMBER"; //ロール.デフォルトは"MEMBER"
     public Member toEntity() {
         Member m = new Member(mid, name, password, role);
         return m;
     }
 }

解説
- パスワード,ロールともに文字列型で定義
- ロールはデフォルトでMEMBERとしている


*** S2: UserDetailsの実装クラスUserDetailsImplを作る [#h2b315a8]
- dto/UserDetailsImpl.java

 package jp.ac.kobe_u.cs.itspecialist.todoapp.dto;
 
 import java.util.ArrayList;
 import java.util.Collection;
 
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.userdetails.UserDetails;
 
 import jp.ac.kobe_u.cs.itspecialist.todoapp.entity.Member;
 
 /**
  * 認証に必要なUserDetailsの実装クラス.Memberをラップする
  */
 public class UserDetailsImpl implements UserDetails {
     Member member;
     Collection<GrantedAuthority> authorities = new ArrayList<>();
 
     /**
      * コンストラクタ
      * @param member
      */
     public UserDetailsImpl(Member member) {
         this.member=member;
         //メンバーのロールから権限を生成して追加
         this.authorities.add(new SimpleGrantedAuthority("ROLE_" + member.getRole()));
     }
 
     public Member getMember() {
         return member;
     }
 
     @Override
     public Collection<? extends GrantedAuthority> getAuthorities() {
         return authorities;
     }
 
     @Override
     public String getPassword() {
         return member.getPassword();
     }
 
     @Override
     public String getUsername() {
         return member.getMid();
     }
 
     @Override
     public boolean isAccountNonExpired() {
         return true;
     }
 
     @Override
     public boolean isAccountNonLocked() {
        return true;
     }
 
     @Override
     public boolean isCredentialsNonExpired() {
         return true;
     }
 
     @Override
     public boolean isEnabled() {
         return true;
     }
 }


*** S3: MemverService にUserDetailsServiceを継承させ,loadUserByUsername()を実装する [#e41f4707]
- service/MemberService.java

 package jp.ac.kobe_u.cs.itspecialist.todoapp.service;
 
 import java.util.List;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.stereotype.Service;
 
 import jp.ac.kobe_u.cs.itspecialist.todoapp.dto.MemberForm;
 import jp.ac.kobe_u.cs.itspecialist.todoapp.dto.UserDetailsImpl;
 import jp.ac.kobe_u.cs.itspecialist.todoapp.entity.Member;
 import jp.ac.kobe_u.cs.itspecialist.todoapp.exception.ToDoAppException;
 import jp.ac.kobe_u.cs.itspecialist.todoapp.repository.MemberRepository;
 
 /**
  * メンバーのCRUDを行うサービス
  */
 @Service
 public class MemberService implements UserDetailsService{
     @Autowired
     MemberRepository mRepo;
     @Autowired
     BCryptPasswordEncoder encoder;
 
     /**
      * メンバーを作成する (C)
      * @param form
      * @return
      */
     public Member createMember(MemberForm form) {
         //IDの重複チェック
         String mid = form.getMid();
         if (mRepo.existsById(mid)) {
             throw new ToDoAppException(ToDoAppException.MEMBER_ALREADY_EXISTS, mid + ": Member already exists");
         }
 
         
         Member m = form.toEntity();
         m.setPassword(encoder.encode(m.getPassword())); //エンコードしてセーブする
         return mRepo.save(m);
     }
 
     /**
      * メンバーを取得する (R)
      * @param mid
      * @return
      */
     public Member getMember(String mid) {
         Member m = mRepo.findById(mid).orElseThrow(
                 () -> new ToDoAppException(ToDoAppException.NO_SUCH_MEMBER_EXISTS, mid + ": No such member exists"));
         return m;
     }
 
     /**
      * 全メンバーを取得する (R)
      * @return
      */
     public List<Member> getAllMembers() {
         return mRepo.findAll();
     }
 
     public boolean existById(String mid) {
         return mRepo.existsById(mid);
     }
 
     /**
      * メンバーを削除する (D)
      */
     public void deleteMember(String mid) {
         Member m = getMember(mid);
         mRepo.delete(m);
     }
 
     /* ------------------ ここから追加分  ---------------------------*/
     /**
      * Spring Securityのメソッド.ユーザIDを与えて,ユーザ詳細を生成する
      */
     @Override
     public UserDetails loadUserByUsername(String mid) throws UsernameNotFoundException {
         Member m = mRepo.findById(mid).orElseThrow(
             () -> new UsernameNotFoundException(mid + ": no such user exists")
         );
         return new UserDetailsImpl(m);
     }
 
     /**
      * 管理者を登録するサービス.
      */
     public Member registerAdmin(String adminPassword) {
         Member m = new Member();
         m.setMid("admin");
         m.setName("System Administrator");
         m.setPassword(encoder.encode(adminPassword));
         m.setRole("ADMIN");
         return mRepo.save(m);
     }
 
 
 }

*** S4: ToDoAppSecurityConfig を作る [#v8890f94]
*** ToDoアプリのセキュリティ設定方針 [#kf10be3b]
3つのconfigure()でやっている設定
+ 静的コンテンツ /img/**, /js/**, /css/** は認証なしでアクセス許可
+ HTTPアクセスについて
-- /sign_in は認証不要 (ログインの前)
-- /sign_up/** は認証不要 (新規アカウントの作成)
-- /admin/** はADMINロールが必要
-- それ以外はすべて認証が必要
+ ログインの設定
-- フォームログインで行う 
-- ログイン画面は /sign_in
-- ログイン画面のポスト先は, /authenticate
-- ログインフォームのユーザIDは,mid
-- ログインフォームのパスワードは,password
-- ログイン成功したら,/sign_in_success に着地する
-- ログイン失敗したら,/sign_in?error に飛ぶ
+ ログアウト設定
-- ログアウトは /sign_out にPOST
-- ログアウト成功したら,/sign_in?sign_out に着地
-- クッキー,セッションともに消去
-- ログアウトは認証なしでアクセス可能
+ 認証方法の実装の指定
-- MemberServiceに委任
-- パスワードエンコーダは,ファイル内で生成したBeanを使う

*** 具体的な設定クラス [#cd0a8033]
- 新規にconfigurationという名前でフォルダを作成
- configuration/ToDoAppSecurityConfig.java

 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.builders.WebSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 
 import jp.ac.kobe_u.cs.itspecialist.todoapp.service.MemberService;
 @Configuration
 @EnableWebSecurity //(1) Spring Securityを使うための設定
 public class ToDoAppSecurityConfig extends WebSecurityConfigurerAdapter {
     @Autowired
     MemberService mService;
     
     @Value("${admin.pass}")
     String adminPass;
 
     /**
      * 静的リソースの認可設定
      */
     @Override
     public void configure(WebSecurity web) throws Exception {
         web.ignoring().antMatchers("/img/**", "/css/**", "/js/**");
 
     }
 
     /**
      * HTTPリクエストに対する認可,および,ログイン・ログアウト設定
      */
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         //認可の設定
         http.authorizeRequests()
        .antMatchers("/sign_in").permitAll()           //サインインページは誰でも許可
        .antMatchers("/sign_up/**").permitAll()
        .antMatchers("/admin/**").hasRole("ADMIN")     //ユーザ管理は管理者のみ許可
        .anyRequest().authenticated();                 //それ以外は全て認証必要
 
        //ログインの設定
        http.formLogin()                               // 1.
        .loginPage("/sign_in")                       // 2. ログインページ
        .loginProcessingUrl("/authenticate")       // 3. フォームのPOST先URL.認証処理を実行する
        .usernameParameter("mid")                  // 4. ユーザ名に該当するリクエストパラメタ
        .passwordParameter("password")             // 5. パスワードに該当するリクエストパラメタ
        .defaultSuccessUrl("/sign_in_success", true)        // 6. 成功時のページ (trueは以前どこにアクセスしてもここに遷移する設定)
        .failureUrl("/sign_in?error");               // 7. 失敗時のページ
 
         //ログアウトの設定
         http.logout()                               //1.
         .logoutUrl("/sign_out")                      //2. ログアウトのURL
         .logoutSuccessUrl("/sign_in?sign_out")         //3. ログアウト完了したらこのページへ
         .deleteCookies("JSESSIONID")               //4. クッキー削除
         .invalidateHttpSession(true)               //5. セッション情報消去
         .permitAll();                              //6. ログアウトはいつでもアクセスできる
     }
 
     /**
      * 認証の方法を設定
      */
     @Override
     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
         auth.userDetailsService(mService).passwordEncoder(passwordEncoder());
         //ついでに管理者を登録しておく
         mService.registerAdmin(adminPass);
     }
 
     /**
      * アプリで共通のパスワード生成器を作る.
      * @Beanをつけているので任意の箇所でAutowired可能になる
      */
     @Bean
     public BCryptPasswordEncoder passwordEncoder() {
         BCryptPasswordEncoder bcpe = new BCryptPasswordEncoder();
         return bcpe;
     }
 }
- resources/application.properties に追記
 admin.pass=s3cret


** 認証・認可に伴う更新 [#if9191e0]

*** サインアップ機能のためのコントローラと画面 [#mcf06920]
- controller/SignUpController.java
 package jp.ac.kobe_u.cs.itspecialist.todoapp.controller;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
 
 import org.springframework.ui.Model;
 import org.springframework.validation.BindingResult;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.ModelAttribute;
 import org.springframework.web.bind.annotation.PostMapping;
 
 import jp.ac.kobe_u.cs.itspecialist.todoapp.dto.MemberForm;
 import jp.ac.kobe_u.cs.itspecialist.todoapp.entity.Member;
 import jp.ac.kobe_u.cs.itspecialist.todoapp.service.MemberService;
 
 @Controller
 @RequestMapping("/sign_up")
 public class SignUpController {
     @Autowired
     MemberService mService;
 
     /**
      * 一般ユーザの登録ページ HTTP-GET /sign_up
      * 
      * @param model
      * @return
      */
     @GetMapping("")
     String showSignUpForm(@ModelAttribute MemberForm form, Model model) {
         model.addAttribute("MemberForm", form);
 
         return "signup";
     }
 
     /**
      * ユーザ登録確認ページを表示 HTTP-POST /sign_up/check
      * 
      * @param form
      * @param model
      * @return
      */
     @PostMapping("/check")
     String checkMemberForm(@Validated @ModelAttribute(name = "MemberForm") MemberForm form, BindingResult bindingResult,
             Model model) {
         // 入力チェックに引っかかった場合、ユーザー登録画面に戻る
         if (bindingResult.hasErrors()) {
             // GETリクエスト用のメソッドを呼び出して、ユーザー登録画面に戻る
             return showSignUpForm(form, model);
         }
 
         model.addAttribute("MemberForm", form);
 
         return "signup_check";
     }
 
     /**
      * ユーザ登録処理 -> 完了ページ HTTP-POST /sign_up/register
      * 
      * @param form
      * @param model
      * @return
      */
     @PostMapping("")
     String createMember(@ModelAttribute(name = "MemberForm") MemberForm form, Model model) {
         Member m = mService.createMember(form);
         model.addAttribute("MemberForm", m);
 
         return "signup_complete";
     }
 
 }

- resources/templates/signup.html
 <!DOCTYPE html>
 <html lang="ja" xmlns:th="http://www.thymeleaf.org">
 
 <head>
     <meta charset="UTF-8">
     <title>サインアップ</title>
 </head>
 
 <body>
     <h1>サインアップ</h1>
     <h2>メンバー新規登録</h2>
     <p> メンバーIDと氏名を入力して,「確認する」を押してください.</p>
     <ul>
         <li>メンバーIDには,アルファベット小文字,数字,ハイフン,アンダーバーのみ使用できます.4文字以上16文字未満.</li>
         <li>氏名は最大32文字.半角・全角が使用できます.</li>
         <li>パスワードは8文字以上でつけてください.</li>
     </ul>
     <form role="form" th:action="@{/sign_up/check}" th:object="${MemberForm}" method="post">
         <table>
             <tr>
                 <td><label>メンバーID: </label></td>
                 <td><input type="text" required th:field="*{mid}" />
                     <span th:if="${#fields.hasErrors('mid')}" th:errors="*{mid}" style="color: red"></span>
                 </td>
             </tr>
             <tr>
                 <td><label>氏名: </label></td>
                 <td><input type="text" required th:field="*{name}" />
                     <span th:if="${#fields.hasErrors('name')}" th:errors="*{name}" style="color: red"></span>
                 </td>
             </tr>
             <tr>
                 <td><label>パスワード: </label></td>
                 <td><input type="password" required th:field="*{password}" />
                     <span th:if="${#fields.hasErrors('password')}" th:errors="*{password}" style="color: red"></span>
                 </td>
             </tr>
         </table>
         <p><input type="submit" value="確認する" /></p>
     </form>
 </body>
 
 </html>

- resources/templates/signup_check.html
 <!DOCTYPE html>
 <html lang="ja" xmlns:th="http://www.thymeleaf.org">
 
 <head>
     <meta charset="UTF-8">
     <title>登録確認</title>
 </head>
 
 <body>
     <h1>登録確認</h1>
     <p>
         以下の情報でメンバーを登録します.よろしければ登録を押してください.
     </p>
 
     <form role="form" th:action="@{/sign_up}" th:object="${MemberForm}" method="post">
         <p>
             <label>メンバーID: </label>
             [[*{mid}]]
         </p>
         <p>
             <label>氏名: </label>
             [[*{name}]]
         </p>
         <p>
             <label>パスワード: </label>
             [[*{password}]]
         </p>
         <p>
             <input type="button" value="戻る" onclick="history.back()" />
             <input type="submit" value="登録する" />
         </p>
         <input type="hidden" th:field="*{mid}" />
         <input type="hidden" th:field="*{name}" />
         <input type="hidden" th:field="*{password}" />
     </form>
 </body>
 </html>

- resources/templates/signup_complete.html

 <!DOCTYPE html>
 <html lang="ja" xmlns:th="http://www.thymeleaf.org">
 
 <head>
     <meta charset="UTF-8">
     <title>登録完了</title>
 </head>
 
 <body>
     <h1>登録完了</h1>
     <p>
         メンバー登録を完了しました.
     </p>
 
     <div th:object="${MemberForm}">
         <p>
             <label>メンバーID: </label>
             [[*{mid}]]
         </p>
         <p>
             <label>氏名: </label>
             [[*{name}]]
         </p>
         <p>
             <a th:href=@{/}>初めに戻る</a>
         </p>
     </div>
 </body>
 
 </html>


*** ToDpControllerの改訂方針 [#ica16b6d]
- ログインしていないと,/sign_in に飛ばされる
- ログインが成功すると,/sign_in_success にリダイレクトされるので,そこからさらにリダイレクト
-- MEMBER: /{uid}/todos にリダイレクト
-- ADMIN: /admin/register にリダイレクト
- 各画面のログアウトも変更
-- /sign_out にPOSTリクエストしなければならない

*** ToDpControllerと関連画面の改訂 [#zaff7b09]
- controller/ToDoController.java
 
 import java.util.List;
 import java.util.Map;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.validation.BindingResult;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.ModelAttribute;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 
 import jp.ac.kobe_u.cs.itspecialist.todoapp.dto.LoginForm;
 import jp.ac.kobe_u.cs.itspecialist.todoapp.dto.ToDoForm;
 import jp.ac.kobe_u.cs.itspecialist.todoapp.dto.UserDetailsImpl;
 import jp.ac.kobe_u.cs.itspecialist.todoapp.entity.Member;
 import jp.ac.kobe_u.cs.itspecialist.todoapp.entity.ToDo;
 import jp.ac.kobe_u.cs.itspecialist.todoapp.exception.ToDoAppException;
 import jp.ac.kobe_u.cs.itspecialist.todoapp.service.MemberService;
 import jp.ac.kobe_u.cs.itspecialist.todoapp.service.ToDoService;
 
 @Controller
 public class ToDoController {
     @Autowired
     MemberService mService;
     @Autowired
     ToDoService tService;
 
     /**
      * トップページ
      */
     @GetMapping("/sign_in")
     String showIndex(@RequestParam Map<String, String> params, @ModelAttribute LoginForm form, Model model) {
         //パラメータ処理.ログアウト時は?logout, 認証失敗時は?errorが帰ってくる(WebSecurityConfig.java参照) 
 		if (params.containsKey("sign_out")) {
 			model.addAttribute("message", "サインアウトしました");
 		} else if (params.containsKey("error")) {
 			model.addAttribute("message", "サインインに失敗しました");
 		} 
         //model.addAttribute("loginForm", loginForm);
         return "signin";
     }
 
     /**
      * ログイン処理.midの存在確認をして,ユーザページにリダイレクト
      */
     @GetMapping("/sign_in_success")
     String login() {
         Authentication auth = SecurityContextHolder.getContext().getAuthentication();
         Member m  = ((UserDetailsImpl) auth.getPrincipal()).getMember();
         if (m.getRole().equals("ADMIN")) {
             return "redirect:/admin/register";
         }
         return "redirect:/" + m.getMid() + "/todos";
     }
 
     /**
      * ユーザのToDoリストのページ
      */
     @GetMapping("/{mid}/todos")
     String showToDoList(@PathVariable String mid, @ModelAttribute(name = "ToDoForm") ToDoForm form, Model model) {
         checkIdentity(mid);
 
         Member m = mService.getMember(mid);
         model.addAttribute("member", m);
         model.addAttribute("ToDoForm", form);
         List<ToDo> todos = tService.getToDoList(mid);
         model.addAttribute("todos", todos);
         List<ToDo> dones = tService.getDoneList(mid);
         model.addAttribute("dones", dones);
         return "list";
     }
 
     /**
      * 全員のToDoリストのページ
      */
     @GetMapping("/{mid}/todos/all")
     String showAllToDoList(@PathVariable String mid, Model model) {
         checkIdentity(mid);
         Member m = mService.getMember(mid);
         model.addAttribute("member", m);
         List<ToDo> todos = tService.getToDoList();
         model.addAttribute("todos", todos);
         List<ToDo> dones = tService.getDoneList();
         model.addAttribute("dones", dones);
         return "alllist";
     }
 
     /**
      * ToDoの作成.作成処理後,ユーザページへリダイレクト
      */
     @PostMapping("/{mid}/todos")
     String createToDo(@PathVariable String mid, @Validated @ModelAttribute(name = "ToDoForm") ToDoForm form,
             BindingResult bindingResult, Model model) {
         checkIdentity(mid);
 
         if (bindingResult.hasErrors()) {
             return showToDoList(mid, form, model);
         }
         tService.createToDo(mid, form);
         return "redirect:/" + mid + "/todos";
     }
 
     /**
      * ToDoの完了.完了処理後,ユーザページへリダイレクト
      */
     @GetMapping("/{mid}/todos/{seq}/done")
     String doneToDo(@PathVariable String mid, @PathVariable Long seq, Model model) {
         checkIdentity(mid);
         tService.done(mid, seq);
         return "redirect:/" + mid + "/todos";
     }
 
 
     /**
      * 認可チェック.与えられたmidがログイン中のmidに等しいかチェックする
      */
     private void checkIdentity(String mid) {
         Authentication auth = SecurityContextHolder.getContext().getAuthentication();
         Member m  = ((UserDetailsImpl) auth.getPrincipal()).getMember();
         if (!mid.equals(m.getMid())) {
             throw new ToDoAppException(ToDoAppException.INVALID_TODO_OPERATION, 
             m.getMid() + ": not authorized to access resources of " + mid);
         }
     }
 
 
 }

- resources/templates/signin.html

 <!DOCTYPE html>
 <html lang="ja" xmlns:th="http://www.thymeleaf.org">
 
 <head>
     <meta charset="UTF-8">
     <title>ToDoアプリケーション</title>
 </head>
 
 <body>
     <h1>ToDoアプリケーション</h1>
     <img th:src=@{/img/todo_people.png} width="640px">
     <p>
         サインインしてください.初めての方は,<a th:href="@{/sign_up}">サインアップ</a>してください.
     </p>
     <form role="form" th:action="@{/authenticate}" th:object="${loginForm}" method="post">
         <p>
             <label>メンバーID: </label>
             <input type="text" th:field="*{mid}" /> 
             <span th:if="${#fields.hasErrors('mid')}" th:errors="*{mid}" style="color: red"></span>
         </p>
         <p>
             <label>パスワード: </label>
             <input type="password" th:field="*{password}" /> 
             <span th:if="${#fields.hasErrors('password')}" th:errors="*{password}" style="color: red"></span>
         </p>
 
         <p>
             <input type="submit" value="サインイン" />
         </p>
         <p>
             <span th:if="${message}" th:text="${message}" style="color: red"></span>
         </p>
     </form>
 </body>
 
 </html>

- resources/templates/list.html
-- サインアウトボタンはPOST

 <!DOCTYPE html>
 <html lang="ja" xmlns:th="http://www.thymeleaf.org">
 
 <head>
     <meta charset="UTF-8">
     <title>ToDoList</title>
 </head>
 
 <body>
     <h1> ようこそ [[${member.name}]] さん!</h1>
     <p>
         <a th:href="@{/{mid}/todos/all(mid=${member.mid})}">みんなのToDo</a>
         <form th:action="@{/sign_out}" method="post">
             <input type="submit" value="サインアウト" />
         </form>
     </p>
     <h2>ToDo</h2>
     <table border="1">
         <tr>
             <th>#</th>
             <th>タイトル</th>
             <th>作成日時</th>
             <th>コマンド</th>
         </tr>
         <tr th:each="todo: ${todos}">
             <td>[[${todo.seq}]]</td>
             <td>[[${todo.title}]]</td>
             <td>[[${todo.createdAt}]]</td>
             <td>
                 <a th:href="@{/{mid}/todos/{seq}/done(mid=${member.mid},seq=${todo.seq})}">完了</a>
             </td>
         </tr>
         <tr>
             <td>
                 *
             </td>
             <td colspan="3">
                 <form role="form" th:action="@{/{mid}/todos(mid=${member.mid})}" th:object="${ToDoForm}" method="post">
                     <input type="text" required th:field="*{title}" />
                     <input type="submit" value="新規作成" />
                     <div th:if="${#fields.hasErrors('title')}" th:errors="*{title}" style="color: red"></div>
                 </form>
             </td>
         </tr>
     </table>
 
     <h2>Done</h2>
     <table border="1">
         <tr>
             <th>#</th>
             <th>タイトル</th>
             <th>作成日時</th>
             <th>完了日時</th>
         </tr>
         <tr th:each="done: ${dones}">
             <td>[[${done.seq}]]</td>
             <td>[[${done.title}]]</td>
             <td>[[${done.createdAt}]]</td>
             <td>[[${done.doneAt}]]</td>
         </tr>
     </table>
 
 </body>
 
 </html>

- resources/templates/alllist.html

 <!DOCTYPE html>
 <html lang="ja" xmlns:th="http://www.thymeleaf.org">
 
 <head>
     <meta charset="UTF-8">
     <title>ToDoList</title>
 </head>
 
 <body>
     <h1> みんなのToDoリスト</h1>
     <p>
         <a th:href="@{/{mid}/todos(mid=${member.mid})}">自分のToDo</a>
         <form th:action="@{/sign_out}" method="post">
             <input type="submit" value="サインアウト" />
         </form>
     </p>
     <h2>ToDo</h2>
     <table border="1">
         <tr>
             <th>#</th>
             <th>タイトル</th>
             <th>作成者</th>
             <th>作成日時</th>
         </tr>
         <tr th:each="todo: ${todos}">
             <td>[[${todo.seq}]]</td>
             <td>[[${todo.title}]]</td>
             <td>[[${todo.mid}]]</td>
             <td>[[${todo.createdAt}]]</td>
         </tr>
     </table>
 
     <h2>Done</h2>
     <table border="1">
         <tr>
             <th>#</th>
             <th>タイトル</th>
             <th>作成者</th>
             <th>完了日時</th>
         </tr>
         <tr th:each="done: ${dones}">
             <td>[[${done.seq}]]</td>
             <td>[[${done.title}]]</td>
             <td>[[${done.mid}]]</td>
             <td>[[${done.doneAt}]]</td>
         </tr>
     </table>
 
 </body>
 
 </html>

** 管理者画面 /admin/register [#p3793983]

同様に変更してみてください!

** 参考 [#z7b7ba69]
- [[SpringBoot/Form認証]]

トップ   編集 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS