이 기사에는 아이디어 만 포함되어 있으며 구체적이고 완전한 구현을 제공하지 않습니다 (블로거는 너무 게으르지 않기에는 너무 게으 릅니다). 질문이 있거나 알고 싶다면 개인 메시지 나 의견을 보낼 수 있습니다.
배경
전통적인 Java 웹 중소 규모 프로젝트에서 세션은 일반적으로 로거의 신원 정보와 같은 세션 정보를 일시적으로 저장하는 데 사용됩니다. 이 메커니즘은 HTTP의 쿠키 메커니즘을 빌려서 구현되지만 앱이 요청할 때마다 쿠키 정보를 저장하고 공유하는 것이 더 번거 롭고 기존 세션은 클러스터 친화적이지 않으므로 일반 앱 백엔드 서비스는 토큰을 사용하여 사용자 로그인 정보를 구별합니다.
모든 사람은 J2EE의 세션 메커니즘을 알고 있는데, 이는 사용하기에 매우 편리하며 기존 Java 웹 응용 프로그램에서 매우 유용합니다. 그러나 인터넷 프로젝트 나 클러스터에서 사용할 수있는 일부 프로젝트에는 직렬화 문제, 동기화 지연 문제 등과 같은 몇 가지 문제가 있으므로 세션과 유사한 클러스터 문제를 해결할 수있는 도구가 필요합니다.
계획
캐시 메커니즘을 사용 하여이 문제를 해결합니다. 더 인기있는 Redis는 NOSQL 메모리 데이터베이스이며 캐시 실패 메커니즘이있어 세션 데이터를 저장하는 데 매우 적합합니다. 첫 번째 요청에 따라 토큰 문자열을 클라이언트에게 반환해야하며, 클라이언트는이 토큰을 사용하여 미래에 요청할 때마다 신원을 식별합니다. 비즈니스 개발에 대해 투명하게하기 위해 앱의 요청 및 응답으로 만든 패킷을 캡슐화합니다. 클라이언트의 HTTP 요청 도구 클래스 및 서버의 MVC 프레임 워크에 대한 몇 가지 트릭 만 수행하면됩니다. 클라이언트의 HTTP 도구 클래스의 수정은 매우 간단합니다. 주로 서버의 프로토콜 캡슐화입니다.
구현 아이디어
1. 요청 응답 메시지 프로토콜을 공식화하십시오.
2. 구문 분석 프로토콜은 토큰 문자열을 프로세스합니다.
3. Redis를 사용하여 저장하여 토큰 및 해당 세션 정보를 관리합니다.
4. 세션 정보를 저장하고 얻기위한 API를 제공합니다.
각 단계 단계의 구현 계획을 설명합니다.
1. 요청 응답 메시지 프로토콜을 공식화하십시오.
메시지 프로토콜을 캡슐화하려면 공개 필드, 서비스 필드, 메시지의 데이터 구조 등을 고려해야합니다.
요청 된 공공 분야에는 일반적으로 토큰, 버전, 플랫폼, 모델, IMEI, 앱 소스 등이 포함되며, 그 중 토큰은 이번에 우리의 주인공입니다.
응답의 공통 필드에는 일반적으로 토큰, 결과 상태 (성공, 실패), 결과 코드 (코드), 결과 정보 등이 포함됩니다.
패킷 데이터 구조의 경우 JSON이 일반적이며 시각화가 우수하며 바이트 점유율이 낮기 때문에 JSON을 선택합니다.
요청 메시지는 다음과 같습니다. Body는 로그인 된 사용자 이름 및 비밀번호 등과 같은 비즈니스 정보를 저장합니다.
{ "토큰": "클라이언트 토큰", / ** 클라이언트 빌드 버전 번호* / "버전": 11, / ** 클라이언트 플랫폼 유형* / "플랫폼": / ** 클라이언트 장치 모델* / "machineModel": "iPhone 6S", "imei": "클라이언트 문자열 번호 (휴대 전화)", / ** 실제 메시지가 Map* / "" "" "" "" "" { "key21": "value21"}, "key3": [1,]}}반응 형 메시지
{ /** success* /"success": false, /** 모든 요청은 토큰으로 돌아갑니다. 클라이언트는 각 요청에 대해 최신 토큰을 사용해야합니다* /"토큰": "현재 요청에 대한 서버 선택 토큰", /** 실패한 코드* /"실패한 메시지": 1, /** 비즈니스 메시지 또는 실패 메시지* /"msg": "미지의 대상", /** 실제 비즈니스 데이터가 될 수 있습니다. "Body": Null}}2. 구문 분석 프로토콜은 토큰 문자열을 프로세스합니다.
서버 측 MVC 프레임 워크의 경우 SpringMVC 프레임 워크를 사용합니다.이 프레임 워크도 일반적이며 설명되지 않습니다.
당분간 토큰 처리에 대해서는 언급하지 마십시오. 먼저, 패킷을 공식화 한 후 매개 변수를 전달하는 방법.
요청 정보가 캡슐화되므로 SpringMVC 프레임 워크가 컨트롤러에 필요한 매개 변수를 올바르게 주입하려면 패킷을 구문 분석하고 변환해야합니다.
요청 정보를 구문 분석하려면 SpringMVC의 매개 변수 변환기를 사용자 정의해야합니다. Handlermethodargumentresolver 인터페이스를 구현하면 매개 변수 변환기를 정의 할 수 있습니다.
요청 BodyResolver는 ResolVeargument 메소드를 구현하고 매개 변수를 주입합니다. 다음 코드는 샘플 코드이며 직접 사용하지 않습니다.
@override public Object ResolVeargument (MethodParameter 매개 변수, ModelandViewContainer MavContainer, NativeWebRequest WebRequest, WebDatabinderFactory Binderfactory) 예외 {String RequestBodystr = WebRequest.getParameter (요청 BodyParamName); // 여기에 메시지를 전달할 수 있습니다. if (stringUtils.isnotBlank (requestBodystr)) {String paramname = parameter.getParameterName (); // 컨트롤러 클래스에서 매개 변수 이름 가져 오기 <?> paramclass = parameter.getParameterType (); // json 툴 클래스를 통해 컨트롤러를 통해 패키지 유형을 얻습니다. if (paramclass.equals (servicerequest.class)) {// servicerequest는 요청 패킷에 해당하는 VO입니다. serviceRequest serviceRequest = ObjectMapper.ReadValue (jsonNode.traverse (), serviceRequest.class); return serviceRequest; //이 개체를 매개 변수에 주입하려면이 개체를 반환하십시오. 유형에 해당해야합니다. 그렇지 않으면 예외는 쉽게 잡을 수 없습니다} (jsonnode! = null) {// 메시지 jsonnode paramjsonnode = jsonnode.findValue (paramname)에서 컨트롤러에서 필요한 매개 변수를 찾으십시오. if (paramjsonnode! = null) {return objectMapper.ReadValue (paramjsonNode.traverse (), paramclass); }}} return null; }srpingmvc 구성 파일에 정의 된 매개 변수 변환기 구성 <mvc : argument-resolvers>
<MVC : Argument-Resolvers> <!-통합 요청 정보 처리, ServiceRequest에서 데이터를 가져오고-> <bean id = "requestBodyResolver"> <property name = "objectMapper"> <ean> </bean> </property> <!-구성 요청에서 ServiceRequest에 해당하는 필드 이름은 requestible-> 이름 = "requestBodyParamName"> <alue> requestBody </value> </property> </bean> </mvc : Argument-Resolvers>
이를 통해 메시지의 매개 변수는 SpringMVC에 의해 올바르게 식별 될 수 있습니다.
다음으로 토큰을 처리해야합니다. 각 요청을 가로 채려면 srpingmvc 인터셉터를 추가해야합니다. 이것은 일반적인 기능이며 자세히 설명되지 않습니다.
MATCHER M1 = PATUCT.COMPILE ( "/"Token /":/"(.*?)/ ""). 매치 자 (requestBodyStr); if (m1.find ()) {token = m1.group (1);} tokenmappool.verifytoken (token); // 토큰의 공개 처리를 수행하고 확인이런 식으로 토큰을 얻을 수 있으며 공개 처리를 수행 할 수 있습니다.
3. Redis를 사용하여 저장하여 토큰 및 해당 세션 정보를 관리합니다.
실제로, 그것은 Redis Operation Tool 클래스를 작성하는 것입니다. Spring은 프로젝트의 주요 프레임 워크로 사용되며 Redis의 많은 기능을 사용하지 않기 때문에 Spring에서 제공하는 CacheManager 기능을 직접 사용합니다.
org.springframework.data.redis.cache.rediscachemanager를 구성하십시오
<!-캐시 관리자 글로벌 변수 등은 액세스하는 데 사용될 수 있습니다-> <bean id = "cachemanager"> <compeructor-arg> <ref bean = "redistemplate"/> </constructor-arg> <property name = "useprefix"value = "true"/> <property name = "cacheprefix"> <bean> <bean> <protuctor-arg-arg-arg 이름 = "delimiter" " value = ":@webserviceInterface"/> </bean> </property> <속성 이름 = "Fexipes"> <!-캐시 타당성 기간-> <key> <key> <key> <value> tokenpoolcache </value> </key> <!-tokenpool cache name-> <value> <2592000 </bant> </legent> </legent> </legent>.
4. 세션 정보를 저장하고 얻기위한 API를 제공합니다.
위의 전희를 통해 우리는 거의 토큰을 처리했습니다. 다음으로 토큰 관리 작업을 구현합니다.
세션 정보를 저장하고 얻기 위해 비즈니스 개발을 편리하게 만들어야하며 토큰은 투명합니다.
import java.util.hashmap; import java.util.map; import org.apache.commons.logging.log; import org.apache.commons.logging.logfactory; import org.springframework.cache; import org.springframework.cache.cache.valuewprapper; import org.springframework.cache.cache.valuewrapper; import org.springframework.cache.cachemanager;/** * * 클래스 이름 : tokenmappoolbean * 설명 : 토큰 및 관련 정보 통화 처리 클래스 * 수정 레코드 : * @version v1.0 * @date 4 월 22, 2016 */public classbean */public tokenmbean {/public the kenmbean {/auxtor niuxtor niuxt 정적 최종 로그 로그 = logfactory.getLog (tokenmappoolbean.class); / ** 현재 요청에 해당하는 토큰*/ private ThreadLocal <string> currentToken; 개인 Cachemanager Cachemanager; 개인 문자열 캐시 이름; 개인 토크 engerenerator 토크 engerenerator; Public TokenMappoolbean (Cachemanager CacheManager, String Cachename, Tokengenerator Tokengenerator) {this.cachemanager = CacheManager; this.cachename = 캐시 이름; this.tokengenerator = Tokengenerator; currentToken = new ThreadLocal <string> (); } /*** 토큰이 합법적 인 경우 토큰을 반환하십시오. 새 토큰을 만들고 반환, * 토큰을 Threadlocal에 넣고 TokenMap * @param token * @return token */ public string verifyToken (String Token) {// log.info("CheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheC heCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheC verifiedToken = null (tokengenerator.checktokenformat (token)) {// log.info ( "checktoken :/"+token+"/" "); verifyedToken = token;} else {verifyedToken = newToken (); cache Cache CACHEN); Cachmanager.getCache (Cachename) {Throw Runtimeexception (Token이 저장된 캐시 풀 : " + Cachename); value.get == null). {CacheManager.getCache (Cache == NULL) { "Token이 저장된 캐시 풀 :"+CACHENAME = int++; (cache.get (newtoken)! = null); "goken을 성공적으로 만듭니다 :/"+newtoken+"/"return newtoken;}/*** 현재 요청의 해당 키에서 해당 키의 대상을 얻으십시오. 세션 */ 공개 getAttribute (String key) {Cachemanager.getCache (Cache == NULL) { "Token이 저장된 상태에서 캐시 풀을 얻을 수 없습니다 tokenmap = null; tokenmapwrapper.get (); 데이터 손실을 유발할 수 있습니다. <br> * @param key void vovel value (string key, 객체 값) {cache = cachemanage (cachename) (cache) the get the the the the the the the the the the the the the the the 저장, chacename : " + cachename);} valuewrapper tokenmapwrapper = cache.get (currentToken.get ()); map <string, object> tokenmap = null; if (tokenmapwrapper! = null) {tokenmap = (map <string, object>) TokenMapwrapper.get ();} if (tokenmapwrapper.get); {currentToken.get ()); CACHE.PUT (currentToken.get (), TokenMap) /** * @return token * /public string getToken () {// extoken (null); @param token */ public void removetokenmap (string token) {if (token == null) {return} cachemanager.getCache (cache == null) { "Cachename :" + kacename; 토큰); CACHENAME} public tokengenerator gettokengenerator () {return tokengenerator} void endokengerator (tokengenerator tokengenerator) {tokengenerator = tokengenerator;요청이 서블릿 컨테이너의 스레드에 해당하고 요청의 수명주기 동안 동일한 스레드에 있고 동시에 토큰 관리자를 공유하기 때문에 Threadlocal 변수는 여기서 사용됩니다. 따라서이 스레드 로컬 변수는 토큰 문자열을 저장하기 위해 필요합니다.
참고 :
1. VerifyToken 메소드 호출은 각 요청의 시작 부분에서 호출되어야합니다. 요청이 완료된 후 다음 번에 확인이 실행되지 않도록 Clear가 지우도록 요청되지만, 반환하면 Token이 ThreadLocal에서 꺼집니다. (이 버그는 며칠 동안 나를 괴롭 혔고 회사의 N 개발 점검 코드는 찾을 수 없었습니다. 마지막으로 테스트 후 404 개가 발생했을 때 인터셉터가 입력되지 않았으므로 verificationToken 방법을 호출하지 않았으므로 지난 시간에 예외 정보가 요청 된 토큰이 발생하여 스트레인 숫자 문제를 겪었습니다.
2. 클라이언트는 HTTP 도구를 캡슐화 할 때 각 토큰을 저장하고 다음 요청에 사용해야합니다. 회사의 iOS 개발은 아웃소싱을 요청했지만 아웃소싱은 필요에 따라 수행되지 않았습니다. 로그인하지 않으면 토큰이 저장되지 않습니다. 토큰이 통과 될 때마다 널이어서 각 요청마다 토큰이 생성되고 서버는 많은 쓸모없는 토큰을 만듭니다.
사용
사용법도 매우 간단합니다. 다음은 캡슐화 된 로그인 관리자입니다. 로그인 관리자의 토큰 관리자 응용 프로그램을 참조 할 수 있습니다.
import org.apache.commons.logging.log; import org.apache.commons.logging.logfactory; import org.springframework.cache.cache; import org.sprameframework.cache.cache.valuewrapper; import org.springframework.cachemanager; import com.niuxz 이름 : LoginManager * 설명 : loginManager * 레코드 수정 : * @version v1.0 * @Date 2016 년 7 월 19 일 * @Author niuxz * */public class loginManager {private static final log = logfactory.getLog (loginManager.class); 개인 Cachemanager Cachemanager; 개인 문자열 캐시 이름; 개인 TokenMappoolbean TokenMappool; public loginmanager (Cachemanager Cachemanager, String Cachename, Tokenmappoolbean Tokenmappool) {this.cachemanager = CacheManager; this.cachename = 캐시 이름; this.tokenmappool = TokenMappool; } public void login (string userId) {log.info ( "사용자 로그인 : userId =" + userId); 캐시 캐시 = CacheManager.getCache (캐시 이름); ValueWrapper valueWrapper = cache.get (userId); String token = (string) (ValueWrapper == null? null : valuewrapper.get ()); tokenmappool.removetokenmap (token); // 로그인하기 전에 TokenMappool.setAttribute (constants.logged_user_id, userId); cache.put (userId, tokenmappool.getToken ()); } public void logoutCurrent (String Phonetel) {String curuserid = getCurrentUserId (); log.info ( "사용자 로그 아웃 : userId =" + curuserId); tokenmappool.removetokenmap (tokenmappool.gettoken ()); // login if (curuserid! = null) {cache cache = cachemanager.getCache (cachename); CACHE.EVICT (CURUSERID); CACHE.EVICT (PHONETEL); }} / ** * 현재 사용자의 ID를 가져옵니다 * @return * / public String getCurrentUserId () {return (string) tokenmappool.getAttribute (constants.loged_user_id); } public cachemanager getCacheManager () {return cachemanager; } public String getCachename () {return CacheName; } public tokenmappoolbean getTokenMappool () {return tokenmappool; } public void setCacheManager (CacheManager Cachemanager) {this.cacheManager = Cachemanager; } public void setCacheName (String Cachename) {this.cachename = Cachename; } public void settokenmappool (TokenMappoolbean tokenmappool) {this.tokenmappool = tokenmappool; }}아래는 일반적인 SMS 검증 코드 인터페이스입니다. 일부 응용 프로그램은 세션을 사용하여 확인 코드를 저장합니다. 이 방법을 사용하지 않는 것이 좋습니다. 세션 저장의 단점은 상당히 큽니다. 그냥보세요. 제가 쓴 것이 아니 었습니다
public void sendvalicodebyphonenum (String phonenum, String hintmsg, String logsuffix) {validatephonetimespace (); // 6 비트 랜덤 숫자 문자열 code = codeUtil.getValidAteCode (); log.info (code + "------>" + phonenum); // SMS 확인 코드를 호출하여 인터페이스 retstatus retstatus = msgsendutils.sendsms (code + hintmsg, phonenum); if (! retstatus.getisok ()) {log.info (retstatus.tostring ()); New ThrowStodataException (serviceresponsecode.fail_invalid_params, "휴대 전화 확인 코드가 얻지 못했습니다. 나중에 다시 시도하십시오"); } // 세션 재설정 tokenmappool.setattribute (constants.validate_phone, phonenum); tokenmappool.setattribute (constants.validate_phone_code, code.tostring ()); tokenmappool.setattribute (constants.send_code_wrongnu, 0); tokenmappool.setattribute (constants.send_code_time, new date (). gettime ()); log.info (logsuffix + phonenum + "SMS 확인 코드 :" + 코드); }처리 응답
어떤 학생들은 그런 반응 형 메시지 포장이 있는지 묻습니다.
@requestmapping ( "record")@responseBodyPublic ServicerEsponse 레코드 (문자열 메시지) {String userId = loginManager.getCurrentUserId (); MessageBoardService.recordMessage (userId, message); return serviceresponsebuilder.buildsuccess (null);}그중에서도 ServicerPonse는 캡슐화 된 응답 패킷 VO입니다. SpringMVC의 @ResponseBody 주석 만 사용하면됩니다. 열쇠는이 빌더입니다.
import org.apache.commons.lang3.stringutils; import com.niuxz.base.pojo.serviceresponse; import com.niuxz.utils.spring.springcontextutil; import com.niuxz.web.server.token.token.token.tokenmappoolbean;/** classe : servicer esponsupsongebuilder v1 @oresponseponseponsepons. * @Author niuxz * */public class serviceresponsebuilder {/** * 성공적인 응답 메시지 구축 * * @param body * @return 성공적인 운영 */public static serviceresponse buildsuccess (객체) {return new servicerespons ((tokenmappoolbean)). SpringContextUtil.getBean ( "TokenMappool")) .getToken (), "Action Success", Body); } / ** * 성공적인 응답 메시지 구축 * * @param body * @return 성공적인 작업을 가진 서비스 응답 * / public static serviceresponse buildsuccess (문자열 토큰, 객체 바디) {return new ServicerSponse (토큰, "Action Success", Body); } / ** * 실패한 응답 메시지 구축 * * @param failcode * msg * @return 작동 실패 * / public static serviceresponse buildfail (int failcode, string msg) {return buildfail (실패 코드, msg, null); } / ** * 실패한 응답 메시지 구축 * * @param failcode * msg body * @return 작동 실패 * / public static serviceresponse buildfail (int failcode, string msg, 객체 바디) {return new serviceresponse (((tokenmappoolbean)) springcontextutil.getbean ( "Tokenmappool")) StringUtils.isnotblank (MSG) : "작동 실패", 본체); }}정적 공구 클래스의 형태를 사용하기 때문에 스프링을 통해 TokenMappool (Token Manager) 객체에 주입 할 수없고 Spring에서 제공 한 API를 통해이를 얻을 수 있습니다. 그런 다음 응답 정보를 구성 할 때 TokenMappool의 getToken () 메소드를 직접 호출하십시오. 이 메소드는 현재 스레드에 묶인 토큰 문자열을 반환합니다. 다시 한 번, 요청이 끝난 후 수동으로 Clear를 호출하는 것이 중요합니다 (글로벌 인터셉터를 통해 호출합니다).
J2EE의 세션 메커니즘을 모방하는 앱 백엔드 세션 정보 관리의 위의 예는 내가 공유하는 모든 컨텐츠입니다. 나는 당신이 당신에게 참조를 줄 수 있기를 바랍니다. 그리고 당신이 wulin.com을 더 지원할 수 있기를 바랍니다.