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

[[1-overview]]

1.概要

React は、Facebookが開発したコンポーネントベースのJavaScriptライブラリです。 Reactを使えば、複雑なWebアプリケーションを簡単に構築できます。この記事では、Spring SecurityがReact Loginページと連携するようにします。

前の例の既存のSpring Security構成を利用します。そのため、https://www.baeldung.com/spring-security-login[Spring Securityでのフォームログイン]の作成に関する以前の記事の上に構築します。

[[2-setup-react]]

2. Reactを設定する

まず、コマンドラインツールhttps://github.com/facebook/create-react-app[create-react-app]を使用して、「 create-react-app react」 コマンドを実行してアプリケーションを作成しましょう。 。

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"
    }
}

次に、MavenでReactプロジェクトを構築するために、https://github.com/eirslett/frontend-maven-plugin[frontend-maven-plugin]を使用します。

<plugin>
    <groupId>com.github.eirslett</groupId>
    <artifactId>frontend-maven-plugin</artifactId>
    <version>1.6</version>
    <configuration>
        <nodeVersion>v8.11.3</nodeVersion>
        <npmVersion>6.1.0</npmVersion>
        <workingDirectory>src/main/webapp/WEB-INF/view/react</workingDirectory>
    </configuration>
    <executions>
        <execution>
            <id>install node and npm</id>
            <goals>
                <goal>install-node-and-npm</goal>
            </goals>
        </execution>
        <execution>
            <id>npm install</id>
            <goals>
                <goal>npm</goal>
            </goals>
        </execution>
        <execution>
            <id>npm run build</id>
            <goals>
                <goal>npm</goal>
            </goals>
            <configuration>
                <arguments>run build</arguments>
            </configuration>
        </execution>
    </executions>
</plugin>

プラグインの最新バージョンはhttps://search.maven.org/classic/#search%7C1%7Cg%3A%22com.github.eirslett%22%20AND%20a%3A%22frontend-maven-にありますプラグイン%22[ここ]。

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

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

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

[[3-spring-security-configuration]]

3. Springのセキュリティ設定

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

@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の代わりに、ログインページ “ index.html” を静的リソースとして追加しています。

次に、これらの静的リソースへのアクセスを許可するようにSpring Security設定を更新します。

前のフォームのログイン の記事で行ったように "login.jsp" を使用する代わりに、ここでは "index.html" Login ページとして使用します。

@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-components]]

4. Reactコンポーネント

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

アプリケーションの構築にはES6(ECMAScript 2015)構文を使用します。

[[41-input]]

4.1. 入力

react/src/Input.js にあるログインフォームの <input/> 要素を裏付ける 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 {...opts} value={this.state.value}
            onChange={this.inputChange} className={this.state.className}/>
        )
    }
}

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はhttps://reactjs.org/docs/typechecking-with-proptypes.html[ PropTypes ]を使って型を検証する方法を提供します。

具体的には、 Input.propTypes = \ {…​} を使用して、ユーザーから渡されたプロパティの種類を検証します。

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

無作為の制作中断に驚かれるよりも、それを持っているほうが良いです。

[[42-form]]

4.2. 形

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

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) => (
            <Input key={index} name={name} placeholder={placeholder} type={type} value={value}
              className={type==='submit'? className : ''} handleError={this.handleError}/>
          )
        )
        const errors = this.renderError()
        return (
            <form {...this.props} onSubmit={this.handleSubmit} ref={fm => {this.form=fm}} >
              {inputs}
              {errors}
            </form>
        )
    }
}

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 <div className="error">{errmsg}</div>
        }
    }

   //...

}

このスニペットでは、フォームのエラー状態を管理するために handleError 関数を定義します。 Input フィールドの検証にも使用したことを思い出してください。 ** 実際には、 handleError() render() 関数内のコールバックとして Input Components に渡されます。

エラーメッセージ要素を構築するために renderError() を使用します。 Formの コンストラクタは 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 にラップし、https://developer.mozilla.org/en-US/docs/Web/API/Fetch API[ fetch__API]を使用してサーバーに送信します。

忘れずに、ログインフォームに successUrl failureUrl が付いています。つまり、リクエストが成功したかどうかに関係なく、レスポンスにリダイレクトが必要になります。

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

[[43-form-rendering]]

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

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

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- ... -->
  </head>
  <body>

    <div id="root">
      <div id="container"></div>
    </div>

  </body>
</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(
  <Form {...props} error={params.get('error')}/>,
  document.getElementById('container'))

そのため、フォームには username password の2つの入力フィールドと送信ボタンがあります。

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

フォームログインエラー

これで、Reactを使ったSpring Securityログインアプリケーションの構築が完了しました。最後にやらなければいけないことは、 mvn compile を実行することです。

その過程で、MavenプラグインはReactアプリケーションをビルドし、ビルド結果を src/main/webapp/WEB-INF/view/react/build に集めます。

[[5-summary]]

5.まとめ

この記事では、Reactログインアプリを作成し、それをSpring Securityバックエンドと対話させる方法について説明しました。より複雑なアプリケーションはhttps://reacttraining.com/react-router/[React Router]またはhttps://redux.js.org/[Redux]を使用した状態遷移とルーティングを含みますが、それは範囲外この記事の

いつものように、完全な実装はhttps://github.com/eugenp/tutorials/tree/master/spring-security-react[over Github]で見つけることができます。ローカルで実行するには、プロジェクトのルートフォルダで mvn jetty:run を実行します。それから http://localhost:8080 にあるReactログインページにアクセスできます。

前の投稿:Mockitoを使った最終クラスとメソッドのモック
次の投稿:Springのクイックガイド@Enableアノテーション