Halaman Log Masuk Keselamatan Spring dengan React

1. Gambaran keseluruhan

React adalah perpustakaan JavaScript berasaskan komponen yang dibina oleh Facebook. Dengan React, kami dapat membina aplikasi web yang kompleks dengan mudah. Dalam artikel ini, kami akan membuat Spring Security berfungsi bersama dengan halaman React Login.

Kami akan memanfaatkan konfigurasi Spring Security yang ada pada contoh sebelumnya. Oleh itu, kita akan membina artikel terdahulu mengenai membuat Form Login dengan Spring Security.

2. Siapkan React

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

Kami akan mempunyai 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 membina projek 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 terkini plugin boleh didapati di sini.

Apabila kita menjalankan kompilasi mvn , plugin ini akan memuat turun node dan npm , memasang semua kebergantungan modul node dan membina projek react untuk kita.

Terdapat beberapa sifat konfigurasi yang perlu kita jelaskan di sini. Kami menentukan versi node dan npm , supaya pemalam mengetahui versi mana yang akan dimuat turun.

Halaman log masuk React kami akan berfungsi sebagai halaman statis pada musim bunga, jadi kami menggunakan " src / main / webapp / WEB-INF / view / react " sebagai direktori kerja npm .

3. Konfigurasi Keselamatan Musim Semi

Sebelum menyelami komponen React, kami mengemas kini konfigurasi Spring untuk menyediakan sumber statik 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 bahawa kami menambahkan halaman log masuk “index.html” sebagai sumber statik dan bukannya JSP yang dilayan secara dinamik.

Seterusnya, kami mengemas kini konfigurasi Spring Security untuk membenarkan akses ke sumber statik ini.

Daripada menggunakan "login.jsp" seperti yang kami lakukan dalam artikel login borang sebelumnya, di sini kami menggunakan "index.html" sebagai halaman Login kami:

@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 cuplikan di atas ketika kita mengeposkan data formulir ke " / perform_login ", Spring akan mengalihkan kita ke " /homepage.html " jika kelayakan sesuai dan ke " /index.html?error=true " sebaliknya.

4. Komponen Bertindak

Sekarang mari kita kotor tangan kita di React. Kami akan membina dan menguruskan log masuk borang menggunakan komponen.

Perhatikan bahawa kami akan menggunakan sintaks ES6 (ECMAScript 2015) untuk membina aplikasi kami.

4.1. Masukan

Mari kita mulakan dengan komponen Input yang menyokongelemen borang log masuk 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 dilihat di atas, kami membungkus elemen menjadi komponen yang dikendalikan React untuk dapat menguruskan keadaannya dan melakukan pengesahan medan.

React menyediakan cara untuk mengesahkan jenis menggunakan PropTypes . Secara khusus, kami menggunakan Input.propTypes = {…} untuk mengesahkan jenis sifat yang dilalui oleh pengguna.

Perhatikan bahawa pengesahan PropType berfungsi untuk pembangunan sahaja. Pengesahan PropType adalah untuk memeriksa bahawa semua andaian yang kami buat mengenai komponen kami dipenuhi.

Lebih baik memilikinya daripada terkejut dengan cegukan pengeluaran secara rawak.

4.2. Bentuk

Seterusnya, kami akan membina komponen Bentuk generik dalam fail Form.js yang menggabungkan beberapa contoh komponen Input kami di mana kami dapat mendasarkan borang log masuk kami.

Dalam komponen Form , kita mengambil atribut HTMLelemen dan membuat komponen Input daripadanya.

Kemudian komponen input dan ralat pengesahan dimasukkan ke dalam Borang:

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 menguruskan kesalahan pengesahan medan dan ralat 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 coretan ini, kami menentukan fungsi handleError untuk menguruskan keadaan ralat borang. Ingatlah bahawa kami juga menggunakannya untuk pengesahan medan Input . Sebenarnya, handleError () diluluskan untuk Komponen Input sebagai panggil balik dengan menjadikan () fungsi .

Kami menggunakan renderError () untuk membina elemen mesej ralat. Perhatikan bahawa konstruktor Form menggunakan harta ralat . Properti ini menunjukkan sekiranya tindakan log masuk gagal.

Kemudian datang pengendali penyerahan borang:

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

Dalam artikel ini, kami telah membahas cara membina aplikasi log masuk React dan membiarkannya berinteraksi dengan backend Spring Security. Aplikasi yang lebih kompleks akan melibatkan peralihan dan perutean keadaan menggunakan React Router atau Redux, tetapi itu akan berada di luar ruang lingkup artikel ini.

Seperti biasa, pelaksanaan sepenuhnya dapat dilihat di GitHub. Untuk menjalankannya secara tempatan, jalankan jeti mvn: jalankan di folder root projek, maka kita dapat mengakses halaman log masuk React di // localhost: 8080 .