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

基于redis程序登录" title="小程序登录">小程序登录实现

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;

}

}

以上是 【小程序】基于redis的小程序登录实现 的全部内容, 来源链接: utcz.com/a/106198.html

回到顶部