Halaman Login Keamanan Musim Semi dengan React

1. Ikhtisar

React adalah pustaka JavaScript berbasis komponen yang dibuat oleh Facebook. Dengan React, kita dapat membangun aplikasi web yang kompleks dengan mudah. Pada artikel ini, kita akan membuat Keamanan Musim Semi berfungsi bersama dengan halaman Login React.

Kami akan memanfaatkan konfigurasi Keamanan Musim Semi yang ada dari contoh sebelumnya. Jadi kita akan membangun di atas artikel sebelumnya tentang membuat Login Formulir dengan Keamanan Musim Semi.

2. Atur React

Pertama, mari gunakan alat baris perintah create-react-app untuk membuat aplikasi dengan menjalankan perintah " create-react-app react" .

Kami akan memiliki konfigurasi seperti berikut di 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" } }

Kemudian, kami akan menggunakan plugin frontend-maven untuk membantu membangun proyek React kami dengan 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    

Versi terbaru plugin dapat ditemukan di sini.

Ketika kita menjalankan kompilasi mvn , plugin ini akan mengunduh node dan npm , menginstal semua dependensi modul node dan membangun proyek react untuk kita.

Ada beberapa properti konfigurasi yang perlu kami jelaskan di sini. Kami menentukan versi node dan npm , sehingga plugin akan mengetahui versi mana yang akan diunduh.

Halaman login React kami akan berfungsi sebagai halaman statis di Spring, jadi kami menggunakan " src / main / webapp / WEB-INF / view / react " sebagai direktori kerja npm .

3. Konfigurasi Keamanan Pegas

Sebelum kita menyelami komponen React, kami memperbarui konfigurasi Spring untuk melayani sumber daya statis aplikasi React kami:

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

Perhatikan bahwa kami menambahkan halaman login "index.html" sebagai sumber daya statis, bukan JSP yang disajikan secara dinamis.

Selanjutnya, kami memperbarui konfigurasi Keamanan Musim Semi untuk mengizinkan akses ke sumber daya statis ini.

Alih-alih menggunakan "login.jsp" seperti yang kita lakukan di artikel login formulir sebelumnya, di sini kita menggunakan "index.html" sebagai halaman Login kita :

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

Seperti yang dapat kita lihat dari potongan di atas ketika kita mengirim data formulir ke " / perform_login ", Spring akan mengarahkan kita ke " /homepage.html " jika kredensial berhasil cocok dan ke " /index.html?error=true " sebaliknya.

4. Komponen React

Sekarang mari kita tangani React. Kami akan membangun dan mengelola login formulir menggunakan komponen.

Perhatikan bahwa kami akan menggunakan sintaks ES6 (ECMAScript 2015) untuk membangun aplikasi kami.

4.1. Memasukkan

Mari kita mulai dengan komponen Input yang mendukungelemen formulir login di react / src / Input.js :

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

Seperti yang terlihat di atas, kami membungkus file elemen ke dalam komponen yang dikontrol React untuk dapat mengelola statusnya dan melakukan validasi lapangan.

React menyediakan cara untuk memvalidasi tipe menggunakan PropTypes . Secara khusus, kami menggunakan Input.propTypes = {…} untuk memvalidasi tipe properti yang diteruskan oleh pengguna.

Perhatikan bahwa validasi PropType hanya berfungsi untuk pengembangan. Validasi propType adalah untuk memeriksa apakah semua asumsi yang kami buat tentang komponen kami terpenuhi.

Lebih baik memilikinya daripada terkejut oleh cegukan acak dalam produksi.

4.2. Bentuk

Selanjutnya, kita akan membuat komponen Formulir generik di file Form.js yang menggabungkan beberapa contoh komponen Input kita di mana kita dapat mendasarkan formulir login kita.

Dalam komponen Formulir , kami mengambil atribut HTMLelemen dan membuat komponen Input dari mereka.

Kemudian komponen Input dan pesan kesalahan validasi dimasukkan ke dalam Formulir:

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

Sekarang mari kita lihat bagaimana kita mengelola kesalahan validasi bidang dan kesalahan masuk:

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

Dalam cuplikan ini, kami mendefinisikan fungsi handleError untuk mengelola status kesalahan formulir. Ingatlah bahwa kami juga menggunakannya untuk validasi kolom Input . Sebenarnya, handleError () dilewatkan ke Komponen input sebagai callback di render () fungsi .

Kami menggunakan renderError () untuk membangun elemen pesan kesalahan. Perhatikan bahwa konstruktor Formulir menggunakan properti kesalahan . Properti ini menunjukkan jika tindakan login gagal.

Kemudian muncul handler pengiriman formulir:

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

We wrap all form fields into FormData and send it to the server using the fetch API.

Let's not forget our login form comes with a successUrl and failureUrl, meaning that no matter if the request is successful or not, the response would require a redirection.

That's why we need to handle redirection in the response callback.

4.3. Form Rendering

Now that we've set up all the components we need, we can continue to put them in the DOM. The basic HTML structure is as follows (find it under react/public/index.html):

Finally, we'll render the Form into the with id “container” in react/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'))

So our form now contains two input fields: username and password, and a submit button.

Here we pass an additional error attribute to the Form component because we want to handle login error after redirection to the failure URL: /index.html?error=true.

Now we've finished building a Spring Security login application using React. The last thing we need to do is to run mvn compile.

During the process, the Maven plugin will help build our React application and gather the build result in src/main/webapp/WEB-INF/view/react/build.

5. Conclusion

Di artikel ini, kami telah membahas cara membuat aplikasi login React dan membiarkannya berinteraksi dengan backend Keamanan Musim Semi. Aplikasi yang lebih kompleks akan melibatkan transisi dan perutean status menggunakan React Router atau Redux, tetapi itu berada di luar cakupan artikel ini.

Seperti biasa, implementasi penuh dapat ditemukan di GitHub. Untuk menjalankannya secara lokal, jalankan mvn jetty: run di folder root project, lalu kita bisa mengakses halaman login React di // localhost: 8080 .