基於redis的小程式登入實現方法流程分析
這張圖是小程式的登入流程解析:
小程式登陸授權流程:
在小程式端呼叫wx.login()獲取code,由於我是做後端開發的這邊不做贅述,直接貼上程式碼了.有興趣的直接去官方文件看下,連結放這裡: wx.login()
wx.login({ success (res) { if (res.code) { //發起網路請求 wx.request({ url: 'https://test.com/onLogin',data: { code: res.code } }) } else { console.log('登入失敗!' + res.errMsg) } } })
小程式前端登入後會獲取code,呼叫自己的開發者服務介面,呼叫個get請求:
GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
需要得四個引數:
appid:小程式appid
secret: 小程式金鑰
js_code: 剛才獲取的code
grant_type:‘authorization_code' //這個是固定的
如果不出意外的話,微信介面伺服器會返回四個引數:
詳情可以看下官方文件:jscode2session
下面附上我的程式碼:
@AuthIgnore @RequestMapping("/login") @ResponseBody public ResponseBean openId(@RequestParam(value = "code",required = true) String code,@RequestParam(value = "avatarUrl") String avatarUrl,@RequestParam(value = "city") String city,@RequestParam(value = "country") String country,@RequestParam(value = "gender") String gender,@RequestParam(value = "language") String language,@RequestParam(value = "nickName") String nickName,@RequestParam(value = "province") String province,HttpServletRequest request) { // 小程式端獲取的CODE ResponseBean responseBean = new ResponseBean(); try { boolean check = (StringUtils.isEmpty(code)) ? true : false; if (check) { responseBean = new ResponseBean(false,UnicomResponseEnums.NO_CODE); return responseBean; } //將獲取的使用者資料存入資料庫; Map<String,Object> msgs = new HashMap<>(); msgs.put("appid",appId); msgs.put("secret",secret); msgs.put("js_code",code); msgs.put("grant_type","authorization_code"); // java的網路請求,返回字串 String data = HttpUtils.get(msgs,Constants.JSCODE2SESSION); logger.info("======> " + data); String openId = JSONObject.parseObject(data).getString("openid"); String session_key = JSONObject.parseObject(data).getString("session_key"); String unionid = JSONObject.parseObject(data).getString("unionid"); String errcode = JSONObject.parseObject(data).getString("errcode"); String errmsg = JSONObject.parseObject(data).getString("errmsg"); JSONObject json = new JSONObject(); int userId = -1; if (!StringUtils.isBlank(openId)) { Users user = userService.selectUserByOpenId(openId); if (user == null) { //新建一個使用者資訊 Users newUser = new Users(); newUser.setOpenid(openId); newUser.setArea(city); newUser.setSex(Integer.parseInt(gender)); newUser.setNickName(nickName); newUser.setCreateTime(new Date()); newUser.setStatus(0);//初始 if (!StringUtils.isBlank(unionid)) { newUser.setUnionid(unionid); } userService.instert(newUser); userId = newUser.getId(); } else { userId = user.getId(); } json.put("userId",userId); } if (!StringUtils.isBlank(session_key) && !StringUtils.isBlank(openId)) { //這段可不用redis存,直接返回session_key也可以 String userAgent = request.getHeader("user-agent"); String sessionid = tokenService.generateToken(userAgent,session_key); //將session_key存入redis redisUtil.setex(sessionid,session_key + "###" + userId,Constants.SESSION_KEY_EX); json.put("token",sessionid); responseBean = new ResponseBean(true,json); } else { responseBean = new ResponseBean<>(false,null,errmsg); } return responseBean; } catch (Exception e) { e.printStackTrace(); responseBean = new ResponseBean(false,UnicomResponseEnums.JSCODE2SESSION_ERRO); return responseBean; } }
解析:
這邊我的登入獲取的session_key出於安全性考慮沒有直接在前端傳輸,而是存到了redis裡面給到前端session_key的token傳輸,
而且session_key的銷燬時間是20分鐘,時間內可以重複獲取使用者資料.
如果只是簡單使用或者對安全性要求不嚴的話可以直接傳session_key到前端儲存.
session_key的作用:
校驗使用者資訊(wx.getUserInfo(OBJECT)返回的signature);
解密(wx.getUserInfo(OBJECT)返回的encryptedData);
按照官方的說法,wx.checksession是用來檢查 wx.login(OBJECT) 的時效性,判斷登入是否過期;
如果使用了wx.getUserInfo(OBJECT)獲得的使用者資訊,還是有必要使用wx.checksession()來檢查wx.login(OBJECT) 是否過期的,因為使用者有可能修改了頭像、暱稱、城市,省份等資訊,可以通過檢查wx.login(OBJECT) 是否過期來更新著些資訊;
小程式的登入狀態維護本質就是維護session_key的時效性
這邊附上我的HttpUtils工具程式碼,如果只要用get的話可以複製部分:
如果是直
/** * HttpUtils工具類 * * @author */ public class HttpUtils { /** * 請求方式:post */ public static String POST = "post"; /** * 編碼格式:utf-8 */ private static final String CHARSET_UTF_8 = "UTF-8"; /** * 報文頭部json */ private static final String APPLICATION_JSON = "application/json"; /** * 請求超時時間 */ private static final int CONNECT_TIMEOUT = 60 * 1000; /** * 傳輸超時時間 */ private static final int SO_TIMEOUT = 60 * 1000; /** * 日誌 */ private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class); /** * @param protocol * @param url * @param paraMap * @return * @throws Exception */ public static String doPost(String protocol,String url,Map<String,Object> paraMap) throws Exception { CloseableHttpClient httpClient = null; CloseableHttpResponse resp = null; String rtnValue = null; try { if (protocol.equals("http")) { httpClient = HttpClients.createDefault(); } else { // 獲取https安全客戶端 httpClient = HttpUtils.getHttpsClient(); } HttpPost httpPost = new HttpPost(url); List<NameValuePair> list = msgs2valuePairs(paraMap); // List<NameValuePair> list = new ArrayList<NameValuePair>(); // if (null != paraMap &¶Map.size() > 0) { // for (Entry<String,Object> entry : paraMap.entrySet()) { // list.add(new BasicNameValuePair(entry.getKey(),entry // .getValue().toString())); // } // } RequestConfig requestConfig = RequestConfig.custom() .setSocketTimeout(SO_TIMEOUT) .setConnectTimeout(CONNECT_TIMEOUT).build();// 設定請求和傳輸超時時間 httpPost.setConfig(requestConfig); httpPost.setEntity(new UrlEncodedFormEntity(list,CHARSET_UTF_8)); resp = httpClient.execute(httpPost); rtnValue = EntityUtils.toString(resp.getEntity(),CHARSET_UTF_8); } catch (Exception e) { logger.error(e.getMessage()); throw e; } finally { if (null != resp) { resp.close(); } if (null != httpClient) { httpClient.close(); } } return rtnValue; } /** * 獲取https,單向驗證 * * @return * @throws Exception */ public static CloseableHttpClient getHttpsClient() throws Exception { try { TrustManager[] trustManagers = new TrustManager[]{new X509TrustManager() { public void checkClientTrusted( X509Certificate[] paramArrayOfX509Certificate,String paramString) throws CertificateException { } public void checkServerTrusted( X509Certificate[] paramArrayOfX509Certificate,String paramString) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }}; SSLContext sslContext = SSLContext .getInstance(SSLConnectionSocketFactory.TLS); sslContext.init(new KeyManager[0],trustManagers,new SecureRandom()); SSLContext.setDefault(sslContext); sslContext.init(null,null); SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory( sslContext,SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); HttpClientBuilder clientBuilder = HttpClients.custom() .setSSLSocketFactory(connectionSocketFactory); clientBuilder.setRedirectStrategy(new LaxRedirectStrategy()); CloseableHttpClient httpClient = clientBuilder.build(); return httpClient; } catch (Exception e) { throw new Exception("http client 遠端連線失敗",e); } } /** * post請求 * * @param msgs * @param url * @return * @throws ClientProtocolException * @throws UnknownHostException * @throws IOException */ public static String post(Map<String,Object> msgs,String url) throws ClientProtocolException,UnknownHostException,IOException { CloseableHttpClient httpClient = HttpClients.createDefault(); try { HttpPost request = new HttpPost(url); List<NameValuePair> valuePairs = msgs2valuePairs(msgs); // List<NameValuePair> valuePairs = new ArrayList<NameValuePair>(); // if (null != msgs) { // for (Entry<String,Object> entry : msgs.entrySet()) { // if (entry.getValue() != null) { // valuePairs.add(new BasicNameValuePair(entry.getKey(),// entry.getValue().toString())); // } // } // } request.setEntity(new UrlEncodedFormEntity(valuePairs,CHARSET_UTF_8)); CloseableHttpResponse resp = httpClient.execute(request); return EntityUtils.toString(resp.getEntity(),CHARSET_UTF_8); } finally { httpClient.close(); } } /** * post請求 * * @param msgs * @param url * @return * @throws ClientProtocolException * @throws UnknownHostException * @throws IOException */ public static byte[] postGetByte(Map<String,IOException { CloseableHttpClient httpClient = HttpClients.createDefault(); InputStream inputStream = null; byte[] data = null; try { HttpPost request = new HttpPost(url); List<NameValuePair> valuePairs = msgs2valuePairs(msgs); // List<NameValuePair> valuePairs = new ArrayList<NameValuePair>(); // if (null != msgs) { // for (Entry<String,CHARSET_UTF_8)); CloseableHttpResponse response = httpClient.execute(request); try { // 獲取相應實體 HttpEntity entity = response.getEntity(); if (entity != null) { inputStream = entity.getContent(); data = readInputStream(inputStream); } return data; } catch (Exception e) { e.printStackTrace(); } finally { httpClient.close(); return null; } } finally { httpClient.close(); } } /** 將流 儲存為資料陣列 * @param inStream * @return * @throws Exception */ public static byte[] readInputStream(InputStream inStream) throws Exception { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); // 建立一個Buffer字串 byte[] buffer = new byte[1024]; // 每次讀取的字串長度,如果為-1,代表全部讀取完畢 int len = 0; // 使用一個輸入流從buffer裡把資料讀取出來 while ((len = inStream.read(buffer)) != -1) { // 用輸出流往buffer裡寫入資料,中間引數代表從哪個位置開始讀,len代表讀取的長度 outStream.write(buffer,len); } // 關閉輸入流 inStream.close(); // 把outStream裡的資料寫入記憶體 return outStream.toByteArray(); } /** * get請求 * * @param msgs * @param url * @return * @throws ClientProtocolException * @throws UnknownHostException * @throws IOException */ public static String get(Map<String,IOException { CloseableHttpClient httpClient = HttpClients.createDefault(); try { List<NameValuePair> valuePairs = msgs2valuePairs(msgs); // List<NameValuePair> valuePairs = new ArrayList<NameValuePair>(); // if (null != msgs) { // for (Entry<String,// entry.getValue().toString())); // } // } // } // EntityUtils.toString(new UrlEncodedFormEntity(valuePairs),// CHARSET); url = url + "?" + URLEncodedUtils.format(valuePairs,CHARSET_UTF_8); HttpGet request = new HttpGet(url); CloseableHttpResponse resp = httpClient.execute(request); return EntityUtils.toString(resp.getEntity(),CHARSET_UTF_8); } finally { httpClient.close(); } } public static <T> T post(Map<String,Class<T> clazz) throws ClientProtocolException,IOException { String json = HttpUtils.post(msgs,url); T t = JSON.parseObject(json,clazz); return t; } public static <T> T get(Map<String,Class<T> clazz) throws ClientProtocolException,IOException { String json = HttpUtils.get(msgs,clazz); return t; } public static String postWithJson(Map<String,IOException { CloseableHttpClient httpClient = HttpClients.createDefault(); try { String jsonParam = JSON.toJSONString(msgs); HttpPost post = new HttpPost(url); post.setHeader("Content-Type",APPLICATION_JSON); post.setEntity(new StringEntity(jsonParam,CHARSET_UTF_8)); CloseableHttpResponse response = httpClient.execute(post); return new String(EntityUtils.toString(response.getEntity()).getBytes("iso8859-1"),CHARSET_UTF_8); } finally { httpClient.close(); } } public static <T> T postWithJson(Map<String,IOException { String json = HttpUtils.postWithJson(msgs,clazz); return t; } public static List<NameValuePair> msgs2valuePairs(Map<String,Object> msgs) { List<NameValuePair> valuePairs = new ArrayList<NameValuePair>(); if (null != msgs) { for (Entry<String,Object> entry : msgs.entrySet()) { if (entry.getValue() != null) { valuePairs.add(new BasicNameValuePair(entry.getKey(),entry.getValue().toString())); } } } return valuePairs; } }
接傳session_key到前端的,下面的可以不用看了.
如果是redis存的sesssion_key的token的話,這邊附上登陸的時候的token轉換為session_key.
自定義攔截器註解:
import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AuthIgnore { }
攔截器部分程式碼:
@Override public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception { AuthIgnore annotation; if(handler instanceof HandlerMethod) { annotation = ((HandlerMethod) handler).getMethodAnnotation(AuthIgnore.class); }else{ return true; } //如果有@AuthIgnore註解,則不驗證token if(annotation != null){ return true; } // //獲取微信access_token; // if(!redisUtil.exists(Constants.ACCESS_TOKEN)){ // Map<String,Object> msgs = new HashMap<>(); // msgs.put("appid",appId); // msgs.put("secret",secret); // msgs.put("grant_type","client_credential"); // String data = HttpUtils.get(msgs,Constants.GETACCESSTOKEN); // java的網路請求,返回字串 // String errcode= JSONObject.parseObject(data).getString("errcode"); // String errmsg= JSONObject.parseObject(data).getString("errmsg"); // if(StringUtils.isBlank(errcode)){ // //儲存access_token // String access_token= JSONObject.parseObject(data).getString("access_token"); // long expires_in=Long.parseLong(JSONObject.parseObject(data).getString("expires_in")); // redisUtil.setex("ACCESS_TOKEN",access_token,expires_in); // }else{ // //異常返回資料攔截,返回json資料 // response.setCharacterEncoding("UTF-8"); // response.setContentType("application/json; charset=utf-8"); // PrintWriter out = response.getWriter(); // ResponseBean<Object> responseBean=new ResponseBean<>(false,errmsg); // out = response.getWriter(); // out.append(JSON.toJSON(responseBean).toString()); // return false; // } // } //獲取使用者憑證 String token = request.getHeader(Constants.USER_TOKEN); // if(StringUtils.isBlank(token)){ // token = request.getParameter(Constants.USER_TOKEN); // } // if(StringUtils.isBlank(token)){ // Object obj = request.getAttribute(Constants.USER_TOKEN); // if(null!=obj){ // token=obj.toString(); // } // } // //token憑證為空 // if(StringUtils.isBlank(token)){ // //token不存在攔截,返回json資料 // response.setCharacterEncoding("UTF-8"); // response.setContentType("application/json; charset=utf-8"); // PrintWriter out = response.getWriter(); // try{ // ResponseBean<Object> responseBean=new ResponseBean<>(false,UnicomResponseEnums.TOKEN_EMPTY); // out = response.getWriter(); // out.append(JSON.toJSON(responseBean).toString()); // return false; // } // catch (Exception e) { // e.printStackTrace(); // response.sendError(500); // return false; // } // } if(token==null||!redisUtil.exists(token)){ //使用者未登入,返回json資料 response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); PrintWriter out = response.getWriter(); try{ ResponseBean<Object> responseBean=new ResponseBean<>(false,UnicomResponseEnums.DIS_LOGIN); out = response.getWriter(); out.append(JSON.toJSON(responseBean).toString()); return false; } catch (Exception e) { e.printStackTrace(); response.sendError(500); return false; } } tokenManager.refreshUserToken(token); return true; } }
過濾器配置:
@Configuration public class InterceptorConfig extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authorizationInterceptor()) .addPathPatterns("/**")// 攔截所有請求,通過判斷是否有 @AuthIgnore註解 決定是否需要登入 .excludePathPatterns("/user/login");//排除登入 } @Bean public AuthorizationInterceptor authorizationInterceptor() { return new AuthorizationInterceptor(); } }
token管理:
@Service public class TokenManager { @Resource private RedisUtil redisUtil; //生成token(格式為token:裝置-加密的使用者名稱-時間-六位隨機數) public String generateToken(String userAgentStr,String username) { StringBuilder token = new StringBuilder("token:"); //裝置 UserAgent userAgent = UserAgent.parseUserAgentString(userAgentStr); if (userAgent.getOperatingSystem().isMobileDevice()) { token.append("MOBILE-"); } else { token.append("PC-"); } //加密的使用者名稱 token.append(MD5Utils.MD5Encode(username) + "-"); //時間 token.append(new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + "-"); //六位隨機字串 token.append(UUID.randomUUID().toString()); System.out.println("token-->" + token.toString()); return token.toString(); } /** * 登入使用者,建立token * * @param token */ //把token存到redis中 public void save(String token,Users user) { if (token.startsWith("token:PC")) { redisUtil.setex(token,JSON.toJSONString(user),Constants.TOKEN_EX); } else { redisUtil.setex(token,Constants.TOKEN_EX); } } /** * 重新整理使用者 * * @param token */ public void refreshUserToken(String token) { if (redisUtil.exists(token)) { String value=redisUtil.get(token); redisUtil.setex(token,value,Constants.TOKEN_EX); } } /** * 使用者退出登陸 * * @param token */ public void loginOut(String token) { redisUtil.remove(token); } /** * 獲取使用者資訊 * * @param token * @return */ public Users getUserInfoByToken(String token) { if (redisUtil.exists(token)) { String jsonString = redisUtil.get(token); Users user =JSONObject.parseObject(jsonString,Users.class); return user; } return null; } }
redis工具類:
@Component public class RedisUtil { @Resource private RedisTemplate<String,String> redisTemplate; public void set(String key,String value) { ValueOperations<String,String> valueOperations = redisTemplate.opsForValue(); valueOperations.set(key,value); } public void setex(String key,String value,long seconds) { ValueOperations<String,seconds,TimeUnit.SECONDS); } public Boolean exists(String key) { return redisTemplate.hasKey(key); } public void remove(String key) { redisTemplate.delete(key); } public String get(String key) { ValueOperations<String,String> valueOperations = redisTemplate.opsForValue(); return valueOperations.get(key); } }
最後redis要實現序列化,序列化最終的目的是為了物件可以跨平臺儲存,和進行網路傳輸。而我們進行跨平臺儲存和網路傳輸的方式就是IO,而我們的IO支援的資料格式就是位元組陣列。本質上儲存和網路傳輸 都需要經過 把一個物件狀態儲存成一種跨平臺識別的位元組格式,然後其他的平臺才可以通過位元組資訊解析還原物件資訊。
@Configuration @ConditionalOnClass(RedisOperations.class) @EnableConfigurationProperties(RedisProperties.class) public class RedisConfig { @Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object,Object> redisTemplate( RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object,Object> template = new RedisTemplate<>(); //使用fastjson序列化 FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class); // value值的序列化採用fastJsonRedisSerializer template.setValueSerializer(fastJsonRedisSerializer); template.setHashValueSerializer(fastJsonRedisSerializer); // key的序列化採用StringRedisSerializer template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean(StringRedisTemplate.class) public StringRedisTemplate stringRedisTemplate( RedisConnectionFactory redisConnectionFactory) { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
作者:gigass
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
總結
到此這篇關於基於redis的小程式登入實現的文章就介紹到這了,更多相關redis小程式登入內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!