Spring?Security實(shí)現(xiàn)HTTP認(rèn)證
Spring Security是一個(gè)能夠?yàn)榛赟pring的企業(yè)應(yīng)用系統(tǒng)提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應(yīng)用上下文中配置的Bean,充分利用了Spring IoC(控制反轉(zhuǎn)),DI(依賴注入)和AOP(面向切面編程)功能,為應(yīng)用系統(tǒng)提供聲明式的安全訪問控制功能,減少了為企業(yè)系統(tǒng)安全控制編寫大量重復(fù)代碼的工作。
前言
除系統(tǒng)內(nèi)維護(hù)的用戶名和密碼認(rèn)證技術(shù)外,Spring Security還支持HTTP層面的認(rèn)證,包括HTTP基本認(rèn)證和HTTP摘要認(rèn)證
一、HTTP基本認(rèn)證是什么?
HTTP基本認(rèn)證是在RFC2616中定義的一種認(rèn)證模式。
二、HTTP基本認(rèn)證流程
- 客戶端發(fā)起一條沒有攜帶認(rèn)證信息的請(qǐng)求。
- 服務(wù)器返回一條401 Unauthorized響應(yīng), 并在WWW-Authentication首部說明認(rèn)證形式, 當(dāng)進(jìn)行HTTP基本認(rèn)證時(shí), WWW-Authentication會(huì)被設(shè)置為Basic realm=“被保護(hù)頁(yè)面”。
- 客戶端收到401 Unauthorized 響應(yīng)后, 彈出對(duì)話框, 詢問用戶名和密碼。 當(dāng)用戶完成后, 客戶端將用戶名和密碼使用冒號(hào)拼接并編碼為Base64形式, 然后放入請(qǐng)求的Authorization首部發(fā)送給服務(wù)器。
- 服務(wù)器解碼得到客戶端發(fā)來的用戶名和密碼,并在驗(yàn)證它們是正確的之后,返回客戶端請(qǐng)求的報(bào)文

有上面可以看出只需要驗(yàn)證Authentication即可,因此如果不使用瀏覽器訪問HTTP基本認(rèn)證保護(hù)的頁(yè)面,則自行在請(qǐng)求頭中設(shè)置Authorization也是可以.
HTTP基本認(rèn)證是一種無狀態(tài)的認(rèn)證方式,與表單認(rèn)證相比,HTTP基本認(rèn)證是一種基于HTTP層面的認(rèn)證方式,無法攜帶session,即無法實(shí)現(xiàn)Remember-ME功能。另外,用戶名和密碼在傳遞時(shí)僅做一次簡(jiǎn)單的Base64編碼,幾乎等同于明文傳輸,極易出現(xiàn)密碼被竊聽和重放攻擊等安全性問題,在實(shí)際系統(tǒng)開發(fā)中很少使用這種方式來進(jìn)行安全驗(yàn)證。 如果有必要,也應(yīng)使用加密的傳輸層HTTPS來保障安全.
一.Spring Security使用HTTP基本認(rèn)證
1.創(chuàng)建項(xiàng)目spring-security-http-auth
pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.創(chuàng)建配置文件WebSecurityConfig
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
}
上面的配置最后添加了httpBasic(),使用http基本認(rèn)證
3.運(yùn)行項(xiàng)目
訪問本地項(xiàng)目,http://localhost:8080

會(huì)彈出登陸框,我們看到調(diào)試工具中返回了401無權(quán)限。

我們使用Spring Security提供的默認(rèn)的用戶名和密碼登陸。

登陸成功后,header中就會(huì)有Authorization: Basic dXNlcjo0NWU2NzViOC1hZGYwLTQzNzMtYjA2MS02MGE0YzkzZjA2ZGU=
二.Spring Security HTTP基本認(rèn)證原理
上面我們實(shí)現(xiàn)了HTTP基本認(rèn)證,我們看看其中Spring Security中是如何做到的?
我們使用HTTP基本認(rèn)證的時(shí)候,在配置類中使用httpBasic()進(jìn)行處理。
httpBasic方法:
public HttpBasicConfigurer<HttpSecurity> httpBasic() throws Exception {
return (HttpBasicConfigurer)this.getOrApply(new HttpBasicConfigurer());
}
上面可以看出,Spring Security進(jìn)行HTTP基本認(rèn)證是使用HttpBasicConfigurer配置類進(jìn)行的。HttpBasicConfigurer.class:
//構(gòu)建HttpBasicConfigurer
public HttpBasicConfigurer() {
this.realmName("Realm");
LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> entryPoints = new LinkedHashMap();
entryPoints.put(X_REQUESTED_WITH, new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
DelegatingAuthenticationEntryPoint defaultEntryPoint = new DelegatingAuthenticationEntryPoint(entryPoints);
defaultEntryPoint.setDefaultEntryPoint(this.basicAuthEntryPoint);
this.authenticationEntryPoint = defaultEntryPoint;
}
//進(jìn)行配置
public void configure(B http) {
//進(jìn)行認(rèn)證管理
AuthenticationManager authenticationManager = (AuthenticationManager)http.getSharedObject(AuthenticationManager.class);
//聲明basic認(rèn)證攔截器
BasicAuthenticationFilter basicAuthenticationFilter = new BasicAuthenticationFilter(authenticationManager, this.authenticationEntryPoint);
if (this.authenticationDetailsSource != null) {
basicAuthenticationFilter.setAuthenticationDetailsSource(this.authenticationDetailsSource);
}
//注冊(cè)一個(gè)RememberMeServices
RememberMeServices rememberMeServices = (RememberMeServices)http.getSharedObject(RememberMeServices.class);
if (rememberMeServices != null) {
//設(shè)置rememberMeServices
basicAuthenticationFilter.setRememberMeServices(rememberMeServices);
}
//申明basicAuthenticationFilter過濾器
basicAuthenticationFilter = (BasicAuthenticationFilter)this.postProcess(basicAuthenticationFilter);
http.addFilter(basicAuthenticationFilter);
}
上面聲明BasicAuthenticationFilter并添加到攔截器鏈中BasicAuthenticationFilter.class:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
//獲取token
UsernamePasswordAuthenticationToken authRequest = this.authenticationConverter.convert(request);
//authRequest為空直接放行
if (authRequest == null) {
this.logger.trace("Did not process authentication request since failed to find username and password in Basic Authorization header");
chain.doFilter(request, response);
return;
}
//獲取用戶名
String username = authRequest.getName();
this.logger.trace(LogMessage.format("Found username '%s' in Basic Authorization header", username));
if (this.authenticationIsRequired(username)) {
Authentication authResult = this.authenticationManager.authenticate(authRequest);
//創(chuàng)建上下文
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authResult);
//設(shè)置響應(yīng)的上下文
SecurityContextHolder.setContext(context);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
}
this.rememberMeServices.loginSuccess(request, response, authResult);
this.onSuccessfulAuthentication(request, response, authResult);
}
} catch (AuthenticationException var8) {
SecurityContextHolder.clearContext();
this.logger.debug("Failed to process authentication request", var8);
this.rememberMeServices.loginFail(request, response);
this.onUnsuccessfulAuthentication(request, response, var8);
if (this.ignoreFailure) {
chain.doFilter(request, response);
} else {
this.authenticationEntryPoint.commence(request, response, var8);
}
return;
}
chain.doFilter(request, response);
}
BasicAuthenticationEntryPoint返回進(jìn)行響應(yīng)的處理
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
//添加響應(yīng)響應(yīng)頭
response.addHeader("WWW-Authenticate", "Basic realm=\"" + this.realmName + "\"");
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
}
三.HTTP摘要認(rèn)證是什么?
HTTP摘要認(rèn)證和HTTP基本認(rèn)證一樣,也是在RFC2616中定義的認(rèn)證模式,RFC2617專門對(duì)這兩種認(rèn)證模式做了規(guī)定。與 HTTP 基本認(rèn)證相比,HTTP 摘要認(rèn)證使用對(duì)通信雙方都可知的口令進(jìn)行校驗(yàn),且最終的傳輸數(shù)據(jù)并非明文形式。
摘要認(rèn)證是一種協(xié)議規(guī)定的Web服務(wù)器用來同網(wǎng)頁(yè)瀏覽器進(jìn)行認(rèn)證信息協(xié)商的方法。它在密碼發(fā)出前,先對(duì)其應(yīng)用哈希函數(shù),這相對(duì)于HTTP基本認(rèn)證發(fā)送明文而言,更安全。
從技術(shù)上講,摘要認(rèn)證是使用隨機(jī)數(shù)來阻止進(jìn)行密碼分析的MD5加密哈希函數(shù)應(yīng)用。
HTTP摘要認(rèn)證流程:

HTTP摘要認(rèn)證中的相關(guān)參數(shù):
- username: 用戶名。
- password: 用戶密碼。
- realm: 認(rèn)證域, 由服務(wù)器返回。
- opaque: 透?jìng)髯址?客戶端應(yīng)原樣返回。
- method: 請(qǐng)求的方法。
- nonce: 由服務(wù)器生成的隨機(jī)字符串。
- nc: 即nonce-count, 指請(qǐng)求的次數(shù), 用于計(jì)數(shù), 防止重放攻擊。 qop被指定時(shí), nc也必須被指定。
- cnonce: 客戶端發(fā)給服務(wù)器的隨機(jī)字符串, qop被指定時(shí), cnonce也必須被指定。
- qop: 保護(hù)級(jí)別, 客戶端根據(jù)此參數(shù)指定摘要算法。 若取值為auth, 則只進(jìn)行身份驗(yàn)證; 若取
- 值為auth-int, 則還需要校驗(yàn)內(nèi)容完整性。
- uri: 請(qǐng)求的uri。
- response:客戶端根據(jù)算法算出的摘要值。
- algorithm:摘要算法, 目前僅支持MD5。
- entity-body:頁(yè)面實(shí)體,非消息實(shí)體,僅在auth-int中支持。
- 通常服務(wù)器攜帶的數(shù)據(jù)包括realm、 opaque、 nonce、 qop等字段, 如果客戶端需要做出驗(yàn)證回應(yīng),就必須按照一定的算法計(jì)算得到一些新的數(shù)據(jù)并一起返回。
四.Spring Security使用HTTP摘要認(rèn)證流程?
在Spring Security中沒有像HTTP基礎(chǔ)認(rèn)證那樣,通過httpBasic()方法進(jìn)行集成HTTP摘要認(rèn)證,但是Spring Security提供了像BasicAuthenticationEntryPoint一樣的DigestAuthenticationEntryPoint.就是我們需要將DigestAuthenticationEntryPoint添加到filter過濾器中去處理。
代碼如下:WebSecurityConfig類:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DigestAuthenticationEntryPoint digestAuthenticationEntryPoint;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and()
.exceptionHandling()
.authenticationEntryPoint(digestAuthenticationEntryPoint)
.and().addFilter(digestAuthenticationFilter());
}
public DigestAuthenticationFilter digestAuthenticationFilter(){
DigestAuthenticationFilter digestAuthenticationFilter = new DigestAuthenticationFilter();
digestAuthenticationFilter.setUserDetailsService(userDetailsService);
digestAuthenticationFilter.setAuthenticationEntryPoint(digestAuthenticationEntryPoint);
return digestAuthenticationFilter;
}
}
申明DigestAuthenticationEntryPointBean:
@Bean
public DigestAuthenticationEntryPoint digestAuthenticationEntryPoint(){
DigestAuthenticationEntryPoint digestAuthenticationEntryPoint = new DigestAuthenticationEntryPoint();
digestAuthenticationEntryPoint.setRealmName("realName");
digestAuthenticationEntryPoint.setKey("tony");
return digestAuthenticationEntryPoint;
}
@Bean
public DigestAuthenticationEntryPoint digestAuthenticationEntryPoint(){
DigestAuthenticationEntryPoint digestAuthenticationEntryPoint = new DigestAuthenticationEntryPoint();
digestAuthenticationEntryPoint.setRealmName("realm");
digestAuthenticationEntryPoint.setKey("tony");
return digestAuthenticationEntryPoint;
}
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("tony").password("123456").roles("admin").build());
return manager;
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
運(yùn)行項(xiàng)目
訪問主頁(yè),http://localhost:8080,返回如下頁(yè)面:

我們輸入用戶名和密碼登陸。

當(dāng)長(zhǎng)時(shí)間未登錄,隨機(jī)字符串到期了也登陸不上。
默認(rèn)的過期時(shí)間為300s,我們可以通過設(shè)置時(shí)間。DigestAuthenticationEntryPoint中realmName和key是必須要設(shè)置的。
相關(guān)源碼:
public void afterPropertiesSet() {
Assert.hasLength(this.realmName, "realmName must be specified");
Assert.hasLength(this.key, "key must be specified");
}
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
//計(jì)算過期時(shí)間
long expiryTime = System.currentTimeMillis() + (long)(this.nonceValiditySeconds * 1000);
//計(jì)算簽名值
String signatureValue = DigestAuthUtils.md5Hex(expiryTime + ":" + this.key);
//隨機(jī)字符串
String nonceValue = expiryTime + ":" + signatureValue;
//隨機(jī)字符串base64
String nonceValueBase64 = new String(Base64.getEncoder().encode(nonceValue.getBytes()));
String authenticateHeader = "Digest realm=\"" + this.realmName + "\", qop=\"auth\", nonce=\"" + nonceValueBase64 + "\"";
if (authException instanceof NonceExpiredException) {
authenticateHeader = authenticateHeader + ", stale=\"true\"";
}
logger.debug(LogMessage.format("WWW-Authenticate header sent to user agent: %s", authenticateHeader));
response.addHeader("WWW-Authenticate", authenticateHeader);
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
}
進(jìn)行處理的時(shí)候使用DigestAuthenticationFilter進(jìn)行處理
public void afterPropertiesSet() {
//必須設(shè)置userDetailsService
Assert.notNull(this.userDetailsService, "A UserDetailsService is required");
//必須設(shè)置authenticationEntryPoint
Assert.notNull(this.authenticationEntryPoint, "A DigestAuthenticationEntryPoint is required");
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Digest ")) {
logger.debug(LogMessage.format("Digest Authorization header received from user agent: %s", header));
DigestAuthenticationFilter.DigestData digestAuth = new DigestAuthenticationFilter.DigestData(header);
try {
//驗(yàn)證并且解密
digestAuth.validateAndDecode(this.authenticationEntryPoint.getKey(), this.authenticationEntryPoint.getRealmName());
} catch (BadCredentialsException var11) {
this.fail(request, response, var11);
return;
}
//緩存
boolean cacheWasUsed = true;
//緩存用戶數(shù)據(jù)
UserDetails user = this.userCache.getUserFromCache(digestAuth.getUsername());
String serverDigestMd5;
try {
if (user == null) {
cacheWasUsed = false;
user = this.userDetailsService.loadUserByUsername(digestAuth.getUsername());
if (user == null) {
throw new AuthenticationServiceException("AuthenticationDao returned null, which is an interface contract violation");
}
this.userCache.putUserInCache(user);
}
//服務(wù)器md5摘要
serverDigestMd5 = digestAuth.calculateServerDigest(user.getPassword(), request.getMethod());
if (!serverDigestMd5.equals(digestAuth.getResponse()) && cacheWasUsed) {
logger.debug("Digest comparison failure; trying to refresh user from DAO in case password had changed");
user = this.userDetailsService.loadUserByUsername(digestAuth.getUsername());
this.userCache.putUserInCache(user);
serverDigestMd5 = digestAuth.calculateServerDigest(user.getPassword(), request.getMethod());
}
} catch (UsernameNotFoundException var12) {
String message = this.messages.getMessage("DigestAuthenticationFilter.usernameNotFound", new Object[]{digestAuth.getUsername()}, "Username {0} not found");
this.fail(request, response, new BadCredentialsException(message));
return;
}
String message;
if (!serverDigestMd5.equals(digestAuth.getResponse())) {
logger.debug(LogMessage.format("Expected response: '%s' but received: '%s'; is AuthenticationDao returning clear text passwords?", serverDigestMd5, digestAuth.getResponse()));
message = this.messages.getMessage("DigestAuthenticationFilter.incorrectResponse", "Incorrect response");
this.fail(request, response, new BadCredentialsException(message));
} else if (digestAuth.isNonceExpired()) {
message = this.messages.getMessage("DigestAuthenticationFilter.nonceExpired", "Nonce has expired/timed out");
this.fail(request, response, new NonceExpiredException(message));
} else {
logger.debug(LogMessage.format("Authentication success for user: '%s' with response: '%s'", digestAuth.getUsername(), digestAuth.getResponse()));
Authentication authentication = this.createSuccessfulAuthentication(request, user);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
chain.doFilter(request, response);
}
} else {
chain.doFilter(request, response);
}
}
DigestData為摘要數(shù)據(jù):
private class DigestData {
//用戶名
private final String username;
//認(rèn)證域
private final String realm;
//隨機(jī)字符串
private final String nonce;
private final String uri;
private final String response;
//保護(hù)級(jí)別
private final String qop;
//即nonce-count, 指請(qǐng)求的次數(shù), 用于計(jì)數(shù), 防止重放攻擊
private final String nc;
private final String cnonce;
private final String section212response;
private long nonceExpiryTime;
DigestData(String header) {
this.section212response = header.substring(7);
String[] headerEntries = DigestAuthUtils.splitIgnoringQuotes(this.section212response, ',');
Map<String, String> headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
this.username = (String)headerMap.get("username");
this.realm = (String)headerMap.get("realm");
this.nonce = (String)headerMap.get("nonce");
this.uri = (String)headerMap.get("uri");
this.response = (String)headerMap.get("response");
this.qop = (String)headerMap.get("qop");
this.nc = (String)headerMap.get("nc");
this.cnonce = (String)headerMap.get("cnonce");
DigestAuthenticationFilter.logger.debug(LogMessage.format("Extracted username: '%s'; realm: '%s'; nonce: '%s'; uri: '%s'; response: '%s'", new Object[]{this.username, this.realm, this.nonce, this.uri, this.response}));
}
//驗(yàn)證和解密
void validateAndDecode(String entryPointKey, String expectedRealm) throws BadCredentialsException {
if (this.username != null && this.realm != null && this.nonce != null && this.uri != null && this.response != null) {
if ("auth".equals(this.qop) && (this.nc == null || this.cnonce == null)) {
DigestAuthenticationFilter.logger.debug(LogMessage.format("extracted nc: '%s'; cnonce: '%s'", this.nc, this.cnonce));
throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.missingAuth", new Object[]{this.section212response}, "Missing mandatory digest value; received header {0}"));
} else if (!expectedRealm.equals(this.realm)) {
throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.incorrectRealm", new Object[]{this.realm, expectedRealm}, "Response realm name '{0}' does not match system realm name of '{1}'"));
} else {
byte[] nonceBytes;
try {
nonceBytes = Base64.getDecoder().decode(this.nonce.getBytes());
} catch (IllegalArgumentException var8) {
throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.nonceEncoding", new Object[]{this.nonce}, "Nonce is not encoded in Base64; received nonce {0}"));
}
String nonceAsPlainText = new String(nonceBytes);
String[] nonceTokens = StringUtils.delimitedListToStringArray(nonceAsPlainText, ":");
if (nonceTokens.length != 2) {
throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.nonceNotTwoTokens", new Object[]{nonceAsPlainText}, "Nonce should have yielded two tokens but was {0}"));
} else {
try {
this.nonceExpiryTime = new Long(nonceTokens[0]);
} catch (NumberFormatException var7) {
throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.nonceNotNumeric", new Object[]{nonceAsPlainText}, "Nonce token should have yielded a numeric first token, but was {0}"));
}
String expectedNonceSignature = DigestAuthUtils.md5Hex(this.nonceExpiryTime + ":" + entryPointKey);
if (!expectedNonceSignature.equals(nonceTokens[1])) {
throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.nonceCompromised", new Object[]{nonceAsPlainText}, "Nonce token compromised {0}"));
}
}
}
} else {
throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.missingMandatory", new Object[]{this.section212response}, "Missing mandatory digest value; received header {0}"));
}
}
//計(jì)算服務(wù)摘要
String calculateServerDigest(String password, String httpMethod) {
//生產(chǎn)摘要
return DigestAuthUtils.generateDigest(DigestAuthenticationFilter.this.passwordAlreadyEncoded, this.username, this.realm, password, httpMethod, this.uri, this.qop, this.nonce, this.nc, this.cnonce);
}
//判斷隨機(jī)數(shù)是否到期
boolean isNonceExpired() {
long now = System.currentTimeMillis();
return this.nonceExpiryTime < now;
}
String getUsername() {
return this.username;
}
String getResponse() {
return this.response;
}
}
到此這篇關(guān)于Spring Security實(shí)現(xiàn)HTTP認(rèn)證的文章就介紹到這了,更多相關(guān)Spring Security HTTP認(rèn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis控制臺(tái)打印SQL語句的兩種方式實(shí)現(xiàn)
這篇文章主要介紹了Mybatis控制臺(tái)打印SQL語句的兩種方式實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
利用Jackson解析JSON的詳細(xì)實(shí)現(xiàn)教程
JSON對(duì)于開發(fā)者并不陌生,如今的WEB服務(wù)等都是以JSON作為數(shù)據(jù)交換的格式。學(xué)習(xí)JSON格式的操作工具對(duì)開發(fā)者來說是必不可少的。本文將介紹如何使用Jackson開源工具庫(kù)對(duì)JSON進(jìn)行常見操作,需要的可以參考一下2022-07-07
Java的StringBuilder在高性能場(chǎng)景下的正確用法
StringBuilder?對(duì)字符串的操作是直接改變字符串對(duì)象本身,而不是生成新的對(duì)象,所以新能開銷小.與StringBuffer相比StringBuilder的性能略高,StringBuilder則沒有保證線程的安全,從而性能略高于StringBuffer,需要的朋友可以參考下2023-05-05
Java架構(gòu)設(shè)計(jì)之六步拆解 DDD
DDD(Domain-Driven Design 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì))是由Eric Evans最先提出,目的是對(duì)軟件所涉及到的領(lǐng)域進(jìn)行建模,以應(yīng)對(duì)系統(tǒng)規(guī)模過大時(shí)引起的軟件復(fù)雜性的問題2022-02-02
詳解SpringBoot修改啟動(dòng)端口server.port的四種方式
這篇文章主要介紹了詳解SpringBoot修改啟動(dòng)端口server.port的四種方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
spring boot項(xiàng)目application.properties文件存放及使用介紹
這篇文章主要介紹了spring boot項(xiàng)目application.properties文件存放及使用介紹,我們的application.properties文件中會(huì)有很多敏感信息,大家在使用過程中要多加小心2021-06-06

