使用Spring?Boot?2.x構(gòu)建Web服務(wù)的詳細(xì)代碼
架構(gòu):
- MVC架構(gòu)
- 基于JWT的身份認(rèn)證
- Spring Data (JPA)
- 應(yīng)用用戶密碼加密
- 數(shù)據(jù)庫密碼加密
- SQL Server
- Slf4j
- 基于Swagger的API文檔
庫:
- 應(yīng)用源代碼
- 數(shù)據(jù)庫的SQL腳本以及關(guān)鍵數(shù)據(jù)
- 包含數(shù)據(jù)庫配置信息的DB.txt文件
- 用于測試Web服務(wù)的Postman JSON腳本
運(yùn)行應(yīng)用的步驟
- 安裝JDK11或最新版本
- 克隆項目庫到本地
- Git地址:https://github.com/VishnuViswam/sample-web-service.git
- 安裝SQL server 2012
- 創(chuàng)建應(yīng)用數(shù)據(jù)庫和用戶
- 插入數(shù)據(jù)庫密鑰數(shù)據(jù)
- 將數(shù)據(jù)庫密碼的解碼密鑰添加到系統(tǒng)變量,它位于DB.txt文件中
- 有時可能需要重新啟動窗口以獲取更新后的系統(tǒng)變量
- 運(yùn)行項目源代碼
- 導(dǎo)入預(yù)先提供的postman JSON腳本到postman客戶端,調(diào)用Web服務(wù)
關(guān)于項目配置
Web服務(wù)聲明
應(yīng)用程序的每個Web服務(wù)都將在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<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服務(wù)的類別
- @RestController注解配置該類來接收Restful的 Web服務(wù)調(diào)用
- @PostMapping()注解決定了HTTP請求類型
- consume和consume標(biāo)記來確定HTTP請求和響應(yīng)的內(nèi)容類型
通過controller層,API請求將被帶到服務(wù)層。所有業(yè)務(wù)邏輯都將在這里處理,然后它將使用JPA與數(shù)據(jù)庫通信。
通用錯誤處理
每當(dāng)異常發(fā)生時,它將從相應(yīng)的類拋出,并在CommonExceptionHandlingController中處理。我們必須分別處理每種異常類型。這個功能是在ControllerAdvice注解的幫助下執(zhí)行的。
示例
@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)配置
- 應(yīng)用程序與數(shù)據(jù)庫的所有交互都將由JPA處理
- JPA將為應(yīng)用程序中的所有邏輯對象提供一個Entity類和一個相應(yīng)的Repository接口
Entity類
@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接口
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數(shù)據(jù)庫配置
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
數(shù)據(jù)庫配置
- 數(shù)據(jù)庫名稱寫在application.properties文件中
- 其他信息(比如URL連接地址和賬號密碼)將寫到另外兩個不同屬性文件中
application-dev.properties
此處寫開發(fā)環(huán)境的配置信息
application-pro.properties
此處寫生產(chǎn)環(huán)境的配置信息
spring.profiles.active=dev
- 上述提到的屬性配置寫到application.properties文件中
- 它將決定系統(tǒng)使用哪個子配置文件(開發(fā)環(huán)境還是生產(chǎn)環(huán)境)
application.properties
#DB?config spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
application-dev.properties
#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
#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)
數(shù)據(jù)庫密碼加密
- 應(yīng)用程序數(shù)據(jù)庫密碼會使用加密密鑰通過__Jasypt __ 庫加密。
- 加密密鑰需要添加到系統(tǒng)環(huán)境變量中的JASYPT_ENCRYPTOR_PASSWORD變量中。
- 必須在屬性文件中聲明加密后的數(shù)據(jù)庫密碼。如此,系統(tǒng)就會了解密碼需要解密,而解密則需要使用添加在系統(tǒng)變量中的密鑰來進(jìn)行。
spring.datasource.password=ENC(tZTfehMYyz4EO0F0uY8fZItE7K35RtkA)
- 對于__Jasypt __加密,我們在屬性文件中使用默認(rèn)的加密配置,如下所示:
jasypt.encryptor.algorithm=PBEWithMD5AndDES jasypt.encryptor.iv-generator-classname=org.jasypt.iv.NoIvGenerator
- 我們也可以在應(yīng)用的main方法中使用@EnableEncryptableProperties注解,規(guī)定數(shù)據(jù)庫密碼的加密配置
SampleWebservice.java
@SpringBootApplication @EnableEncryptableProperties public?class?SampleWebservice?extends?SpringBootServletInitializer?{ -------- --------
JWT身份驗證配置
- 使用Spring Security實(shí)現(xiàn)基于JSON Web令牌的身份驗證
- 當(dāng)用戶登錄成功時,我們會創(chuàng)建兩個token(accessToken 和 refreshToken)并把他們返回給客戶端
- accessToken由私鑰,過期時間(1小時),用戶ID和角色名生成
- refreshToken由私鑰,過期時間(24小時),用戶ID和角色名生成
- 登陸成功后,每個API請求都需要在請求頭Header中的Authorization鍵中添加accessToken
- 在accessToken開頭添加"bearer"字符串
- 即為”bearer accessToken”
- accessToken將會監(jiān)控每一個Web服務(wù)請求
- 如果accessToken過期,系統(tǒng)會以HTTP 401狀態(tài)碼回復(fù)請求
- 此時客戶端需要使用refreshToken重新獲取accessToken
- 然后我們會檢查refreshToken的有效性,如果沒有過期則會生成一個新的accessToken和refreshToken
- 客戶端會繼續(xù)使用這些新令牌
- 如果refreshToken也過期了,就需要用戶使用賬號密碼重新登陸了
創(chuàng)建令牌的過程
UnAuthorisedAccessServiceImpl.java
@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方法會檢查用戶憑據(jù),如果有效則頒發(fā)令牌。
- CreateNewAccessTokenUsingRefreshToken方法會在refreshToken驗證成功后,生成新的accessToken和refreshToken。
過濾和驗證令牌的過程
WebConfig.java
@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過濾器注入到系統(tǒng)的HTTP請求中
JwtAuthenticationTokenFilter.java
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”命名關(guān)鍵字的Web服務(wù)請求
- 所有經(jīng)過過濾的Web服務(wù)請求將到達(dá)attemptAuthentication方法
- 我們可以在這個方法里處理所有的業(yè)務(wù)邏輯
應(yīng)用用戶密碼加密
- 該應(yīng)用中所有的用戶密碼都將會使用BCrypt加密
PasswordEncryptingService.java
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方法用來交叉檢查提供的密碼和用戶的實(shí)際密碼
使用Slf4j配置日志
- 在一個叫l(wèi)ogback-spring.xml的文件中配置日志
- 為了記錄每個類的日志,我們需要在相應(yīng)的類中注入Slf4j
示例
UserServiceImpl.java
@Service("UserService") @Scope("prototype") public?class?UserServiceImpl?implements?UserService?{ ????private?static?final?Logger?logger?=?LoggerFactory.getLogger(UserServiceImpl.class);
- 上面的代碼片段顯示了我們?nèi)绾螌㈩愖⑷氲絣ogger
- 以下是記錄日志的一些基本方法
logger.error("Error");
logger.info("Info");
logger.warn("Warn");
基于Swagger的API文檔
- API文檔在Web服務(wù)應(yīng)用程序中扮演著重要的角色
- 之前,我們使用靜態(tài)Excel文檔創(chuàng)建API文檔
- 這個庫將幫助我們在應(yīng)用程序中使用注釋創(chuàng)建API文檔
Pom.xml
??<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文件中添加的庫
- 我們需要在應(yīng)用程序中做一些配置來啟用API文檔
SwaggerAPIDocConfig.java
@Configuration @EnableSwagger2 public?class?SwaggerAPIDocConfig?{ ????public?static?final?Contact?DEFAULT_CONTACT?=?new?Contact("Demo",?"http://www.demo.ae/", ????????????"info@demo.ae"); ????public?static?final?ApiInfo?DEFAUL_API_INFO?=?new?ApiInfo("Sample?Application", ????????????"Sample?Application?description.", ????????????"1.0.0", ????????????"http://www.sampleapplication.ae/", ????????????DEFAULT_CONTACT,?"Open?licence", ????????????"http://www.sampleapplication.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(); ????} }
- 正如在上邊的類中看到的,我們需要添加關(guān)于項目的基本信息
- 我們需要告訴Swagger從哪個類創(chuàng)建API文檔,這是在.apis(RequestHandlerSelectors.withClassAnnotation,(RestController.class))命名行下配置的
- 我們可以通過http://localhost:8080/sampleWebService/apidoc來訪問Swagger的API文檔
Postman腳本
- 我們可以在代碼庫中找到2個Postman JSON腳本,然后將它們導(dǎo)入到Postman客戶端
- 首先執(zhí)行登陸的Web服務(wù)請求,然后執(zhí)行其余web服務(wù)請求
到此這篇關(guān)于使用Spring Boot 2.x構(gòu)建Web服務(wù)的詳細(xì)代碼的文章就介紹到這了,更多相關(guān)Spring Boot 2.x構(gòu)建Web服務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot整合Spring Cache及Redis過程解析
這篇文章主要介紹了Spring Boot整合Spring Cache及Redis過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-12-12logback標(biāo)記日志過濾器MarkerFilter源碼解讀
這篇文章主要為大家介紹了logback標(biāo)記日志過濾器MarkerFilter源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11Java17中record替代Lombok部分功能使用場景探究
這篇文章主要介紹了使用Java17中的record替代Lombok的部分功能,本文來為大家小小的總結(jié)下,我們可以在哪些地方,利用record來替換Lombok2024-01-01Java多線程并發(fā)編程 Volatile關(guān)鍵字
volatile 關(guān)鍵字是一個神秘的關(guān)鍵字,也許在 J2EE 上的 JAVA 程序員會了解多一點(diǎn),但在 Android 上的 JAVA 程序員大多不了解這個關(guān)鍵字。只要稍了解不當(dāng)就好容易導(dǎo)致一些并發(fā)上的錯誤發(fā)生,例如好多人把 volatile 理解成變量的鎖2017-05-05