springboot集成shiro自定義登陸過(guò)濾器方法
前言
在上一篇博客springboot集成shiro權(quán)限管理簡(jiǎn)單實(shí)現(xiàn)中,用戶在登錄的過(guò)程中,有以下幾個(gè)問(wèn)題:
- 用戶在沒(méi)有登陸的情況下,訪問(wèn)需要權(quán)限的接口,服務(wù)器自動(dòng)跳轉(zhuǎn)到登陸頁(yè)面,前端無(wú)法控制;
- 用戶在登錄成功后,服務(wù)器自動(dòng)跳轉(zhuǎn)到成功頁(yè),前端無(wú)法控制;
- 用戶在登錄失敗后,服務(wù)器自動(dòng)刷新登錄頁(yè)面,前端無(wú)法控制;
很顯然,這樣的交互方式,用戶體驗(yàn)上不是很好,并且在某些程度上也無(wú)法滿足業(yè)務(wù)上的要求。所以,我們要對(duì)默認(rèn)的FormAuthenticationFilter進(jìn)行覆蓋,實(shí)現(xiàn)我們自定義的Filter來(lái)解決用戶交互的問(wèn)題。
自定義UsernamePasswordAuthenticationFilter
首先我們需要繼承原先的FormAuthenticationFilter
之所以繼承這個(gè)FormAuthenticationFilter,有以下幾點(diǎn)原因:
1.FormAuthenticationFilter是默認(rèn)攔截登錄功能的過(guò)濾器,我們本身就是要改造登錄功能,所以繼承它很正常;
2.我們自定義的Filter需要復(fù)用里面的邏輯;
public class UsernamePasswordAuthenticationFilter extends FormAuthenticationFilter{}
其次,為了解決第一個(gè)問(wèn)題,我們需要重寫saveRequestAndRedirectToLogin方法
/** * 沒(méi)有登陸的情況下,訪問(wèn)需要權(quán)限的接口,需要引導(dǎo)用戶登陸 * * @param request * @param response * @throws IOException */ @Override protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException { // 保存當(dāng)前請(qǐng)求,以便后續(xù)登陸成功后重新請(qǐng)求 this.saveRequest(request); // 1. 服務(wù)端直接跳轉(zhuǎn) // ? - 服務(wù)端重定向登陸頁(yè)面 if (autoRedirectToLogin) { ?this.redirectToLogin(request, response); } else { ?// 2. json模式 ?// ? - json數(shù)據(jù)格式告知前端需要跳轉(zhuǎn)到登陸頁(yè)面,前端根據(jù)指令跳轉(zhuǎn)登陸頁(yè)面 ?HttpServletRequest req = (HttpServletRequest) request; ?HttpServletResponse res = (HttpServletResponse) response; ?Map<String, String> metaInfo = new HashMap<>(); ?// 告知前端需要跳轉(zhuǎn)的登陸頁(yè)面 ?metaInfo.put("loginUrl", getLoginUrl()); ?// 告知前端當(dāng)前請(qǐng)求的url;這個(gè)信息也可以保存在前端 ?metaInfo.put("currentRequest", req.getRequestURL().toString()); ?ResultWrap.failure(802, "請(qǐng)登陸后再操作!", metaInfo) .writeToResponse(res); } }
在這個(gè)方法中,我們通過(guò)配置autoRedirectToLogin參數(shù)的方式,既保留了原來(lái)服務(wù)器自動(dòng)跳轉(zhuǎn)的功能,又增強(qiáng)了服務(wù)器返回json給前端,讓前端根據(jù)返回結(jié)果跳轉(zhuǎn)到登陸頁(yè)面的功能。這樣就增強(qiáng)了應(yīng)用程序的可控性和靈活性了。
重寫登陸成功的處理方法onLoginSuccess:
@Override protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { // 查詢當(dāng)前用戶自定義的登陸成功需要跳轉(zhuǎn)的頁(yè)面,可以更加靈活控制用戶頁(yè)面跳轉(zhuǎn) String successUrl = loginSuccessPageFetch.successUrl(token, subject); // 如果沒(méi)有自定義的成功頁(yè)面,那么跳轉(zhuǎn)默認(rèn)成功頁(yè) if (StringUtils.isEmpty(successUrl)) { ?successUrl = this.getSuccessUrl(); } if (loginSuccessAutoRedirect) { ?// 服務(wù)端直接重定向到目標(biāo)頁(yè)面 ?WebUtils.redirectToSavedRequest(request, response, successUrl); } else { ?SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(request); ?if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase("GET")) { ? ?successUrl = savedRequest.getRequestUrl(); } ?// 返回json數(shù)據(jù)格式告知前端跳轉(zhuǎn)目標(biāo)頁(yè)面 ?HttpServletResponse res = (HttpServletResponse) response; ?Map<String, String> data = new HashMap<>(); ?// 登陸成功后跳轉(zhuǎn)的目標(biāo)頁(yè)面 ?data.put("successUrl", successUrl); ?ResultWrap.success(data).writeToResponse(res); } return false; }
1.登陸成功后,我們內(nèi)置了一個(gè)個(gè)性化的成功頁(yè),用于保證針對(duì)不同的用戶會(huì)有定制化的登陸成功頁(yè)。
2.通過(guò)自定義的loginSuccessAutoRedirect屬性來(lái)決定用戶登陸成功后是直接由服務(wù)端控制頁(yè)面跳轉(zhuǎn)還是返回json讓前端控制交互行為。
3.我們?cè)谟脩舻顷懗晒螅瑫?huì)獲取前面保存的請(qǐng)求,以便用戶在登錄成功后能直接回到登錄前點(diǎn)擊的頁(yè)面。
重寫用戶登錄失敗的方法onLoginFailure:
@Override protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { if (log.isDebugEnabled()) { log.debug("Authentication exception", e); } this.setFailureAttribute(request, e); if (!loginFailureAutoRedirect) { // 返回json數(shù)據(jù)格式告知前端跳轉(zhuǎn)目標(biāo)頁(yè)面 HttpServletResponse res = (HttpServletResponse) response; ResultWrap.failure(803, "用戶名或密碼錯(cuò)誤,請(qǐng)核對(duì)后無(wú)誤后重新提交!", null).writeToResponse(res); } return true; }
登陸失敗我們使用自定義屬性loginFailureAutoRedirect來(lái)控制失敗的動(dòng)作是由服務(wù)端直接跳轉(zhuǎn)頁(yè)面還是返回json由前端控制用戶交互。
在這個(gè)方法的邏輯里面沒(méi)有看到跳轉(zhuǎn)的功能,是因?yàn)槲覀冎苯影迅割惖哪J(rèn)實(shí)現(xiàn)拿過(guò)來(lái)了,在原有的邏輯上做了修改。既然默認(rèn)是服務(wù)端跳轉(zhuǎn)的功能,那么我們只需要補(bǔ)充返回json的功能即可。
覆蓋默認(rèn)的FormAuthenticationFilter
現(xiàn)在我們已經(jīng)寫好了自定義的用戶名密碼登陸過(guò)濾器,下面我們就把它加入到shiro的配置中去,這樣才能生效:
@Bean public ShiroFilterFactoryBean shiroFilterFactoryBean() { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager()); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 設(shè)置不需要權(quán)限的url String[] permitUrls = properties.getPermitUrls(); if (ArrayUtils.isNotEmpty(permitUrls)) { for (String permitUrl : permitUrls) { filterChainDefinitionMap.put(permitUrl, "anon"); } } // 設(shè)置退出的url String logoutUrl = properties.getLogoutUrl(); filterChainDefinitionMap.put(logoutUrl, "logout"); ? ?// 設(shè)置需要權(quán)限驗(yàn)證的url filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); // 設(shè)置提交登陸的url String loginUrl = properties.getLoginUrl(); shiroFilterFactoryBean.setLoginUrl(loginUrl); // 設(shè)置登陸成功跳轉(zhuǎn)的url String successUrl = properties.getSuccessUrl(); shiroFilterFactoryBean.setSuccessUrl(successUrl); // 添加自定義Filter shiroFilterFactoryBean.setFilters(customFilters()); return shiroFilterFactoryBean; } ? /** * 自定義過(guò)濾器 * * @return */ private Map<String, Filter> customFilters() { Map<String, Filter> filters = new LinkedHashMap<>(); // 自定義FormAuthenticationFilter,用于管理用戶登陸的,包括登陸成功后的動(dòng)作、登陸失敗的動(dòng)作 // 可查看org.apache.shiro.web.filter.mgt.DefaultFilter,可覆蓋里面對(duì)應(yīng)的authc UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = new UsernamePasswordAuthenticationFilter(); // 不允許服務(wù)器自動(dòng)控制頁(yè)面跳轉(zhuǎn) usernamePasswordAuthenticationFilter.setAutoRedirectToLogin(false); usernamePasswordAuthenticationFilter.setLoginSuccessAutoRedirect(false); usernamePasswordAuthenticationFilter.setLoginFailureAutoRedirect(false); filters.put("authc", usernamePasswordAuthenticationFilter); return filters; }
上面的代碼重點(diǎn)看 【添加自定義Filte】 ,其實(shí)原理就是把默認(rèn)的authc過(guò)濾器給覆蓋掉,換成我們自定義的過(guò)濾器,這樣的話,我們的過(guò)濾器才能生效。
完整UsernamePasswordAuthenticationFilter代碼
import com.example.awesomespring.vo.ResultWrap; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; import org.apache.shiro.web.util.SavedRequest; import org.apache.shiro.web.util.WebUtils; ? import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.Map; ? /** * @author zouwei * @className UsernamePasswordAuthenticationFilter * @date: 2022/8/2 上午12:14 * @description: */ @Data @Slf4j public class UsernamePasswordAuthenticationFilter extends FormAuthenticationFilter { // 如果用戶沒(méi)有登陸的情況下訪問(wèn)需要權(quán)限的接口,服務(wù)端是否自動(dòng)調(diào)整到登陸頁(yè)面 private boolean autoRedirectToLogin = true; // 登陸成功后是否自動(dòng)跳轉(zhuǎn) private boolean loginSuccessAutoRedirect = true; // 登陸失敗后是否跳轉(zhuǎn) private boolean loginFailureAutoRedirect = true; /** * 個(gè)性化定制每個(gè)登陸成功的賬號(hào)跳轉(zhuǎn)的url */ private LoginSuccessPageFetch loginSuccessPageFetch = new LoginSuccessPageFetch(){}; ? public UsernamePasswordAuthenticationFilter() { } ? /** * 沒(méi)有登陸的情況下,訪問(wèn)需要權(quán)限的接口,需要引導(dǎo)用戶登陸 * * @param request * @param response * @throws IOException */ @Override protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException { // 保存當(dāng)前請(qǐng)求,以便后續(xù)登陸成功后重新請(qǐng)求 this.saveRequest(request); // 1. 服務(wù)端直接跳轉(zhuǎn) // ? - 服務(wù)端重定向登陸頁(yè)面 if (autoRedirectToLogin) { this.redirectToLogin(request, response); } else { // 2. json模式 // ? - json數(shù)據(jù)格式告知前端需要跳轉(zhuǎn)到登陸頁(yè)面,前端根據(jù)指令跳轉(zhuǎn)登陸頁(yè)面 HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; Map<String, String> metaInfo = new HashMap<>(); // 告知前端需要跳轉(zhuǎn)的登陸頁(yè)面 metaInfo.put("loginUrl", getLoginUrl()); // 告知前端當(dāng)前請(qǐng)求的url;這個(gè)信息也可以保存在前端 metaInfo.put("currentRequest", req.getRequestURL().toString()); ResultWrap.failure(802, "請(qǐng)登陸后再操作!", metaInfo) .writeToResponse(res); } } ? @Override protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { // 查詢當(dāng)前用戶自定義的登陸成功需要跳轉(zhuǎn)的頁(yè)面,可以更加靈活控制用戶頁(yè)面跳轉(zhuǎn) String successUrl = loginSuccessPageFetch.successUrl(token, subject); // 如果沒(méi)有自定義的成功頁(yè)面,那么跳轉(zhuǎn)默認(rèn)成功頁(yè) if (StringUtils.isEmpty(successUrl)) { successUrl = this.getSuccessUrl(); } if (loginSuccessAutoRedirect) { // 服務(wù)端直接重定向到目標(biāo)頁(yè)面 WebUtils.redirectToSavedRequest(request, response, successUrl); } else { SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(request); if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase("GET")) { successUrl = savedRequest.getRequestUrl(); } // 返回json數(shù)據(jù)格式告知前端跳轉(zhuǎn)目標(biāo)頁(yè)面 HttpServletResponse res = (HttpServletResponse) response; Map<String, String> data = new HashMap<>(); // 登陸成功后跳轉(zhuǎn)的目標(biāo)頁(yè)面 data.put("successUrl", successUrl); ResultWrap.success(data).writeToResponse(res); } return false; } ? @Override protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { if (log.isDebugEnabled()) { log.debug("Authentication exception", e); } this.setFailureAttribute(request, e); if (!loginFailureAutoRedirect) { // 返回json數(shù)據(jù)格式告知前端跳轉(zhuǎn)目標(biāo)頁(yè)面 HttpServletResponse res = (HttpServletResponse) response; ResultWrap.failure(803, "用戶名或密碼錯(cuò)誤,請(qǐng)核對(duì)后無(wú)誤后重新提交!", null).writeToResponse(res); } return true; } /** * 針對(duì)不同的人員登陸成功后有不同的跳轉(zhuǎn)頁(yè)面而設(shè)計(jì) */ public interface LoginSuccessPageFetch { ? default String successUrl(AuthenticationToken token, Subject subject) { return StringUtils.EMPTY; } } }
ResultWrap.java
import com.example.awesomespring.util.JsonUtil; import lombok.AllArgsConstructor; import lombok.Data; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpStatus; ? import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.Objects; ? /** * @author zouwei * @className ResultWrap * @date: 2022/8/2 下午2:02 * @description: */ @Data @AllArgsConstructor public class ResultWrap<T, M> { // 方便前端判斷當(dāng)前請(qǐng)求處理結(jié)果是否正常 private int code; // 業(yè)務(wù)處理結(jié)果 private T data; // 產(chǎn)生錯(cuò)誤的情況下,提示用戶信息 private String message; // 產(chǎn)生錯(cuò)誤情況下的異常堆棧,提示開發(fā)人員 private String error; // 發(fā)生錯(cuò)誤的時(shí)候,返回的附加信息 private M metaInfo; ? /** * 成功帶處理結(jié)果 * * @param data * @param <T> * @return */ public static <T> ResultWrap success(T data) { return new ResultWrap(HttpStatus.OK.value(), data, StringUtils.EMPTY, StringUtils.EMPTY, null); } ? /** * 成功不帶處理結(jié)果 * * @return */ public static ResultWrap success() { return success(HttpStatus.OK.name()); } ? /** * 失敗 * * @param code * @param message * @param error * @return */ public static <M> ResultWrap failure(int code, String message, String error, M metaInfo) { return new ResultWrap(code, null, message, error, metaInfo); } ? /** * 失敗 * * @param code * @param message * @param error * @param metaInfo * @param <M> * @return */ public static <M> ResultWrap failure(int code, String message, Exception error, M metaInfo) { return failure(code, message, error.getStackTrace().toString(), metaInfo); } ? /** * 失敗 * * @param code * @param message * @param error * @return */ public static ResultWrap failure(int code, String message, Exception error) { String errorMessage = StringUtils.EMPTY; if (Objects.nonNull(error)) { errorMessage = error.getStackTrace().toString(); } return failure(code, message, errorMessage, null); } ? /** * 失敗 * * @param code * @param message * @param metaInfo * @param <M> * @return */ public static <M> ResultWrap failure(int code, String message, M metaInfo) { return failure(code, message, StringUtils.EMPTY, metaInfo); } ? private static final String APPLICATION_JSON_VALUE = "application/json;charset=UTF-8"; ? /** * 把結(jié)果寫入響應(yīng)中 * * @param response */ public void writeToResponse(HttpServletResponse response) { int code = this.getCode(); if (Objects.isNull(HttpStatus.resolve(code))) { response.setStatus(HttpStatus.OK.value()); } else { response.setStatus(code); } response.setContentType(APPLICATION_JSON_VALUE); try (PrintWriter writer = response.getWriter()) { writer.write(JsonUtil.obj2String(this)); writer.flush(); } catch (IOException e) { e.printStackTrace(); } } }
JsonUtil.java
?import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; ? import java.util.Objects; ? /** * @author zouwei * @className JsonUtil * @date: 2022/8/2 下午3:08 * @description: */ @Slf4j public final class JsonUtil { ? /** 防止使用者直接new JsonUtil() */ private JsonUtil() {} ? private static ObjectMapper objectMapper = new ObjectMapper(); ? static { // 對(duì)象所有字段全部列入序列化 objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); /** 所有日期全部格式化成時(shí)間戳 因?yàn)榧词怪付薉ateFormat,也不一定能滿足所有的格式化情況,所以統(tǒng)一為時(shí)間戳,讓使用者按需轉(zhuǎn)換 */ objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true); /** 忽略空Bean轉(zhuǎn)json的錯(cuò)誤 假設(shè)只是new方式創(chuàng)建對(duì)象,并且沒(méi)有對(duì)里面的屬性賦值,也要保證序列化的時(shí)候不報(bào)錯(cuò) */ objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); /** 忽略反序列化中json字符串中存在,但java對(duì)象中不存在的字段 */ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } ? /** * 對(duì)象轉(zhuǎn)換成json字符串 * * @param obj * @param <T> * @return */ public static <T> String obj2String(T obj) { return obj2String(obj, null); } /** * 對(duì)象轉(zhuǎn)換成json字符串 * * @param obj * @param <T> * @return */ public static <T> String obj2String(T obj, String defaultValue) { if (Objects.isNull(obj)) { return defaultValue; } try { return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj); } catch (Exception e) { log.warn("Parse object to String error", e); // 即使序列化出錯(cuò),也要保證程序走下去 return null; } } ? /** * 對(duì)象轉(zhuǎn)json字符串(帶美化效果) * * @param obj * @param <T> * @return */ public static <T> String obj2StringPretty(T obj) { if (Objects.isNull(obj)) { return null; } try { return obj instanceof String ? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); } catch (Exception e) { log.warn("Parse object to String error", e); // 即使序列化出錯(cuò),也要保證程序走下去 return null; } } ? /** * json字符串轉(zhuǎn)簡(jiǎn)單對(duì)象 * * @param <T> * @param json * @param clazz * @return */ public static <T> T string2Obj(String json, Class<T> clazz) { if (StringUtils.isEmpty(json) || Objects.isNull(clazz)) { return null; } try { return clazz.equals(String.class) ? (T) json : objectMapper.readValue(json, clazz); } catch (Exception e) { log.warn("Parse String to Object error", e); // 即使序列化出錯(cuò),也要保證程序走下去 return null; } } ? /** * json字符串轉(zhuǎn)復(fù)雜對(duì)象 * * @param json * @param typeReference 例如:new TypeReference<List<User>>(){} * @param <T> 例如:List<User> * @return */ public static <T> T string2Obj(String json, TypeReference<T> typeReference) { if (StringUtils.isEmpty(json) || Objects.isNull(typeReference)) { return null; } try { return (T) (typeReference.getType().equals(String.class) ? (T) json : objectMapper.readValue(json, typeReference)); } catch (Exception e) { log.warn("Parse String to Object error", e); // 即使序列化出錯(cuò),也要保證程序走下去 return null; } } ? /** * json字符串轉(zhuǎn)復(fù)雜對(duì)象 * * @param json * @param collectionClass 例如:List.class * @param elementClasses 例如:User.class * @param <T> 例如:List<User> * @return */ public static <T> T string2Obj( String json, Class<?> collectionClass, Class<?>... elementClasses) { if (StringUtils.isEmpty(json) || Objects.isNull(collectionClass) || Objects.isNull(elementClasses)) { return null; } JavaType javaType = objectMapper .getTypeFactory() .constructParametricType(collectionClass, elementClasses); try { return objectMapper.readValue(json, javaType); } catch (Exception e) { log.warn("Parse String to Object error", e); // 即使序列化出錯(cuò),也要保證程序走下去 return null; } } }
這樣在shiro中如何實(shí)現(xiàn)更靈活的登陸控制就編寫完畢了。后面會(huì)陸續(xù)講解我在使用shiro時(shí)遇到的其他問(wèn)題,以及相應(yīng)的解決方案。
到此這篇關(guān)于springboot集成shiro自定義登陸過(guò)濾器方法的文章就介紹到這了,更多相關(guān)springboot集成shiro 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot普通類中如何獲取session問(wèn)題
這篇文章主要介紹了springboot普通類中如何獲取session問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01Flink實(shí)現(xiàn)特定統(tǒng)計(jì)的歸約聚合reduce操作
這篇文章主要介紹了Flink實(shí)現(xiàn)特定統(tǒng)計(jì)的歸約聚合reduce操作,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-02-02java實(shí)現(xiàn)順序結(jié)構(gòu)線性列表的函數(shù)代碼
java實(shí)現(xiàn)順序結(jié)構(gòu)線性列表的函數(shù)代碼。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-10-10MyEclipse2017創(chuàng)建Spring項(xiàng)目的方法
這篇文章主要為大家詳細(xì)介紹了MyEclipse2017創(chuàng)建Spring項(xiàng)目的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03java利用CompletionService保證任務(wù)先完成先獲取到執(zhí)行結(jié)果
這篇文章主要為大家詳細(xì)介紹了java如何利用CompletionService來(lái)保證任務(wù)先完成先獲取到執(zhí)行結(jié)果,文中的示例代碼講解詳細(xì),需要的可以參考下2023-08-08解讀RedisTemplate的各種操作(set、hash、list、string)
這篇文章主要介紹了解讀RedisTemplate的各種操作(set、hash、list、string),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12Java使用黑盒方式模擬實(shí)現(xiàn)內(nèi)網(wǎng)穿透
這篇文章主要介紹了Java使用黑盒方式模擬實(shí)現(xiàn)內(nèi)網(wǎng)穿透,內(nèi)網(wǎng)穿透,也即 NAT 穿透,進(jìn)行 NAT 穿透是為了使具有某一個(gè)特定源 IP 地址和源端口號(hào)的數(shù)據(jù)包不被 NAT 設(shè)備屏蔽而正確路由到內(nèi)網(wǎng)主機(jī),需要的朋友可以參考下2023-05-05System.getProperty(“l(fā)ine.separator“)含義及意義詳解
這篇文章主要介紹了System.getProperty(“l(fā)ine.separator“)含義,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05Spring?Security權(quán)限控制的實(shí)現(xiàn)接口
這篇文章主要介紹了Spring?Security的很多功能,在這些眾多功能中,我們知道其核心功能其實(shí)就是認(rèn)證+授權(quán)。Spring教程之Spring?Security的四種權(quán)限控制方式2023-03-03