好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

使用Spring Boot 2.x构建Web服务的详细代码

架构:

MVC架构 基于JWT的身份认证 Spring Data (JPA) 应用用户密码加密 数据库密码加密 SQL Server Slf4j 基于Swagger的API文档

库:

应用源代码 数据库的SQL脚本以及关键数据 包含数据库配置信息的DB.txt文件 用于测试Web服务的Postman JSON脚本

运行应用的步骤

安装JDK11或最新版本 克隆项目库到本地 Git地址:https://github测试数据/VishnuViswam/sample-web-service.git 安装SQL server 2012 创建应用数据库和用户 插入数据库密钥数据 将数据库密码的解码密钥添加到系统变量,它位于DB.txt文件中 有时可能需要重新启动窗口以获取更新后的系统变量 运行项目源代码 导入预先提供的postman JSON脚本到postman客户端,调用Web服务

关于项目配置

Web服务声明

应用程序的每个Web服务都将在controller层中声明。

示例

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

@RequestMapping ( "/api/v1/user" )

@RestController

@Validated

public class UserController {

 

   private static final Logger logger = LoggerFactory.getLogger(UserController. class );

 

     @Autowired

    private GeneralServices generalServices;

 

    @Autowired

    private UserService userService;

 

    /**

      * Web service to create new user

      *

      * @param httpServletRequest

     * @param user

      * @return

     */

    @PostMapping (consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)

    public ResponseEntity<Object> createUser(HttpServletRequest httpServletRequest,

                                              @Valid @RequestBody UserCreateModel user) {

        logger.debug( "<--- Service to save new user request : received --->" );

        ApiSuccessResponse apiResponse = userService.createUser(user, generalServices.getApiRequestedUserId(httpServletRequest));

         logger.debug( "<--- Service to save new user response : given --->" );

        return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse);

 

     }

 

}

@RequestMapping("/api/v1/user")注解用来声明Web服务的类别 @RestController注解配置该类来接收Restful的 Web服务调用 @PostMapping()注解决定了HTTP请求类型 consume和consume标记来确定HTTP请求和响应的内容类型

通过controller层,API请求将被带到服务层。所有业务逻辑都将在这里处理,然后它将使用JPA与数据库通信。

通用错误处理

每当异常发生时,它将从相应的类抛出,并在CommonExceptionHandlingController中处理。我们必须分别处理每种异常类型。这个功能是在ControllerAdvice注解的帮助下执行的。

示例

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

@ControllerAdvice

public   class   CommonExceptionHandlingController  extends   ResponseEntityExceptionHandler {

 

     private   static   final   Logger logger = 

         LoggerFactory.getLogger(CommonExceptionHandlingController. class );

    @Override

     protected   ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException httpRequestMethodNotSupportedException,

                                                                          HttpHeaders headers, HttpStatus status, WebRequest request) {

        return   ResponseEntity.status(HttpStatus.NOT_FOUND).body( new   ApiErrorResponse(Constants.WRONG_HTTP_METHOD,

                 Constants.WRONG_HTTP_METHOD_ERROR_MESSAGE, Calendar.getInstance().getTimeInMillis()));

    }

    protected   ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException methodArgumentNotValidException,

                                                                  HttpHeaders headers, HttpStatus status, WebRequest request) {

        return   ResponseEntity.status(HttpStatus.BAD_REQUEST).body( new   ApiErrorResponse(Constants.MANDATORY_FIELDS_ARE_NOT_PRESENT_CODE,

                Constants.MANDATORY_FIELDS_ARE_NOT_PRESENT_ERROR_MESSAGE, Calendar.getInstance().getTimeInMillis()));

Spring Data(JPA)配置

应用程序与数据库的所有交互都将由JPA处理 JPA将为应用程序中的所有逻辑对象提供一个Entity类和一个相应的Repository接口

Entity类

?

1

2

3

4

5

6

7

8

9

10

11

12

13

@Entity

@Table (name =  "tbl_users" )

public   class   Users  implements   Serializable {

    private   static   final   long   serialVersionUID = 1L;

 

   @Id

    @GeneratedValue (strategy = GenerationType.IDENTITY)

    @Column (name =  "id" , columnDefinition =  "bigint" )

     private   Long id;

 

   @OneToOne (fetch = FetchType.EAGER)

   @JoinColumn (name =  "user_account_id" , columnDefinition =  "bigint" , nullable =  false )

    private   UserAccounts userAccount;

Repository接口

?

1

2

3

4

5

6

7

8

9

public   interface   UserRepository  extends   JpaRepository<Users, Long> {

 

    /**

      * To find user object using username

      *

     * @param username

     * @return

      */

   Users findByUserAccountUsername(String username);

其他的JPA配置将会在application.properties文件中完成

在application.properties中的JPA数据库配置

?

1

2

3

4

5

6

7

8

9

10

11

spring.jpa.show-sql= false

spring.jpa.hibernate.dialect=org.hibernate.dialect.SQLServer2012Dialect

spring.jpa.hibernate.ddl-auto = update

 

spring.jpa.properties.hibernate.show_sql= false

spring.jpa.properties.hibernate.format_sql= false

spring.jpa.properties.hibernate.use_sql= true

spring.jpa.open-in-view= false

spring.jpa.properties.hibernate.hbm2ddl.auto=update

spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

spring.jpa.hibernate.connection.provider_class=org.hibernate.hikaricp.internal.HikariCPConnectionProvider

数据库配置

数据库名称写在application.properties文件中 其他信息(比如URL连接地址和账号密码)将写到另外两个不同属性文件中

application-dev.properties

此处写开发环境的配置信息

application-pro.properties

此处写生产环境的配置信息

?

1

spring.profiles.active=dev

上述提到的属性配置写到application.properties文件中 它将决定系统使用哪个子配置文件(开发环境还是生产环境)

application.properties

?

1

2

#DB config

spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver

application-dev.properties

?

1

2

3

4

5

6

#DB config

spring.datasource.url=jdbc:sqlserver: //localhost:1433;databaseName=sample_webservice_db_dev

spring.datasource.username=dbuser

spring.datasource.password=ENC(tZTfehMYyz4EO0F0uY8fZItE7K35RtkA)

#spring.datasource.username=dbuser

#spring.datasource.password=dbuserpassword

application-pro.properties

?

1

2

3

4

#DB config

spring.datasource.url=jdbc:sqlserver: //192.168.1.119:1433;databaseName=sample_webservice_db

spring.datasource.username=proUser

spring.datasource.password=ENC(proUserPswd)

数据库密码加密

应用程序数据库密码会使用加密密钥通过__Jasypt __ 库加密。 加密密钥需要添加到系统环境变量中的JASYPT_ENCRYPTOR_PASSWORD变量中。 必须在属性文件中声明加密后的数据库密码。如此,系统就会了解密码需要解密,而解密则需要使用添加在系统变量中的密钥来进行。

?

1

spring.datasource.password=ENC(tZTfehMYyz4EO0F0uY8fZItE7K35RtkA)

对于__Jasypt __加密,我们在属性文件中使用默认的加密配置,如下所示:

?

1

2

jasypt.encryptor.algorithm=PBEWithMD5AndDES

jasypt.encryptor.iv-generator-classname=org.jasypt.iv.NoIvGenerator

我们也可以在应用的main方法中使用@EnableEncryptableProperties注解,规定数据库密码的加密配置

SampleWebservice.java

?

1

2

3

4

5

@SpringBootApplication

@EnableEncryptableProperties

public   class   SampleWebservice  extends   SpringBootServletInitializer {

--------

--------

JWT身份验证配置

使用Spring Security实现基于JSON Web令牌的身份验证 当用户登录成功时,我们会创建两个token(accessToken 和 refreshToken)并把他们返回给客户端 accessToken由私钥,过期时间(1小时),用户ID和角色名生成 refreshToken由私钥,过期时间(24小时),用户ID和角色名生成 登陆成功后,每个API请求都需要在请求头Header中的Authorization键中添加accessToken 在accessToken开头添加"bearer"字符串 即为]bearer accessToken]  accessToken将会监控每一个Web服务请求 如果accessToken过期,系统会以HTTP 401状态码回复请求 此时客户端需要使用refreshToken重新获取accessToken 然后我们会检查refreshToken的有效性,如果没有过期则会生成一个新的accessToken和refreshToken 客户端会继续使用这些新令牌 如果refreshToken也过期了,就需要用户使用账号密码重新登陆了

创建令牌的过程

UnAuthorisedAccessServiceImpl.java

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

@Override

    public   ApiSuccessResponse userLoginService(String username, String password) {

       Tokens tokens =  null ;

        Users user = userService.findByUsername(username);

        if   (user !=  null ) {

            if   (passwordEncryptingService.matches(password,

                    user.getUserAccount().getPassword())) {

                if   (user.getUserAccount().getStatus() == Constants.ACTIVE_STATUS) {

                    String roleName = user.getUserAccount().getUserRole().getRoleName();

                    // Creating new tokens

                   try   {

                        tokens = createTokens(user.getUserAccount().getId().toString(), roleName);

                   }  catch   (Exception exception) {

                         logger.error( "Token creation failed : " , exception);

                       throw   new   UnknownException();

                    }

 

                    // Validating tokens

                   if   (validationService.validateTokens(tokens)) {

                        tokens.setUserId(user.getUserAccount().getId());

                      return   new   ApiSuccessResponse(tokens);

                   }  else   {

                        throw   new   UnknownException();

                   }

               }  else   {

                  return   new   ApiSuccessResponse( new   ApiResponseWithCode(Constants.USER_ACCOUNT_IS_INACTIVE_ERROR_CODE,

                            Constants.USER_ACCOUNT_IS_INACTIVE_ERROR_MESSAGE));

              }

           }  else   {

               return   new   ApiSuccessResponse( new   ApiResponseWithCode(Constants.USERNAME_OR_PASSWORD_IS_INCORRECT_ERROR_CODE,

                        Constants.USERNAME_OR_PASSWORD_IS_INCORRECT_ERROR_MESSAGE));

            }

       }  else   {

            return   new   ApiSuccessResponse( new   ApiResponseWithCode(Constants.USERNAME_OR_PASSWORD_IS_INCORRECT_ERROR_CODE,

                   Constants.USERNAME_OR_PASSWORD_IS_INCORRECT_ERROR_MESSAGE));

        }

     }

     @Override

    public   ApiSuccessResponse createNewAccessTokenUsingRefreshToken(String refreshToken) {

         UserAccounts userAccount =  null ;

        AppConfigSettings configSettings = appConfigSettingsService.findByConfigKeyAndStatus(Constants.JWT_SECRET_KEY,

                Constants.ACTIVE_STATUS);

       // Validate Refresh token

        userAccount = jwtTokenHandler.validate(configSettings.getConfigValue(), refreshToken);

        if   (userAccount !=  null ) {

             // Creating new tokens if provided refresh token is valid

            try   {

                tokens = createTokens(userAccount.getId().toString(), userAccount.getRole());

            }  catch   (Exception exception) {

               logger.error( "Token creation failed : " , exception);

                throw   new   UnknownException();

           if   (validationService.validateTokens(tokens)) {

                tokens.setUserId(userAccount.getId());

               return   new   ApiSuccessResponse(tokens);

            }  else   {

           }

        }  else   {

            return   new   ApiSuccessResponse( new   ApiResponseWithCode(Constants.REFRESH_TOKEN_EXPIRED_ERROR_CODE,

                   Constants.REFRESH_TOKEN_EXPIRED_ERROR_MESSAGE));

       }

    }

上述代码中的userLoginService方法会检查用户凭据,如果有效则颁发令牌。 CreateNewAccessTokenUsingRefreshToken方法会在refreshToken验证成功后,生成新的accessToken和refreshToken。

过滤和验证令牌的过程

WebConfig.java

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

@Configuration

@EnableWebSecurity

@EnableGlobalMethodSecurity (prePostEnabled =  true )

public   class   WebConfig  extends   WebSecurityConfigurerAdapter {

 

    @Autowired

    private   JwtAuthenticationProvider authenticationProvider;

     @Autowired

    private   JwtAuthenticationEntryPoint entryPoint;

     @Bean

     public   AuthenticationManager authenticationManager() {

         return   new   ProviderManager(Collections.singletonList(authenticationProvider));

    }

   public   JwtAuthenticationTokenFilter authenticationTokenFilter() {

       JwtAuthenticationTokenFilter filter =  new   JwtAuthenticationTokenFilter();

         filter.setAuthenticationManager(authenticationManager());

       filter.setAuthenticationSuccessHandler( new   JwtSuccessHandler());

         return   filter;

     @Override

     protected   void   configure(HttpSecurity http)  throws   Exception {

        http.csrf().disable()

                .exceptionHandling().authenticationEntryPoint(entryPoint).and().sessionManagement()

                 .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

              .addFilterBefore( new   WebSecurityCorsFilter(), ChannelProcessingFilter. class )

                .addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter. class )

                 .headers().cacheControl();

     }

}

这个配置将使用@EnableWebSecurity和@EnableGlobalMethodSecurity(prePostEnabled = true)两个注解来启用spring security模块 这里,我们将把JWT过滤器注入到系统的HTTP请求中

JwtAuthenticationTokenFilter.java

?

1

2

3

4

5

6

7

8

9

10

11

12

13

public   class   JwtAuthenticationTokenFilter  extends   AbstractAuthenticationProcessingFilter {

 

    private   final   Logger logger = LoggerFactory.getLogger( this .getClass());

     @Autowired

    private   GeneralServices generalServices;

   public   JwtAuthenticationTokenFilter() {

        super ( "/api/**" );

    }

     @Override

     public   Authentication attemptAuthentication(HttpServletRequest httpServletRequest,

                                                 HttpServletResponse httpServletResponse)  throws   AuthenticationException, IOException, ServletException {

                                                  -------

                                                  --------

在如上的类中,JwtAuthenticationTokenFilter()的方法将过滤所有URL中含有[api]命名关键字的Web服务请求 所有经过过滤的Web服务请求将到达attemptAuthentication方法 我们可以在这个方法里处理所有的业务逻辑

应用用户密码加密

该应用中所有的用户密码都将会使用BCrypt加密

PasswordEncryptingService.java

?

1

2

3

4

5

6

7

8

9

public   class   PasswordEncryptingService {

 

     public   String encode(CharSequence rawPassword) {

         return   BCrypt.hashpw(rawPassword.toString(), BCrypt.gensalt( 6 ));

     }

 

     public   boolean   matches(CharSequence rawPassword, String encodedPassword) {

         return   BCrypt.checkpw(rawPassword.toString(), encodedPassword);

     }

这里,encode方法用来加密密码 matches方法用来交叉检查提供的密码和用户的实际密码

使用Slf4j配置日志

在一个叫logback-spring.xml的文件中配置日志 为了记录每个类的日志,我们需要在相应的类中注入Slf4j

示例

UserServiceImpl.java

?

1

2

3

4

@Service ( "UserService" )

@Scope ( "prototype" )

public   class   UserServiceImpl  implements   UserService {

     private   static   final   Logger logger = LoggerFactory.getLogger(UserServiceImpl. class );

上面的代码片段显示了我们如何将类注入到logger 以下是记录日志的一些基本方法

logger.error("Error");

logger.info("Info");

logger.warn("Warn");

基于Swagger的API文档

API文档在Web服务应用程序中扮演着重要的角色 之前,我们使用静态Excel文档创建API文档 这个库将帮助我们在应用程序中使用注释创建API文档

Pom.xml

?

1

2

3

4

5

6

7

8

9

10

11

   <dependency>

             <groupId>io.springfox</groupId>

             <artifactId>springfox-boot-starter</artifactId>

             <version>${springfox.swagger.version}</version>

         </dependency>

 

         <dependency>

             <groupId>io.springfox</groupId>

             <artifactId>springfox-swagger-ui</artifactId>

             <version>${springfox.swagger.version}</version>

         </dependency>

为了集成Swagger,上述这些是我们要在pom文件中添加的库 我们需要在应用程序中做一些配置来启用API文档

SwaggerAPIDocConfig.java

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

@Configuration

@EnableSwagger2

public   class   SwaggerAPIDocConfig {

 

     public   static   final   Contact DEFAULT_CONTACT =  new   Contact( "Demo" ,  "http://HdhCmsTestdemo.ae/" ,

             "info@demo.ae" );

     public   static   final   ApiInfo DEFAUL_API_INFO =  new   ApiInfo( "Sample Application" ,

             "Sample Application description." ,

             "1.0.0" ,

             "http://HdhCmsTestsampleapplication.ae/" ,

             DEFAULT_CONTACT,  "Open licence" ,

             "http://HdhCmsTestsampleapplication.ae/#license" ,

             new   ArrayList<VendorExtension>());

     private   static   final   Set<String> DEFAULT_PRODICERS_AND_CONSUMERS =

             new   HashSet<>(Arrays.asList( "application/json" ,  "application/xml" ));

     @Bean

     public   Docket api() {

         return   new   Docket(DocumentationType.SWAGGER_2)

                 .apiInfo(DEFAUL_API_INFO)

                 .produces(DEFAULT_PRODICERS_AND_CONSUMERS)

                 .consumes(DEFAULT_PRODICERS_AND_CONSUMERS)

                 .select()

                 .apis(RequestHandlerSelectors.withClassAnnotation(RestController. class ))

                 .paths(PathSelectors.any())

                 .build();

     }

}

正如在上边的类中看到的,我们需要添加关于项目的基本信息 我们需要告诉Swagger从哪个类创建API文档,这是在.apis(RequestHandlerSelectors.withClassAnnotation,(RestController.class))命名行下配置的 我们可以通过http://localhost:8080/sampleWebService/apidoc来访问Swagger的API文档

Postman脚本

我们可以在代码库中找到2个Postman JSON脚本,然后将它们导入到Postman客户端 首先执行登陆的Web服务请求,然后执行其余web服务请求

到此这篇关于使用Spring Boot 2.x构建Web服务的详细代码的文章就介绍到这了,更多相关Spring Boot 2.x构建Web服务内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

原文链接:https://developer.51cto测试数据/article/704232.html

查看更多关于使用Spring Boot 2.x构建Web服务的详细代码的详细内容...

  阅读:17次