日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

使用React和Spring Boot构建一个简单的CRUD应用

發布時間:2023/12/3 javascript 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用React和Spring Boot构建一个简单的CRUD应用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

“我喜歡編寫身份驗證和授權代碼。” ?從來沒有Java開發人員。 厭倦了一次又一次地建立相同的登錄屏幕? 嘗試使用Okta API進行托管身份驗證,授權和多因素身份驗證。

React的設計使創建交互式UI變得輕松自如。 它的狀態管理非常有效,并且僅在數據更改時才更新組件。 組件邏輯是用JavaScript編寫的,這意味著您可以將狀態保持在DOM之外,并創建封裝的組件。

開發人員喜歡CRUD(創建,讀取,更新和刪除)應用程序,因為它們顯示了創建應用程序時需要的許多基本功能。 一旦在應用程序中完成了CRUD的基礎知識,大多數客戶端-服務器管道就完成了,您可以繼續實施必要的業務邏輯。

今天,我將向您展示如何在React中使用Spring Boot創建一個基本的CRUD應用。 您可能還記得我去年為Angular撰寫的一篇類似文章: 使用Angular 5.0和Spring Boot 2.0構建Ba??sic CRUD應用程序 。 該教程使用OAuth 2.0的隱式流程和我們的Okta Angular SDK 。 在本教程中,我將使用OAuth 2.0授權代碼流,并將React應用打包在Spring Boot應用中進行生產。 同時,我將向您展示如何保持React高效的工作流以進行本地開發。

您將需要安裝Java 8 , Node.js 8和Yarn才能完成本教程。 您可以使用npm代替Yarn,但是您需要將Yarn語法轉換為npm。

使用Spring Boot 2.0創建API應用

我經常在世界各地的會議和用戶組中演講。 我最喜歡發言的用戶組是Java用戶組(JUG)。 我從事Java開發人員已有近20年的時間,而且我喜歡Java社區。 我的一個好朋友詹姆斯·沃德(James Ward)表示,進行水罐巡游是他當時最喜歡的開發商倡導者活動之一。 我最近接受了他的建議,并在海外會議上進行了JUG聚會在美國的聚會。

我為什么要告訴你呢? 因為我認為今天創建一個“ JUG Tours”應用很有趣,它允許您創建/編輯/刪除JUG,以及查看即將發生的事件。

首先,導航至start.spring.io并進行以下選擇:

  • 組: com.okta.developer
  • 神器: jugtours
  • 依賴項 : JPA , H2 , Web , Lombok

單擊生成項目 ,下載后展開jugtours.zip ,然后在您喜歡的IDE中打開該項目。

提示:如果您使用的是IntelliJ IDEA或Spring Tool Suite,則在創建新項目時也可以使用Spring Initializr。

添加一個JPA域模型

您需要做的第一件事是創建一個保存數據的域模型。 在高層次上,有一個Group表示酒壺,一個Event有一個多到一的關系Group ,以及User具有與一個一對多的關系Group 。

創建一個src/main/java/com/okta/developer/jugtours/model目錄和其中的Group.java類。

package com.okta.developer.jugtours.model;import lombok.Data; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.RequiredArgsConstructor;import javax.persistence.*; import java.util.Set;@Data @NoArgsConstructor @RequiredArgsConstructor @Entity @Table(name = "user_group") public class Group {@Id@GeneratedValueprivate Long id;@NonNullprivate String name;private String address;private String city;private String stateOrProvince;private String country;private String postalCode;@ManyToOne(cascade=CascadeType.PERSIST)private User user;@OneToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL)private Set<Event> events; }

在同一包中創建一個Event.java類。

package com.okta.developer.jugtours.model;import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor;import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToMany; import java.time.Instant; import java.util.Set;@Data @NoArgsConstructor @AllArgsConstructor @Builder @Entity public class Event {@Id@GeneratedValueprivate Long id;private Instant date;private String title;private String description;@ManyToManyprivate Set<User> attendees; }

還有一個User.java類。

package com.okta.developer.jugtours.model;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import javax.persistence.Entity; import javax.persistence.Id;@Data @NoArgsConstructor @AllArgsConstructor @Entity public class User {@Idprivate String id;private String name;private String email; }

創建一個GroupRepository.java來管理組實體。

package com.okta.developer.jugtours.model;import org.springframework.data.jpa.repository.JpaRepository;import java.util.List;public interface GroupRepository extends JpaRepository<Group, Long> {Group findByName(String name); }

要加載一些默認數據,請在com.okta.developer.jugtours包中創建一個Initializer.java類。

package com.okta.developer.jugtours;import com.okta.developer.jugtours.model.Event; import com.okta.developer.jugtours.model.Group; import com.okta.developer.jugtours.model.GroupRepository; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component;import java.time.Instant; import java.util.Collections; import java.util.stream.Stream;@Component class Initializer implements CommandLineRunner {private final GroupRepository repository;public Initializer(GroupRepository repository) {this.repository = repository;}@Overridepublic void run(String... strings) {Stream.of("Denver JUG", "Utah JUG", "Seattle JUG","Richmond JUG").forEach(name ->repository.save(new Group(name)));Group djug = repository.findByName("Denver JUG");Event e = Event.builder().title("Full Stack Reactive").description("Reactive with Spring Boot + React").date(Instant.parse("2018-12-12T18:00:00.000Z")).build();djug.setEvents(Collections.singleton(e));repository.save(djug);repository.findAll().forEach(System.out::println);} }

提示:如果您的IDE Event.builder()問題,則意味著您需要打開注釋處理和/或安裝Lombok插件。 我必須在IntelliJ IDEA中卸載/重新安裝Lombok插件才能正常工作。

如果在添加此代碼后啟動應用程序(使用./mvnw spring-boot:run ),您將看到控制臺中顯示的組和事件列表。

Group(id=1, name=Denver JUG, address=null, city=null, stateOrProvince=null, country=null, postalCode=null, user=null, events=[Event(id=5, date=2018-12-12T18:00:00Z, title=Full Stack Reactive, description=Reactive with Spring Boot + React, attendees=[])]) Group(id=2, name=Utah JUG, address=null, city=null, stateOrProvince=null, country=null, postalCode=null, user=null, events=[]) Group(id=3, name=Seattle JUG, address=null, city=null, stateOrProvince=null, country=null, postalCode=null, user=null, events=[]) Group(id=4, name=Richmond JUG, address=null, city=null, stateOrProvince=null, country=null, postalCode=null, user=null, events=[])

添加一個GroupController.java類(在src/main/java/.../jugtours/web/GroupController.java ), src/main/java/.../jugtours/web/GroupController.java可用于CRUD組。

package com.okta.developer.jugtours.web;import com.okta.developer.jugtours.model.Group; import com.okta.developer.jugtours.model.GroupRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*;import javax.validation.Valid; import java.net.URI; import java.net.URISyntaxException; import java.util.Collection; import java.util.Optional;@RestController @RequestMapping("/api") class GroupController {private final Logger log = LoggerFactory.getLogger(GroupController.class);private GroupRepository groupRepository;public GroupController(GroupRepository groupRepository) {this.groupRepository = groupRepository;}@GetMapping("/groups")Collection<Group> groups() {return groupRepository.findAll();}@GetMapping("/group/{id}")ResponseEntity<?> getGroup(@PathVariable Long id) {Optional<Group> group = groupRepository.findById(id);return group.map(response -> ResponseEntity.ok().body(response)).orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));}@PostMapping("/group")ResponseEntity<Group> createGroup(@Valid @RequestBody Group group) throws URISyntaxException {log.info("Request to create group: {}", group);Group result = groupRepository.save(group);return ResponseEntity.created(new URI("/api/group/" + result.getId())).body(result);}@PutMapping("/group/{id}")ResponseEntity<Group> updateGroup(@PathVariable Long id, @Valid @RequestBody Group group) {group.setId(id);log.info("Request to update group: {}", group);Group result = groupRepository.save(group);return ResponseEntity.ok().body(result);}@DeleteMapping("/group/{id}")public ResponseEntity<?> deleteGroup(@PathVariable Long id) {log.info("Request to delete group: {}", id);groupRepository.deleteById(id);return ResponseEntity.ok().build();} }

如果重新啟動服務器應用程序,并使用瀏覽器或命令行客戶端訪問http://localhost:8080/api/groups ,則應看到組列表。

您可以使用以下HTTPie命令創建,讀取,更新和刪除組。

http POST :8080/api/group name='Dublin JUG' city=Dublin country=Ireland http :8080/api/group/6 http PUT :8080/api/group/6 name='Dublin JUG' city=Dublin country=Ireland address=Downtown http DELETE :8080/api/group/6

使用Create React App創建一個React UI

Create React App是一個命令行實用程序,可為您生成React項目。 這是一個方便的工具,因為它還提供了一些命令,這些命令將生成和優化您的項目以進行生產。 它使用webpack在后臺進行構建。 如果您想了解更多關于webpack的信息,我建議使用webpack.academy 。

使用Yarn在jugtours目錄中創建一個新項目。

yarn create react-app app

應用程序創建過程完成后,導航至app目錄并安裝Bootstrap ,對React的cookie支持,React Router和Reactstrap 。

cd app yarn add bootstrap@4.1.2 react-cookie@2.2.0 react-router-dom@4.3.1 reactstrap@6.3.0

您將使用BootstrapCSS和Reactstrap的組件來使UI看起來更好,尤其是在手機上。 如果您想了解有關Reactstrap的更多信息,請參見https://reactstrap.github.io 。 它具有有關其各種組件以及如何使用它們的大量文檔。

將BootstrapCSS文件添加為app/src/index.js的導入文件。

import 'bootstrap/dist/css/bootstrap.min.css';

調用您的Spring Boot API并顯示結果

修改app/src/App.js以使用以下代碼調用/api/groups并在UI中顯示列表。

import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css';class App extends Component {state = {isLoading: true,groups: []};async componentDidMount() {const response = await fetch('/api/groups');const body = await response.json();this.setState({ groups: body, isLoading: false });}render() {const {groups, isLoading} = this.state;if (isLoading) {return <p>Loading...</p>;}return (<div className="App"><header className="App-header"><img src={logo} className="App-logo" alt="logo" /><h1 className="App-title">Welcome to React</h1></header><div className="App-intro"><h2>JUG List</h2>{groups.map(group =><div key={group.id}>{group.name}</div>)}</div></div>);} }export default App;

要將代理從/api代理到http://localhost:8080/api ,請將代理設置添加到app/package.json 。

"scripts": {...}, "proxy": "http://localhost:8080"

要了解有關此功能的更多信息,請在app/README.md搜索“ proxy”。 Create React App隨該文件附帶了各種文檔,這有多酷?

確保Spring Boot正在運行,然后在您的app目錄中運行yarn start 。 您應該看到默認組的列表。

構建一個React GroupList組件

React完全是關于組件的,您不想在主App呈現所有內容,因此請創建app/src/GroupList.js并使用以下JavaScript進行填充。

import React, { Component } from 'react'; import { Button, ButtonGroup, Container, Table } from 'reactstrap'; import AppNavbar from './AppNavbar'; import { Link } from 'react-router-dom';class GroupList extends Component {constructor(props) {super(props);this.state = {groups: [], isLoading: true};this.remove = this.remove.bind(this);}componentDidMount() {this.setState({isLoading: true});fetch('api/groups').then(response => response.json()).then(data => this.setState({groups: data, isLoading: false}));}async remove(id) {await fetch(`/api/group/${id}`, {method: 'DELETE',headers: {'Accept': 'application/json','Content-Type': 'application/json'}}).then(() => {let updatedGroups = [...this.state.groups].filter(i => i.id !== id);this.setState({groups: updatedGroups});});}render() {const {groups, isLoading} = this.state;if (isLoading) {return <p>Loading...</p>;}const groupList = groups.map(group => {const address = `${group.address || ''} ${group.city || ''} ${group.stateOrProvince || ''}`;return <tr key={group.id}><td style={{whiteSpace: 'nowrap'}}>{group.name}</td><td>{address}</td><td>{group.events.map(event => {return <div key={event.id}>{new Intl.DateTimeFormat('en-US', {year: 'numeric',month: 'long',day: '2-digit'}).format(new Date(event.date))}: {event.title}</div>})}</td><td><ButtonGroup><Button size="sm" color="primary" tag={Link} to={"/groups/" + group.id}>Edit</Button><Button size="sm" color="danger" onClick={() => this.remove(group.id)}>Delete</Button></ButtonGroup></td></tr>});return (<div><AppNavbar/><Container fluid><div className="float-right"><Button color="success" tag={Link} to="/groups/new">Add Group</Button></div><h3>My JUG Tour</h3><Table className="mt-4"><thead><tr><th width="20%">Name</th><th width="20%">Location</th><th>Events</th><th width="10%">Actions</th></tr></thead><tbody>{groupList}</tbody></Table></Container></div>);} }export default GroupList;

在同一目錄中創建AppNavbar.js ,以在組件之間建立通用的UI功能。

import React, { Component } from 'react'; import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'; import { Link } from 'react-router-dom';export default class AppNavbar extends Component {constructor(props) {super(props);this.state = {isOpen: false};this.toggle = this.toggle.bind(this);}toggle() {this.setState({isOpen: !this.state.isOpen});}render() {return <Navbar color="dark" dark expand="md"><NavbarBrand tag={Link} to="/">Home</NavbarBrand><NavbarToggler onClick={this.toggle}/><Collapse isOpen={this.state.isOpen} navbar><Nav className="ml-auto" navbar><NavItem><NavLinkhref="https://twitter.com/oktadev">@oktadev</NavLink></NavItem><NavItem><NavLink href="https://github.com/oktadeveloper/okta-spring-boot-react-crud-example">GitHub</NavLink></NavItem></Nav></Collapse></Navbar>;} }

創建app/src/Home.js作為應用程序的登錄頁面。

import React, { Component } from 'react'; import './App.css'; import AppNavbar from './AppNavbar'; import { Link } from 'react-router-dom'; import { Button, Container } from 'reactstrap';class Home extends Component {render() {return (<div><AppNavbar/><Container fluid><Button color="link"><Link to="/groups">Manage JUG Tour</Link></Button></Container></div>);} }export default Home;

另外,更改app/src/App.js以使用React Router在組件之間導航。

import React, { Component } from 'react'; import './App.css'; import Home from './Home'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import GroupList from './GroupList';class App extends Component {render() {return (<Router><Switch><Route path='/' exact={true} component={Home}/><Route path='/groups' exact={true} component={GroupList}/></Switch></Router>)} }export default App;

為了使您的UI更加寬敞,請在app/src/App.css容器類中添加一個上邊距。

.container, .container-fluid {margin-top: 20px }

當您進行更改時,您的React應用程序應該會自我更新,并且您應該在http://localhost:3000看到如下屏幕。 點擊Manage JUG Tour ,您將看到默認組的列表。 可以在React應用程序中查看Spring Boot API的數據真是太好了,但是如果您不能編輯它就不好玩了!

添加一個React GroupEdit組件

創建app/src/GroupEdit.js并使用其componentDidMount()從URL中獲取具有ID的組資源。

import React, { Component } from 'react'; import { Link, withRouter } from 'react-router-dom'; import { Button, Container, Form, FormGroup, Input, Label } from 'reactstrap'; import AppNavbar from './AppNavbar';class GroupEdit extends Component {emptyItem = {name: '',address: '',city: '',stateOrProvince: '',country: '',postalCode: ''};constructor(props) {super(props);this.state = {item: this.emptyItem};this.handleChange = this.handleChange.bind(this);this.handleSubmit = this.handleSubmit.bind(this);}async componentDidMount() {if (this.props.match.params.id !== 'new') {const group = await (await fetch(`/api/group/${this.props.match.params.id}`)).json();this.setState({item: group});}}handleChange(event) {const target = event.target;const value = target.value;const name = target.name;let item = {...this.state.item};item[name] = value;this.setState({item});}async handleSubmit(event) {event.preventDefault();const {item} = this.state;await fetch('/api/group', {method: (item.id) ? 'PUT' : 'POST',headers: {'Accept': 'application/json','Content-Type': 'application/json'},body: JSON.stringify(item),});this.props.history.push('/groups');}render() {const {item} = this.state;const title = <h2>{item.id ? 'Edit Group' : 'Add Group'}</h2>;return <div><AppNavbar/><Container>{title}<Form onSubmit={this.handleSubmit}><FormGroup><Label for="name">Name</Label><Input type="text" name="name" id="name" value={item.name || ''}onChange={this.handleChange} autoComplete="name"/></FormGroup><FormGroup><Label for="address">Address</Label><Input type="text" name="address" id="address" value={item.address || ''}onChange={this.handleChange} autoComplete="address-level1"/></FormGroup><FormGroup><Label for="city">City</Label><Input type="text" name="city" id="city" value={item.city || ''}onChange={this.handleChange} autoComplete="address-level1"/></FormGroup><div className="row"><FormGroup className="col-md-4 mb-3"><Label for="stateOrProvince">State/Province</Label><Input type="text" name="stateOrProvince" id="stateOrProvince" value={item.stateOrProvince || ''}onChange={this.handleChange} autoComplete="address-level1"/></FormGroup><FormGroup className="col-md-5 mb-3"><Label for="country">Country</Label><Input type="text" name="country" id="country" value={item.country || ''}onChange={this.handleChange} autoComplete="address-level1"/></FormGroup><FormGroup className="col-md-3 mb-3"><Label for="country">Postal Code</Label><Input type="text" name="postalCode" id="postalCode" value={item.postalCode || ''}onChange={this.handleChange} autoComplete="address-level1"/></FormGroup></div><FormGroup><Button color="primary" type="submit">Save</Button>{' '}<Button color="secondary" tag={Link} to="/groups">Cancel</Button></FormGroup></Form></Container></div>} }export default withRouter(GroupEdit);

底部需要使用withRouter()高階組件來顯示this.props.history因此您可以在添加或保存GroupList后導航回this.props.history 。

修改app/src/App.js以導入GroupEdit并指定其路徑。

import GroupEdit from './GroupEdit';class App extends Component {render() {return (<Router><Switch>...<Route path='/groups/:id' component={GroupEdit}/></Switch></Router>)} }

現在,您應該可以添加和編輯組了!

使用Okta添加身份驗證

構建CRUD應用程序非常酷,但是構建安全的應用程序甚至更酷。 為此,您需要添加身份驗證,以便用戶必須先登錄才能查看/修改組。 為簡化起見,您可以使用Okta的OIDC API。 在Okta,我們的目標是使身份管理比您以往更加輕松,安全和可擴展。 Okta是一項云服務,允許開發人員創建,編輯和安全地存儲用戶帳戶和用戶帳戶數據,并將它們與一個或多個應用程序連接。 我們的API使您能夠:

  • 驗證和授權用戶
  • 存儲有關您的用戶的數據
  • 執行基于密碼的社交登錄
  • 通過多因素身份驗證保護您的應用程序
  • 以及更多! 查看我們的產品文檔

你賣了嗎 注冊一個永久免費的開發者帳戶 ,完成后再回來,這樣您就可以了解有關使用Spring Boot構建安全應用程序的更多信息!

Spring Security + OIDC

Spring Security在其5.0版本中增加了OIDC支持 。 從那時起,他們進行了許多改進并簡化了所需的配置。 我認為探索最新和最有趣的東西很有趣,所以我首先使用Spring的快照存儲庫更新pom.xml ,將Spring Boot和Spring Security升級到夜間構建,并添加必要的Spring Security依賴項來進行OIDC身份驗證。

<?xml version="1.0" encoding="UTF-8"?> <project>...<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.0.BUILD-SNAPSHOT</version><relativePath/> <!-- lookup parent from repository --></parent><properties>...<spring-security.version>5.1.0.BUILD-SNAPSHOT</spring-security.version></properties><dependencies>...<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-client</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-jose</artifactId></dependency></dependencies><build...><pluginRepositories><pluginRepository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><snapshots><enabled>true</enabled></snapshots></pluginRepository></pluginRepositories><repositories><repository><id>spring-snapshots</id><name>Spring Snapshot</name><url>http://repo.spring.io/snapshot</url></repository></repositories> </project>

在Okta中創建OIDC應用

登錄到您的1563開發者帳戶(或者注冊 ,如果你沒有一個帳戶)并導航到應用程序 > 添加應用程序 。 單擊“ Web” ,然后單擊“ 下一步” 。 給應用程序起一個您會記住的名稱,并指定http://localhost:8080/login/oauth2/code/okta作為登錄重定向URI。 點擊完成 ,然后點擊編輯以編輯常規設置。 添加http://localhost:3000和http://localhost:8080作為注銷重定向URI,然后點擊保存

將默認授權服務器的URI,客戶端ID和客戶端密鑰復制并粘貼到src/main/resources/application.yml 。 創建此文件,然后可以刪除同一目錄中的application.properties文件。

spring:security:oauth2:client:registration:okta:client-id: {clientId}client-secret: {clientSecret}scope: openid email profileprovider:okta:issuer-uri: https://{yourOktaDomain}/oauth2/default

為React和用戶身份配置Spring Security

為了使Spring Security React友好,請在src/main/java/.../jugtours/config創建一個SecurityConfiguration.java文件。 創建config目錄并將該類放入其中。

package com.okta.developer.jugtours.config;import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.SavedRequest;import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map;@Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter {private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);@Overrideprotected void configure(HttpSecurity http) throws Exception {RequestCache requestCache = refererRequestCache();SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();handler.setRequestCache(requestCache);http.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/okta")).and().oauth2Login().successHandler(handler).and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and().requestCache().requestCache(requestCache).and().authorizeRequests().antMatchers("/**/*.{js,html,css}").permitAll().antMatchers("/", "/api/user").permitAll().anyRequest().authenticated();}@Beanpublic RequestCache refererRequestCache() {return new RequestCache() {private String savedAttrName = getClass().getName().concat(".SAVED");@Overridepublic void saveRequest(HttpServletRequest request, HttpServletResponse response) {String referrer = request.getHeader("referer");if (referrer != null) {request.getSession().setAttribute(this.savedAttrName, referrerRequest(referrer));}}@Overridepublic SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response) {HttpSession session = request.getSession(false);if (session != null) {return (SavedRequest) session.getAttribute(this.savedAttrName);}return null;}@Overridepublic HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) {return request;}@Overridepublic void removeRequest(HttpServletRequest request, HttpServletResponse response) {HttpSession session = request.getSession(false);if (session != null) {log.debug("Removing SavedRequest from session if present");session.removeAttribute(this.savedAttrName);}}};}private SavedRequest referrerRequest(final String referrer) {return new SavedRequest() {@Overridepublic String getRedirectUrl() {return referrer;}@Overridepublic List<Cookie> getCookies() {return null;}@Overridepublic String getMethod() {return null;}@Overridepublic List<String> getHeaderValues(String name) {return null;}@Overridepublic Collection<String> getHeaderNames() {return null;}@Overridepublic List<Locale> getLocales() {return null;}@Overridepublic String[] getParameterValues(String name) {return new String[0];}@Overridepublic Map<String, String[]> getParameterMap() {return null;}};} }

這堂課正在進行很多,所以讓我解釋一些事情。 在年初configure()方法,你建立一個新類型,緩存網址標頭(拼錯請求緩存的referer在現實生活中),所以Spring Security可以驗證后回重定向到它。 當您在http://localhost:3000上開發React并希望在登錄后重定向到那里時,基于引用者的請求緩存會派上用場。

@Override protected void configure(HttpSecurity http) throws Exception {RequestCache requestCache = refererRequestCache();SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();handler.setRequestCache(requestCache);http.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/okta")).and().oauth2Login().successHandler(handler).and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and().requestCache().requestCache(requestCache).and().authorizeRequests().antMatchers("/**/*.{js,html,css}").permitAll().antMatchers("/", "/api/user").permitAll().anyRequest().authenticated(); }

authenticationEntryPoint()行使Spring Security自動重定向到Okta。 在Spring Security 5.1.0.RELEASE中,當您僅配置一個OIDC提供程序時,將不需要此行。 它會自動重定向。

使用CookieCsrfTokenRepository.withHttpOnlyFalse()配置CSRF(跨站點請求偽造)保護意味著XSRF-TOKEN cookie將不會被標記為僅HTTP,因此React可以讀取它并在嘗試操作數據時將其發送回去。

antMatchers行定義了匿名用戶可以使用哪些URL。 您將很快進行配置,以便由Spring Boot應用程序服務您的React應用程序,因此允許使用Web文件和“ /”的原因。 您可能會注意到也有一個公開的/api/user路徑。 創建src/main/java/.../jugtours/web/UserController.java并使用以下代碼填充它。 React將使用此API來1)找出用戶是否已通過身份驗證,以及2)執行全局注銷。

package com.okta.developer.jugtours.web;import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map;@RestController public class UserController {@Value("${spring.security.oauth2.client.provider.okta.issuer-uri}")String issuerUri;@GetMapping("/api/user")public ResponseEntity<?> getUser(@AuthenticationPrincipal OAuth2User user) {if (user == null) {return new ResponseEntity<>("", HttpStatus.OK);} else {return ResponseEntity.ok().body(user.getAttributes());}}@PostMapping("/api/logout")public ResponseEntity<?> logout(HttpServletRequest request,@AuthenticationPrincipal(expression = "idToken") OidcIdToken idToken) {// send logout URL to client so they can initiate logout - doesn't work from the server side// Make it easier: https://github.com/spring-projects/spring-security/issues/5540String logoutUrl = issuerUri + "/v1/logout";Map<String, String> logoutDetails = new HashMap<>();logoutDetails.put("logoutUrl", logoutUrl);logoutDetails.put("idToken", idToken.getTokenValue());request.getSession(false).invalidate();return ResponseEntity.ok().body(logoutDetails);} }

您還需要創建組時,這樣就可以通過你的壺之旅篩選添加用戶信息。 在與GroupRepository.java相同的目錄中添加UserRepository.java 。

package com.okta.developer.jugtours.model;import org.springframework.data.jpa.repository.JpaRepository;public interface UserRepository extends JpaRepository<User, String> { }

將新的findAllByUserId(String id)方法添加到GroupRepository.java 。

List<Group> findAllByUserId(String id);

然后將UserRepository注入GroupController.java并在添加新組時使用它來創建(或獲取現有用戶)。 在那里,請修改groups()方法以按用戶過濾。

package com.okta.developer.jugtours.web;import com.okta.developer.jugtours.model.Group; import com.okta.developer.jugtours.model.GroupRepository; import com.okta.developer.jugtours.model.User; import com.okta.developer.jugtours.model.UserRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.web.bind.annotation.*;import javax.validation.Valid; import java.net.URI; import java.net.URISyntaxException; import java.security.Principal; import java.util.Collection; import java.util.Map; import java.util.Optional;@RestController @RequestMapping("/api") class GroupController {private final Logger log = LoggerFactory.getLogger(GroupController.class);private GroupRepository groupRepository;private UserRepository userRepository;public GroupController(GroupRepository groupRepository, UserRepository userRepository) {this.groupRepository = groupRepository;this.userRepository = userRepository;}@GetMapping("/groups")Collection<Group> groups(Principal principal) {return groupRepository.findAllByUserId(principal.getName());}@GetMapping("/group/{id}")ResponseEntity<?> getGroup(@PathVariable Long id) {Optional<Group> group = groupRepository.findById(id);return group.map(response -> ResponseEntity.ok().body(response)).orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));}@PostMapping("/group")ResponseEntity<Group> createGroup(@Valid @RequestBody Group group,@AuthenticationPrincipal OAuth2User principal) throws URISyntaxException {log.info("Request to create group: {}", group);Map<String, Object> details = principal.getAttributes();String userId = details.get("sub").toString();// check to see if user already existsOptional<User> user = userRepository.findById(userId);group.setUser(user.orElse(new User(userId,details.get("name").toString(), details.get("email").toString())));Group result = groupRepository.save(group);return ResponseEntity.created(new URI("/api/group/" + result.getId())).body(result);}@PutMapping("/group")ResponseEntity<Group> updateGroup(@Valid @RequestBody Group group) {log.info("Request to update group: {}", group);Group result = groupRepository.save(group);return ResponseEntity.ok().body(result);}@DeleteMapping("/group/{id}")public ResponseEntity<?> deleteGroup(@PathVariable Long id) {log.info("Request to delete group: {}", id);groupRepository.deleteById(id);return ResponseEntity.ok().build();} }

為了放大更改,它們在groups()和createGroup()方法中。 Spring JPA會為您創建findAllByUserId()方法/查詢,并且userRepository.findById()使用Java 8的Optional ,這是一個很好的選擇 。

@GetMapping("/groups") Collection<Group> groups(Principal principal) {return groupRepository.findAllByUserId(principal.getName()); }@PostMapping("/group") ResponseEntity<Group> createGroup(@Valid @RequestBody Group group,@AuthenticationPrincipal OAuth2User principal) throws URISyntaxException {log.info("Request to create group: {}", group);Map<String, Object> details = principal.getAttributes();String userId = details.get("sub").toString();// check to see if user already existsOptional<User> user = userRepository.findById(userId);group.setUser(user.orElse(new User(userId,details.get("name").toString(), details.get("email").toString())));Group result = groupRepository.save(group);return ResponseEntity.created(new URI("/api/group/" + result.getId())).body(result); }

修改React Handle CSRF并識別身份

您需要對React組件進行一些更改,以使它們能夠識別身份。 您要做的第一件事是修改App.js以將所有內容包裝在CookieProvider 。 該組件允許您讀取CSRF cookie并將其作為標題發送回。

import { CookiesProvider } from 'react-cookie';class App extends Component {render() {return (<CookiesProvider><Router...></CookiesProvider>)} }

修改app/src/Home.js以調用/api/user來查看用戶是否已登錄。如果沒有Login ,請顯示“ Login按鈕。

import React, { Component } from 'react'; import './App.css'; import AppNavbar from './AppNavbar'; import { Link } from 'react-router-dom'; import { Button, Container } from 'reactstrap'; import { withCookies } from 'react-cookie';class Home extends Component {state = {isLoading: true,isAuthenticated: false,user: undefined};constructor(props) {super(props);const {cookies} = props;this.state.csrfToken = cookies.get('XSRF-TOKEN');this.login = this.login.bind(this);this.logout = this.logout.bind(this);}async componentDidMount() {const response = await fetch('/api/user', {credentials: 'include'});const body = await response.text();if (body === '') {this.setState(({isAuthenticated: false}))} else {this.setState({isAuthenticated: true, user: JSON.parse(body)})}}login() {let port = (window.location.port ? ':' + window.location.port : '');if (port === ':3000') {port = ':8080';}window.location.href = '//' + window.location.hostname + port + '/private';}logout() {console.log('logging out...');fetch('/api/logout', {method: 'POST', credentials: 'include',headers: {'X-XSRF-TOKEN': this.state.csrfToken}}).then(res => res.json()).then(response => {window.location.href = response.logoutUrl + "?id_token_hint=" +response.idToken + "&post_logout_redirect_uri=" + window.location.origin;});}render() {const message = this.state.user ?<h2>Welcome, {this.state.user.name}!</h2> :<p>Please log in to manage your JUG Tour.</p>;const button = this.state.isAuthenticated ?<div><Button color="link"><Link to="/groups">Manage JUG Tour</Link></Button><br/><Button color="link" onClick={this.logout}>Logout</Button></div> :<Button color="primary" onClick={this.login}>Login</Button>;return (<div><AppNavbar/><Container fluid>{message}{button}</Container></div>);} }export default withCookies(Home);

您應該在此組件中注意一些事項:

  • withCookies()將Home組件包裝在底部,以使其可以訪問cookie。 然后,您可以在構造const {cookies} = props中使用const {cookies} = props ,并使用cookies.get('XSRF-TOKEN')獲取cookie。
  • 使用fetch() ,需要包括{credentials: 'include'}來傳輸cookie。 如果不包含此選項,則將獲得“ 403禁止訪問”。
  • Spring Security的CSRF cookie的名稱與您需要發回的標頭的名稱不同。 cookie名稱是XSRF-TOKEN ,而標題名稱是X-XSRF-TOKEN 。
  • 更新app/src/GroupList.js以進行類似更改。 好消息是您不需要對render()方法進行任何更改。

    import { Link, withRouter } from 'react-router-dom'; import { instanceOf } from 'prop-types'; import { withCookies, Cookies } from 'react-cookie';class GroupList extends Component {static propTypes = {cookies: instanceOf(Cookies).isRequired};constructor(props) {super(props);const {cookies} = props;this.state = {groups: [], csrfToken: cookies.get('XSRF-TOKEN'), isLoading: true};this.remove = this.remove.bind(this);}componentDidMount() {this.setState({isLoading: true});fetch('api/groups', {credentials: 'include'}).then(response => response.json()).then(data => this.setState({groups: data, isLoading: false})).catch(() => this.props.history.push('/'))}async remove(id) {await fetch(`/api/group/${id}`, {method: 'DELETE',headers: {'X-XSRF-TOKEN': this.state.csrfToken,'Accept': 'application/json','Content-Type': 'application/json'},credentials: 'include'}).then(() => {let updatedGroups = [...this.state.groups].filter(i => i.id !== id);this.setState({groups: updatedGroups});});}render() {...} }export default withCookies(withRouter(GroupList));

    也更新GroupEdit.js 。

    import { instanceOf } from 'prop-types'; import { Cookies, withCookies } from 'react-cookie';class GroupEdit extends Component {static propTypes = {cookies: instanceOf(Cookies).isRequired};emptyItem = {name: '',address: '',city: '',stateOrProvince: '',country: '',postalCode: ''};constructor(props) {super(props);const {cookies} = props;this.state = {item: this.emptyItem,csrfToken: cookies.get('XSRF-TOKEN')};this.handleChange = this.handleChange.bind(this);this.handleSubmit = this.handleSubmit.bind(this);}async componentDidMount() {if (this.props.match.params.id !== 'new') {try {const group = await (await fetch(`/api/group/${this.props.match.params.id}`, {credentials: 'include'})).json();this.setState({item: group});} catch (error) {this.props.history.push('/');}}}handleChange(event) {const target = event.target;const value = target.value;const name = target.name;let item = {...this.state.item};item[name] = value;this.setState({item});}async handleSubmit(event) {event.preventDefault();const {item, csrfToken} = this.state;await fetch('/api/group', {method: (item.id) ? 'PUT' : 'POST',headers: {'X-XSRF-TOKEN': csrfToken,'Accept': 'application/json','Content-Type': 'application/json'},body: JSON.stringify(item),credentials: 'include'});this.props.history.push('/groups');}render() {...} }export default withCookies(withRouter(GroupEdit));

    完成所有這些更改之后,您應該能夠重新啟動Spring Boot和React,并見證計劃自己的JUG Tour的榮耀!

    配置Maven以使用Spring Boot構建和打包React

    要使用Maven構建和打包React應用,可以使用frontend-maven-plugin和Maven的配置文件將其激活。 將版本的屬性和<profiles>部分添加到pom.xml 。

    <properties>...<frontend-maven-plugin.version>1.6</frontend-maven-plugin.version><node.version>v10.6.0</node.version><yarn.version>v1.8.0</yarn.version> </properties><profiles><profile><id>dev</id><activation><activeByDefault>true</activeByDefault></activation><properties><spring.profiles.active>dev</spring.profiles.active></properties></profile><profile><id>prod</id><build><plugins><plugin><artifactId>maven-resources-plugin</artifactId><executions><execution><id>copy-resources</id><phase>process-classes</phase><goals><goal>copy-resources</goal></goals><configuration><outputDirectory>${basedir}/target/classes/static</outputDirectory><resources><resource><directory>app/build</directory></resource></resources></configuration></execution></executions></plugin><plugin><groupId>com.github.eirslett</groupId><artifactId>frontend-maven-plugin</artifactId><version>${frontend-maven-plugin.version}</version><configuration><workingDirectory>app</workingDirectory></configuration><executions><execution><id>install node</id><goals><goal>install-node-and-yarn</goal></goals><configuration><nodeVersion>${node.version}</nodeVersion><yarnVersion>${yarn.version}</yarnVersion></configuration></execution><execution><id>yarn install</id><goals><goal>yarn</goal></goals><phase>generate-resources</phase></execution><execution><id>yarn test</id><goals><goal>yarn</goal></goals><phase>test</phase><configuration><arguments>test</arguments></configuration></execution><execution><id>yarn build</id><goals><goal>yarn</goal></goals><phase>compile</phase><configuration><arguments>build</arguments></configuration></execution></executions></plugin></plugins></build><properties><spring.profiles.active>prod</spring.profiles.active></properties></profile> </profiles>

    在使用時,將活動配置文件設置添加到src/main/resources/application.yml :

    spring:profiles:active: @spring.profiles.active@security:

    添加./mvnw spring-boot:run -Pprod之后,您應該可以運行./mvnw spring-boot:run -Pprod并且您的應用程序可以看到您的應用程序在http://localhost:8080 。

    注意:如果您無法登錄,則可以嘗試在隱身窗口中打開您的應用程序。

    Spring Security的OAuth 2.0與OIDC支持

    在撰寫這篇文章時,我與Rob Winch (Spring Security Lead)合作,以確保我有效地使用了Spring Security。 我開始使用Spring Security的OAuth 2.0支持及其@EnableOAuth2Sso批注。 Rob鼓勵我改用Spring Security的OIDC支持,這對使一切正常發揮了作用。

    隨著Spring Boot 2.1和Spring Security 5.1的里程碑和發行版的發布,我將更新此帖子以刪除不再需要的代碼。

    了解有關Spring Boot和React的更多信息

    我希望您喜歡本教程,了解如何使用React,Spring Boot和Spring Security進行CRUD。 您可以看到Spring Security的OIDC支持非常強大,并且不需要大量配置。 添加CSRF保護并將Spring Boot + React應用打包為單個工件也很酷!

    您可以在GitHub上的https://github.com/oktadeveloper/okta-spring-boot-react-crud-example上找到本教程中創建的示例。

    我們還編寫了其他一些很棒的Spring Boot和React教程,如果您有興趣的話可以查看它們。

    • 使用Spring Boot和React進行Bootiful開發
    • 構建一個React Native應用程序并使用OAuth 2.0進行身份驗證
    • 使用Jenkins X和Kubernetes將CI / CD添加到您的Spring Boot應用程序
    • 15分鐘內通過用戶身份驗證構建React應用程序

    如有任何疑問,請隨時在下面發表評論,或在我們的Okta開發者論壇上向我們提問。 如果您想查看更多類似的教程,請在Twitter上關注我們!

    “我喜歡編寫身份驗證和授權代碼。” ?從來沒有Java開發人員。 厭倦了一次又一次地建立相同的登錄屏幕? 嘗試使用Okta API進行托管身份驗證,授權和多因素身份驗證。

    ``使用React和Spring Boot構建簡單的CRUD應用程序''最初于2018年7月19日發布在Okta開發者博客上。

    翻譯自: https://www.javacodegeeks.com/2018/07/react-spring-boot-build-crud-app.html

    總結

    以上是生活随笔為你收集整理的使用React和Spring Boot构建一个简单的CRUD应用的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    欧美一区二区三区四区夜夜大片 | 国产精品精品国产婷婷这里av | 美女国产精品 | 国产 字幕 制服 中文 在线 | 人人澡人人舔 | av中文字幕在线电影 | 午夜精品久久久99热福利 | 久久精品看片 | 午夜丰满寂寞少妇精品 | 国产精品免费大片视频 | 欧美成年人在线视频 | 亚洲黄色a| 亚洲日本va在线观看 | 特级西西444www大精品视频免费看 | 天天做日日爱夜夜爽 | 极品国产91在线网站 | www.av在线.com| 国产九九热视频 | 日日操夜夜操狠狠操 | 人人爽人人爽人人片av免 | 日本不卡123 | 国内精品久久天天躁人人爽 | 久久久久久久久久久久国产精品 | 亚洲成人av电影 | 国产美女网站视频 | 亚洲视频99| 免费在线精品视频 | 99精品国产在热久久下载 | 欧美二区三区91 | 91香蕉视频在线下载 | 九色视频网址 | 91精品在线观看入口 | 久久伊99综合婷婷久久伊 | 欧美一级黄大片 | 国产小视频在线免费观看 | 亚洲日本va午夜在线电影 | 国产亚洲精品久久 | 色婷婷亚洲精品 | 麻豆av一区二区三区在线观看 | 国产精品视频 | 青青五月天 | 青青河边草观看完整版高清 | 丁香六月五月婷婷 | 麻豆传媒视频观看 | 人人插人人射 | www.看片网站 | 999日韩| 久久伦理 | 国产精品久久久久久久久久不蜜月 | 在线中文字幕av观看 | 国产男女爽爽爽免费视频 | 日韩精品免费在线 | 欧美成人h版电影 | 中文字幕视频一区 | 最近免费观看的电影完整版 | www免费网站在线观看 | 欧美精品久久久久性色 | www.夜夜操.com | 国产日产精品一区二区三区四区的观看方式 | 国产日韩欧美视频 | 在线不卡中文字幕播放 | 天天做天天爱夜夜爽 | 亚洲免费在线观看视频 | avcom在线| 97日日碰人人模人人澡分享吧 | 啪啪免费试看 | 日本三级大片 | av福利资源 | 草久久久久久 | 国产精品久久久久av福利动漫 | 精品亚洲视频在线观看 | 国产视频一区二区在线 | 亚洲精品h | 婷婷色视频 | 又黄又爽的免费高潮视频 | 91精品1区2区 | 97精品国自产拍在线观看 | 欧美天堂视频在线 | 国产资源网站 | 在线观看日韩中文字幕 | 99精品视频网站 | 精品亚洲va在线va天堂资源站 | 999在线视频 | 久草视频精品 | 美女很黄免费网站 | 国产免费专区 | 91豆花在线观看 | 久久天天躁狠狠躁夜夜不卡公司 | 亚洲欧美日韩国产一区二区三区 | 欧美一区二区在线刺激视频 | 午夜国产成人 | 婷婷深爱五月 | 高清色免费 | 国内三级在线观看 | 黄色一级在线免费观看 | 中文字幕超清在线免费 | 黄av免费在线观看 | 日韩高清在线一区二区 | 91香蕉视频好色先生 | 中文字幕免费高清在线 | 91av网址| 欧美成人h版电影 | 婷婷丁香激情综合 | 在线观看韩日电影免费 | 久久精品国产美女 | 在线不卡a | 国产精品视频99 | 国产精品 中文在线 | 一区二区三区四区五区在线视频 | 97视频在线观看免费 | 日本黄区免费视频观看 | 久久精品com | 在线成人中文字幕 | 日韩av影视在线 | 国产五月 | 久久手机精品视频 | 在线观看久 | 99综合久久 | 99视频精品视频高清免费 | 国产精品电影在线 | 免费av在线网站 | 蜜臀av夜夜澡人人爽人人 | 最近最新mv字幕免费观看 | 97人人澡人人添人人爽超碰 | 超碰大片| 国产资源在线免费观看 | 亚洲一区 av | 97超碰人人爱 | 国产午夜剧场 | 国产一区在线视频播放 | www.久久久精品 | 91在线免费视频观看 | 久草网视频在线观看 | 香蕉影视在线观看 | 91麻豆传媒 | 99免费在线播放99久久免费 | 又黄又爽又刺激 | 亚洲欧洲av | a视频在线播放 | 亚洲精品网址在线观看 | 国产涩涩在线观看 | 久久久久激情电影 | www.色午夜,com | 亚洲精品tv| 欧美日韩视频一区二区三区 | 精品视频免费在线 | 正在播放日韩 | 国产免费视频一区二区裸体 | 久久综合九色综合97_ 久久久 | 精品日韩在线 | 亚洲 欧洲 国产 精品 | 久久久久99精品成人片三人毛片 | 久草资源在线观看 | 亚洲综合五月 | 免费国产在线观看 | 最近中文字幕完整高清 | 久久九九视频 | 青青草视频精品 | 国产99久久精品一区二区永久免费 | 国产一区二区在线免费 | 99在线高清视频在线播放 | 欧美日本日韩aⅴ在线视频 插插插色综合 | 日日干网址 | 91激情在线视频 | a√资源在线 | 天天插天天色 | 国产 欧美 日本 | 午夜免费福利片 | 天天综合区| 91久久国产综合精品女同国语 | 精品国产一区二区三区日日嗨 | 激情狠狠干| 波多野结衣电影一区 | 日韩欧美国产成人 | 精品亚洲成人 | 婷婷久草 | 国产精品黄网站在线观看 | 亚洲资源一区 | 九九在线视频 | 亚洲欧美综合精品久久成人 | 久久精品中文字幕免费mv | 天天操天天干天天爽 | 国产免费精彩视频 | 国产色视频网站 | 精品一区 精品二区 | 日韩性网站| 黄色网在线播放 | 韩国在线一区 | 亚洲日日夜夜 | 激情综合五月 | 久色小说 | 日韩色一区二区三区 | 久久久资源网 | 在线色亚洲 | 中文字幕免费成人 | 欧洲色吧 | 色七七亚洲影院 | 在线观看久草 | 色爱成人网 | 91亚色视频在线观看 | 色开心| 九九热精品在线 | 99久久精品免费看国产四区 | 亚洲一区二区三区四区精品 | 国产精品久久久久久模特 | 91麻豆精品国产91久久久久 | 欧美日视频 | 国产亚洲aⅴaaaaaa毛片 | 深夜国产在线 | 高清不卡毛片 | 国产夫妻性生活自拍 | 91chinesexxx| 久久久久免费精品视频 | 久草观看 | 国产亚洲精品电影 | 婷婷激情综合网 | 日韩av一区二区在线播放 | 成人a免费 | 在线观看视频免费播放 | 日韩精品免费在线观看视频 | 亚洲国产精品500在线观看 | 亚州av成人 | 丁香在线 | 99久久婷婷国产综合亚洲 | 欧美激情综合网 | 久草精品视频在线播放 | 国产资源在线播放 | 中文字幕中文字幕在线一区 | 91看片在线观看 | 欧美一区二区三区在线看 | 国产精品1区2区在线观看 | 日韩电影在线一区 | 国产乱码精品一区二区蜜臀 | 视频一区二区免费 | 97在线观看免费高清完整版在线观看 | 日韩免费一区二区在线观看 | 国产精品久久久久久欧美 | 久久久在线免费观看 | 久久国内免费视频 | 国产黄在线看 | 婷婷网五月天 | 婷婷丁香六月天 | 99久久99久久 | 精品在线亚洲视频 | 久久久国产影视 | www五月天com | 激情喷水 | 欧美在线视频不卡 | 国产毛片久久 | 91片黄在线观看 | 97精品国产一二三产区 | 91成人精品一区在线播放69 | 国产精品久久久久久久久蜜臀 | 国产精品久久久久免费观看 | 国产成人av电影在线 | 黄www在线观看 | 91人人澡 | 中文字幕精品一区二区三区电影 | 国产在线日本 | 色的网站在线观看 | 国产原创在线视频 | 精品自拍sae8—视频 | 亚洲激情综合 | 国产成人精品久久二区二区 | 最近的中文字幕大全免费版 | 在线成人中文字幕 | 成人av在线网 | 免费高清在线观看成人 | 久艹在线观看视频 | 青青草国产成人99久久 | 久久综合五月天 | 国产理伦在线 | 尤物一区二区三区 | 久久黄网站 | 中文字幕精品www乱入免费视频 | 丁香六月婷婷激情 | 久久九九网站 | av一级片网站 | 精品伊人久久久 | 日本在线观看一区 | 黄色毛片在线看 | 中文字幕av电影下载 | 久久艹久久 | 国产精品久久久久影院日本 | 精品美女国产在线 | 狠狠操.com | 欧美一区二区三区在线视频观看 | 97综合网 | 国产三级久久久 | 性色av免费在线观看 | 婷婷伊人综合 | 狠狠色丁香久久婷婷综合丁香 | 日韩在线免费不卡 | 久久久久一区二区三区四区 | 在线观看免费成人av | 成人黄色av网站 | 热re99久久精品国产66热 | 在线免费观看视频你懂的 | 亚洲 欧美 国产 va在线影院 | 香蕉在线视频播放网站 | 女人久久久久 | 日本精品久久 | 久草在线电影网 | 美女国产网站 | 国产精品久久久一区二区 | 精品视频在线观看 | 中文字幕一区二区在线观看 | 97超碰在线久草超碰在线观看 | 国产精品久久久久久麻豆一区 | 日韩视频www | 一级欧美日韩 | 欧美日韩国产在线一区 | 国产生活一级片 | 国产在线第三页 | 韩日av在线| av国产网站| 亚洲日本va中文字幕 | 色综合久久精品 | 日韩在线高清免费视频 | 丁香视频免费观看 | 久草在线中文视频 | www.com久久 | 亚洲天天综合 | 人人插人人草 | 六月丁香综合 | 激情六月婷婷久久 | 伊人伊成久久人综合网站 | 亚洲另类人人澡 | 成人三级网址 | 亚洲永久精品国产 | 日韩免费观看一区二区三区 | 国产成人三级一区二区在线观看一 | 日本中文字幕系列 | 中文字幕av免费在线观看 | 日韩精品免费 | av一级片 | 久久婷婷国产色一区二区三区 | 久久热亚洲 | 久久亚洲免费 | a√资源在线 | 日韩在线视频线视频免费网站 | 国产999精品久久久久久绿帽 | www最近高清中文国语在线观看 | 免费看污在线观看 | 狂野欧美激情性xxxx | 99精彩视频在线观看免费 | 91完整版在线观看 | 曰本三级在线 | 在线日本看片免费人成视久网 | 天天射天天舔天天干 | 香蕉视频在线播放 | 视频三区在线 | 国产视频一二区 | 日韩簧片在线观看 | 操高跟美女 | 久久久久久久久国产 | 免费黄色在线播放 | 超碰97人人在线 | 天天干天天做 | 国产成人精品综合久久久久99 | 久久精品国产亚洲精品 | 国产成人三级在线 | 在线精品视频在线观看高清 | 日本中文字幕久久 | 久久成人黄色 | 日韩av免费在线电影 | 国产原创在线 | 996久久国产精品线观看 | 黄色大全视频 | 日本在线观看中文字幕无线观看 | 99精品偷拍视频一区二区三区 | 天天天操天天天干 | 欧美在线aa | 五月婷婷六月丁香在线观看 | 久久公开免费视频 | 伊人超碰在线 | 激情五月***国产精品 | 国产免费黄视频在线观看 | 国产丝袜制服在线 | 久久av不卡 | 精品欧美小视频在线观看 | 国产视频资源在线观看 | www色网站| 亚洲欧洲中文日韩久久av乱码 | 成人毛片a | 亚洲视频在线视频 | av激情五月 | 国内精品视频在线 | 久草视频在线看 | 日本中文字幕电影在线免费观看 | 亚洲伦理精品 | 97人人射| 天天爱天天操 | 国产精品18久久久久白浆 | 久久99视频免费观看 | 国产精品日韩久久久久 | 欧美精品在线视频 | 中文有码在线视频 | 色婷婷国产 | 在线中文字幕视频 | 丁香婷婷久久久综合精品国产 | 日韩精品无| 久久视频在线观看免费 | 亚洲精品美女久久 | 欧洲在线免费视频 | 最新在线你懂的 | 中文字幕免费在线看 | h动漫中文字幕 | 青草视频免费观看 | 亚洲欧美视频一区二区三区 | 中文不卡视频在线 | 亚洲最新av在线网站 | 国内少妇自拍视频一区 | 色综合久久网 | 免费污片| 色综合网| 国产在线最新 | 成人在线黄色 | 成人91在线观看 | 日韩电影在线观看中文字幕 | 成片免费观看视频999 | 精品在线99 | 一区二区三区在线观看免费 | 亚洲春色综合另类校园电影 | 色视频网址| 日韩免费成人av | 亚洲一区二区三区四区在线视频 | 天天操天天操天天操天天操天天操 | 丁香婷婷亚洲 | 国产精品久久99综合免费观看尤物 | 久久精品视频中文字幕 | 96久久欧美麻豆网站 | 亚洲精品色 | 婷婷国产一区二区三区 | 91福利视频久久久久 | 国产一区二区在线观看视频 | 国内精品亚洲 | 国产一级二级在线观看 | 免费在线观看一区 | 日韩免费 | 欧美在线观看视频一区二区 | 黄色激情网址 | 免费色视频网站 | 麻豆精品在线 | 天天干 天天摸 天天操 | 久久久久久国产精品999 | 91在线视频免费观看 | 欧美夫妻性生活电影 | 国产福利小视频在线 | 在线精品观看国产 | 成人性生活大片 | 婷婷丁香色综合狠狠色 | 国产破处精品 | 麻豆免费精品视频 | 国产视频网站在线观看 | 欧美日韩在线免费视频 | 久久国产视频网站 | 又粗又长又大又爽又黄少妇毛片 | 成人精品视频久久久久 | 国产精品久久亚洲 | 手机成人在线电影 | 免费av一级电影 | 欧美激精品 | 黄色资源网站 | 国产精品破处视频 | 成人一区在线观看 | 99精品免费久久久久久久久日本 | 欧美一二三专区 | 久久精品国产一区 | 国产xvideos免费视频播放 | 亚洲成av人片 | 亚洲精选视频在线 | 久久久久久久久久网站 | 99爱视频 | 国内精品视频一区二区三区八戒 | 欧美成人h版电影 | 国产精品成人久久久久久久 | 日韩在线视频国产 | 狠狠色丁香九九婷婷综合五月 | 欧美性网站 | 人人澡人人草 | 免费日韩高清 | 久久精品这里热有精品 | 久久久综合九色合综国产精品 | 国产精品高清在线观看 | 亚洲日本va午夜在线影院 | 不卡日韩av | 亚洲成人动漫在线观看 | 午夜性色 | 国产视频18 | 狠狠色噜噜狠狠狠狠 | 午夜精品久久久久久99热明星 | 色播五月激情综合网 | 亚洲91中文字幕无线码三区 | 丁香视频五月 | 日韩午夜电影院 | 久久人人爽人人 | 欧美视频在线观看免费网址 | 久久一区二区三区国产精品 | 五月天中文在线 | 日黄网站 | 99在线视频观看 | 久久精品观看 | 亚洲伊人成综合网 | 免费电影一区二区三区 | 欧美精品久久人人躁人人爽 | 日韩精品一区二区三区外面 | av线上看| 成年人在线播放视频 | 久久久亚洲麻豆日韩精品一区三区 | 99综合电影在线视频 | 一级一片免费观看 | 成人av电影免费 | 中文字幕免费观看全部电影 | 91视频这里只有精品 | 国产在线播放一区二区三区 | 成人一级免费视频 | 丁香六月婷婷开心 | 草久久精品 | 精品一区二三区 | 欧美色噜噜 | 99精品偷拍视频一区二区三区 | 亚洲国产午夜精品 | 久久久麻豆视频 | 一二三区高清 | 久久久www成人免费毛片麻豆 | 午夜丁香网| 久草在线免费电影 | 午夜的福利| 成人中文字幕在线观看 | 日日爽视频 | 久久精品视频网 | 天天爱天天操天天爽 | av一级在线观看 | 国产91aaa| 中文字幕第一页av | 性色av香蕉一区二区 | 最新av网站在线观看 | 日本一区二区三区视频在线播放 | 国产丝袜美腿在线 | 黄色大全免费观看 | 在线播放视频一区 | 国产美女免费观看 | 欧美美女一级片 | 亚洲精品美女在线观看 | 亚洲激精日韩激精欧美精品 | 美女视频又黄又免费 | 久久毛片网站 | 九九九在线观看视频 | 成年人在线免费看视频 | 99热9| 久久人人插| 国产精品欧美精品 | 99精品网站 | 五月婷婷操| 精品自拍sae8—视频 | av黄色成人| 亚洲成人av电影 | 国产精品中文久久久久久久 | 国产在线观看av | 91九色porny蝌蚪视频 | 中文字幕日本在线观看 | 五月婷婷激情综合网 | 中文字幕2021 | 99久久精品免费视频 | 91精品国产一区二区三区 | 麻豆成人网 | 亚洲天堂香蕉 | 久久综合给合久久狠狠色 | 中文字幕在线观看的网站 | 三级动态视频在线观看 | 亚洲欧洲在线视频 | 亚洲精品在线一区二区 | 中文字幕亚洲综合久久五月天色无吗'' | 成人亚洲免费 | 精品国产一区二区三区免费 | 五月av在线 | 亚洲成av人片一区二区梦乃 | 国产成人黄色在线 | 日韩精品视频在线观看免费 | 国模一二三区 | 国产精品婷婷午夜在线观看 | 欧美亚洲成人xxx | 日韩免费在线观看 | 亚州国产精品视频 | 懂色av一区二区三区蜜臀 | 波多野结衣精品在线 | 国产精品毛片完整版 | 97超碰在| 91网在线看 | 国产做a爱一级久久 | 西西www4444大胆视频 | 久草在线观看 | 成人久久久精品国产乱码一区二区 | 亚洲男人天堂2018 | 色天天综合久久久久综合片 | 激情www | 99福利影院 | 欧美精品在线观看一区 | av观看在线观看 | 日韩在线电影一区二区 | 日韩二区三区 | 91 中文字幕 | 成年人在线电影 | 日韩系列在线 | 日韩久久在线 | 992tv又爽又黄的免费视频 | av高清一区二区三区 | 99久久精品国 | 在线播放一区二区三区 | 国产伦理一区二区 | 国产一区二区网址 | 国产永久免费观看 | 亚洲天堂va | 中文区中文字幕免费看 | 国产精品入口久久 | 久久一级片 | 国色天香在线 | 国产专区第一页 | 欧美性色xo影院 | 亚洲永久国产精品 | www九九热 | 久久久性| 亚洲国产精品va在线看黑人动漫 | 国产成人一区二区在线观看 | 欧美精品一区二区蜜臀亚洲 | 天堂va欧美va亚洲va老司机 | 天天摸天天舔 | 中文字幕在线影院 | 天天草夜夜 | 91麻豆精品久久久久久 | 在线免费观看不卡av | 成人宗合网 | 五月婷婷久 | 国产精品免费小视频 | 亚洲综合视频网 | 欧美日韩破处 | 久久精品永久免费 | 99视频播放| 婷香五月| 久久99精品国产麻豆宅宅 | 国产精品一区二区在线观看免费 | 久久亚洲欧美日韩精品专区 | 日韩在线观看你懂的 | 少妇av网 | 国产免费又爽又刺激在线观看 | 日韩精品一区二区三区视频播放 | 99精品久久久久久久久久综合 | 99精品视频免费全部在线 | 日韩精品你懂的 | 亚洲国产精品久久久 | 国产91九色蝌蚪 | 91精品久久久久久久99蜜桃 | 九九九视频精品 | 婷婷av在线| 69人人 | 国产a视频免费观看 | 在线观看小视频 | 日韩在线观看中文字幕 | 男女啪啪网站 | 成年人视频免费在线 | 国产精品久久久久久久久久直播 | 精品少妇一区二区三区在线 | 免费福利在线播放 | 中文字幕免费在线 | 国产资源网 | 久久久精品国产免费观看同学 | 免费成人黄色 | 天堂在线一区二区 | av在线等 | 成人av一二三区 | 国产理论片在线观看 | 久久精品视频网址 | 最近中文国产在线视频 | 亚洲成年人免费网站 | 欧美日韩国产精品久久 | 少妇bbb好爽 | 久久久精品国产一区二区三区 | 韩国av一区二区三区在线观看 | 久草综合在线 | 国产精品久久久久9999吃药 | 色婷婷欧美| 精品亚洲欧美无人区乱码 | 91最新网址在线观看 | 人人插人人射 | 国产亚洲一区二区在线观看 | 日本aaaa级毛片在线看 | 精品欧美一区二区三区久久久 | 中文字幕在线免费播放 | 激情综合国产 | 色噜噜在线观看 | 亚洲激情久久 | 天天综合成人网 | 日本精品久久久久中文字幕 | 欧美日韩中文字幕在线视频 | 欧美精品一二 | 久久久精品在线观看 | 午夜丁香网 | 亚洲精品在线播放视频 | 成人小视频在线观看免费 | 亚洲综合视频在线 | 三级黄色在线 | 天天弄天天干 | 夜夜操天天操 | 午夜视频导航 | 97超碰资源总站 | 手机在线观看国产精品 | 欧美一区二区三区四区夜夜大片 | 人人插超碰 | 国产精品视频免费 | 人人干人人添 | 亚洲电影久久久 | 国产精国产精品 | 中文字幕资源网 | 午夜精品一区二区三区免费视频 | 一区二区三区四区不卡 | www久久| www.久草视频| 91色九色 | 欧美少妇18p| 久久久久免费网站 | 麻豆传媒在线免费看 | 热re99久久精品国产99热 | 日日干干 | 中文字幕av免费在线观看 | 久久久麻豆视频 | 最新中文字幕视频 | 午夜在线资源 | 99久久网站 | 毛片网站免费在线观看 | 99精品国产成人一区二区 | 婷婷丁香色 | 久久久久国产视频 | 92中文资源在线 | 午夜在线免费观看视频 | 在线观看视频你懂的 | 一级黄色片在线免费看 | 日韩小视频 | 国产精品视频免费看 | zzijzzij亚洲成熟少妇 | 高清av网 | 九9热这里真品2 | 久久99精品久久久久久秒播蜜臀 | 超碰日韩 | 成人黄色毛片 | 菠萝菠萝蜜在线播放 | 国产黄色特级片 | 韩国av一区二区三区在线观看 | 婷婷在线免费视频 | 天天插视频 | 91麻豆精品国产91久久久久久久久 | 国产探花| 天天爽夜夜爽精品视频婷婷 | 天天综合色网 | 亚洲在线a | 久久er99热精品一区二区三区 | 亚洲精品自拍视频在线观看 | 日韩av免费观看网站 | 四虎影视www | 久久婷婷综合激情 | 在线中文字母电影观看 | 中文字幕综合在线 | 免费成人黄色片 | 日日夜夜天天操 | 最新成人av | 午夜精品成人一区二区三区 | 黄色午夜| 国产亚洲激情视频在线 | 久久久麻豆| 亚洲精品中文字幕在线 | 国产视频亚洲精品 | 免费成人av在线看 | 97视频免费在线看 | 日韩不卡高清视频 | 欧美a√在线 | 色偷偷88欧美精品久久久 | 精品亚洲在线 | 日韩免费观看一区二区三区 | 看黄色.com | 18av在线视频 | 国产精品va在线观看入 | 国产精品久久久久永久免费 | www.久久99| 免费男女羞羞的视频网站中文字幕 | 久久国产精品免费观看 | 日韩免费在线观看视频 | 中文字幕第一 | 999视频在线播放 | 激情婷婷av | 激情www | 最近免费在线观看 | 成人天堂网 | 久久精品—区二区三区 | 91精品入口| 国产91学生粉嫩喷水 | 成年人在线播放视频 | 欧美日韩3p | 91av在线免费 | 日韩中文字幕91 | 黄在线免费观看 | 成人黄色电影在线播放 | 91精品久久久久久久久 | 中国黄色一级大片 | 又黄又爽又刺激 | 日韩精品资源 | 一本一本久久a久久 | 午夜av免费观看 | 国产一级在线视频 | 五月天高清欧美mv | 成人久久久久久久久久 | 国产精品第2页 | 久久a久久 | 女人18片毛片90分钟 | 三级在线视频观看 | 美女网站色在线观看 | 久久久精品国产免费观看同学 | 久草综合在线观看 | 国产一线二线三线在线观看 | 成人97人人超碰人人99 | 91久久精品一区二区二区 | 亚洲va欧美 | 欧美一级电影免费观看 | 中文字幕一区二区三区乱码在线 | 久久美女视频 | 精品成人在线 | 成人小视频在线 | 亚洲久草网 | 久草在线中文888 | 亚洲精选在线观看 | 欧美久久久久久久久久久久久 | 91日韩精品视频 | 蜜桃麻豆www久久囤产精品 | 日本中文字幕网 | 久久一区二区三区超碰国产精品 | 最近中文字幕高清字幕在线视频 | 日韩伦理一区二区三区av在线 | 97狠狠操| 国产又黄又猛又粗 | 狠狠色婷婷丁香六月 | 午夜精品一区二区三区四区 | 国产婷婷 | 91精品视频一区二区三区 | 亚洲黄色免费在线看 | 国产成人免费观看久久久 | 天天插综合网 | 国产在线2020 | 免费观看性生交 | 在线观看国产日韩欧美 | 伊人伊成久久人综合网站 | 国产999精品久久久 免费a网站 | 激情综合五月网 | 久久婷婷久久 | av综合站| 99人成在线观看视频 | 久久久久激情 | 精品国产精品国产偷麻豆 | 亚洲精品成人网 | 久久免费视频这里只有精品 | 欧美一区二区精美视频 | 岛国精品一区二区 | 黄色小说在线免费观看 | 操碰av | 天躁狠狠躁 | 欧美性生活一级片 | 国产精品免费视频网站 | 中文字幕av在线免费 | 亚洲精品高清视频 | 麻豆精品视频在线观看免费 | .国产精品成人自产拍在线观看6 | 免费色网| 三上悠亚在线免费 | 日韩欧美国产精品 | 天天色天天色天天色 | 天天操天天操天天 | 日韩一级成人av | 成片人卡1卡2卡3手机免费看 | av直接看| 超碰97公开| 国产又粗又长又硬免费视频 | 亚洲国产精品日韩 | 香蕉影视在线观看 | 亚洲精品动漫在线 | 国产99久久精品 | 久久久久久国产精品久久 | 一本一道久久a久久精品蜜桃 | av激情五月 | 日韩三级.com| 国产一区二区在线免费播放 | 成av人电影| 午夜精品久久久久久久久久久久久久 | 久久久久久久久久电影 | 日狠狠 | 欧美日韩国产综合一区二区 | 成人啪啪18免费游戏链接 | 欧美日韩国产三级 | 婷婷激情在线观看 | 超碰99在线 | 久久在线观看视频 | 久久久精品国产免费观看同学 | 成人四虎| 国产欧美高清 | 日本少妇视频 | 中文字幕一区二区三区在线观看 | 久久激情日本aⅴ | 处女av在线 | 精品福利网站 | 国产精品二区三区 | 黄色免费观看网址 | 久久黄色精品视频 | 日本视频网 | 国产色久 | 久久精品亚洲一区二区三区观看模式 | 国产在线毛片 | 国产精品成人a免费观看 | 欧美成人精品三级在线观看播放 | 在线午夜电影神马影院 | 人人爽人人爽人人爽 | 51精品国自产在线 | 国产 日韩 欧美 中文 在线播放 | 五月天亚洲综合小说网 | 成人91av| 国产一区国产二区在线观看 | 91在线操| av一级在线| 五月天婷婷狠狠 | 国产高清久久久 | 中文字幕国产 | 久久综合狠狠综合久久激情 | 中文不卡视频在线 | 一区二区中文字幕在线播放 | 国产精品久久久久亚洲影视 | 日韩欧美专区 | 丁香九月婷婷 | 九九久久免费视频 | 日韩首页| 久久久久久久久久电影 | 国产精品伦一区二区三区视频 | 99久久精品免费看国产四区 | 三级黄色三级 | 不卡中文字幕在线 | 免费日韩视频 | 不卡的av | 日韩网站在线免费观看 | 成人毛片一区二区三区 | 色丁香综合 | 国产 成人 久久 | 国产.精品.日韩.另类.中文.在线.播放 | 黄色av网站在线观看免费 | 香蕉视频色 | 成人黄色国产 | 成人h在线观看 | 国产一区二区久久 | 欧美精品久久久久 | 日韩免费观看一区二区 | 国产一级91| 国产成人一区二区三区在线观看 | 男女日麻批 | 午夜国产福利在线观看 | 欧美日韩a视频 | 久久免费看a级毛毛片 | 国产午夜视频在线观看 | 亚洲国产精品久久久久婷婷884 | av片在线观看| 免费观看性生交大片3 | 天天色综合三 | 亚洲国产资源 | 人人射人人射 | 国产xx在线| 国产精品av在线免费观看 | 伊人伊成久久人综合网小说 | 国产一级一级国产 | 亚洲尺码电影av久久 | av资源免费看 | 久久午夜国产 | 成人九九视频 | 亚洲资源 | 操夜夜操 | 九九视频网 | 亚洲精品久久久蜜臀下载官网 | 国产免费久久久久 | 亚洲 欧美日韩 国产 中文 | 免费h精品视频在线播放 | 天天干天天拍天天操天天拍 | 99国产精品久久久久久久久久 | 久久五月网 | 啪一啪在线 | 久久99久久99久久 | 9992tv成人免费看片 | 五月婷婷视频在线 | 国产亚洲精品久久网站 | 国产一区二区三精品久久久无广告 | 日韩性久久 | 黄色大片日本 | 久久电影日韩 | 天躁狠狠躁 | 欧美日韩国产一区二区三区在线观看 | 2019精品手机国产品在线 | 国产色女人 |