教你使用springSecurity+jwt實(shí)現(xiàn)互踢功能
jwt介紹:
? ? ? ? JWT是一種用于雙方之間傳遞安全信息的簡潔的、URL安全的表述性聲明規(guī)范。JWT作為一個(gè)開放的標(biāo)準(zhǔn)( RFC 7519 ),定義了一種簡潔的,自包含的方法用于通信雙方之間以Json對象的形式安全的傳遞信息。
jwt認(rèn)證流程介紹:
1. 用戶使用賬號和面發(fā)出post請求;?
2. 服務(wù)器使用私鑰創(chuàng)建一個(gè)jwt;?
3. 服務(wù)器返回這個(gè)jwt給瀏覽器;?
4. 瀏覽器將該jwt串在請求頭中像服務(wù)器發(fā)送請求;?
5. 服務(wù)器驗(yàn)證該jwt;?
6. 返回響應(yīng)的資源給瀏覽器。
一.思路:
原來的實(shí)現(xiàn)用戶登錄態(tài)是:
1.后臺(tái)登陸成功后生成一個(gè)令牌(uuid)----JwtAuthenticationSuccessHandler
2.后臺(tái)把它包裝成jwt數(shù)據(jù),然后返回給前端—JwtAuthenticationSuccessHandler
3.后臺(tái)把它加入redis緩存中,并設(shè)置失效時(shí)間----JwtAuthenticationSuccessHandler
4.前端調(diào)用接口時(shí),帶入jwt
5.后臺(tái)寫個(gè)攔截器或者過濾器,在前端調(diào)用接口的時(shí)候,從request的header中獲取jwt,在緩存中搜索,如果存在則處于登錄態(tài),并重置失效時(shí)間(這樣用戶在有效時(shí)間內(nèi)就處于登錄態(tài))—JwtSecurityContextRepository
6.解釋下:springSecurity是個(gè)過濾器璉,是由一個(gè)一個(gè)的過濾器組成的
現(xiàn)在的互踢:
1.后臺(tái)在登陸成功后,用用戶id組成一個(gè)key,查詢r(jià)edis緩存中的value
2.和新的jwt比較,如果不一樣則把查到的jwt當(dāng)做key從redis中刪掉,就是上面第三步存儲(chǔ)的
3.把用戶id組成一個(gè)key,把上面jwt當(dāng)做value傳入緩存中
4.在上面的第5步,也重置下我們這里存儲(chǔ)的值
*上面 指的是原來的實(shí)現(xiàn)用戶登錄態(tài)
package com.lc.gansu.security.component.jwt; import com.fasterxml.jackson.databind.ObjectMapper; import com.lc.gansu.framework.core.ConstantOfReturnCode; import com.lc.gansu.framework.core.RedisKey; import com.lc.gansu.framework.core.ReturnObject; import com.lc.gansu.security.component.SecurityConstants; import com.lc.gansu.security.domain.User; import com.lc.gansu.security.utility.JWTHS256; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.core.Authentication; import org.springframework.security.web.WebAttributes; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.io.PrintWriter; import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; @Slf4j public class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandler { //private final RequestCache requestCache = new HttpSessionRequestCache(); private final ObjectMapper jacksonObjectMapper; private final RedisTemplate<String, Object> redisTemplate; public JwtAuthenticationSuccessHandler(ObjectMapper jacksonObjectMapper, RedisTemplate<String, Object> redisTemplate) { this.jacksonObjectMapper = jacksonObjectMapper; this.redisTemplate = redisTemplate; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { log.info("JwtAuthenticationSuccessHandler=success"); clearAuthenticationAttributes(request); handle(response, authentication); } protected final void clearAuthenticationAttributes(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) return; session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); } protected void handle(HttpServletResponse response, Authentication authentication) throws IOException { if (response.isCommitted()) { log.debug("Response has already been committed."); return; } User sysUser = (User) authentication.getPrincipal(); sysUser.setClazz(authentication.getClass()); //AuthenticationAdapter authenticationAdapter=AuthenticationAdapter.authentication2AuthenticationAdapter(authentication); String authOfjson = jacksonObjectMapper.writeValueAsString(sysUser); String subject = UUID.randomUUID().toString(); String authOfjwt = JWTHS256.buildJWT(subject, authOfjson); response.addHeader("jwt", authOfjwt); //跨域時(shí)允許header攜帶jwt response.addHeader("Access-Control-Expose-Headers" ,"jwt"); redisTemplate.boundValueOps(SecurityConstants.getJwtKey(subject)).set("w", 60, TimeUnit.MINUTES); //---------互踢start------------- // 在緩存中傳入key="R_V_USERIDLOGINKEY_" + userId + "_LOGIN",value=SecurityConstants.getJwtKey(subject),有新登錄時(shí)如果用戶一樣則把緩存里之前的jwt刪除,這個(gè):(redisTemplate.boundValueOps(SecurityConstants.getJwtKey(subject)).set("w", 60, TimeUnit.MINUTES);) log.info("設(shè)置jwt,并在緩存中傳入:{}",SecurityConstants.getJwtKey(subject)); String uuid = (String) redisTemplate.opsForValue().get(RedisKey.getUserIdKey(sysUser.getId())); log.info("查詢原有jwt:{}",uuid); if(!SecurityConstants.getJwtKey(subject).equals(uuid)&& Objects.nonNull(uuid)){ assert uuid != null; redisTemplate.delete(uuid); log.info("刪除原有jwt:{}",uuid); } redisTemplate.opsForValue().set(RedisKey.getUserIdKey(sysUser.getId()), SecurityConstants.getJwtKey(subject),60, TimeUnit.MINUTES); log.info("在緩存里塞入新jwt:{}",SecurityConstants.getJwtKey(subject)); //---------互踢end---------------------- response.setContentType("application/json;charset=utf-8"); PrintWriter out = response.getWriter(); User returnSysUser = new User(); returnSysUser .setName(sysUser.getName()) .setCurrentOrg(sysUser.getCurrentOrg()) .setOrgIdMapRoleList(sysUser.getOrgIdMapRoleList()) .setCurrentMenuList(sysUser.getCurrentMenuList()) .setOrgList(sysUser.getOrgList()); out.write(jacksonObjectMapper.writeValueAsString(new ReturnObject<>(this.getClass().getName(), ConstantOfReturnCode.GLOBAL_RESULT_SUCESS, "登錄成功", returnSysUser))); out.flush(); out.close(); } }
package com.lc.gansu.security.component.jwt; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.lc.gansu.framework.core.RedisKey; import com.lc.gansu.security.component.SecurityConstants; import com.lc.gansu.security.domain.User; import com.lc.gansu.security.utility.JWTHS256; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.context.HttpRequestResponseHolder; import org.springframework.security.web.context.SecurityContextRepository; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; @Slf4j public class JwtSecurityContextRepository implements SecurityContextRepository { protected final Log logger = LogFactory.getLog(this.getClass()); private final RedisTemplate<String, Object> redisTemplate; private final ObjectMapper jacksonObjectMapper; public JwtSecurityContextRepository(RedisTemplate<String, Object> redisTemplate, ObjectMapper jacksonObjectMapper) { this.redisTemplate = redisTemplate; this.jacksonObjectMapper = jacksonObjectMapper; } @Override public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) { HttpServletRequest request = requestResponseHolder.getRequest(); return readSecurityContextFromJWT(request); } @Override public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { } @Override public boolean containsContext(HttpServletRequest request) { return false; } private SecurityContext readSecurityContextFromJWT(HttpServletRequest request) { SecurityContext context = generateNewContext(); String authenticationOfjwt = request.getHeader("jwt"); if (StringUtils.isNotBlank(authenticationOfjwt)) { try { Map<String, Object> map = JWTHS256.vaildToken(authenticationOfjwt); if (Objects.nonNull(map) && map.size() == 2) { String subject = (String) map.get("subject"); Boolean isExp = redisTemplate.hasKey(SecurityConstants.getJwtKey(subject)); if (Objects.nonNull(isExp) && isExp) {//redis key 未過期 redisTemplate.expire(SecurityConstants.getJwtKey(subject), 60, TimeUnit.MINUTES);//延期 String obj = (String) map.get("claim"); //AuthenticationAdapter authenticationAdapter=jacksonObjectMapper.readValue(obj, new TypeReference<>(){}); //Authentication authentication=AuthenticationAdapter.authenticationAdapter2Authentication(authenticationAdapter); //Authentication authentication=jacksonObjectMapper.readValue(obj, new TypeReference<>(){}); //Authentication authentication=jacksonObjectMapper.readValue(obj,Authentication.class); User sysUser = jacksonObjectMapper.readValue(obj, new TypeReference<User>() { }); Authentication authentication = new UsernamePasswordAuthenticationToken(sysUser, null, sysUser.getAuthorities()); context.setAuthentication(authentication); //-----互踢start------- if(Objects.nonNull(RedisKey.getUserIdKey(sysUser.getId()))) redisTemplate.expire(RedisKey.getUserIdKey(sysUser.getId()), 60, TimeUnit.MINUTES); //-----互踢end--------- //if(obj instanceof Authentication){ //context.setAuthentication((Authentication)obj); //}else log.error("jwt包含authentication的數(shù)據(jù)非法"); } else log.error("jwt數(shù)據(jù)過期"); } else log.error("jwt數(shù)據(jù)非法"); } catch (Exception e) { e.printStackTrace(); logger.error(e.getLocalizedMessage()); } } else { if (logger.isDebugEnabled()) { logger.debug("No JWT was available from the HttpServletRequestHeader!"); } } return context; } protected SecurityContext generateNewContext() { return SecurityContextHolder.createEmptyContext(); } }
package com.lc.gansu.framework.core; /** * TODO * * @author songtianxiong * @version 1.0 * @date 2021/11/16 19:15 */ public class RedisKey { //線上投放計(jì)劃反饋結(jié)果催辦 public static String getOlpmIdAndUserIdRedisKey(Long OlpmId, Long userId) { return "R_V_OLPMURGE_" + OlpmId + "_" + userId; } //催辦 public static String getOdpIdAndUserIdRedisKey(Long OdpId, Long userId) { return "R_V_ODPMURGE_" + OdpId + "_" + userId; } //催辦 public static String getJwtAndUrl(String jwt, String url) { return "R_V_REPEAT_" + jwt + "_" + url; } //用戶登錄互踢 public static String getUserIdKey(Long userId) { return "R_V_USERIDLOGINKEY_" + userId + "_LOGIN"; } }
---------------------------關(guān)鍵詞:互踢
關(guān)鍵代碼:
//---------互踢start------------- // 在緩存中傳入key="R_V_USERIDLOGINKEY_" + userId + "_LOGIN",value=SecurityConstants.getJwtKey(subject),有新登錄時(shí)如果用戶一樣則把緩存里之前的jwt刪除,這個(gè):(redisTemplate.boundValueOps(SecurityConstants.getJwtKey(subject)).set("w", 60, TimeUnit.MINUTES);) log.info("設(shè)置jwt,并在緩存中傳入:{}",SecurityConstants.getJwtKey(subject)); String uuid = (String) redisTemplate.opsForValue().get(RedisKey.getUserIdKey(sysUser.getId())); log.info("查詢原有jwt:{}",uuid); if(!SecurityConstants.getJwtKey(subject).equals(uuid)&& Objects.nonNull(uuid)){ assert uuid != null; redisTemplate.delete(uuid); log.info("刪除原有jwt:{}",uuid); } redisTemplate.opsForValue().set(RedisKey.getUserIdKey(sysUser.getId()), SecurityConstants.getJwtKey(subject),60, TimeUnit.MINUTES); log.info("在緩存里塞入新jwt:{}",SecurityConstants.getJwtKey(subject)); //---------互踢end----------------------
//-----互踢start------- if(Objects.nonNull(RedisKey.getUserIdKey(sysUser.getId()))) redisTemplate.expire(RedisKey.getUserIdKey(sysUser.getId()), 60, TimeUnit.MINUTES); //-----互踢end---------
//用戶登錄互踢 public static String getUserIdKey(Long userId) { return "R_V_USERIDLOGINKEY_" + userId + "_LOGIN"; }
到此這篇關(guān)于springSecurity+jwt中實(shí)現(xiàn)互踢功能的文章就介紹到這了,更多相關(guān)springSecurity+jwt互踢內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談@FeignClient中name和value屬性的區(qū)別
這篇文章主要介紹了@FeignClient中name和value屬性的區(qū)別,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07Python基礎(chǔ)之如何使用multiprocessing模塊
今天帶大家學(xué)習(xí)python多進(jìn)程的相關(guān)知識(shí),文中對multiprocessing模塊的使用作了非常詳細(xì)的介紹,需要的朋友可以參考下2021-06-06Spring中ApplicationEventPublisher發(fā)布訂閱模式的實(shí)現(xiàn)
本文主要介紹了Spring中ApplicationEventPublisher發(fā)布訂閱模式的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07java 實(shí)現(xiàn)圖片像素質(zhì)量壓縮與圖片長寬縮放
這篇文章主要介紹了java 實(shí)現(xiàn)圖片像素質(zhì)量壓縮與圖片長寬縮放,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11servlet之web路徑問題_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了servlet之web路徑問題的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Java實(shí)現(xiàn)畫線、矩形、橢圓、字符串功能
本篇文章主要介紹了Java實(shí)現(xiàn)畫線、矩形、橢圓、字符串功能的實(shí)例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05Java中Date,Calendar,Timestamp的區(qū)別以及相互轉(zhuǎn)換與使用
以下是對Java中Date,Calendar,Timestamp的區(qū)別以及相互轉(zhuǎn)換與使用進(jìn)行了詳細(xì)的介紹,需要的朋友可以過來參考下2013-09-09Spring Cloud中各組件超時(shí)總結(jié)
在大家學(xué)習(xí)spring cloud的時(shí)候組件是必不可少的一部分,下面這篇文章主要給大家介紹了關(guān)于Spring Cloud中各組件超時(shí)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-11-11JDK1.8中的ConcurrentHashMap使用及場景分析
這篇文章主要介紹了JDK1.8中的ConcurrentHashMap使用及場景分析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01