Este artículo solo contiene ideas y no proporciona una implementación específica y completa (el blogger es demasiado vago para resolverlo). Si tiene alguna pregunta o quiere saber, puede enviar un mensaje o comentario privado.
fondo
En los proyectos tradicionales de Java Web pequeños y medianos, la sesión generalmente se usa para almacenar temporalmente la información de la sesión, como la información de identidad del registrador. Este mecanismo se implementa mediante el mecanismo de cookies de HTTP prestado, pero es más problemático para la aplicación guardar y compartir información de cookies cada vez que lo solicita, y la sesión tradicional no es amigable con el clúster, por lo que los servicios de backend de la aplicación general usan tokens para distinguir la información de inicio de sesión del usuario.
Todos conocen el mecanismo de sesión de J2EE, que es muy conveniente de usar y es muy útil en las aplicaciones web tradicionales de Java. Sin embargo, algunos proyectos que pueden usarse en proyectos o grupos de Internet tienen algunos problemas, como problemas de serialización, problemas de retraso de sincronización, etc., por lo que necesitamos una herramienta que pueda resolver problemas de clúster que sean similares a las sesiones.
plan
Utilizamos el mecanismo de caché para resolver este problema. El Redis más popular es una base de datos de memoria NoSQL y tiene un mecanismo de falla de caché, que es muy adecuado para almacenar datos de sesión. La cadena token debe devolverse al cliente en la primera solicitud, y el cliente usa este token para identificar la identidad cada vez que solicita en el futuro. Para ser transparentes sobre el desarrollo de negocios, encapsulamos los paquetes realizados por la solicitud y la respuesta de la aplicación. Solo necesitamos hacer algunos trucos a la clase de herramientas de solicitud HTTP del cliente y el marco MVC del servidor. La modificación de la clase de herramienta HTTP del cliente es muy simple, principalmente la encapsulación del protocolo del servidor.
Ideas de implementación
1. Formular un protocolo de mensaje de respuesta de solicitud.
2. El protocolo de análisis procesa cadenas de token.
3. Use Redis para almacenar para administrar tokens y la información de sesión correspondiente.
4. Proporcione una API para almacenar y obtener información de sesión.
Explicaremos el plan de implementación de cada paso.
1. Formular un protocolo de mensaje de respuesta de solicitud.
Dado que desea encapsular el protocolo de mensajes, debe considerar qué es un campo público, qué es un campo de servicio, la estructura de datos del mensaje, etc.
Los campos públicos solicitados generalmente incluyen token, versión, plataforma, modelo, IMEI, fuente de aplicaciones, etc., entre los cuales el token es el protagonista de nuestro tiempo.
Los campos comunes de la respuesta generalmente incluyen token, estado de resultados (éxito, fallas), código de resultados (código), información de resultados, etc.
Para la estructura de datos de paquetes, elegimos JSON porque JSON es común, tiene una buena visualización y tiene baja ocupación de bytes.
El mensaje de solicitud es el siguiente, y el cuerpo almacena información comercial, como el nombre de usuario y la contraseña de inicio de sesión, etc.
{ "token": "Client token", /**Client build version number*/ "version": 11, /**Client platform type*/ "platform": "IOS", /**Client device model*/ "machineModel": "Iphone 6s", "imei": "Client string number (mobile phone)", /**Real message body should be map*/ "body": { "key1": "value1", "key2": {"Key21": "Value21"}, "Key3": [1,]}}Mensaje receptivo
{ / ** éxito* / "éxito": falso, / ** Cada solicitud volverá a un token, y el cliente debe usar el último token para cada solicitud* / "Token": "El token de servidor seleccionado para la solicitud actual", / ** Código fallido* / "FailCode": 1, / ** Mensaje comercial o mensaje de falla* / "MSG": "Causa desconocida Cause", puede ser un hecho de que se vuelva el negocio real ". nulo }}2. El protocolo de análisis procesa cadenas de token.
Para el marco MVC del lado del servidor, utilizamos el marco SpringMVC, que también es común y no se describirá.
No mencionemos el procesamiento de tokens por el momento. Primero, cómo pasar los parámetros después de formular el paquete.
Debido a que la información de la solicitud está encapsulada, para que el marco SpringMVC inyecte correctamente los parámetros que necesitamos en el controlador, necesitamos analizar y convertir los paquetes.
Para analizar la información de la solicitud, necesitamos personalizar el convertidor de parámetros de SpringMVC. Al implementar la interfaz handlermethodargumentResolver, podemos definir un convertidor de parámetros
RequestBodyResolver implementa el método resolveargument e inyecta parámetros. El siguiente código es el código de muestra y no lo usa directamente.
@Override public Object resolVearGument (MethodParameter Parameter, ModelAndViewContainer MavContainer, nationWebRequest WebRequest, WebDatabinderFactory BinderFactory) lanza la excepción {String requestBodyStr = webRequest.getParameter (requestbodyParamName); // El mensaje de solicitud puede usar cualquier método para aprobar el mensaje, como lo puede obtener aquí, como lo obtenga aquí, ya que obtenga el mensaje, como lo obtenga, puede obtener el mensaje, al igual que el mensaje, al igual que el mensaje, al igual que el mensaje, al igual que puede obtenerlo aquí, puede obtenerlo aquí. if (stringUtils.isNotBlank (requestBodyStr)) {String paramName = parameter.getParametername (); // Obtener el nombre de parámetro en la clase del controlador <?> paramclass = parameter.getPareMeTerType (); // Tipo de parámetro en el controlador/* paquetes de parses a través de la clase json*/jsonnode jsonnode = objectMapper.Readtree (requeststyStyStyStyStReT if (paramclass.equals (serviceequest.class)) {// ServiceRequest es el VO correspondiente al paquete de solicitud ServiceRequest ServiceRequest = ObjectMapper.ReadValue (jsonnode.traverse (), ServiceRequest.class); return ServiceRequest; // Devuelve este objeto para inyectar en el parámetro, debe corresponder al tipo; de lo contrario, la excepción no se atrapará fácilmente} if (jsonnode! = null) {// Encuentre los parámetros requeridos en el controlador del mensaje jsonnode paramjsonnode = jsonnode.FindValue (paramName); if (paramjsonnode! = null) {return ObjectMapper.ReadValue (paramjsonnode.traverse (), paramclass); }}} return null; }Configure el convertidor de parámetros que definió en el archivo de configuración de SRPingMVC <MVC: Argument-Resolvers>
<mvc: argumentos-resueltos> <!-Procesamiento de información de solicitud unificada, obteniendo datos de ServiceRequest-> <bean id = "requestBodyResolver"> <Property name = "ObjectMapper"> <Bean> </bean> </sperties> <!-El nombre de campo corresponde a ServiceRequest de la solicitud de configuración de la configuración es requestbody-> <Nombre de propiedad = "SolicitudeBodyParamname"> <Valor de la propiedad> </bean> </mvc: argumentos-resueltos>
Esto permite que los parámetros en el mensaje se identifiquen correctamente por SpringMVC.
A continuación tenemos que procesar el token. Necesitamos agregar un interceptor SRPingMVC para interceptar cada solicitud. Esta es una función común y no se describirá en detalle.
Matcher M1 = Pattern.Compile ("/" Token /":/"(.*?)/ ""). Matcher (requestBodystr); if (m1.find ()) {token = m1.group (1);} tokenMappool.verifyToken (token); // Realizar el procesamiento público de token y verificarDe esta manera, puede obtener el token y puede hacer un procesamiento público.
3. Use Redis para almacenar para administrar tokens y la información de sesión correspondiente.
De hecho, solo está escribiendo una clase de herramienta de operación Redis. Debido a que Spring se usa como el marco principal del proyecto, y no utilizamos muchas funciones de Redis, usamos directamente la función Cachemanager proporcionada por Spring.
Configurar org.springframework.data.redis.cache.rediscachemanager
<!-Las variables globales del gerente de caché, etc. se pueden usar para acceder-> <bean id = "cachemanager"> <constructor-arg> <ref bean = "redistemplate"/> </ constructor-arg> <propiedad name = "usePrefix" value = "true"/> <name de propiedad = "cacheeprefix"> <ane> <ructor-arg name = "delimiter" value = ":@webserviceinterface"/> </bean> </propiety> <propiedad name = "expires"> <!-Periodo de validez de caché-> <map> <nedy <nypel> <seper> tokenpoolcache </value> </key> <!-tokenPool Cache Nombre-> <valor> 2592000 </valor> <!-Tiempo válido-> </</map> </maps> </maps> </mapa> </maps.
4. Proporcione una API para almacenar y obtener información de sesión.
A través del juego previo anterior, casi hemos procesado el token. A continuación, implementaremos el trabajo de gestión del token.
Necesitamos que el desarrollo comercial sea conveniente para guardar y obtener información de sesión, y los tokens son transparentes.
import java.util.hashmap; import java.util.map; import org.apache.commons.logging.log; importar org.apache.commons.logging.logfactory; import org.springframework.cache.cache.valuewrapper; import org.springframework.cache.cachemanager;/** * * nombre de clase: tokenMappoolBean * Descripción: token e información relacionada Procesamiento de llamadas Registro de clase * de modificación: * @version v1.0 * @Date 22 de abril de 2016 * @author Niuxz * */Class de llamada pública TokekenMap Log final log = logFactory.getLog (tokenMappoolBean.class); / ** Token correspondiente a la solicitud actual*/ private ThreadLocal <String> CurrentToken; Cachemanager privado Cachemanager; cadena privada Cachename; Tokengenerator privado Tokengenerator; Public tokenMappoolBean (Cachemanager Cachemanager, String Cachename, Tokengenerator Tokengenerator) {this.cachemanager = Cachemanager; this.cachename = Cachename; this.tokengenerator = tokengenerator; currentToken = new ThreadLocal <String> (); } /*** Si el token es legal, devuelva el token. Cree un nuevo token y retorno, * coloque el token en ThreadLocal e inicialice un tokenmap * @param token * @return token */ public string VerifyToken (token de cadena) {// log.info ("ChechechechechechecechecechecechecechecechecechecechecechecechececheCHechecheCHecheCHechecheCHechechechechecechecechecechecechecechecechececheCHechecheCHechecheCHechecheCHechechechechechechecechecechecechecechecechecechececheCHechechecheCHechecheCHechecheCHechechechechechecechececheCHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECELO HechecechecechecellechecechecechecellecanchanChoCHechecechecechecellecanchanCHechecechecellechecellecellecanchanCHechecellechecellecellecellechecechecechecellechecellechecellechecanchechecellechecellhoCHechecellhoCHechecellechecellechecellechecellechecellecellecookchechecechecellecanchanCHechecellhoCHechecellecellhoCHechecellechecechecellecanCHechechecelle VerifyedToken = null; Cachemanager.getCache (Cachename); || value.get () == NULL) {VerifyedToken = NewToken (); {Cache Cache = Cachemana. (cache.get (newToken)! = null); // log.info ("cree el token correctamente:/"+newToken+"/" intente generar: "+count+" Times "); return NewToken;}/*** Obtenga el objeto de la clave de Tokenmap de la solicitud actual* @Param* @@return el atributo de la Atribute de la Corresponsal en el TokeMaP de la Corresponsal en el TokenMap de la Corresponsal de la Corresponsable. Session */ Public Object GetAttribute (Key String) {Cache Cache = Cachemanager.getCache (Cachename); tokenmap = null; Objeto>) tokenmapwrapper.get (); TokenMap puede no ser el último, lo que causará la pérdida de datos. donde se almacena el token, chachename: " + cachename);} valueWrapper tokenMapwrapper = cache.get (currentToken.get ()); map <string, objeto> tokenmap = null; if (tokenMapwrapper! = null) {tokenMap = (map <string <string, objeto>) tokenmapwrapper.get (); ifg. (TokenMap == NULL) {VerifyToken (CurrentToken.get ()); tokenmap.put (clave, valor); Eliminar token y tokenmap * @param token */ public void RemoveTokenMap (string token) {if (token == null) {return} caché cache = cachemanager.getCache (cachename) Token: token = " + token); cache.evict (token);} public Cachemanager getCachemanager () {return Cachemanager;} public void setCachemanager (Cachemanager Cachemanager) {this.Cachemanager = Cachemanager; {this.cachename = cachename;La variable ThreadLocal se usa aquí porque una solicitud corresponde a un hilo en el contenedor de servlet, y está en el mismo hilo durante el ciclo de vida de una solicitud, y múltiples subprocesos comparten el administrador de tokens al mismo tiempo, por lo que esta variable local de hilo es necesaria para guardar la cadena de token.
Notas:
1. El método de llamada a VerifyToken debe llamarse al comienzo de cada solicitud. Y después de que se complete la solicitud, se llama a Clear a Clear, para no hacer que la verificación se ejecute la próxima vez, pero el token se saca de ThreadLocal cuando regresa. (Este error me ha molestado durante varios días, y no se encontraron los códigos de verificación de desarrollo de la compañía. Finalmente, después de las pruebas, descubrí que el interceptor no se ingresó cuando ocurrió 404, por lo que no llamé el método de verificación de verificación, que causó el token en la información de excepción de retorno para ser el token la última vez, lo que resultó en un problema de numeros de cadena. Bien, recuerda, recordarme una gran cantidad).
2. El cliente debe guardar cada token al encapsular la herramienta HTTP y usarla para la siguiente solicitud. El desarrollo de iOS de la compañía solicitó la subcontratación, pero la subcontratación no hizo lo requerido. Cuando no se inició sesión, el token no se guarda. Cada vez que se pasa el token, es nulo, lo que resulta en que se cree un token para cada solicitud, y el servidor crea una gran cantidad de tokens inútiles.
usar
El método de uso también es muy simple. El siguiente es el administrador de inicio de sesión encapsulado. Puede referirse a la aplicación de Token Manager para Iniced Manager.
importar org.apache.commons.logging.log; import org.apache.commons.logging.logfactory; import org.springframework.cache.cache; import og.springframework.cache.cache.valuewrapers; import og.springframework.cache.cache.cachemanerer; import.niuxzzzz.constants; Nombre: LoginManager * Descripción: LoginManager * Modificar registro: * @version v1.0 * @Date 19 de julio de 2016 * @author niuxz * */public class LoginManager {private static final log = logFactory.getLog (loginmanager.class); Cachemanager privado Cachemanager; cadena privada Cachename; Tokenmapopool de TokenMapool privado; Public LoginManager (Cachemanager Cachemanager, String Cachename, TokenMappoolBean TokenMappool) {this.cachemanager = Cachemanager; this.cachename = Cachename; this.TokenMappool = tokenMappool; } public void login (String UserId) {log.info ("Useric Login: userId =" + userId); Cache Cache = Cachemanager.getCache (Cachename); ValueWrapper ValueWrapper = Cache.get (UserId); String token = (string) (valuewraper == null? Null: valuewraper.get ()); TokenMappool.RemoVetoKenMap (token); // registro de inicio de sesión antes de iniciar sesión TokenMappool.SetAttribute (constants.logged_user_id, userId); Cache.put (UserID, TokenMappool.getToken ()); } public void logroutCurrent (String Phonetel) {String curuserId = getCurrentUserId (); log.info ("User logrout: userId =" + curuserId); tokenmappool.removetokenmap (tokenMappool.getToken ()); // login if (curuserId! = null) {cache cache = cachemanager.getcache (cachename); cache.evict (curuserid); cache.evict (Phonetel); }} / ** * Obtenga la ID del usuario actual * @return * / public String getCurrentUserID () {return (String) tokenMappool.getAttribute (constants.logged_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 setkokenMappool (tokenMappoolBean tokenMappool) {this.TokenMappool = tokenMappool; }}A continuación se muestra una interfaz de código de verificación SMS común. Algunas aplicaciones también usan la sesión para almacenar códigos de verificación. No recomiendo usar este método. Las desventajas de las sesiones de almacenamiento son bastante grandes. Solo miralo, no fue lo que escribí
public void sendValiCodeByphonenum (String Phonenum, String Hintmsg, String logSuffix) {ValidatePhonetimesPace (); // Obtener un número aleatorio de 6 bits Code = CodeUtil.GetValidateCode (); log.info (código + "------>" + Phonenum); // Llame al código de verificación SMS para enviar la interfaz rettatus retstatus = msgsendutils.sendsms (código + hintmsg, fonenum); if (! retstatus.getisok ()) {log.info (retstatus.ToString ()); SHOLE SHONE SHONGSTODATAEXCECTION (ServiceResponseCode.fail_invalid_params, "El código de verificación del teléfono móvil no ha podido obtener, intente nuevamente más tarde"); } // restablecer la sesión 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, nueva fecha (). getTime ()); log.info (logSuffix + Phonenum + "Código de verificación SMS:" + código); }Respuesta de procesamiento
¿Algunos estudiantes preguntarán si hay un envasado de mensajes tan receptivo?
@RequestMapping ("Record")@ResponseBodyPublic ServiceReSponse Record (String Message) {String UserId = LoginManager.getCurrentUserId (); MessageBoatardService.RecordMessage (UserId, Mensaje); return ServiceSponseBuilder.BuildSuccess (NULL);}Entre ellos, ServiceSponse es el paquete de respuesta encapsulado VO. Solo necesitamos usar la anotación @ResponseBody de SpringMVC. La clave es este constructor.
importar org.apache.commons.lang3.StringUtils; import @Date 25 de abril de 2016 * @author niuxz * */public class ServiceReSponseBuilder {/** * Cree un mensaje de respuesta exitoso * * @param Body * @return A ServiceSponse con operación exitosa */Public Static ServiceSponse BuildSuccess (Body) {return New ServiceSesponse (((TokenMappoolBean) SpringContextUtil.getBean ("TokenMappool")) .getToken (), "Acción Success", Body); } / ** * Cree un mensaje de respuesta exitoso * * @param Body * @return A ServiceSponse con operación exitosa * / public static ServicereSponse BuildSuccess (String Token, Object Body) {return New ServiceSponse (token, "Action Exitoso", Body); } / ** * CONSTRUIR un mensaje de respuesta fallido * * @Param FailCode * msg * @return un ServiceResponse que falló operación * / public static ServicerSponse BuildFail (int FailCode, String MSG) {return BuildFail (FailCode, MSG, NULL); } / ** * construya un mensaje de respuesta fallido * * @param failcode * msg body * @return un serviceroPonse que falló operación * / public static ServiceSponse BuildFail (int FailCode, String Msg, Object Body) {return New ServiceSponse (((TokenMappeolBean) springContextUtil.getBean ("TokenMapPool")).).).).).).).).).).). StringUtils.isnotblank (msg)? }}Dado que usamos la forma de una clase de herramienta estática, no podemos inyectar el objeto TokenMappool (administrador de tokens) a través de la primavera, y luego podemos obtenerlo a través de la API proporcionada por la primavera. Luego, al construir la información de respuesta, llame directamente al método getToken () de TokenMappool. Este método devolverá la cadena de token unida por el hilo actual. Una vez más, es importante llamar a Clare Manualmente después de que termine la solicitud (lo llamo a través del Interceptor Global).
El ejemplo anterior de la gestión de la información de la sesión de backend de la aplicación que imita el mecanismo de sesión de J2EE es todo el contenido que comparto con usted. Espero que pueda darle una referencia y espero que pueda apoyar más a Wulin.com.