本文共 8232 字,大约阅读时间需要 27 分钟。
微信小程序登录通常通过在后台上传一个 code,后台利用 code 调用微信端接口获取 openId 和 sessionKey。在某些场景下,sessionKey 可能不需要。接着,后台将 token 返回给小程序,后续请求中小程序需携带 token,后台需解析验证 token。
值得注意的是,小程序通常没有用户信息表,这与后台的用户表并不完全一致。
为简化代码改动,可以通过 openId 和 sessionKey 伪造一个用户名密码。这样可以在不修改用户表的情况下,直接在 LoginUser 类中添加 WxUser 属性。在 SysLoginService 类中扩展一个微信登录方法,该方法仅进行假授权,避免调用 UserDetailsServiceImpl.loadUserByUsername。这样可以实现对 token 的统一管理和验证。
WxUser 类在 common 模块下新建 WxUser 类,与 SysUser 同级。
package com.ruoyi.common.core.domain.entity;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 属性,并生成 getter 和 setter。同时重写 getPassword 和 getUsername 方法。
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;public class LoginUser implements UserDetails { private static final long serialVersionUID = 1L; private String token; private Long loginTime; private Long expireTime; 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; } @JsonIgnore @Override public boolean isAccountNonLocked() { return true; } @JsonIgnore @Override public boolean isCredentialsNonExpired() { return true; } @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 类中增加微信登录处理方法,进行假授权操作。
package com.ruoyi.sys.login.service;import com.ruoyi.common.core.domain.model.LoginUser;import com.ruoyi.security.service.SecurityContextHolder;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.stereotype.service;@Servicepublic class SysLoginService { private static final String Wx_PREFIX = "WX_"; public String wxLogin(String openId, String sessionKey) { LoginUser loginUser = new LoginUser(new WxUser(openId, sessionKey)); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( openId, sessionKey, loginUser ); SecurityContextHolder.getContext().setAuthentication(authenticationToken); return tokenService.createToken(loginUser); }} SysLoginController 类在 SysLoginController 类中增加微信登录接口。
package com.ruoyi.sys.login.controller;import com.ruoyi.common.constant.Constants;import com.ruoyi.security.service.SecurityContextHolder;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.stereotype.controller;import java.util.HashMap;import java.util.Map;@Controllerpublic class SysLoginController { private static final String LOGIN_PATH = "/api/wx/login"; @PostMapping(LOGIN_PATH) public ResponseEntity login(String appId, String code) { String _prefix = "【小程序登录】"; if (StringUtils.isBlank(appId)) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST, null, _prefix + "appId为空"); } if (StringUtils.isBlank(code)) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST, null, _prefix + "登录凭证不能为空"); } 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.isBlank(openId)) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST, null, _prefix + "登录失败,无效的登录凭证"); } Map response = new HashMap<>(); response.put(Constants.TOKEN, loginService.wxLogin(openId, sessionKey)); _log.info("{}生成的token:{}", _prefix, response.get(Constants.TOKEN)); return new ResponseEntity<>(response, HttpStatus.OK); }} SecurityConfig 类在 SecurityConfig 类中放行登录接口。
package com.ruoyi.security.config;import org.springframework.security.config.annotation.builders.WebSecurity;import org.springframework.security.config.annotation.web.builders.HttpInterceptor;import org.springframework.security.config.annotation.web.config.WebMvcConfigurer;@Configurationpublic class SecurityConfig extends WebMvcConfigurer { @Override public void addInterceptors(WebSecurity webSecurity) { webSecurity .antMatchers("/login", "/captchaImage", "/api/wx/login") .anonymous() ; }} 通过以上改造,可以实现微信小程序的登录授权。小程序登录时,后台接收 code,调用微信接口获取 openId 和 sessionKey,然后调用 wxLogin 方法生成 token。小程序后续请求需携带 token,后台通过 LoginUser 类验证 token。这一方案无需修改用户表,且通过伪造用户名密码的方式实现了统一管理,保证了系统安全性。
转载地址:http://nkatz.baihongyu.com/