微信小程序登录
- 微信用户,授权信息,相关实体类
- 微信用户表
- 实体类
- 获取微信的token
- 常量抽取
- httpClient工具,用于发送请求获取token
- nbplus pom
- 工具类
- 请求微信后台,获取openid,返回的是json
- 登录或者注册完整业务实现,登录成功返回jwt签名
- cotroller实现
- 业务实现
- 设置拦截器对要访问的方法拦截,并判断jwt token
- 增加注解,灵活判断哪些controlelr方法需要配token验证
- 拦截器
登录,微信api
后台获取微信token,包括openid在内
登录流程
1. 小程序发起wx.login请求获取jscode
2. 获取jscode,发送到自己的后端
3. 自己的后台发请求带上jscode, appid(当前用户的唯一标识), secretkey到微信的后端获取session_key, openid
4. 返回session_key或者openid到页面
5. 页面再发请求到后台验证openid 是否存在, 存在即登录。
不存在 保存openid到个人数据库,即注册。
6. 登录成功后,返回jwt token签名
7. 页面通过微信app.js 的globalData保存openid,jwt token信息
微信用户,授权信息,相关实体类
微信用户表
create table forum_info.wei_xin_user
(id int auto_increment primary key,user_name varchar(30) null,user_pwd varchar(30) null,# 这里的密码没用,登录只是验证open_id, # 所以说小程序登录是不需要密码的额。它问你是否授权。# 然后收集你的其它个人信息。create_date date null,wx_open_id varchar(30) null
);
实体类
package top.bitqian.entity;import java.util.Date;
import java.io.Serializable;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;/*** <p>* 微信用户注册实体类~* </p>** @author echo lovely* @since 2020-12-04*/
@Data
@EqualsAndHashCode(callSuper = false)
public class WeiXinUser implements Serializable {private static final long serialVersionUID = 1L;@TableId(type = IdType.AUTO)private Integer id;private String userName;private String userPwd;private Date createDate;private String wxOpenId;}
获取微信的token
文档里面的字段搬过来。
package top.bitqian.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** <p>* 调用微信接口 返回授权信息~* </p>* @author echo lovely* @date 2020/12/4 21:56*/@Data
@NoArgsConstructor
@AllArgsConstructor
public class Code2Session {//用户唯一标识private String openid;// 会话密钥private String session_key;// 用户在开放平台的唯一标识符private String unionid;// 错误码private Integer errcode;// 错误信息private String errmsg;}
常量抽取
package top.bitqian.config;/*** @author echo lovely* @date 2020/12/4 22:03*/public class WeiXinPostParamConstant {/*** 小程序appid*/public static String APP_ID = "小程序开发者官网生成";/*** 小程序 appSecret*/public static String SECRET = "小程序开发者官网生成";/*** 登录时获取的 code*/public static String JS_CODE = "";/*** 授权类型,此处只需填写 authorization_code* grant_type ctrl + shit + u*/public static String GRANT_TYPE = "";}
httpClient工具,用于发送请求获取token
nbplus pom
这里是boot,省略了version
<!-- http工具类 --><!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><!--<version>4.5.6</version>--></dependency><!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore --><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpcore</artifactId><!--<version>4.4.10</version>--></dependency>
工具类
package top.bitqian.config;import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Map;import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;/*** HTTPClient 工具类.* @author echo lovely* @date 2020/12/4 21:44*/@Configuration
@SuppressWarnings(value = "all")
public class HttpClientUtil {private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);private static PoolingHttpClientConnectionManager connectionManager;private static String DEFAULT_STR = "";private static String UTF_8 = "UTF-8";private final static int CONNECT_TIMEOUT = 3000;// 连接超时毫秒 ps:表示建立连接的超时时间private final static int SOCKET_TIMEOUT = 10000;// 传输超时毫秒 ps:表示数据传输处理时间private final static int REQUESTCONNECT_TIMEOUT = 2000;// 从线程池获取连接超时时间毫秒private final static int MAX_TOTAL = 50;// 线程池的最大连接数private final static int CONNECT_DEFAULT_ROUTE = 5;// 每个路由默认基础的连接数private static void init() {if (connectionManager == null) {connectionManager = new PoolingHttpClientConnectionManager();connectionManager.setMaxTotal(MAX_TOTAL);// 整个连接池最大连接数// 可用空闲连接过期时间,重用空闲连接时会先检查是否空闲时间超过这个时间,如果超过,释放socket重新建立//connectionManager.setValidateAfterInactivity(50000);connectionManager.setDefaultMaxPerRoute(CONNECT_DEFAULT_ROUTE);// 每路由最大连接数,默认值是2}}/*** 通过连接池获取HttpClient** @return*/private static CloseableHttpClient getHttpClient() {init();Builder builder = RequestConfig.custom();RequestConfig config = builder.setSocketTimeout(SOCKET_TIMEOUT).setConnectTimeout(CONNECT_TIMEOUT).setConnectionRequestTimeout(REQUESTCONNECT_TIMEOUT).build();CloseableHttpClient client = HttpClients.custom().setMaxConnPerRoute(CONNECT_DEFAULT_ROUTE).disableConnectionState().setDefaultRequestConfig(config).setConnectionManager(connectionManager).build();return client;}/*** @param url* @return* @throws IOException*/public static String httpGetRequest(String url) throws Exception {HttpGet httpGet = new HttpGet(url);return getResult(httpGet);}public static String httpGetRequest(String url, Map<String, Object> params) throws Exception {URIBuilder ub = new URIBuilder();ub.setPath(url);ArrayList<NameValuePair> pairs = covertParams2NVPS(params);ub.setParameters(pairs);HttpGet httpGet = new HttpGet(ub.build());return getResult(httpGet);}public static String httpGetRequest(String url, Map<String, Object> headers, Map<String, Object> params) throws Exception {URIBuilder ub = new URIBuilder();ub.setPath(url);ArrayList<NameValuePair> pairs = covertParams2NVPS(params);ub.setParameters(pairs);HttpGet httpGet = new HttpGet(ub.build());for (Map.Entry<String, Object> param : headers.entrySet()) {httpGet.addHeader(param.getKey(), String.valueOf(param.getValue()));}return getResult(httpGet);}public static String httpPostRequest(String url) throws IOException {HttpPost httpPost = new HttpPost(url);return getResult(httpPost);}public static String httpPostRequest(String url, Map<String, Object> params) throws Exception {HttpPost httpPost = new HttpPost(url);ArrayList<NameValuePair> pairs = covertParams2NVPS(params);httpPost.setEntity(new UrlEncodedFormEntity(pairs, UTF_8));return getResult(httpPost);}public static String httpPostRequest(String url, String jsonParams) throws Exception {HttpPost httpPost = new HttpPost(url);StringEntity se = new StringEntity(jsonParams, UTF_8);httpPost.setEntity(se);httpPost.setHeader("Content-Type", "application/json");return getResult(httpPost);}public static String httpPostXMLDataRequest(String url, String xmlData) throws Exception {HttpPost httpPost = new HttpPost(url);httpPost.addHeader("Content-Type", "text/xml");httpPost.setEntity(new StringEntity(xmlData, UTF_8));return getResult(httpPost);}/*** post** @param url (a=3&b=2 形式)* @param headers 请求头* @param params 参数* @return* @throws IOException*/public static String httpPostRequest(String url, Map<String, Object> headers, Map<String, Object> params) throws Exception {HttpPost httpPost = new HttpPost(url);for (Map.Entry<String, Object> param : headers.entrySet()) {httpPost.addHeader(param.getKey(), String.valueOf(param.getValue()));}ArrayList<NameValuePair> pairs = covertParams2NVPS(params);httpPost.setEntity(new UrlEncodedFormEntity(pairs, UTF_8));return getResult(httpPost);}private static ArrayList<NameValuePair> covertParams2NVPS(Map<String, Object> params) {ArrayList<NameValuePair> pairs = new ArrayList<NameValuePair>();for (Map.Entry<String, Object> param : params.entrySet()) {pairs.add(new BasicNameValuePair(param.getKey(), String.valueOf(param.getValue())));}return pairs;}/*** post** @param url (JSON 形式)* @param headers 请求头* @param params 参数* @return* @throws IOException*/public static String httpPostRequest(String url, Map<String, Object> headers, String jsonParams) throws Exception {HttpPost httpPost = new HttpPost(url);for (Map.Entry<String, Object> param : headers.entrySet()) {httpPost.setHeader(param.getKey(), String.valueOf(param.getValue()));}StringEntity se = new StringEntity(jsonParams, UTF_8);httpPost.setEntity(se);return getResult(httpPost);}/*** 处理Http请求** @param request* @return string* @throws IOException*/private static String getResult(HttpRequestBase request) throws IOException {CloseableHttpClient httpClient = getHttpClient();CloseableHttpResponse response = null;InputStream in = null;try {response = httpClient.execute(request);HttpEntity entity = response.getEntity();in = response.getEntity().getContent();if (entity != null) {String result = EntityUtils.toString(entity, Charset.forName(UTF_8));response.close();return result;}} catch (ConnectTimeoutException e) {// 连接超时异常logger.error("connect timeout {}", e);} catch (SocketTimeoutException e) {// 读取超时异常logger.error("read timeout {}", e);} catch (ClientProtocolException e) {// 该异常通常是协议错误导致:比如构造HttpGet对象时传入协议不对(将'http'写成'htp')or响应内容不符合logger.error("protocol exception {}", e);} catch (ParseException e) {// 解析异常logger.error("parse exception {}", e);} catch (IOException e) {// 该异常通常是网络原因引起的,如HTTP服务器未启动等logger.error("network exception {}", e);} catch (Exception e) {logger.error("other exception {}", e);} finally {if (response != null) {try {response.close();} catch (IOException e) {e.printStackTrace();}}//in.close();作用就是将用完的连接释放,下次请求可以复用//这里特别注意的是,如果不使用in.close();而仅仅使用response.close();结果就是连接会被关闭,并且不能被复用,这样就失去了采用连接池的意义。if (in != null) {try {in.close();} catch (IOException e) {e.printStackTrace();}}}return DEFAULT_STR;}public static void main(String[] args) {String str = null;try {str = HttpClientUtil.httpGetRequest("https://www.baidu.com");} catch (Exception e) {e.printStackTrace();}System.out.println(str);}
}
请求微信后台,获取openid,返回的是json
json转实体类用
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency>
/*** 获取openid session_key* @param jsCode 小程序请求到的jsCode* @return 授权信息~*/@Overridepublic Code2Session getWinXinJson(String jsCode) {// https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_codeStringBuilder url = new StringBuilder();url.append("https://api.weixin.qq.com/sns/jscode2session?appid=");url.append(WeiXinPostParamConstant.APP_ID);url.append("&secret=");url.append(WeiXinPostParamConstant.SECRET);url.append("&js_code=");url.append(jsCode);url.append("&grant_type=authorization_code");try {String weiXinJson = HttpClientUtil.httpGetRequest(url.toString());System.out.println(weiXinJson);return new ObjectMapper().readValue(weiXinJson, Code2Session.class);} catch (Exception e) {e.printStackTrace();}return null;}
登录或者注册完整业务实现,登录成功返回jwt签名
我的java json web token签名实现,工具类
cotroller实现
package top.bitqian.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.bitqian.config.AuthTokenCheck;
import top.bitqian.entity.Code2Session;
import top.bitqian.entity.WeiXinUser;
import top.bitqian.service.WeiXinUserService;import javax.servlet.http.HttpServletRequest;
import java.util.Date;/*** 微信登录 controller~* @author echo lovely* @date 2020/12/4 21:36*/@RestController
@Slf4j
public class WeiXinUserController {@Autowiredprivate WeiXinUserService weiXinUserService;// 调用微信的接口获取 app_id@RequestMapping("/getCode/{jsCode}")public Code2Session getWinXinJson(@PathVariable("jsCode") String jsCode) {return weiXinUserService.getWinXinJson(jsCode);}// 用户提交wx_id 过来注册@RequestMapping("/wx_user/register")public boolean doRegister(WeiXinUser user) {// 账号存在..WeiXinUser tmpUser = weiXinUserService.getWeiXinUserByOpenId(user.getWxOpenId());if (tmpUser != null) {return false;}// 不存在,即注册user.setCreateDate(new Date(System.currentTimeMillis()));weiXinUserService.addWeiXinUser(user);return true;}@RequestMapping("/wx_user/login")public String doLogin(WeiXinUser weiXinUser) throws Exception {return weiXinUserService.doLogin(weiXinUser);}@AuthTokenCheck@RequestMapping("/wx_user/msg")public String testJwtToken(HttpServletRequest request) {Object userId = request.getAttribute("id");System.out.println("userId from page token~=======>" + userId);return "auth by token~" + userId;}}
业务实现
package top.bitqian.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import top.bitqian.config.HttpClientUtil;
import top.bitqian.config.JwtUtil;
import top.bitqian.config.WeiXinPostParamConstant;
import top.bitqian.entity.Code2Session;
import top.bitqian.entity.WeiXinUser;
import top.bitqian.mapper.WeiXinUserMapper;
import top.bitqian.service.WeiXinUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;/*** <p>* 服务实现类* </p>** @author echo lovely* @since 2020-12-04*/
@Service
public class WeiXinUserServiceImpl extends ServiceImpl<WeiXinUserMapper, WeiXinUser> implements WeiXinUserService {/*** 获取openid session_key* @param jsCode 小程序请求到的jsCode* @return 授权信息~*/@Overridepublic Code2Session getWinXinJson(String jsCode) {// https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_codeStringBuilder url = new StringBuilder();url.append("https://api.weixin.qq.com/sns/jscode2session?appid=");url.append(WeiXinPostParamConstant.APP_ID);url.append("&secret=");url.append(WeiXinPostParamConstant.SECRET);url.append("&js_code=");url.append(jsCode);url.append("&grant_type=authorization_code");try {String weiXinJson = HttpClientUtil.httpGetRequest(url.toString());System.out.println(weiXinJson);return new ObjectMapper().readValue(weiXinJson, Code2Session.class);} catch (Exception e) {e.printStackTrace();}return null;}@Autowiredprivate WeiXinUserMapper userMapper;public WeiXinUser getWeiXinUserByOpenId(String openId) {WeiXinUser tmpUser = new WeiXinUser();tmpUser.setWxOpenId(openId);QueryWrapper<WeiXinUser> queryWrapper = new QueryWrapper<>(tmpUser);return userMapper.selectOne(queryWrapper);}@Overridepublic String doLogin(WeiXinUser user) throws Exception {// 登录需要:// 1. 根据小程序传来的openid验证数据库中的id,看是否存在~WeiXinUser weiXInUser = getWeiXinUserByOpenId(user.getWxOpenId());System.out.println("doLogin----->" + weiXInUser);if (weiXInUser != null) {// 2. 存在 返回jwt签名~ 页面保存return JwtUtil.createToken(weiXInUser);}// 2. 不存在 return ""return null;}@Overridepublic void addWeiXinUser(WeiXinUser user) {// userMapper.addWeiXinUser(user);userMapper.insert(user);}
}
设置拦截器对要访问的方法拦截,并判断jwt token
增加注解,灵活判断哪些controlelr方法需要配token验证
package top.bitqian.config;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 要给哪些方法进行token验证*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AuthTokenCheck {}
拦截器
package top.bitqian.config;import com.auth0.jwt.interfaces.Claim;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;/*** 拦截器* @author echo lovely* @date 2020/12/5 20:28*/@Configuration
public class InterceptorConfig implements WebMvcConfigurer {/*** 拦截器需要被注册进..* @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new TokenInterceptor());}}class TokenInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {System.out.println("进来l...");// 对方法进行拦截if (! (handler instanceof HandlerMethod)) {return true;}// 方法对象HandlerMethod handlerMethod = (HandlerMethod) handler;Method methodTarget = handlerMethod.getMethod();// 对加了AuthTokenCheck注解 的方法进行token验证~boolean isTokenTarget = methodTarget.isAnnotationPresent(AuthTokenCheck.class);if (isTokenTarget) {// 进行token验证, 头部里面的token// String authorizeToken = request.getHeader("authorize_token");String authorizeToken = request.getParameter("authorize_token");try {Map<String, Claim> claimMap = JwtUtil.parseToken(authorizeToken);// 解析获取token中的用户id~ 也可根据相应的键获取其它信息Integer id = claimMap.get("id").asInt();String userName = claimMap.get("userName").asString();System.out.println(id + "\t" + userName);// 放入request中request.setAttribute("id", id);return true;} catch (Exception e) {return false;}}return true;}
}