security with restful

简介: 国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html内部邀请码:C8E245J (不写邀请码,没有现金送)国内私募机构九鼎控股打造,九鼎投资是在全国股份转让系统挂牌的公众公司,股票代码为430719,为“中国PE第一股”,市值超1000亿元。

国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html
内部邀请码:C8E245J (不写邀请码,没有现金送)
国内私募机构九鼎控股打造,九鼎投资是在全国股份转让系统挂牌的公众公司,股票代码为430719,为中国PE第一股,市值超1000亿元。 

------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

http://marcin-michalski.pl/

Webservice (Spring Security + JavaScript + AJAX)

Recently I was playing a little bit with different types of authentications. As you know we distinguish Basic, Digest and Form-Based authentications (more about that here). When we are working with web application it is usually sufficient to use either of them but the problem arises when we need to work with RESTfull system where there is no login page or any browser pop-up window to enter the credentials.

One of the solutions would be to store credentials as a plain text somewhere in the request, however it is not very secure way and I would not recommend this solution. It is better to use Digest Authentication (RFC 2617). With this approach we always send data in encrypted format. Spring Security gives us most of that functionality out of the box however it does not support pure JavaScript clients. That is why I had to do some refinements in order to make it work.

Below I’m presenting step by step what needs to be done in order to configure Spring Security and jQuery/Ajax client that calls secured resource.

You may also download whole code from our github repository.

Setting Spring Contexts

Main Application context file – web-application-context.xml:

 

 XML |     copy code | ?  
01
<?xml version="1.0" encoding="UTF-8"?>
02
<beans xmlns="http://www.springframework.org/schema/beans"
03
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04
       xmlns:context="http://www.springframework.org/schema/context"
05
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
06
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
07
 
08
   <import resource="security.xml"/>
09
   <import resource="security-inmemory-auth-provider.xml"/>
10
 
11
</beans>

 

Security configuration context – security.xml

 

 XML |     copy code | ?  
01
<?xml version="1.0" encoding="UTF-8"?>
02
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
03
       xmlns:security="http://www.springframework.org/schema/security"
04
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
05
 http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
06
 
07
   <security:global-method-security secured-annotations="enabled" pre-post-annotations="enabled"/>
08
 
09
   <security:http pattern="/resources/**" security="none"/>
10
   <security:http pattern="/guest/**" security="none"/>
11
 
12
   <security:http 
13
                 entry-point-ref="digestEntryPoint" >
14
   <security:custom-filter ref="digestFilter" position="BASIC_AUTH_FILTER"/>
15
      <security:intercept-url pattern="/user/**" access="ROLE_USER"/>
16
   </security:http>
17
 
18
 
19
 <bean id="digestFilter" class="org.springframework.security.web.authentication.www.DigestAuthenticationFilter">
20
   <property name="userDetailsService" ref="inMemoryUserService"/>
21
   <property name="authenticationEntryPoint" ref="digestEntryPoint"/>
22
 </bean>
23
 <bean id="digestEntryPoint" class="pl.arrowgroup.restauth.security.AjaxDigestAuthenticationEntryPoint">
24
   <property name="realmName" value="REST-Realm"/>
25
   <property name="key" value="testNonce"/>
26
   <property name="nonceValiditySeconds" value="10000"/>
27
 </bean>
28
 
29
 
30
   <security:authentication-manager alias="authenticationManager">
31
      <security:authentication-provider ref="inMemoryAuthenticationProvider"/>
32
   </security:authentication-manager>
33
</beans>

 

Authentication provider settings (users/passwords/roles) – security-inmemory-auth-provider.xml

 

 XML |     copy code | ?  
01
<?xml version="1.0" encoding="UTF-8"?>
02
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
03
       xmlns:security="http://www.springframework.org/schema/security"
04
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
05
 http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
06
 
07
 <bean id="inMemoryAuthenticationProvider"
08
         class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
09
      <property name="hideUserNotFoundExceptions" value="false"/>
10
      <property name="userDetailsService" ref="inMemoryUserService"/>
11
      <property name="messageSource" ref="messageSource"/>
12
   </bean>
13
 
14
 <security:user-service id="inMemoryUserService">
15
 <security:user name="marcin" password="michalski" authorities="ROLE_USER"/> 
16
 </security:user-service>
17
</beans> 

 

Servlet front controller configuration (SpringMVC) – servlet-context.xml

 

 XML |     copy code | ?  
01
<?xml version="1.0" encoding="UTF-8"?>
02
<beans xmlns="http://www.springframework.org/schema/beans"
03
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04
       xmlns:context="http://www.springframework.org/schema/context"
05
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p"
06
       xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
07
 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
08
 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
09
 
10
   <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
11
      <property name="defaultStatusCode" value="200"/>
12
      <property name="defaultErrorView" value="/error"/>
13
      <property name="exceptionMappings">
14
       <props>
15
       <prop key="org.springframework.security.access.AccessDeniedException">/denied</prop>
16
       </props>
17
      </property>
18
   </bean>
19
 
20
  <context:component-scan base-package="pl.arrowgroup.restauth.controllers"/>
21
 
22
  <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
23
      <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
24
      <property name="prefix" value="/WEB-INF/jsp/"/>
25
      <property name="suffix" value=".jsp"/>
26
   </bean>
27
 
28
  <mvc:annotation-driven>
29
      <mvc:message-converters>
30
         <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
31
         <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
32
         <bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
33
         <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
34
      </mvc:message-converters>
35
   </mvc:annotation-driven>
36
 
37
 
38
  <mvc:resources mapping="/resources/**" location="/resources/"/>
39
  <bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
40
   <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
41
      <property name="supportedMethods" value="GET,POST,PUT,HEAD,DELETE"/>
42
      <property name="messageConverters">
43
         <list>
44
            <ref bean="jsonMessageConverter"/>
45
         </list>
46
      </property>
47
   </bean>
48
</beans>

 

For simplicity I have created security layer that uses inmemory authentication provider probably in production environment you would like to have user/password information stored in database or LDAP. So in that case all you have to do is change the authentication provider.

Not to get in to much details I just say that I have configured typical SpringMVC context and provided basic security layer with digest authentication.

As you can see almost everything is provided using default Spring classes with one exception AjaxDigestAuthenticationEntryPoint. Although it is my custom class  it  actually acts like Spring’s DigestAuthenticationEntryPoint and the only difference it does is that it sends Forbidded (403) http status code instead of Unauthorized(401) once authentication fails.  It was done to prevent browser from displaying unwanted pop-ups when 401 status is returned. 

AjaxDigestAuthenticationEntryPoint.java:

 

 Java |     copy code | ?  
01
package pl.arrowgroup.restauth.security;
02
import java.io.IOException;
03
 
04
import javax.servlet.ServletException;
05
import javax.servlet.http.HttpServletRequest;
06
import javax.servlet.http.HttpServletResponse;
07
import javax.servlet.http.HttpServletResponseWrapper;
08
 
09
import org.springframework.security.core.AuthenticationException;
10
import org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint;
11
 
12
public class AjaxDigestAuthenticationEntryPoint extends DigestAuthenticationEntryPoint{
13
 
14
  @Override
15
  public void commence(HttpServletRequest request, HttpServletResponse response, 
16
             AuthenticationException authException) throws IOException, ServletException {
17
    super.commence(request, new UnauthorizedHttpResponse(response), authException);
18
  }
19
 
20
  private static class UnauthorizedHttpResponse extends HttpServletResponseWrapper{
21
    public UnauthorizedHttpResponse(HttpServletResponse response) {
22
      super(response);
23
    }
24
    @Override
25
    public void sendError(int sc, String msg) throws IOException {
26
      if(sc == HttpServletResponse.SC_UNAUTHORIZED){
27
        sc = HttpServletResponse.SC_FORBIDDEN;
28
      }
29
      super.sendError(sc, msg);
30
    }
31
  }
32
}

 

Web.xml

 

 XML |     copy code | ?  
01
<?xml version="1.0" encoding="UTF-8"?>
02
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
03
         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
04
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
05
         version="2.5">
06
   <display-name>${project.name} ${project.version} SVN@${buildNumber}</display-name>
07
   <description>${project.description}</description>
08
   <context-param>
09
      <param-name>contextConfigLocation</param-name>
10
      <param-value>/META-INF/spring/web-application-context.xml</param-value>
11
   </context-param>
12
   <context-param>
13
      <param-name>log4jConfigLocation</param-name>
14
      <param-value>classpath:log4j.xml</param-value>
15
   </context-param>
16
   <!-- ================================================================== -->
17
 
18
   <listener>
19
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
20
   </listener>
21
 
22
   <!-- ================================================================== -->
23
 
24
   <filter>
25
      <filter-name>characterEncodingFilter</filter-name>
26
      <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
27
      <init-param>
28
         <param-name>encoding</param-name>
29
         <param-value>utf-8</param-value>
30
      </init-param>
31
   </filter>
32
   <filter-mapping>
33
      <filter-name>characterEncodingFilter</filter-name>
34
      <url-pattern>/*</url-pattern>
35
   </filter-mapping>
36
 
37
   <filter>
38
      <filter-name>springSecurityFilterChain</filter-name>
39
      <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
40
   </filter>
41
   <filter-mapping>
42
      <filter-name>springSecurityFilterChain</filter-name>
43
      <url-pattern>/*</url-pattern>
44
   </filter-mapping>
45
 
46
   <!-- ================================================================== -->
47
 
48
    <servlet>
49
       <servlet-name>springServlet</servlet-name>
50
       <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
51
       <init-param>
52
          <param-name>contextConfigLocation</param-name>
53
          <param-value>/META-INF/spring/servlet-context.xml</param-value>
54
       </init-param>
55
       <load-on-startup>1</load-on-startup>
56
    </servlet>
57
    <servlet-mapping>
58
       <servlet-name>springServlet</servlet-name>
59
       <url-pattern>/</url-pattern>
60
    </servlet-mapping>
61
</web-app>

 

No magic in the web.xml. Just typical Spring + Spring Security configuration.

JavaScript client

As you can see Spring provides almost everything in server side and if we were using Java as a Rest client we could use RestTemplate to have all the work made for us.  However we want to use JavaScript instead. In order to do that I had to write mechanism that parses incomming WWW-Authenticate header and generate Authorization header according to RFC 2617.

digest-auth.js:

 

 Javascript |     copy code | ?  
01
/*
02
 * A JavaScript implementation of the Digest Authentication
03
 * Digest Authentication, as defined in RFC 2617.
04
 * Version 1.0 Copyright (C) Maricn Michalski (http://marcin-michalski.pl) 
05
 * Distributed under the BSD License
06
 * 
07
 * site: http://arrowgroup.eu
08
 */
09
 
10
$.Class("pl.arrowgroup.DigestAuthentication", {
11
 MAX_ATTEMPTS : 1,
12
 AUTHORIZATION_HEADER : "Authorization",
13
 WWW_AUTHENTICATE_HEADER : 'WWW-Authenticate',
14
 NC : "00000001", //currently nc value is fixed it is not incremented
15
 HTTP_METHOD : "GET",
16
 /**
17
  * settings json:
18
  *  - onSuccess - on success callback
19
  *  - onFailure - on failure callback
20
  *  - username - user name
21
  *  - password - user password
22
  *  - cnonce - client nonce
23
  */
24
 init : function(settings) {
25
   this.settings = settings;
26
 },
27
 setCredentials: function(username, password){
28
   this.settings.username = username;
29
   this.settings.password = password;
30
 },
31
 call : function(uri){
32
   this.attempts = 0;
33
   this.invokeCall(uri);
34
 },
35
 invokeCall: function(uri,authorizationHeader){
36
   var digestAuth = this;
37
   $.ajax({
38
         url: uri,
39
         type: this.HTTP_METHOD,
40
         beforeSend: function(request){
41
          if(typeof authorizationHeader != 'undefined'){
42
          request.setRequestHeader(digestAuth.AUTHORIZATION_HEADER, authorizationHeader);           
43
          }
44
         },
45
         success: function(response) {
46
          digestAuth.settings.onSuccess(response);          
47
         },
48
         error: function(response) { 
49
          if(digestAuth.attempts == digestAuth.MAX_ATTEMPTS){
50
      digestAuth.settings.onFailure(response);
51
      return;
52
      }
53
          var paramParser = new pl.arrowgroup.HeaderParamsParser(response.getResponseHeader(digestAuth.WWW_AUTHENTICATE_HEADER));
54
          var nonce = paramParser.getParam("nonce");
55
          var realm = paramParser.getParam("realm");
56
          var qop = paramParser.getParam("qop");
57
          var response = digestAuth.calculateResponse(uri, nonce, realm, qop);
58
          var authorizationHeaderValue = digestAuth.generateAuthorizationHeader(paramParser.headerValue, response, uri); 
59
          digestAuth.attempts++;
60
          digestAuth.invokeCall(uri, authorizationHeaderValue);
61
         }             
62
     });
63
 },
64
 calculateResponse : function(uri, nonce, realm, qop){
65
   var a2 = this.HTTP_METHOD + ":" + uri;
66
   var a2Md5 = hex_md5(a2);
67
   var a1Md5 = hex_md5(this.settings.username + ":" + realm + ":" + this.settings.password);
68
   var digest = a1Md5 + ":" + nonce + ":" + this.NC + ":" + this.settings.cnonce + ":" + qop + ":" +a2Md5;
69
   return hex_md5(digest);
70
 },
71
 generateAuthorizationHeader : function(wwwAuthenticationHeader, response, uri){
72
   return wwwAuthenticationHeader+', username="'+this.settings.username+'", uri="'+
73
      uri+'", response="'+response+'", nc='+
74
      this.NC+', cnonce="'+this.settings.cnonce+'"';
75
   }
76
});
77
$.Class("pl.arrowgroup.HeaderParamsParser",{
78
 init : function(headerValue) {
79
   this.headerValue = headerValue;
80
   this.headerParams = this.headerValue.split(",");
81
 },
82
 getParam: function(paramName){
83
   var paramVal = null;
84
   $.each(this.headerParams, function(index, value){
85
     if(value.indexOf(paramName)>0){
86
     paramVal = value.split(paramName+"=")[1];
87
     paramVal = paramVal.substring(1, paramVal.length-1);
88
     }
89
   });
90
   return paramVal;
91
 }
92
});

 

In order to prepare JavaScript client I had to use external JS library responsible for MD5 calculation.

Example:
 
Now when we have both client and server functionality let’s prepare some example.
 
Server side UserController.java:

 

 Java |     copy code | ?  
01
package pl.arrowgroup.restauth.controllers;
02
 
03
import java.util.Date;
04
 
05
import javax.servlet.http.HttpServletRequest;
06
import javax.servlet.http.HttpServletResponse;
07
 
08
import org.springframework.security.core.context.SecurityContextHolder;
09
import org.springframework.security.core.userdetails.User;
10
import org.springframework.stereotype.Controller;
11
import org.springframework.web.bind.annotation.RequestMapping;
12
import org.springframework.web.bind.annotation.ResponseBody;
13
 
14
 
15
@Controller
16
@RequestMapping("/user")
17
public class UserController {
18
   @RequestMapping(value="/echo")
19
   public @ResponseBody String echo(HttpServletRequest request, HttpServletResponse resp){ 
20
     return "Hello "+((User) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername()+
21
            " - Current Date is : "+new Date() +" - Visit us at : http://arrowgroup.eu";
22
   }
23
}

 

Client side html – test.html

 

 HTML |     copy code | ?  
01  
02
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
03
 <head >
04
 <title>Ajax test call</title>
05
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
06
     <script type="text/javascript" src="../../resources/js/jquery.min.js" ></script>
07
     <script type="text/javascript" src="../../resources/js/jquery.class.min.js" ></script>
08
     <script type="text/javascript" src="../../resources/js/md5-min.js" ></script>
09
     <script type="text/javascript" src="../../resources/js/digest-auth.js" ></script>
10
 
11
     <script type="text/javascript">
12
       var digestAuth = new pl.arrowgroup.DigestAuthentication(
13
         {
14
           onSuccess : function(response){
15
             $("#response").html(response);
16
           },
17
           onFailure : function(response){
18
             $("#response").html('Invalid credentials !!!');
19
           },
20
           cnonce : 'testCnonce'
21
         }
22
       );
23
       function callREST(){
24
         digestAuth.setCredentials($('#user').val(),$('#password').val());
25
         digestAuth.call('/restauth/user/echo');
26
       }
27
     </script>
28
 </head>
29
 <body>
30
   <div>
31
     <h3>Test example of Digest Authentication using Ajax request and Spring Security</h3>  
32
   </div>
33
   <div>
34
     <form >
35
       <p>User <input id="user" type="text" value="marcin"></p>
36
       <p>Password <input id="password" type="text" value="michalski"></p>
37
       <p> <button onclick="callREST(); return false;" >Execute</button> 
38
     </form>
39
   </div>
40
   <div id="response">
41
   </div>
42
   <div style="color: gray; font-size: 12px;">
43
     Copyright 2012: <a href="http://arrowgroup.eu">ArrowGroup</a>, Author: <a href="http://marcin-michalski.pl">Marcin Michalski</a> 
44
   </div>
45
 </body>
46
</html>

 

No when we enter the page and provide incorrect password we will be notified that provided password is incorrect:

invalid

And when we enter correct password we are able to access the controller method:

valid
目录
相关文章
|
安全 Java Spring
Spring Security 匹配Restful url
Spring Security 匹配Restful url
315 0
|
Java API 数据安全/隐私保护
Spring Boot+Spring Security+JWT 实现 RESTful Api 权限控制
链接网址:https://www.jianshu.com/p/6307c89fe3fa/ 链接网址2:https://blog.csdn.net/linzhiqiang0316/article/details/78358907 jwt介绍:         JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。
4240 0
|
安全 Java 数据安全/隐私保护
springboot+jjwt+security完美解决restful接口无状态鉴权
微服务大行其道的现在,如果我们还在用wsdl之类的提供接口,给人的感觉就会很low,虽然说不能为了炫技而炫技,但是既然restful接口已经越来越流行,必然有它的道理。
3470 0
|
JSON 安全 Java
使用Spring Security和OAuth2实现RESTful服务安全认证
这篇教程是展示如何设置一个OAuth2服务来保护REST资源. 源代码下载github. (https://github.com/iainporter/oauth2-provider)你能下载这个源码就开始编写一个被OAuth方法保护的服务。
1433 0
|
23天前
|
JSON 缓存 JavaScript
深入浅出:使用Node.js构建RESTful API
在这个数字时代,API已成为软件开发的基石之一。本文旨在引导初学者通过Node.js和Express框架快速搭建一个功能完备的RESTful API。我们将从零开始,逐步深入,不仅涉及代码编写,还包括设计原则、最佳实践及调试技巧。无论你是初探后端开发,还是希望扩展你的技术栈,这篇文章都将是你的理想指南。
|
16天前
|
JSON JavaScript 前端开发
深入浅出Node.js:从零开始构建RESTful API
在数字化时代的浪潮中,后端开发作为连接用户与数据的桥梁,扮演着至关重要的角色。本文将引导您步入Node.js的奇妙世界,通过实践操作,掌握如何使用这一强大的JavaScript运行时环境构建高效、可扩展的RESTful API。我们将一同探索Express框架的使用,学习如何设计API端点,处理数据请求,并实现身份验证机制,最终部署我们的成果到云服务器上。无论您是初学者还是有一定基础的开发者,这篇文章都将为您打开一扇通往后端开发深层知识的大门。
31 12
|
18天前
|
XML JSON 缓存
深入理解RESTful API设计原则与实践
在现代软件开发中,构建高效、可扩展的应用程序接口(API)是至关重要的。本文旨在探讨RESTful API的核心设计理念,包括其基于HTTP协议的特性,以及如何在实际应用中遵循这些原则来优化API设计。我们将通过具体示例和最佳实践,展示如何创建易于理解、维护且性能优良的RESTful服务,从而提升前后端分离架构下的开发效率和用户体验。
|
22天前
|
监控 安全 API
深入浅出:构建高效RESTful API的最佳实践
在数字化时代,API已成为连接不同软件和服务的桥梁。本文将带你深入了解如何设计和维护一个高效、可扩展且安全的RESTful API。我们将从基础概念出发,逐步深入到高级技巧,让你能够掌握创建优质API的关键要素。无论你是初学者还是有经验的开发者,这篇文章都将为你提供实用的指导和启示。让我们一起探索API设计的奥秘,打造出色的后端服务吧!
|
20天前
|
JSON 缓存 测试技术
构建高效RESTful API的后端实践指南####
本文将深入探讨如何设计并实现一个高效、可扩展且易于维护的RESTful API。不同于传统的摘要概述,本节将直接以行动指南的形式,列出构建RESTful API时必须遵循的核心原则与最佳实践,旨在为开发者提供一套直接可行的实施框架,快速提升API设计与开发能力。 ####
|
22天前
|
JavaScript NoSQL API
深入浅出Node.js:从零开始构建RESTful API
在数字化时代的浪潮中,后端开发如同一座灯塔,指引着数据的海洋。本文将带你航行在Node.js的海域,探索如何从一张白纸到完成一个功能完备的RESTful API。我们将一起学习如何搭建开发环境、设计API结构、处理数据请求与响应,以及实现数据库交互。准备好了吗?启航吧!