yuusuke-roughの日記

Java,SpringBoot,趣味等

JavaScriptでFormに値を格納して送信する in SpringBoot

HTML内で選択した値をJavaScriptでFormに格納してPOSTで受け取るという処理。

 

HTML Thymeleaf

 <div id="joinDialogBox" style="display:none">
               <form th:action='@{/joinRoom/moving}' th:object="${sendJoinRoomForm}" method= 'post'>
                <input id="joinPassword" th:field='*{joinPassword}' type='hidden'>
                <input id="joinId" th:field='*{joinId}' type='hidden' >
                <button>参加する!</button>
            </form>
        </div>

 

Controller

@PostMapping("/joinRoom/moving")
    public String joinRoom_moving(@Valid @ModelAttribute("sendJoinRoomForm") SendJoinRoomForm sendJoinRoomForm, BindingResult result) {
        
        if(result.hasErrors()) {
            System.out.println("nulldesuyo");
            return "redirect:/joinRoom";
        }
        System.out.println(sendJoinRoomForm.getJoinId());
        return "/home";
    }

 

JavaScriptでクリックした値をjoinIdに格納したい。

結果はtextContentとvalueで結果が変わった。

textContentに直接値を入れるとController側ではnullになる。

valueだと値を受け取れる。

 

 

<input>&colon; 入力欄(フォーム入力)要素 - HTML&colon; HyperText Markup Language | MDN

text:既定値です。単一行のテキスト入力欄です。改行は自動的に入力値から取り除かれます。

value:入力コントロールの値です。 HTML の中で指定されると、これは初期値となり、その後で JavaScript を使用してそれぞれの HTMLInputElement オブジェクトの value プロパティにアクセスすることで、いつでも変更したり受け取ったりすることができます。

 

ひとまず違いは分かりましたが、なぜtextはダメなのでしょうか。

未解決でした。

onclickから値の取得ができなかった in html

切り分け

 

①onclickで値を取得する。→OK

 

html

<input id="numberOfLogs" type="hidden" th:value="${numberOfLogs}">

 

javascript

 document.getElementById("numberOfLogs").onclick =  function test(e){
         console.log(e.target.value);
       }

 

②値が動的でも問題ないかの切り分け その1

1.値を固定

 

html

 <input id="test0"  th:value="${numberOfLogs}">

 

javascript

 for(let i=0; i<1; i++){
           document.getElementById("test" + i).onclick =  function test(e){
                 console.log(e.target.value);
               }
        }

 

2.動的に生成した値の確認

コンソールにて

<div id="'joinRoom' + ${stat.index}" onclick="getLog()">貯蓄の定義</div>

 

${stat.index}を先に評価しないといけない事が確認できた。

idでは式を評価できないので、th:idに修正した。

 

次にクリック時のイベントで発生したエラーについて。

 

html

<td ><div th:id="'joinRoom' + ${stat.index}" th:text="${room.roomName}" ></div></td>

 

JavaScript

const numberOfLogs = document.getElementById("numberOfLogs").value;
               for(let i = 0; i<numberOfLogs; i++){
                   document.getElementById("joinRoom" + i).onclick = function (){
                       console.log(document.getElementById("getRoom" + i).textContent)
                   }
                    
               }

         }

 

Java

model.addAttribute("numberOfLogs", roomPage.getSize());

 

cannot set properties of null (setting 'onclick')が発生した。しかし、クリックイベントは動作した。

以上の事からnullの原因がJava側のroomPage.getSize()側にあると判明。元々はPageableの取得件数を表示するというものだ。

ただ、これが実際に取得した件数と相違がある場合にこのfor文ではnullが発生する。

なので、実際に返されるListであるroomPage.getContent().size()で取得をするとうまくいった。

 

 

無限スクロールとPageableでログを取得する in Spring Boot

はじめに

2022年9月16日、開発始めてから早くも3カ月。

別の大会が控えているため、貴重な休日はプログラミングと大会に備えるという日々。

今日は、長らく悩んでいたWebSocketでのセッションについて、いくつかの疑問を残すもおおかた解決したので貯めていた記録を書くことにした。

参考にしたサイトを思い出しながら探し、端的にまとめられるよう努めたい。

 

まず、Pageable。

PageableHandlerMethodArgumentResolverをOverrideしたものを使用していたが、アノテーションで事足りると知ったので結論だけを残したい。

 

つづいて、無限スクロール。

スクロールした時にログ取得のリクエストを送るように設定した。

 

あまり良い組み合わせじゃないかもしれないので、ご指摘があった時は適宜修正したい。

 

内容

Pageableについては単純に、以下を参考にした。

qiita.com

 

無限スクロールは

qiita.com

 

developer.mozilla.org

 

具体的な処理は、ターゲットがビューポートと交差したタイミングで、既存ログを保存→ログ取得のリクエストを送信→JSONで受け取り表示。ページの上限になったら読み込み停止。

バッグエンドでは、Pageで呼び出したログをJacksonでJSONに変換している。

CSRFトークンを用いたリクエスト送信ができなかった in SpringSecurity

はじめに

CSRFトークンを送信しても403が返ってくる事態に際して奮闘した2日間の記録である。

なお、コロナとも戦っていた模様。

 

内容

CSRFトークンを使用してリクエストを送信するコードを以下とした。

 

    var csrfToken = document.getElementById("_csrf").value;
        var token = csrfToken.replace("XSRF-TOKEN=", "")
        
        var header = "X-CSRF-TOKEN"
    
        var mail = document.getElementById("email").value
        
        const XHR = new XMLHttpRequest();
        XHR.open('POST', '/resetPassword?mail=' + mail)
        XHR.setRequestHeader(header, token)
        
        XHR.send()

 

Cookieに保存されているXSRF-TOKENから値を取り出し_csrfにセットするという物だが、403がでる。

 

原因を調べても全然ヒットしない。

その時に気づいた。

CookieXSRF-TOKENの値がずっと同じ...?」

CSRFトークンはログイン時に付与されるのでは...?」

「Remember-meと関係あるのかな...deleteCookiesで試してみよう」

 

と、さっそくlogout時にdeleteCookiesでXSRF-TOKENごと削除した。

すると、今度はCookieXSRF-TOKENがない...

結局、SessionのAttributesからトークンを持ってくる事にした。

 

DefaultCsrfToken token = (DefaultCsrfToken)    request.getSession().getAttribute("org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN"
            );

 

チャットルームの作成を試みた in WebSocket

はじめに

突然、暑くなりましたね。

私のような素人のブログでは、ただの日記のようになっていますが、久しぶりに更新しました。

 

動作環境

Spring Boot 2.7.3

Java SE11

実装しようとしたこと

チャットルームの実装です。

前回試したチュートリアルを用いて作成しました。

 

@MessageMapping("/hello/{id}")
      @SendTo("/topic/greetings/{id}" )
      public Greeting greeting(HelloMessage message,@PathVariable String id) throws Exception {
        Thread.sleep(1000); // simulated delay
        return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
      }
    
    @GetMapping("/topic/greetings/{id}")
    public String messageIndex(@PathVariable String id, Model model) {
        model.addAttribute("id", id);
        return "index";
    }

 

"/topic/greetings/{id}"へアクセスした際にidがstompClient.sendでの宛先にセットできるようにし、stompClient.subscribeで/topic/greetings/{id}に送られたメッセージを受け取るという形式にしました。

 

パスワードの試行回数を制限する in Spring Security

はじめに

夏も近いと思わせるような木々の緑や山の風と、いつも同じ場所から聞こえるウグイスの鳴き声で頑張れそうな気がする日々です。

 

動作環境

Spring Boot 2.7.3

Java SE11

Spring Security 5.6.5

 

実装しようとしたこと

パスワードの試行回数を制限するというのがテーマです。

正しい実装方法ではないと思います。というのも、UserのAccountNonLockedを十分に使用できておらず、コードをAuthenticationProvider、Controllerに記載しなくてはならないからです。なので、仮の実装とします。

 

試行した回数を記録する countOfLoginAttemptと試行した時間を記録するfirstAttemptLoginTimeを準備し認証時にアカウントをロックするか決めるというものです。

 

認証時に

//パスワード試行回数が10分以内に5回を超えて失敗したらアカウントロックする
        Date now = new Date();
        
            //初回ログイン時
        if(user.getFirstAttemptLoginTime()==null) {
            user.setFirstAttemptLoginTime(now);
        }
        
            //制限される時間を作成
        Calendar addRestrictTime = Calendar.getInstance();
        addRestrictTime.setTime(user.getFirstAttemptLoginTime());
        addRestrictTime.add(Calendar.MINUTE, 10);
        Date RestrictTime = addRestrictTime.getTime();
        
        //ログイン試行5回以上で10分経過ならカウントは0となる。
                if(user.getCountOfLoginAttempt() > 4 && now.after(RestrictTime)) {
                    user.setCountOfLoginAttempt(0);
                    user.setFirstAttemptLoginTime(now);
                }
        
        //アカウントロック
        if(user.getCountOfLoginAttempt() > 4) {
            if(now.before(RestrictTime)) {
                throw new LockedException("It will unlocked 10 minutes after you first try to login");
            }
            throw new LockedException("your account is locked , please wait 10 minutes");
        }
        

  //ログインのカウントをする
        user.setCountOfLoginAttempt(user.getCountOfLoginAttempt()+1);
        siteUserRepository.save(user);
        

 

次に、例外をセッションに設定する。failureHandlerを使用する。

@Component
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler{
    
    @Override
    public void onAuthenticationFailure(HttpServletRequest request,HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        setDefaultFailureUrl("/login?error=true");
        
        super.onAuthenticationFailure(request, response, exception);
        System.out.println(exception.getMessage());
        request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception.getMessage());
    
    }
}

 

configにも設定が必要なため以下を追記する。

 

.failureHandler(authenticationFailureHandler)

 

最後にlogin.htmlで内容を表示できるようにする。

 

<div th:if="${session[SPRING_SECURITY_LAST_EXCEPTION]}">
[[${session[SPRING_SECURITY_LAST_EXCEPTION]}]]

</div>

 

追記:Controllerでログイン成功時にもカウントをリセットしています。

 

 

学習日記 in WebSocket

はじめに

GW関係なく仕事ではありますが、身内の墓掃除に行きました。

春の瑞々しい空気と早朝の山々を照らす弱弱しい太陽がとてもよかった。

職場の人間関係で頭を抱える日々でしたが、亡くなった彼らの人生を思うと、私の悩みは笑い話程度な心地がします。

また、スタートアップの方々のツイートを拝見することが増え、彼らのような想いと熱量で生きられたらいいのにと思いました。

しかし、悩むのは「問題に気づくきっかけ」と思っていますので、笑い話でも無碍にはできないというのが正直なところです。

 

 

参考サイト

spring.pleiades.io

 

日記

上記記事のコードを動かしてみた所、localhost:8080/にて動作をする。

今回の目標は、特定のURLでWebSocketを動作させたい。

 

まずは仕組みが分からないので、サンプルコードのapp.jsから読み進める。

var socket = new SockJS('/gs-guide-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/topic/greetings', function (greeting) {
            showGreeting(JSON.parse(greeting.body).content);
        });
    });

/gs-guide-websocketの部分はハンドシェイクに使用するURL。

WebSocketConfig内のregisterStompEndpointで設定しさせすれば任意のURLで良い。

JavaScriptでのSockJsでも指定するのを忘れずに。