Spring SecurityのログインページとReact

Reactを使用したSpring Securityログインページ

1. 概要

Reactは、Facebookによって構築されたコンポーネントベースのJavaScriptライブラリです。 Reactを使用すると、複雑なWebアプリケーションを簡単に構築できます。 この記事では、SpringSecurityをReactログインページと連携させます。

前の例の既存のSpringSecurity構成を利用します。 したがって、Form Login with Spring Securityの作成に関する以前の記事に基づいて構築します。

2. Reactをセットアップする

まず、コマンド「create-react-app react”」を実行してuse the command-line tool create-react-app to create an applicationを実行しましょう。

react/package.jsonに次のような構成があります。

{
    "name": "react",
    "version": "0.1.0",
    "private": true,
    "dependencies": {
        "react": "^16.4.1",
        "react-dom": "^16.4.1",
        "react-scripts": "1.1.4"
    },
    "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test --env=jsdom",
        "eject": "react-scripts eject"
    }
}

次に、use the frontend-maven-plugin to help build our React project with Maven:


    com.github.eirslett
    frontend-maven-plugin
    1.6
    
        v8.11.3
        6.1.0
        src/main/webapp/WEB-INF/view/react
    
    
        
            install node and npm
            
                install-node-and-npm
            
        
        
            npm install
            
                npm
            
        
        
            npm run build
            
                npm
            
            
                run build
            
        
    

プラグインの最新バージョンはhereにあります。

mvn compileを実行すると、このプラグインはnodenpmをダウンロードし、すべてのノードモジュールの依存関係をインストールして、reactプロジェクトをビルドします。

ここで説明する必要がある構成プロパティがいくつかあります。 プラグインがダウンロードするバージョンを認識できるように、nodenpmのバージョンを指定しました。

ReactログインページはSpringでは静的ページとして機能するため、npmの作業ディレクトリとして「src/main/webapp_ / WEB-INF / view / react_」を使用します。

3. Spring Securityの構成

Reactコンポーネントに飛び込む前に、Spring構成を更新して、Reactアプリの静的リソースを提供します。

@EnableWebMvc
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(
      ResourceHandlerRegistry registry) {

        registry.addResourceHandler("/static/**")
          .addResourceLocations("/WEB-INF/view/react/build/static/");
        registry.addResourceHandler("/*.js")
          .addResourceLocations("/WEB-INF/view/react/build/");
        registry.addResourceHandler("/*.json")
          .addResourceLocations("/WEB-INF/view/react/build/");
        registry.addResourceHandler("/*.ico")
          .addResourceLocations("/WEB-INF/view/react/build/");
        registry.addResourceHandler("/index.html")
          .addResourceLocations("/WEB-INF/view/react/build/index.html");
    }
}

動的に提供されるJSPの代わりにNote that we add the login page “index.html” as a static resource

次に、Spring Security構成を更新して、これらの静的リソースへのアクセスを許可します。

the previous form loginの記事で使用したように“login.jsp”を使用する代わりに、ここではLoginページとして“index.html”を使用します。

@Configuration
@EnableWebSecurity
@Profile("!https")
public class SecSecurityConfig
  extends WebSecurityConfigurerAdapter {

    //...

    @Override
    protected void configure(final HttpSecurity http)
      throws Exception {
        http.csrf().disable().authorizeRequests()
          //...
          .antMatchers(
            HttpMethod.GET,
            "/index*", "/static/**", "/*.js", "/*.json", "/*.ico")
            .permitAll()
          .anyRequest().authenticated()
          .and()
          .formLogin().loginPage("/index.html")
          .loginProcessingUrl("/perform_login")
          .defaultSuccessUrl("/homepage.html",true)
          .failureUrl("/index.html?error=true")
          //...
    }
}

上記のスニペットからわかるように、フォームデータを「/perform_login」に投稿すると、資格情報が正常に一致した場合はSpringが「/homepage.html」にリダイレクトし、そうでない場合は「/index.html?error=true」にリダイレクトします。

4. 反応コンポーネント

それでは、Reactで手を汚しましょう。 コンポーネントを使用してフォームログインを構築および管理します。

ES6(ECMAScript 2015)構文を使用してアプリケーションを構築することに注意してください。

4.1. 入力

ログインフォームの<input />要素をreact/src/Input.jsでバックアップするInputコンポーネントから始めましょう。

import React, { Component } from 'react'
import PropTypes from 'prop-types'

class Input extends Component {
    constructor(props){
        super(props)
        this.state = {
            value: props.value? props.value : '',
            className: props.className? props.className : '',
            error: false
        }
    }

    //...

    render () {
        const {handleError, ...opts} = this.props
        this.handleError = handleError
        return (
          
        )
    }
}

Input.propTypes = {
  name: PropTypes.string,
  placeholder: PropTypes.string,
  type: PropTypes.string,
  className: PropTypes.string,
  value: PropTypes.string,
  handleError: PropTypes.func
}

export default Input

上記のように、<input />要素をReact制御コンポーネントにラップして、その状態を管理し、フィールド検証を実行できるようにします。

Reactは、PropTypesを使用して型を検証する方法を提供します。 具体的には、Input.propTypes = \{…}を使用して、ユーザーから渡されたプロパティのタイプを検証します。

PropType検証は開発でのみ機能することに注意してください。 PropTypeの検証は、コンポーネントに関して行っているすべての仮定が満たされていることを確認することです。

制作中のランダムなしゃっくりに驚かされるよりも、持っている方が良いです。

4.2. Form

次に、ログインフォームのベースとなるInputコンポーネントの複数のインスタンスを組み合わせた汎用フォームコンポーネントをファイルForm.jsに作成します。

Formコンポーネントでは、HTML<input/>要素の属性を取得し、それらからInputコンポーネントを作成します。

次に、Inputコンポーネントと検証エラーメッセージがForm:に挿入されます

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Input from './Input'

class Form extends Component {

    //...

    render() {
        const inputs = this.props.inputs.map(
          ({name, placeholder, type, value, className}, index) => (
            
          )
        )
        const errors = this.renderError()
        return (
            
{this.form=fm}} > {inputs} {errors}
) } } Form.propTypes = { name: PropTypes.string, action: PropTypes.string, method: PropTypes.string, inputs: PropTypes.array, error: PropTypes.string } export default Form

次に、フィールド検証エラーとログインエラーを管理する方法を見てみましょう。

class Form extends Component {

    constructor(props) {
        super(props)
        if(props.error) {
            this.state = {
              failure: 'wrong username or password!',
              errcount: 0
            }
        } else {
            this.state = { errcount: 0 }
        }
    }

    handleError = (field, errmsg) => {
        if(!field) return

        if(errmsg) {
            this.setState((prevState) => ({
                failure: '',
                errcount: prevState.errcount + 1,
                errmsgs: {...prevState.errmsgs, [field]: errmsg}
            }))
        } else {
            this.setState((prevState) => ({
                failure: '',
                errcount: prevState.errcount===1? 0 : prevState.errcount-1,
                errmsgs: {...prevState.errmsgs, [field]: ''}
            }))
        }
    }

    renderError = () => {
        if(this.state.errcount || this.state.failure) {
            const errmsg = this.state.failure
              || Object.values(this.state.errmsgs).find(v=>v)
            return 
{errmsg}
} } //... }

このスニペットでは、フォームのエラー状態を管理するためのhandleError関数を定義します。 Inputのフィールド検証にも使用したことを思い出してください。 Actually, handleError() is passed to the Input Components as a callback in the render() function

renderError()を使用してエラーメッセージ要素を作成します。 Form’sコンストラクターはerrorプロパティを消費することに注意してください。 このプロパティは、ログインアクションが失敗したかどうかを示します。

次に、フォーム送信ハンドラーがあります。

class Form extends Component {

    //...

    handleSubmit = (event) => {
        event.preventDefault()
        if(!this.state.errcount) {
            const data = new FormData(this.form)
            fetch(this.form.action, {
              method: this.form.method,
              body: new URLSearchParams(data)
            })
            .then(v => {
                if(v.redirected) window.location = v.url
            })
            .catch(e => console.warn(e))
        }
    }
}

すべてのフォームフィールドをFormDataにラップし、fetch APIを使用してサーバーに送信します。

ログインフォームにはsuccessUrlfailureUrlが付いていることを忘れないでください。つまり、リクエストが成功したかどうかに関係なく、レスポンスにはリダイレクトが必要になります。

そのため、応答コールバックでリダイレクトを処理する必要があります。

4.3. フォームのレンダリング

必要なすべてのコンポーネントをセットアップしたので、引き続きそれらをDOMに配置できます。 基本的なHTML構造は次のとおりです(react/public/index.htmlの下にあります)。



  
    
  
  

    

最後に、フォームをreact/src/index.jsのID「container”」で<div/>にレンダリングします。

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import Form from './Form'

const inputs = [{
  name: "username",
  placeholder: "username",
  type: "text"
},{
  name: "password",
  placeholder: "password",
  type: "password"
},{
  type: "submit",
  value: "Submit",
  className: "btn"
}]

const props = {
  name: 'loginForm',
  method: 'POST',
  action: '/perform_login',
  inputs: inputs
}

const params = new URLSearchParams(window.location.search)

ReactDOM.render(
  
, document.getElementById('container'))

そのため、フォームには、usernamepasswordの2つの入力フィールドと、送信ボタンが含まれています。

ここでは、失敗したURL/index.html?error=trueにリダイレクトした後のログインエラーを処理するため、追加のerror属性をFormコンポーネントに渡します。

form login error

これで、Reactを使用したSpringSecurityログインアプリケーションの構築が完了しました。 最後に行う必要があるのは、mvn compileを実行することです。

プロセス中、MavenプラグインはReactアプリケーションのビルドを支援し、ビルド結果をsrc/main/webapp/WEB-INF/view/react/buildで収集します。

5. 結論

この記事では、Reactログインアプリを構築し、SpringSecurityバックエンドと相互作用させる方法について説明しました。 より複雑なアプリケーションには、React RouterまたはReduxを使用した状態遷移とルーティングが含まれますが、それはこの記事の範囲を超えています。

いつものように、完全な実装はover on Githubで見つけることができます。 ローカルで実行するには、プロジェクトのルートフォルダーでmvn jetty:runを実行すると、http://localhost:8080でReactログインページにアクセスできます。