您好,登錄后才能下訂單哦!
小編這次要給大家分享的是基于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' //這個是固定的
如果不出意外的話,微信接口服務器會返回四個參數:
下面附上我的代碼:
@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) 的時效性,判斷登錄是否過期;
疑惑的是(openid,unionid )都是用戶唯一標識,不會因為wx.login(OBJECT)的過期而改變,所以要是沒有使用wx.getUserInfo(OBJECT)獲得的用戶信息,確實沒必要使用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, trustManagers, 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, Object> msgs, String url) throws ClientProtocolException, UnknownHostException, 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, 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 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, 0, 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, Object> msgs, String url) throws ClientProtocolException, UnknownHostException, IOException { CloseableHttpClient httpClient = HttpClients.createDefault(); try { 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())); // } // } // } // 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, Object> msgs, String url, Class<T> clazz) throws ClientProtocolException, UnknownHostException, IOException { String json = HttpUtils.post(msgs, url); T t = JSON.parseObject(json, clazz); return t; } public static <T> T get(Map<String, Object> msgs, String url, Class<T> clazz) throws ClientProtocolException, UnknownHostException, IOException { String json = HttpUtils.get(msgs, url); T t = JSON.parseObject(json, clazz); return t; } public static String postWithJson(Map<String, Object> msgs, String url) throws ClientProtocolException, 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, Object> msgs, String url, Class<T> clazz) throws ClientProtocolException, UnknownHostException, IOException { String json = HttpUtils.postWithJson(msgs, url); T t = JSON.parseObject(json, 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,null, 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,null, 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,null, 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,JSON.toJSONString(user), 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, String> valueOperations = redisTemplate.opsForValue(); valueOperations.set(key, value, 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; } }
看完這篇關于基于redis如何實現小程序登錄的文章,如果覺得文章內容寫得不錯的話,可以把它分享出去給更多人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。