博客
关于我
RuoYi-Vue微信小程序登录授权
阅读量:603 次
发布时间:2019-03-11

本文共 8619 字,大约阅读时间需要 28 分钟。

目前的框架中token是使用jwt生成,存储到redis控制token时效,而认证是使用UsernamePasswordAuthenticationToken实现的

微信小程序登录授权

需求分析

  1. 微信小程序登录是通过上传一个code到后台,后台根据code调用微信端接口获取到openIdsessionKey(有些场景下不需要),返回token给小程序,小程序后面的请求带上token,后台要解析验证token
  2. 小程序没有用户信息表,如果有,那和后台用的用户表也不是同一个表

解决方案

如果能通过openIdsessionKey伪造一个用户名密码,那代码改动起来会简单很多,只需要给一个类似SysUserWxUserLoginUser里可以多加一个WxUser属性,在SysLoginService扩展一个微信登录方法即可,而且登录方法内也只是做一个假的授权,并不去调用UserDetailsServiceImpl.loadUserByUsername,这样就很简单的实现统一管理token,验证token时候也没有问题

代码改造

新建WxUser类

common模块下新建一个WxUser,和SysUser同级

package com.ruoyi.common.core.domain.entity;import org.apache.commons.lang3.builder.ToStringBuilder;import org.apache.commons.lang3.builder.ToStringStyle;import com.ruoyi.common.core.domain.BaseEntity;/** * 微信用户实体 *  * @author Ricky */public class WxUser extends BaseEntity{       private static final long serialVersionUID = 1L;    /** 微信唯一标识 */    private String openId;        /** 微信会话秘钥 */    private String sessionKey;    public WxUser(String openId, String sessionKey)    {           super();        this.openId = openId;        this.sessionKey = sessionKey;    }    public String getOpenId()    {           return openId;    }    public void setOpenId(String openId)    {           this.openId = openId;    }    public String getSessionKey()    {           return sessionKey;    }    public void setSessionKey(String sessionKey)    {           this.sessionKey = sessionKey;    }        @Override    public String toString() {           return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)            .append("openId", getOpenId())            .append("sessionKey", getSessionKey())            .toString();    }}

修改LoginUser类

LoginUser中加入WxUser属性,生成gettersetter,并且改写getPasswordgetUsername 方法

package com.ruoyi.common.core.domain.model;import java.util.Collection;import java.util.Set;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import com.fasterxml.jackson.annotation.JsonIgnore;import com.ruoyi.common.core.domain.entity.SysUser;import com.ruoyi.common.core.domain.entity.WxUser;/** * 登录用户身份权限 *  * @author ruoyi */public class LoginUser implements UserDetails{       private static final long serialVersionUID = 1L;    /**     * 用户唯一标识     */    private String token;    /**     * 登录时间     */    private Long loginTime;    /**     * 过期时间     */    private Long expireTime;    /**     * 登录IP地址     */    private String ipaddr;    /**     * 登录地点     */    private String loginLocation;    /**     * 浏览器类型     */    private String browser;    /**     * 操作系统     */    private String os;    /**     * 权限列表     */    private Set
permissions; /** * 用户信息 */ private SysUser user; /** * 微信用户信息 */ private WxUser wxUser; public String getToken() { return token; } public void setToken(String token) { this.token = token; } public LoginUser() { } public LoginUser(SysUser user, Set
permissions) { this.user = user; this.permissions = permissions; } public LoginUser(WxUser wxUser) { this.wxUser = wxUser; } @JsonIgnore @Override public String getPassword() { return user != null ? user.getPassword() : wxUser.getSessionKey(); } @Override public String getUsername() { return user != null ? user.getUserName() : wxUser.getOpenId(); } /** * 账户是否未过期,过期无法验证 */ @JsonIgnore @Override public boolean isAccountNonExpired() { return true; } /** * 指定用户是否解锁,锁定的用户无法进行身份验证 * * @return */ @JsonIgnore @Override public boolean isAccountNonLocked() { return true; } /** * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 * * @return */ @JsonIgnore @Override public boolean isCredentialsNonExpired() { return true; } /** * 是否可用 ,禁用的用户不能身份验证 * * @return */ @JsonIgnore @Override public boolean isEnabled() { return true; } public Long getLoginTime() { return loginTime; } public void setLoginTime(Long loginTime) { this.loginTime = loginTime; } public String getIpaddr() { return ipaddr; } public void setIpaddr(String ipaddr) { this.ipaddr = ipaddr; } public String getLoginLocation() { return loginLocation; } public void setLoginLocation(String loginLocation) { this.loginLocation = loginLocation; } public String getBrowser() { return browser; } public void setBrowser(String browser) { this.browser = browser; } public String getOs() { return os; } public void setOs(String os) { this.os = os; } public Long getExpireTime() { return expireTime; } public void setExpireTime(Long expireTime) { this.expireTime = expireTime; } public Set
getPermissions() { return permissions; } public void setPermissions(Set
permissions) { this.permissions = permissions; } public SysUser getUser() { return user; } public void setUser(SysUser user) { this.user = user; } public WxUser getWxUser() { return wxUser; } public void setWxUser(WxUser wxUser) { this.wxUser = wxUser; } @Override public Collection
getAuthorities() { return null; }}

修改SysLoginService类

SysLoginService加入微信登录处理的方法,这里是假授权,因为没有用户表,如果你有用户表,可以和login方法一样去执行UserDetailsServiceImpl.loadUserByUsername验证用户后授权,至于如何区分是系统用户还是微信用户,可以在调用

authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));

时给用户名上加前缀来处理

/** * 微信登录验证 *  * @param openId 微信唯一标识 * @param sessionKey 微信会话密钥 * @return 结果 */ public String wxLogin(String openId, String sessionKey) {    	LoginUser loginUser = new LoginUser(new WxUser(openId, sessionKey));    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(openId, sessionKey);    authenticationToken.setDetails(loginUser);    SecurityContextHolder.getContext().setAuthentication(authenticationToken);    // 生成token    return tokenService.createToken(loginUser); }

修改SysLoginController类

SysLoginController中加入微信登录接口(请关注重点:String token = loginService.wxLogin(openId, sessionKey);

/**     * 小程序登录     *      * @param appId 小程序的appId     * @param code 小程序wx.login返回的临时凭证     * @return     */    @PostMapping("/api/wx/login")    public JsonResult login(String appId, String code)    {           String _prefix = "【小程序登录】";        _log.info("{}appId:{}, 临时凭证:{}", _prefix, appId, code);        try        {               if (StringUtils.isEmpty(appId))            {                   return JsonResult.error("appId为空");            }            // 用户登录凭证(有效期五分钟)            if (StringUtils.isEmpty(code))            {                   return JsonResult.error("登录凭证不能为空");            }            WxConfig config = new WxConfigImpl(appId);            WxaLogin wxaLogin = new WxaLogin(config);            String result = wxaLogin.code2Session(code);            _log.info("{}微信根据code获取用户信息结果:{}", _prefix, result);            JSONObject code2SessionRes = JSON.parseObject(result);            String openId = code2SessionRes.getString("openid");            String sessionKey = code2SessionRes.getString("session_key");            if (StringUtils.isEmpty(openId))            {                   return JsonResult.error("登录失败,无效的登录凭证");            }            JSONObject res = new JSONObject();            // 生成令牌            String token = loginService.wxLogin(openId, sessionKey);            res.put(Constants.TOKEN, token);            _log.info("{}生成的token:{}", _prefix, token);            return JsonResult.success().put("data", res);        }        catch (Exception e)        {               String msg = "接口异常";            if (StringUtils.isNotEmpty(e.getMessage()))            {                   msg = e.getMessage();            }            _log.error("{}接口发生异常:{}", _prefix, e);            return JsonResult.error(msg);        }    }

修改SecurityConfig类

SecurityConfig放行登录接口

// 对于登录login 验证码captchaImage 允许匿名访问.antMatchers("/login", "/captchaImage", "/api/wx/login").anonymous()

总结

大功告成,使用你的小程序调用接口吧,登录后调用接口需要带上token,携带方式和vue一样,并且已经实现了token续签,如果有其他应用,登录方式还可以按现在这种来,如果可以一张用户表的那都不用改,不是同一张用户表的就可以按这种方式伪造用户名密码,因为code换取openIdsessionKey这个操作一定是安全的操作,而且获取token的前提就是必须成功获取微信的这两个参数,所以不用担心伪造用户名密码欺骗授权对系统安全造成威胁

转载地址:http://nkatz.baihongyu.com/

你可能感兴趣的文章
Mysql8在Windows上离线安装时忘记root密码
查看>>
MySQL8找不到my.ini配置文件以及报sql_mode=only_full_group_by解决方案
查看>>
mysql8的安装与卸载
查看>>
MySQL8,体验不一样的安装方式!
查看>>
MySQL: Host '127.0.0.1' is not allowed to connect to this MySQL server
查看>>
Mysql: 对换(替换)两条记录的同一个字段值
查看>>
mysql:Can‘t connect to local MySQL server through socket ‘/var/run/mysqld/mysqld.sock‘解决方法
查看>>
MYSQL:基础——3N范式的表结构设计
查看>>
MYSQL:基础——触发器
查看>>
Mysql:连接报错“closing inbound before receiving peer‘s close_notify”
查看>>
mysqlbinlog报错unknown variable ‘default-character-set=utf8mb4‘
查看>>
mysqldump 参数--lock-tables浅析
查看>>
mysqldump 导出中文乱码
查看>>
mysqldump 导出数据库中每张表的前n条
查看>>
mysqldump: Got error: 1044: Access denied for user ‘xx’@’xx’ to database ‘xx’ when using LOCK TABLES
查看>>
Mysqldump参数大全(参数来源于mysql5.5.19源码)
查看>>
mysqldump备份时忽略某些表
查看>>
mysqldump实现数据备份及灾难恢复
查看>>
mysqldump数据库备份无法进行操作只能查询 --single-transaction
查看>>
mysqldump的一些用法
查看>>