Spring Security Login-Seite mit React

Anmeldeseite für Spring Security mit React

1. Überblick

React ist eine komponentenbasierte JavaScript-Bibliothek, die von Facebook erstellt wurde. Mit React können wir problemlos komplexe Webanwendungen erstellen. In diesem Artikel wird Spring Security mit einer React Login-Seite zusammenarbeiten.

Wir werden die vorhandenen Spring Security-Konfigurationen der vorherigen Beispiele nutzen. Wir bauen also auf einem früheren Artikel über das Erstellen vonForm Login with Spring Security auf.

2. Richten Sie React ein

Lassen Sie uns zunächstuse the command-line tool create-react-app to create an application ausführen, indem Sie den Befehl "create-react-app react”" ausführen.

Wir haben eine Konfiguration wie die folgende inreact/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"
    }
}

Dann werden wiruse 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
            
        
    

Die neueste Version des Plugins finden Sie unterhere.

Wenn wirmvn compile ausführen, lädt dieses Pluginnode undnpm herunter, installiert alle Knotenmodulabhängigkeiten und erstellt das Reaktionsprojekt für uns.

Es gibt verschiedene Konfigurationseigenschaften, die wir hier erklären müssen. Wir haben die Versionen vonnode undnpm angegeben, damit das Plugin weiß, welche Version heruntergeladen werden soll.

Unsere React-Anmeldeseite wird im Frühjahr als statische Seite dienen. Daher verwenden wir "src/main/webapp_ / WEB-INF / view / react_" als Arbeitsverzeichnis vonnpm.

3. Spring-Sicherheitskonfiguration

Bevor wir uns mit den React-Komponenten befassen, aktualisieren wir die Spring-Konfiguration, um die statischen Ressourcen unserer React-App bereitzustellen:

@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");
    }
}

Note that we add the login page “index.html” as a static resource anstelle einer dynamisch bereitgestellten JSP.

Als Nächstes aktualisieren wir die Spring Security-Konfiguration, um den Zugriff auf diese statischen Ressourcen zu ermöglichen.

Anstatt“login.jsp” wie inthe previous form login Artikel zu verwenden, verwenden wir hier“index.html” alsLogin Seite:

@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")
          //...
    }
}

Wie wir aus dem obigen Snippet sehen können, wenn wir Formulardaten an "/perform_login" senden, leitet Spring uns zu "/homepage.html" um, wenn die Anmeldeinformationen erfolgreich übereinstimmen, und ansonsten zu "/index.html?error=true".

4. Komponenten reagieren

Lassen Sie uns jetzt React die Hände schmutzig machen. Wir erstellen und verwalten eine Formularanmeldung mithilfe von Komponenten.

Beachten Sie, dass wir zum Erstellen unserer Anwendung die ES6-Syntax (ECMAScript 2015) verwenden.

4.1. Eingang

Beginnen wir mit einerInput-Komponente, die die<input />-Elemente des Anmeldeformulars inreact/src/Input.js unterstützt:

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

Wie oben gezeigt, verpacken wir das<input />-Element in eine reaktionsgesteuerte Komponente, um ihren Status verwalten und eine Feldvalidierung durchführen zu können.

React bietet eine Möglichkeit, die Typen mitPropTypes zu validieren. Insbesondere verwenden wirInput.propTypes = \{…}, um den Typ der vom Benutzer übergebenen Eigenschaften zu überprüfen.

Beachten Sie, dass die Validierung vonPropTypenur für die Entwicklung funktioniert. Mit der Validierung vonPropTypeoll überprüft werden, ob alle Annahmen, die wir zu unseren Komponenten treffen, erfüllt sind.

Es ist besser, es zu haben, als sich von zufälligen Schluckaufen in der Produktion überraschen zu lassen.

4.2. Form

Als Nächstes erstellen wir eine generische Formularkomponente in der DateiForm.js, die mehrere Instanzen unsererInput-Komponente kombiniert, auf denen wir unser Anmeldeformular basieren können.

In derForm-Komponente nehmen wir Attribute der HTML<input/>-Elemente und erstellen darausInput-Komponenten.

Dann werden dieInput-Komponenten und Validierungsfehlermeldungen in dieForm: eingefügt

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

Schauen wir uns nun an, wie wir Feldvalidierungsfehler und Anmeldefehler verwalten:

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}
} } //... }

In diesem Snippet definieren wir die FunktionhandleError, um den Fehlerstatus des Formulars zu verwalten. Denken Sie daran, dass wir es auch für die Feldvalidierung vonInputverwendet haben. Actually, handleError() is passed to the Input Components as a callback in the render() function.

Wir verwendenrenderError(), um das Fehlermeldungselement zu erstellen. Beachten Sie, dass der KonstruktorForm’sdie Eigenschafterrorverwendet. Diese Eigenschaft gibt an, ob die Anmeldeaktion fehlschlägt.

Dann kommt der Formularübermittlungs-Handler:

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

Wir verpacken alle Formularfelder inFormData und senden sie mitfetch API an den Server.

Vergessen wir nicht, dass unser Anmeldeformular mitsuccessUrl undfailureUrl geliefert wird. Dies bedeutet, dass die Antwort unabhängig davon, ob die Anforderung erfolgreich ist oder nicht, eine Umleitung erfordern würde.

Aus diesem Grund müssen wir die Umleitung im Antwortrückruf behandeln.

4.3. Formularwiedergabe

Nachdem wir alle benötigten Komponenten eingerichtet haben, können wir sie weiterhin im DOM ablegen. Die grundlegende HTML-Struktur lautet wie folgt (finden Sie unterreact/public/index.html):



  
    
  
  

    

Schließlich rendern wir das Formular in<div/> mit der ID "container” inreact/src/index.js:

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'))

Unser Formular enthält jetzt zwei Eingabefelder:username undpassword sowie eine Schaltfläche zum Senden.

Hier übergeben wir ein zusätzlicheserror-Attribut an dieForm-Komponente, da wir Anmeldefehler nach der Umleitung zur Fehler-URL behandeln möchten:/index.html?error=true.

form login error

Jetzt haben wir die Erstellung einer Spring Security-Anmeldeanwendung mit React abgeschlossen. Als letztes müssen wirmvn compile ausführen.

Während des Vorgangs hilft das Maven-Plugin beim Erstellen unserer React-Anwendung und beim Erfassen des Erstellungsergebnisses insrc/main/webapp/WEB-INF/view/react/build.

5. Fazit

In diesem Artikel wurde erläutert, wie Sie eine React-Anmelde-App erstellen und mit einem Spring Security-Backend interagieren lassen. Eine komplexere Anwendung würde den Statusübergang und das Routing mitReact Router oderRedux umfassen. Dies würde jedoch den Rahmen dieses Artikels sprengen.

Wie immer kann die vollständige Implementierungover on Github gefunden werden. Um es lokal auszuführen, führen Siemvn jetty:run im Projektstammordner aus. Anschließend können Sie unterhttp://localhost:8080 auf die React-Anmeldeseite zugreifen.