H5W3
当前位置:H5W3 > JavaScript > 小程序 > 正文

【小程序】基于redis的小程序登录实现

基于redis的小程序登录实现

Gigass发布于 2020-05-25

基于redis的小程序登录实现

作者:gigass
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

**你好,这是我的第一篇博客.
因为前段时间做过一个小程序,所以去学习了一下小程序的登录流程.废话不多说,下面附上我的学习结果.**
这张图是小程序的登录流程解析:
【小程序】基于redis的小程序登录实现
小程序登陆授权流程:

  1. 在小程序端调用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’ //这个是固定的

如果不出意外的话,微信接口服务器会返回四个参数:
【小程序】基于redis的小程序登录实现

详情可以看下官方文档: 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) 的时效性,判断登录是否过期;
疑惑的是(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 &&paraMap.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;
}
}
redisjava小程序
阅读 385发布于 2020-05-25
本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议
avatar

Gigass
0 声望
0 粉丝

0 条评论
得票时间

avatar

Gigass
0 声望
0 粉丝

宣传栏

基于redis的小程序登录实现

作者:gigass
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

**你好,这是我的第一篇博客.
因为前段时间做过一个小程序,所以去学习了一下小程序的登录流程.废话不多说,下面附上我的学习结果.**
这张图是小程序的登录流程解析:
【小程序】基于redis的小程序登录实现
小程序登陆授权流程:

  1. 在小程序端调用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’ //这个是固定的

如果不出意外的话,微信接口服务器会返回四个参数:
【小程序】基于redis的小程序登录实现

详情可以看下官方文档: 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) 的时效性,判断登录是否过期;
疑惑的是(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 &&paraMap.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;
}
}

本文地址:H5W3 » 【小程序】基于redis的小程序登录实现

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址