回复 刷新

暂无评论

微信小程序登录 + 基于token的身份验证

在这里插入图片描述

图里其实说的很清楚了,清理下流程:

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<String> 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<String> result = new SingleResult<String>(); //第三步:调用service.weChatLogin(model):后台检查openid是否存在,返回openid对应的用户 WeChatLoginResult<UserAccount> 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<UserAccount> weChatLogin(WeChatLoginModel model){ WeChatLoginResult<UserAccount> 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,放行

拦截器介绍一下:

1、preHandle:在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理;
2、postHandle:在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView;
3、afterCompletion:在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面);

第八步:

request里面有userid,后台就可以识别是对哪个用户做处理

  • 130
  • 0
  • 0