这张图是小程序的登录流程解析:
小程序登陆授权流程:
在小程序端调用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"text-align: center">
详情可以看下官方文档: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)) "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; } }作者:gigass
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。总结