Este artigo contém apenas idéias e não fornece implementação específica e completa (o blogueiro é preguiçoso demais para resolver isso). Se você tiver alguma dúvida ou quiser saber, pode enviar uma mensagem ou comentário privado.
fundo
Na Web Java tradicional, pequenos e médios projetos, a sessão é geralmente usada para armazenar temporariamente as informações da sessão, como as informações de identidade do Logger. Esse mecanismo é implementado emprestando o mecanismo de cookies do HTTP, mas é mais problemático para o aplicativo salvar e compartilhar informações de cookies sempre que solicita, e a sessão tradicional não é amigável para o cluster; portanto, os serviços de back -end de aplicativos gerais usam tokens para distinguir informações de login de usuários.
Todo mundo conhece o mecanismo de sessão do J2EE, que é muito conveniente de usar e é muito útil nos aplicativos da Web Java tradicionais. No entanto, alguns projetos que podem ser usados em projetos ou clusters da Internet têm alguns problemas, como problemas de serialização, problemas de atraso de sincronização etc., portanto, precisamos de uma ferramenta que possa resolver problemas de cluster semelhantes às sessões.
plano
Usamos o mecanismo de cache para resolver esse problema. O Redis mais popular é um banco de dados de memória NoSQL e possui um mecanismo de falha de cache, que é muito adequado para armazenar dados da sessão. A sequência de token precisa ser devolvida ao cliente na primeira solicitação, e o cliente usa esse token para identificar a identidade toda vez que solicita no futuro. Para ser transparente em relação ao desenvolvimento de negócios, encapsulamos os pacotes feitos pela solicitação e resposta do aplicativo. Precisamos apenas fazer alguns truques para a classe de ferramenta de solicitação HTTP do cliente e a estrutura MVC do servidor. A modificação da classe de ferramentas HTTP do cliente é muito simples, principalmente o encapsulamento do protocolo do servidor.
Idéias de implementação
1. Formule um protocolo de mensagem de resposta de solicitação.
2. O Protocolo de Parsing processa cordas de token.
3. Use Redis para armazenar para gerenciar tokens e informações de sessão correspondentes.
4. Forneça uma API para armazenar e obter informações de sessão.
Explicaremos o plano de implementação de cada etapa.
1. Formule um protocolo de mensagem de resposta de solicitação.
Como você deseja encapsular o protocolo de mensagem, você precisa considerar o que é um campo público, o que é um campo de serviço, a estrutura de dados da mensagem etc.
Os campos públicos solicitados geralmente incluem token, versão, plataforma, modelo, IMEI, fonte de aplicativo etc., entre os quais o token é o protagonista do nosso tempo.
Os campos comuns da resposta geralmente incluem token, status de resultado (sucesso, falha), código de resultado (código), informações de resultado etc.
Para a estrutura de dados de pacotes, escolhemos o JSON porque o JSON é comum, tem boa visualização e possui baixa ocupação de bytes.
A mensagem de solicitação é a seguinte, e o corpo armazena informações comerciais, como o nome de usuário e a senha conectados, 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,]}}Mensagem responsiva
{ /** Sucesso* /"Sucesso": false, /** Cada solicitação retornará a um token, e o cliente deve usar o token mais recente para cada solicitação* /"token": "o servidor selecionou o token para a solicitação atual ou falha no código da seio /" ** falha* /"falha": 1, /** mensagem de negócios /falha* /"msg": " "Body": null}}2. O Protocolo de Parsing processa cordas de token.
Para a estrutura MVC do lado do servidor, usamos a estrutura Springmvc, que também é comum e não será descrita.
Não vamos mencionar o processamento de tokens por enquanto. Primeiro, como passar os parâmetros após a formulação do pacote.
Como as informações da solicitação são encapsuladas, para que a estrutura Springmvc injete corretamente os parâmetros de que precisamos no controlador, precisamos analisar e converter os pacotes.
Para analisar as informações da solicitação, precisamos personalizar o conversor de parâmetros do SpringMVC. Ao implementar a interface HandlermethodargumentResolver, podemos definir um conversor de parâmetros
RequestbodyResolver implementa o método resolveargument e injeta parâmetros. O código a seguir é o código de amostra e não o use diretamente.
@Override Public Object ResolvearGument (parâmetro MethodParameter, ModelAndViewContainer MavContainer, NativeWebRequest WebRequest, WebDatabinderFactory BinderFactory) lança exceção {String SolictringBodystr = WebRequest. if (stringUtils.isNotBlank (requestBodystr)) {string paramname = parameter.getParameterName (); // Obtenha o nome do parâmetro na classe controladora <? if (paramclass.equals (servicerequest.class)) {// servicerequest é o VO correspondente ao pacote de solicitação servicerequest servicerequest = objectmapper.readValue (jsonnode.traverse (), servicerequest.class); Retornar o Servicerequest; // Retornar este objeto para injetar no parâmetro, ele deve corresponder ao tipo, caso contrário, a exceção não será facilmente capturada} se (jsonnode! = null) {// encontre os parâmetros necessários no controlador da mensagem jsonnode paramjsonnode = jsonNode.findValue (Paramname); if (paramjsonNode! = null) {return objectMapper.readValue (paramjsonNode.Traverse (), paramclass); }}} retornar nulo; }Configure o conversor de parâmetros que você definiu no arquivo de configuração do SRPINGMVC <MVC: Argument-Rolvers>
<MVC: Argumento-Resolvers> <!-Unified Pedido Processamento de informações, buscando dados do servicerequest-> <bean id = "requestbodyresolver"> <propriedade name = "objectmapper"> <Bean> </bean> </oilter name = "requestbodyParamName"> <Value> requestbody </value> </propriedade> </bean> </mvc: argument-resolvers>
Isso permite que os parâmetros na mensagem sejam corretamente identificados pelo SpringMVC.
Em seguida, temos que processar o token. Precisamos adicionar um interceptador SRPINGMVC para interceptar cada solicitação. Esta é uma função comum e não será descrita em detalhes.
Matcher M1 = Pattern.compile ("/" Token /":/"(.*? if (m1.find ()) {token = m1.group (1);} tokenmappol.verifyToken (token); // executa o processamento público de token e verifique seDessa forma, você pode obter o token e fazer processamento público.
3. Use Redis para armazenar para gerenciar tokens e informações de sessão correspondentes.
De fato, está apenas escrevendo uma classe de ferramentas de operação Redis. Como a primavera é usada como a estrutura principal do projeto e não usamos muitas funções do Redis, usamos diretamente a função Cachemanager fornecida pela primavera.
Configure org.springframework.data.redis.cache.rediscachemanager
<!-Variáveis globais do Cache Manager, etc. podem ser usadas para acessar-> <bean id = "cachemanager"> <fructor-arg> <ref bean = "redistemplate"/> </construtor-arg> <names name = "usePrefix" value = "true"/nomes da propriedade = "cacheReReRFix"> value = ":@webServiceInterface"/> </ Bean> </Property> <propriedade name = "expira"> <!-Período de validade do cache-> <pap> <purnt> <key> <daltion> tokenpoolcache </palue> </key> <!-Nome do cache do TokenPool-> </Valual>
4. Forneça uma API para armazenar e obter informações de sessão.
Através das preliminares acima, quase processamos o token. Em seguida, implementaremos o trabalho de gerenciamento de token.
Precisamos tornar o desenvolvimento de negócios conveniente para salvar e obter informações de sessão, e os tokens são transparentes.
importar java.util.hashmap; importar java.util.map; importar org.apache.commons.logging.log; importar org.apache.commons.logging.logfactory; importar org.springframework.cache.cache; importar org.springframework.cache.cache.value; org.springframework.cache.cache.valuewrapper; importar org.springframework.cache.cachemanager;/** * * * Nome da classe: tokenmappoolBean * Descrição: Token e Informações relacionadas Class de processamento de chamadas NUTEN (Class de Modificação: * @VerS V1.0 * @Date 22, 2016 *zuthorn * @BaThor NUB (2016 * @Athor NUBOTEM NUTEM @AUX @BaTr NUB (2016 * @Athor NUBATHOR NUUX @ATHOR NUTEM @ATHOR NUTEM @Aux. Log log = logFactory.getLog (tokenmappolbean.class); / ** token correspondente à solicitação atual*/ private Threadlocal <string> currentToken; Cachemanager privado Cachemanager; Cachename de string privado; Tokengenerator Private Tokengenerator; TokenMappolbean público (Cachemanager Cachemanager, Cachename de String, Tokengenerator Tokengenerator) {this.cachemanager = Cachemanager; this.cachename = Cachename; this.TokenGenerator = Tokengenerator; currentToken = new Threadlocal <String> (); } /*** Se o token for legal, retorne o token. Crie um novo token e retorne, * coloque o token no ThreadLocal e inicialize um tokenmap * @param token * @return token */ public string verifyToken (string token) {// log.info("CheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheC Sechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechechech verifyedToken = null; Cachemanager.getcache (Cachename); value.get () == NULL) {verificou -se o newToken (); Cache do cache = CacheManager.getcache (Cachename); (cache.get (newtoken)! = nulo); sessão */ Public Object getAttribute (chave de string) {cache cache = cachemanager.getcache (Cachename); TokenMapWrapper! = NULL) TokenMapWrapper.get (); pode ser o mais recente, que causará perda de dados. é armazenado, chachename: " + Cachename);} valueWrapper tokenmapWrapper = cache.get (currentToken.get ()); map <string, object> tokenmap = null; if (TokenMapWrapper! = null) {tokenMap = (map <string, object> tkenMapMap! verifyToken (CurrentToken.get ()); cache.put (currentToken.get (), tokenmap); @param token */ public void removeTenMap (string token) {if (token == null) {return; Token); Cachename;A variável Threadlocal é usada aqui porque uma solicitação corresponde a um encadeamento no contêiner do servlet e está no mesmo thread durante o ciclo de vida de uma solicitação, e vários threads compartilham o gerenciador de token ao mesmo tempo, para que essa variável local seja necessária para salvar a sequência de token.
Notas:
1. A chamada para verificar o método deve ser chamado no início de cada solicitação. E após a conclusão da solicitação, o Clear é chamado para limpar, para não fazer com que o verificador seja executado na próxima vez, mas o token é retirado do Threadlocal quando retornar. (Este bug me incomodou por vários dias, e os códigos de verificação de N de desenvolvimento da empresa não foram encontrados. Finalmente, após o teste, descobri que o interceptador não foi inserido quando o 404 ocorreu, por isso não chamei o método VerificationToken, o que causou o token no bom problema. Lembre -se das informações de exceção de retorno para o MELT POT POT).
2. O cliente deve salvar cada token ao encapsular a ferramenta HTTP e usá -lo para a próxima solicitação. O desenvolvimento do iOS da empresa solicitou terceirização, mas a terceirização não fez o que é necessário. Quando não está conectado, o token não é salvo. Cada vez que o token é passado, é nulo, resultando em um token sendo criado para cada solicitação, e o servidor cria um grande número de tokens inúteis.
usar
O método de uso também é muito simples. A seguir, o gerenciador de login encapsulado. Você pode consultar o aplicativo do Token Manager para o Login Manager.
importar org.apache.commons.logging.log; importar org.apache.commons.logging.logfactory; importar org.springframework.cache.cache; import org.springframework.cache.cache.valuewripper; import.springFrameAtE.cache.cache.cache.cache.valuewripper; import.springFrameSting.cacheManger; LoginManager * Descrição: LoginManager * Modificar registro: * @version v1.0 * @date 19 de julho de 2016 * @author niuxz * */public class LoginManager {LOGT STATIC PRIVADO FINAL LOG = logFactory.getLog (loginManager.class); Cachemanager privado Cachemanager; Cachename de string privado; Tokenmappolbean privado Tokenmappool; public LoginManager (Cachemanager Cachemanager, Cachename de String, TokenMappolBean TokenMappol) {this.cachemanager = Cachemanager; this.cachename = Cachename; this.TokenMappol = TokenMappol; } public void login (string userID) {log.info ("Login do usuário: userId =" + userID); Cache cache = cachemanager.getCache (cachene); ValueWrapper valueWrapper = cache.get (userID); String token = (string) (valueWrapper == null? Null: valueWrapper.get ()); tokenmappol.removetokenmap (token); // registro de login antes de registrar tokenmappol.setattribute (constants.logged_user_id, userID); cache.put (userID, tokenmappool.getToken ()); } public void logoutCurrent (string phonetel) {string curuserId = getCurrentUserID (); log.info ("Logout do usuário: userID =" + CURUSERID); tokenmappool.removetokenmap (tokenmappol.gettoken ()); // login if (curuserid! = null) {cache cache = cacheManager.getCache (cachename); cache.evict (curuserid); cache.evict (phonetel); }} / ** * Obtenha o ID do usuário atual * @return * / public string getCurrentUserId () {return (string) tokenmappol.getAttribute (constantes.logged_user_id); } public Cachemanager getCachemanager () {return cachemanager; } public string getCachename () {return Cachename; } public tokenmappolbean getTokenMappol () {return tokenmappool; } public void setCachemanager (Cachemanager Cachemanager) {this.cachemanager = cachemanager; } public void setCachename (String Cachename) {this.cachename = Cachename; } public void SettokenMappol (tokenmappolbean tokenmappool) {this.tokenmappool = tokenmappool; }}Abaixo está uma interface comum de código de verificação de SMS. Alguns aplicativos também usam sessão para armazenar códigos de verificação. Eu não recomendo usar este método. As desvantagens das sessões de armazenamento são bastante grandes. Apenas olhe para isso, não foi o que eu escrevi
public void sendvalicodeByphonenum (String phonenum, String hintmsg, string logSuffix) {validatephoneTimespace (); // Obtenha número de string de número aleatório de 6 bits = código codeUtil.getValidateCode (); log.info (código + "------>" + Phonenum); // Ligue para o código de verificação do SMS para enviar interface retstatus retstatus = msgsendutils.sendsms (código + hintmsg, phonenum); se (! lançar o novo throwstodataException (ServiceResponsecode.fail_invalid_params, "o código de verificação do telefone celular falhou em obter, tente novamente mais tarde"); } // Redefina a sessão tokenmappol.setAttribute (constantes.validate_phone, phonenum); tokenmappol.setAttribute (constantes.validate_phone_code, code.toString ()); tokenmappol.setAttribute (constantes.send_code_wrongnu, 0); tokenmappol.setAttribute (constantes.send_code_time, new Date (). getTime ()); Log.info (LogSuffix + Phonenum + "Código de verificação de SMS:" + código); }Resposta de processamento
Alguns alunos perguntarão se existe uma embalagem de mensagens tão responsiva?
@RequestMapping ("Record")@ResponseBodyPublic ServiceSponse Record (String message) {String userId = LoginManager.getCurrentUserID (); MessageBoardService.RecordMessage (UserID, Mensagem); Retorne o ServiceResponseBuilder.BuildSuccess (NULL);}Entre eles, o ServiceResponse está o pacote de resposta encapsulado VO. Só precisamos usar a anotação @ResponseBody do Springmvc. A chave é este construtor.
importar org.apache.commons.lang3.stringUtils; importar com.niuxz.base.pojo.servicerSponse; importar com.niuxz.utils.spring.springcontextutil; importar.niuxz.web.server.eSeSkokMaPoCoLBean; * @Date 25 de abril de 2016 * @author niuxz * */public class ServiceSesponseBuilder {/** * Crie uma mensagem de resposta bem -sucedida * * @param corpo * @return Uma resposta a service com operação bem -sucedida */public static -sponsewndsuccess (corpo de objeto) {retorna nova servir ((TGEKENMMMAPSOMMAPSONMAPONMAPONMAPONMAPONMAPONMAPONMAPONMAPONMAPEL (Body) {Return New ServicerSonsonse (((TlekenMom Springcontextutil.getbean ("tokenmappol")) .gettoken (), "sucesso de ação", corpo); } / ** * Crie uma mensagem de resposta bem -sucedida * * @param Body * @return Uma resposta do serviço com operação bem -sucedida * / public static serviceSponse buildSuccess (token string, corpo de objeto) {retorna uma nova resposta do servicer (token, "ação bem -sucedida", corpo); } / ** * Construa uma mensagem de resposta com falha * * @param falhascode * msg * @return Uma resposta a servir que falha na operação * / public static serviceSponsend buildFail (int falhecode, string msg) {return BuildFail (FailCode, msg, null); } / ** * Construa uma mensagem de resposta com falha * * @param frilcode * Msg Body * @return a uma resposta do servidor que falhou operação * / public static servicerSponse buildFail (int falhecode, string msg, corpo de objeto) {return s ServicerSponse (((tokenMappolBean) springContetTil.getBeanBeanBeanBeanBeanSonse ((TOKENMAPOLBEAN) SpringToTettil.getBeanBeanBeanBeanBeanSonse, Stringutils.isnotblank (msg)? }}Como usamos a forma de uma classe de ferramentas estáticas, não podemos injetar no objeto TokenMappol (gerenciador de token) através da primavera e, em seguida, podemos obtê -lo através da API fornecida pela primavera. Em seguida, ao construir as informações de resposta, ligue diretamente ao método getToken () do TokenMappol. Este método retornará a sequência de token ligada pelo thread atual. Novamente, é importante chamar claro manualmente após o término da solicitação (eu chamo isso através do interceptador global).
O exemplo acima do gerenciamento de informações de sessão de back -end do aplicativo que imita o mecanismo de sessão do J2EE é todo o conteúdo que compartilho com você. Espero que você possa lhe dar uma referência e espero que você possa apoiar mais o wulin.com.