本文共 8619 字,大约阅读时间需要 28 分钟。
目前的框架中
token
是使用jwt
生成,存储到redis
控制token
时效,而认证是使用UsernamePasswordAuthenticationToken
实现的
code
到后台,后台根据code
调用微信端接口获取到openId
和sessionKey
(有些场景下不需要),返回token
给小程序,小程序后面的请求带上token
,后台要解析验证token
如果能通过openId
和sessionKey
伪造一个用户名密码,那代码改动起来会简单很多,只需要给一个类似SysUser
的WxUser
,LoginUser
里可以多加一个WxUser属性,在SysLoginService
扩展一个微信登录方法即可,而且登录方法内也只是做一个假的授权,并不去调用UserDetailsServiceImpl.loadUserByUsername
,这样就很简单的实现统一管理token
,验证token
时候也没有问题
在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
中加入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;/** * 登录用户身份权限 * * @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 Setpermissions; /** * 用户信息 */ 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
加入微信登录处理的方法,这里是假授权,因为没有用户表,如果你有用户表,可以和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
中加入微信登录接口(请关注重点: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
放行登录接口
// 对于登录login 验证码captchaImage 允许匿名访问.antMatchers("/login", "/captchaImage", "/api/wx/login").anonymous()
大功告成,使用你的小程序调用接口吧,登录后调用接口需要带上
token
,携带方式和vue
一样,并且已经实现了token
续签,如果有其他应用,登录方式还可以按现在这种来,如果可以一张用户表的那都不用改,不是同一张用户表的就可以按这种方式伪造用户名密码,因为code
换取openId
和sessionKey
这个操作一定是安全的操作,而且获取token
的前提就是必须成功获取微信的这两个参数,所以不用担心伪造用户名密码欺骗授权对系统安全造成威胁
转载地址:http://nkatz.baihongyu.com/