官方时序图如下:
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
图里其实说的很清楚了,清理下流程:
1.前端调用wx.login()获取code值
2.前端通过调用wx.getUserInfo获取iv、rawData、signature、encryptedData等加密数据,传递给后端
3.服务器通过code请求api换回session_key和openid
4.服务器通过前端给的rawData 加获取的session_key使用sha1加密,计算出signature1
5.比对前端传的signature和自己算出来的signature1是否一致(防止数据不一致)
6.用AES算法解密encryptedData里的敏感数据
7.拿着敏感数据后做自己的逻辑
8.通知前端登陆成功
** 这里只是想拿到用户的openid,则直接1,3就可以做到了。如下:
第一步:
通过wx.login(微信前端–小程序)接口获取code,将code传到后台
注意:
code的来源:是用户打开小程序的时候,随机生成的,是腾讯生成的,每个code只能使用一次,因此,理论上这个code是安全的
package cn.wmyskxz.springboot.model.user;
/**
* @Author: Yangke
* @Date: 2019/3/31 15:52
**/
public class WeChatLoginModel {
String code;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
第二步:
后台通过code访问微信(腾讯)接口,微信(腾讯)接口返回当前登录的信息:session_key及openid
返回的openid是每个用户唯一的,通过这个 可以匹配 微信(腾讯)的用户 跟 我们的用户,就是我们后台通过openid来判断这个人是谁,
UserController.java 微信小程序登录
/**
* 微信小程序登录
*
* 登录成功后,将用户身份信息及session_key存入token
* @param model
* @return
*/
@ResponseBody
@PostMapping("/weChatLogin")
public SingleResult weChatLogin(@RequestBody WeChatLoginModel model){
/**
* 登录日志:
* id\ userid\ date\ wx_code\ createTime
* create table loginLog (
id varchar(50) primary key,
userId varchar(50),
logindate date,
wxcode varchar(100),
createtime datetime
);
*/
SingleResult result = new SingleResult();
//第三步:调用service.weChatLogin(model):后台检查openid是否存在,返回openid对应的用户
WeChatLoginResult loginResult = service.weChatLogin(model);
//第四步:
UserAccount user = loginResult.getUser();
if(user == null ){
result.setCode(0);
result.setMessage("登录失败");
}
else {
User u = new User();
u.setId(user.getId());
u.setPassword(user.getPassword() == null ? user.getWxopenid() : user.getPassword());
u.setSessionKey(loginResult.getSession_key());
String token = getToken(u);
result.setToken(token);
result.setCode(1);
result.setMessage("登陆成功");
}
return result;
}
其中:就是下面的第三步
//调用service.weChatLogin(model)
WeChatLoginResult loginResult = service.weChatLogin(model);
第三步:
后台检查openid是否存在,
去UserService.java
@Override
public WeChatLoginResult weChatLogin(WeChatLoginModel model){
WeChatLoginResult result = null;
try {
// code -> openid
String urlFormat = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code";
String url = String.format(urlFormat, WeChat.appId, WeChat.secret, model.getCode());
String json = WeChat.sendGet(url);
//将json字符串转化成对象
result = JSON.parseObject(json, WeChatLoginResult.class);
if(result.getErrcode() == null){
// 去数据库 检查 openId 是否存在 不存在就新建用户
UserAccount user = userAccount.wechatOpenIdIsExists(result.getOpenid());
if(user == null || user.getId() == null){
// 不存在,就是第一次登录:新建用户信息
user = new UserAccount();
user.setId(UUID.randomUUID().toString());
user.setWxopenid(result.getOpenid());
user.setLasttime(new Date());
userAccount.insert(user);
}
else {
//如果存在,就不是第一次登录,更新最后登录时间
user.setLasttime(new Date());
userAccount.updateByPrimaryKeySelective(user);
}
result.setUser(user);
// 保存登录日志
LoginLog log = new LoginLog();
log.setId(UUID.randomUUID().toString());
log.setCreatetime(new Date());
log.setLogindate(new Date());
log.setUserid(user.getId());
log.setWxcode(model.getCode());
loginLog.insert(log);
}
else {
System.out.println(json);
}
}
catch (Exception e){
System.out.println(e.getMessage());
}
return result;
}
去数据库中检查openid是否存在:
UserAccountMapper.java
@Select("select * from useraccount where wxOpenId = #{wxOpenId}")
UserAccount wechatOpenIdIsExists(String wxOpenId);
(1)如果不存在:就是该用户的第一次登录,后台数据库新添加一个用户信息
如果存在:就不是该用户的第一次登录,以前登陆过,就更新后台数据库中该用户的第一次登录时间
(2) 返回用户信息
第四步:
下发token
//第四步:
UserAccount user = loginResult.getUser();
if(user == null ){
result.setCode(0);
result.setMessage("登录失败");
}
else {
User u = new User();
u.setId(user.getId());
//用户如果是第一次登录,那就是没有密码的,这里用openid当做密码
u.setPassword(user.getPassword() == null ? user.getWxopenid() : user.getPassword());
u.setSessionKey(loginResult.getSession_key());
//利用User.class中的信息生成token
String token = getToken(u);
//下发token
result.setToken(token);
result.setCode(1);
result.setMessage("登陆成功");
}
return result;
}
其中生成token的步骤:BaseController.java
利用JWT框架生成token
package cn.wmyskxz.springboot.controllers;
import cn.wmyskxz.springboot.model.User;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
/**
* @Author: Yangke
* @Date: 2019/3/28 21:12
**/
public abstract class BaseController {
protected String getToken(User user) {
String token="";
token= JWT.create()
.withKeyId(user.getId())
.withIssuer("www.ikertimes.com")
.withIssuedAt(new Date())
.withJWTId("jwt.ikertimes.com")
.withClaim("session_key", user.getSessionKey())
.withAudience(user.getId())
.sign(Algorithm.HMAC256(user.getPassword()));
return token;
}
}
至此,再理一下上面的步骤:
(1)微信小程序通过访问wx.login获得一个code,返回给后台
(2)后台拿着这个code,调用腾讯的接口,获取到openid、seesion-key等信息,openid是用户唯一的
(3)后台拿着openid去数据库中检查,该用户是否是第一次登陆
如果是第一次登陆,那么就新建一个用户–UserAcount;如果不是第一次登陆,就修改该用户的最后登录时间
不管是不是第一次登录,都有了一个用户
(4)然后根据用户的信息利用JWT生成token,下发给微信小程序
第五步
微信小程序收到token后,存起来
第六步
微信小程序请求后台
微信小程序把token放在请求头中
第七步
先介绍一个注解:
Authorize
说明:如果有这个注解,就需要验证token
package cn.wmyskxz.springboot.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author: Yangke
* @Date: 2019/3/28 19:57
*authorize 是判断 是否需要 token
**/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Authorize {
boolean required() default true;
}
用拦截器,验证token
package cn.wmyskxz.springboot.interceptors;
import cn.wmyskxz.springboot.annotation.AllowAnonymous;
import cn.wmyskxz.springboot.annotation.Authorize;
import cn.wmyskxz.springboot.model.User;
import cn.wmyskxz.springboot.service.UserService;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* @Author: Yangke
* @Date: 2019/3/28 20:00
*
* 获取token并验证token
**/
public class AuthorizationInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
//拦截器:请求之前preHandle
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
// 如果不是映射到方法直接通过
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//检查是否有passtoken注释,有则跳过认证,注意:其中这个注解多余了
if (method.isAnnotationPresent(AllowAnonymous.class)) {
AllowAnonymous passToken = method.getAnnotation(AllowAnonymous.class);
if (passToken.required()) {
return true;
}
}
//检查有没有需要用户权限的注解
//如果有注解Authorize,就需要验证token
if (method.isAnnotationPresent(Authorize.class)) {
Authorize userLoginToken = method.getAnnotation(Authorize.class);
if (userLoginToken.required()) {
String token = httpServletRequest.getHeader("authorization");// 从 http 请求头中取出 token
// 执行认证
if (token == null) {
throw new RuntimeException("无token,请重新登录");
}
// 获取 token 中的 user id
String userId;
try {
// 获取 userid
userId = JWT.decode(token).getKeyId();
// 添加request参数,用于传递userid
httpServletRequest.setAttribute("currentUser", userId);
// 根据userId 查询用户信息
User user = userService.getUserById(userId);
if (user == null) {
throw new RuntimeException("用户不存在,请重新登录");
}
try {
String session_key = JWT.decode(token).getClaim("session_key").as(String.class);
// 添加request参数,用于传递userid
httpServletRequest.setAttribute("sessionKey", session_key);
}
catch (Exception e){
}
// 验证 密码
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("401");
}
} catch (JWTDecodeException j) {
throw new RuntimeException("401");
}
return true;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, ModelAndView modelAndView) throws Exception {
}
//拦截器:请求之后:afterCompletion
@Override
public void afterCompletion(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, Exception e) throws Exception {
}
}
流程:
1、从http请求头中取出token
String token = httpServletRequest.getHeader(“authorization”);
2、如果没有token,抛出异常,请用户登录
如果有token,利用JWT从token中取出userid,添加到request参数
3、根据userid去后台数据库中查询用户是否存在,如果不存在,抛出异常:用户不存在,请重新登录
User user = userService.getUserById(userId);
这个方法:
@Override
public User getUserById(String id) {
UserAccount u = userAccount.selectByPrimaryKey(id);
User user = new User();
user.setId(u.getId());
user.setPassword(u.getPassword() == null ? u.getWxopenid() : u.getPassword());
user.setUsername(u.getUsername());
return user;
}
4、如果用户存在,再利用JWT从token中取出seesion-key,添加到request参数
String session_key = JWT.decode(token).getClaim(“session_key”).as(String.class);
5、验证密码:因为我生成token的时候,是存了密码的,这个就是检查一下密码对不对
验证 token里面的密码 跟 你存的 是不是一样
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("401");
}
6、最终token验证成功,返回true,放行
拦截器介绍一下:
preHandle:在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理;
postHandle:在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView (这个博主就基本不怎么用了);
afterCompletion:在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面);
第八步:
request里面有userid,后台就可以识别是对哪个用户做处理
————————————————
版权声明:本文为CSDN博主「每天开心成为别人的望尘莫及」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_39474604/article/details/100016352