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

网友投稿 243 2022-08-19


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

目录架构:库:运行应用的步骤关于项目配置Web服务声明示例通用错误处理示例Spring Data(JPA)配置Entity类Repository接口在application.properties中的JPA数据库配置数据库配置application.propertiesapplication-dev.propertiesapplication-pro.properties数据库密码加密SampleWebservice.javaJWT身份验证配置创建令牌的过程UnAuthorisedAccessServiceImpl.java过滤和验证令牌的过程WebConfig.javaJwtAuthenticationTokenFilter.java应用用户密码加密PasswordEncryptingService.java使用Slf4j配置日志示例UserServiceImpl.java基于Swagger的API文档Pom.xmlSwaggerAPIDocConfig.javaPostman脚本

架构:

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

库:

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

运行应用的步骤

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

关于项目配置

Web服务声明

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

示例

@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 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注解的帮助下执行的。

示例

@ControllerAdvice

publicclassCommonExceptionHandlingControllerextendsResponseEntityExceptionHandler{

privatestaticfinalLoggerlogger=

LoggerFactory.getLogger(CommonExceptionHandlingController.class);

@Override

protectedResponseEntityhandleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedExceptionhttpRequestMethodNotSupportedException,

HttpHeadersheaders,HttpStatusstatus,WebRequestrequest){

returnResponseEntity.status(HttpStatus.NOT_FOUND).body(newApiErrorResponse(Constants.WRONG_HTTP_METHOD,

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

}

protectedResponseEntityhandleMethodArgumentNotValid(MethodArgumentNotValidExceptionmethodArgumentNotValidException,

HttpHeadersheaders,HttpStatusstatus,WebRequestrequest){

returnResponseEntity.status(HttpStatus.BAD_REQUEST).body(newApiErrorResponse(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类

@Entity

@Table(name="tbl_users")

publicclassUsersimplementsSerializable{

privatestaticfinallongserialVersionUID=1L;

@Id

@GeneratedValue(strategy=GenerationType.IDENTITY)

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

privateLongid;

@OneToOne(fetch=FetchType.EAGER)

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

privateUserAccountsuserAccount;

Repository接口

publicinterfaceUserRepositoryextendsJpaRepository{

/**

*Tofinduserobjectusingusername

*

*@paramusername

*@return

*/

UsersfindByUserAccountUsername(Stringusername);

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

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

spring.jpa.show-sql=false

spring.jpa.hibernate.dialect=org.hibernate.dialect.SQLServer2012Diahttp://lect

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

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

spring.profiles.active=dev

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

application.properties

#DBconfig

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

application-dev.properties

#DBconfig

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

#DBconfig

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

spring.datasource.username=proUser

spring.dataUtIsPgssource.password=ENC(proUserPswd)

数据库密码加密

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

spring.datasource.password=ENC(tZTfehMYyz4EO0F0uY8fZItE7K35RtkA)

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

jasypt.encryptor.algorithm=PBEWithMD5AndDES

jasypt.encryptor.iv-generathttp://or-classname=org.jasypt.iv.NoIvGenerator

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

SampleWebservice.java

@SpringBootApplication

@EnableEncryptableProperties

publicclassSampleWebserviceextendsSpringBootServletInitializer{

--------

--------

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

@Override

publicApiSuccessResponseuserLoginService(Stringusername,Stringpassword){

Tokenstokens=null;

Usersuser=userService.findByUsername(username);

if(user!=null){

if(passwordEncryptingService.matches(password,

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

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

StringroleName=user.getUserAccount().getUserRole().getRoleName();

//Creatingnewtokens

try{

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

}catch(Exceptionexception){

logger.error("Tokencreationfailed:",exception);

thrownewUnknownException();

}

//Validatingtokens

if(validationService.validateTokens(tokens)){

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

returnnewApiSuccessResponse(tokens);

}else{

thrownewUnknownException();

}

}else{

returnnewApiSuccessResponse(newApiResponseWithCode(Constants.USER_ACCOUNT_IS_INACTIVE_ERROR_CODE,

Constants.USER_ACCOUNT_IS_INACTIVE_ERROR_MESSAGE));

}

}else{

returnnewApiSuccessResponse(newApiResponseWithCode(Constants.USERNAME_OR_PASSWORD_IS_INCORRECT_ERROR_CODE,

Constants.USERNAME_OR_PASSWORD_IS_INCORRECT_ERROR_MESSAGE));

}

}else{

returnnewApiSuccessResponse(newApiResponseWithCode(Constants.USERNAME_OR_PASSWORD_IS_INCORRECT_ERROR_CODE,

Constants.USERNAME_OR_PASSWORD_IS_INCORRECT_ERROR_MESSAGE));

}

}

@Override

publicApiSuccessResponsecreateNewAccessTokenUsingRefreshToken(StringrefreshToken){

UserAccountsuserAccount=null;

AppConfigSettingsconfigSettings=appConfigSettingsService.findByConfigKeyAndStatus(Constants.JWT_SECRET_KEY,

Constants.ACTIVE_STATUS);

//ValidateRefreshtoken

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

if(userAccount!=null){

//Creatingnewtokensifprovidedrefreshtokenisvalid

try{

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

}catch(Exceptionexception){

logger.error("Tokencreationfailed:",exception);

thrownewUnknownException();

if(validationService.validateTokens(tokens)){

tokens.setUserId(userAccount.getId());

returnnewApiSuccessResponse(tokens);

}else{

}

}else{

returnnewApiSuccessResponse(newApiResponseWithCode(Constants.REFRESH_TOKEN_EXPIRED_ERROR_CODE,

Constants.REFRESH_TOKEN_EXPIRED_ERROR_MESSAGE));

}

}

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

过滤和验证令牌的过程

WebConfig.java

@Configuration

@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled=true)

publicclassWebConfigextendsWebSecurityConfigurerAdapter{

@Autowired

privateJwtAuthenticationProviderauthenticationProvider;

@Autowired

privateJwtAuthenticationEntryPointentryPoint;

@Bean

publicAuthenticationManagerauthenticationManager(){

returnnewProviderManager(Collections.singletonList(authenticationProvider));

}

publicJwtAuthenticationTokenFilterauthenticationTokenFilter(){

JwtAuthenticationTokenFilterfilter=newJwtAuthenticationTokenFilter();

filter.setAuthenticationManager(authenticationManager());

filter.setAuthenticationSuccessHandler(newJwtSuccessHandler());

returnfilter;

@Override

protectedvoidconfigure(HttpSecurityhttp)throwsException{

http.csrf().disable()

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

.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

.addFilterBefore(newWebSecurityCorsFilter(),ChannelProcessingFilter.class)

.addFilterBefore(authenticationTokenFilter(),UsernamePasswordAuthenticationFilter.class)

.headers().cacheControl();

}

}

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

JwtAuthenticationTokenFilter.java

publicclassJwtAuthenticationTokenFilterextendsAbstractAuthenticationProcessingFilter{

privatefinalLoggerlogger=LoggerFactory.getLogger(this.getClass());

@Autowired

privateGeneralServicesgeneralServices;

publicJwtAuthenticationTokenFilter(){

super("/api/**");

}

@Override

publicAuthenticationattemptAuthentication(HttpServletRequesthttpServletRequest,

HttpServletResponsehttpServletResponse)throwsAuthenticationException,IOException,ServletException{

-------

--------

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

应用用户密码加密

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

PasswordEncryptingService.java

publicclassPasswordEncryptingService{

publicStringencode(CharSequencerawPassword){

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

}

publicbooleanmatches(CharSequencerawPassword,StringencodedPassword){

returnBCrypt.checkpw(rawPassword.toString(),encodedPassword);

}

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

使用Slf4j配置日志

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

示例

UserServiceImpl.java

@Service("UserService")

@Scope("prototype")

publicclassUserServiceImplimplementsUserService{

privatestaticfinalLoggerlogger=LoggerFactory.getLogger(UserServiceImpl.class);

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

logger.error("Error");

logger.info("Info");

logger.warn("Warn");

基于Swagger的API文档

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

Pom.xml

io.springfox

springfox-boot-starter

${springfox.swagger.version}

io.springfox

springfox-swagger-ui

&lhttp://t;version>${springfox.swagger.version}

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

SwaggerAPIDocConfig.java

@Configuration

@EnableSwagger2

publicclassSwaggerAPIDocConfig{

publicstaticfinalContactDEFAULT_CONTACT=newContact("Demo","http://demo.ae/",

"info@demo.ae");

publicstaticfinalApiInfoDEFAUL_API_INFO=newApiInfo("SampleApplication",

"SampleApplicationdescription.",

"1.0.0",

"http://sampleapplication.ae/",

DEFAULT_CONTACT,"Openlicence",

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

newArrayList());

privatestaticfinalSetDEFAULT_PRODICERS_AND_CONSUMERS=

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

@Bean

publicDocketapi(){

returnnewDocket(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服务请求


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:剑指Offer之Java算法习题精讲二叉树与链表
下一篇:剑指Offer之Java算法习题精讲链表专题篇
相关文章

 发表评论

暂时没有评论,来抢沙发吧~