之前也有 发过去 SpringBoot 整合 shiro 实现普通的用户名和密码登录的文章,所以今天主要记录一下,如何实现微信小程序用户使用shiro来登录。
1.首先还是先介绍一下数据库表。
CREATE TABLE `ums_user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`wx_open_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '微信openId',
`wx_union_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '微信unionId',
`wx_nickname` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '微信昵称',
`wx_headimg_url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '微信头像url',
`wx_gender` int(255) DEFAULT NULL COMMENT '微信性别 : ( 0 : 未知 | 1 : 男性 | 2 : 女性 )',
`wx_country` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '微信国家',
`wx_province` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '微信省份',
`wx_city` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '微信城市',
`wx_language` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '微信语言',
`nickname` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '昵称',
`login_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '登入名',
`pass_word` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码',
`salt` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '加盐',
`phone_number` char(11) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号',
`idcard_number` char(18) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '身份证号',
`idcard_name` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '身份证姓名',
`login_ip` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '最后登入ip',
`last_login_date` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后登入时间',
`version_number` int(255) unsigned NOT NULL DEFAULT '0' COMMENT '用于实现乐观锁更新',
`area_code` varchar(12) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '所在区域编码',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_wx_union_id` (`wx_union_id`) USING BTREE,
UNIQUE KEY `uk_phone_number` (`phone_number`) USING BTREE,
UNIQUE KEY `uk_login_name` (`login_name`) USING BTREE,
UNIQUE KEY `uk_incard_number` (`idcard_number`) USING BTREE,
KEY `idx_wx_open_id` (`wx_open_id`) USING BTREE,
KEY `idx_wx_province` (`wx_province`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='统一用户表';
2.导入相关依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--微信开源封装sdk-->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>3.3.0</version>
</dependency>
3.代码编写,数据库连接和实体类我就不写了,如果看过我之前那篇博客,就会发现流程一样,只不过代码编写变了而已
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import com.jt.mapper.UmsUserMapper;
import com.jt.pojo.UmsUser;
import com.jt.pojo.UmsUserExample;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @Author: zh
* @Date: 2020/9/8 9:48
*/
public class CustomWxRealm extends AuthorizingRealm {
@Autowired
private UmsUserMapper userMapper;
@Override
public Class getAuthenticationTokenClass() {
//支持的Token类的class
return WxMiniToken.class;
}
/**
* 是否支持该token
* Realm支持的类可以是token的子类或相同
* @param token
* @return
*/
@Override
public boolean supports(AuthenticationToken token){
return token != null && token instanceof WxMiniToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
WxMaUserInfo userInfo = (WxMaUserInfo) token.getPrincipal();
String openId = userInfo.getOpenId();
UmsUserExample example = new UmsUserExample();
example.createCriteria().andWxOpenIdEqualTo(openId);
// 根据 openId查询出用户,这里openId就相当于用户名
UmsUser umsUser = userMapper.selectOneByExample(example);
/**
* umsUser 为 null
* 表示当前是个新用户,那么就数据库添加一条用户数据,
* 反之 用户登录则更新用户数据
*/
if (null == umsUser){
UmsUser user = new UmsUser();
user.setWxOpenId(openId);
user.setWxNickname(userInfo.getNickName());
user.setWxGender(Integer.valueOf(userInfo.getGender()));
user.setWxLanguage(userInfo.getLanguage());
user.setWxCity(userInfo.getCity());
user.setWxProvince(userInfo.getProvince());
user.setWxCountry(userInfo.getCountry());
user.setWxHeadimgUrl(userInfo.getAvatarUrl());
user.setWxUnionId(userInfo.getUnionId());
userMapper.insertSelective(user);
return new SimpleAuthenticationInfo(user,"ok",this.getClass().getName());
}else {
umsUser.setWxOpenId(openId);
umsUser.setWxNickname(userInfo.getNickName());
umsUser.setWxGender(Integer.valueOf(userInfo.getGender()));
umsUser.setWxLanguage(userInfo.getLanguage());
umsUser.setWxCity(userInfo.getCity());
umsUser.setWxProvince(userInfo.getProvince());
umsUser.setWxCountry(userInfo.getCountry());
umsUser.setWxHeadimgUrl(userInfo.getAvatarUrl());
umsUser.setWxUnionId(userInfo.getUnionId());
userMapper.updateByPrimaryKeySelective(umsUser);
// 不要疑惑 这里的OK,相当于 用户登录的密码,但是微信登录是不用密码的所以我直接写死了
return new SimpleAuthenticationInfo(umsUser,"ok",this.getClass().getName());
}
}
}
import com.jt.conf.CustomSessionManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
1. @Author: zh
2. @Date: 2020/9/8 10:24
*/
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
System.out.println("执行 ShiroFilterFactoryBean.shiroFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/api/401");
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/api/WxLogin","anon");
filterChainDefinitionMap.put("/api/wxUserinfo","anon");
filterChainDefinitionMap.put("/api/401","anon");
// filterChainDefinitionMap.put("/**","authc");
filterChainDefinitionMap.put("/**","anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager());
securityManager.setRealm(customWxRealm());
return securityManager;
}
/**
* 自定义用户名密码登录 realm
* @return
*/
@Bean
public CustomWxRealm customWxRealm(){
CustomWxRealm customWxRealm = new CustomWxRealm();
// 配置 专属凭证匹配器
customWxRealm.setCredentialsMatcher(((token, info) -> true));
return customWxRealm;
}
public SessionManager sessionManager(){
CustomSessionManager sessionManager = new CustomSessionManager();
// session 超时时间 单位 毫秒 默认30分钟
sessionManager.setGlobalSessionTimeout(20000000);
return sessionManager;
}
}
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.shiro.authc.AuthenticationToken;
/**
1. @Author: zh
2. @Date: 2020/9/8 9:50
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WxMiniToken implements AuthenticationToken {
private WxMaUserInfo userInfo;
@Override
public Object getPrincipal() {
return userInfo;
}
@Override
public Object getCredentials() {
return "ok";
}
}
到这里shiro 的配置差不多接结束了,接下来是微信相关的配置的了
wx:
miniapp:
configs:
- appid: ******
secret: *********
token:
aesKey:
msgDataFormat: JSON
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
import com.google.common.collect.Maps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @Author: zh
* @Date: 2020/9/08 10:45
*/
@Configuration
@EnableConfigurationProperties(WxMaProperties.class)
public class WxMaConfiguration {
private WxMaProperties properties;
private static Map<String, WxMaMessageRouter> routers = Maps.newHashMap();
private static Map<String, WxMaService> maServices = Maps.newHashMap();
@Autowired
public WxMaConfiguration(WxMaProperties properties) {
this.properties = properties;
}
public static Map<String, WxMaMessageRouter> getRouters() {
return routers;
}
public static WxMaService getMaService(String appid) {
WxMaService wxService = maServices.get(appid);
if (wxService == null) {
throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid));
}
return wxService;
}
public static WxMaService getMaService() {
if (maServices.size() == 1){
Set<String> keySet = maServices.keySet();
return maServices.get(keySet.iterator().next());
}
throw new RuntimeException("存在多个maService, 无法决策");
}
@PostConstruct
public void init() {
List<WxMaProperties.Config> configs = this.properties.getConfigs();
if (configs == null) {
throw new RuntimeException("相关配置错误!");
}
maServices = configs.stream()
.map(a -> {
WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
config.setAppid(a.getAppid());
config.setSecret(a.getSecret());
config.setToken(a.getToken());
config.setAesKey(a.getAesKey());
config.setMsgDataFormat(a.getMsgDataFormat());
WxMaService service = new WxMaServiceImpl();
service.setWxMaConfig(config);
// routers.put(a.getAppid(), this.newRouter(service));
return service;
}).collect(Collectors.toMap(s -> s.getWxMaConfig().getAppid(), a -> a));
}
}
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* @Author: zh
* @Date: 2020/9/8 10:45
*/
@Data
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxMaProperties {
private List<Config> configs;
@Data
public static class Config {
/**
* 设置微信小程序的appid
*/
private String appid;
/**
* 设置微信小程序的Secret
*/
private String secret;
/**
* 设置微信小程序消息服务器配置的token
*/
private String token;
/**
* 设置微信小程序消息服务器配置的EncodingAESKey
*/
private String aesKey;
/**
* 消息格式,XML或者JSON
*/
private String msgDataFormat;
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
/**
* @Author: zh
* @Date: 2020/9/8 14:59
*/
//@ApiModel("登录信息类")
@Data
public class WxLoginVo {
public WxLoginVo(){}
// @ApiModelProperty("授权码")
@NotNull
private String code;
/**
* 小程序 AppID
*/
private String appid;
// @ApiModelProperty("用户信息对象,不包含 openid 等敏感信息")
private WxUserInfo userInfo;
// @ApiModelProperty("不包括敏感信息的原始数据字符串,用于计算签名")
private String rawData;
// @ApiModelProperty("使用 sha1( rawData + sessionkey ) 得到字符串,用于校验用户信息")
private String signature;
// @ApiModelProperty("包括敏感数据在内的完整用户信息的加密数据")
private String encryptedData;
// @ApiModelProperty("加密算法的初始向量")
private String iv;
@Data
@NoArgsConstructor
@AllArgsConstructor
class WxUserInfo{
/**
* 昵称
*/
private String nickName;
/**
* 头像url
*/
private String avatarUrl;
/**
* 性别 : 0 => 未知, 1 => 男性, 2 => 女性
*/
private Integer gender;
/**
* 国家
*/
private String country;
/**
* 省份
*/
private String province;
/**
* 城市
*/
private String city;
/**
* 语言, 显示 country,province,city 所用的语言
* en : 英文
* zh_CN : 简体中文
* zh_TW : 繁体中文
*/
private String language;
}
}
/**
* @Author: zh
* @Date: 2020/9/8 10:26
*/
@Api("微信用户登录")
@RestController
@RequestMapping("/api")
@Slf4j
public class WxUserLoginController {
public static final String WX_SESSION_KEY = WxUserLoginController.class.getName() + ".wx_session_key";
@ApiOperation("微信小程序用登录")
@PostMapping("/WxLogin")
public Object WxLogin(@RequestBody WxLoginVo loginVo) {
Objects.requireNonNull(loginVo,"code Cannot be null");
log.info("WxLoginVo:{}",loginVo);
Subject subject = SecurityUtils.getSubject();
// 获取微信用户相关信息
WxMaUserService userService
= WxMaConfiguration.getMaService(loginVo.getAppid()).getUserService();
// 获取微信用户session
WxMaJscode2SessionResult sessionInfo = null;
try {
sessionInfo = userService.getSessionInfo(loginVo.getCode());
} catch (WxErrorException e) {
e.printStackTrace();
}
if (!userService.checkUserInfo(sessionInfo.getSessionKey(),loginVo.getRawData(),loginVo.getSignature())){
return ResultUtil.decode();
}
WxMaUserInfo userInfo = userService.getUserInfo(sessionInfo.getSessionKey(), loginVo.getEncryptedData(), loginVo.getIv());
log.info("WxMaUserInfo:{}",userInfo);
subject.login(new WxMiniToken(userInfo));
subject.getSession().setAttribute(WxUserLoginController.WX_SESSION_KEY,sessionInfo.getSessionKey());
return ResultUtil.good(subject.getSession().getId());
}
@ApiOperation("获取用户信息")
@GetMapping("/wxUserinfo")
public Object info(){
UmsUser user = (UmsUser) SecurityUtils.getSubject().getPrincipal();
return ResultUtil.good(user);
}
@ApiOperation("用户未登录")
@GetMapping("/401")
public Object _401(){
return ResultUtil.unauthorized();
}
}
后端到这里就结束了,有可能会有小伙伴会疑惑前端要带什么参数给后端,所以这里也一起 分享出来。
这里的data 带的参数是重点
wx.login({
timeout: 10000,
success: (result) => {
console.log(result);
if (result.code) {
let accInfo = wx.getAccountInfoSync().miniProgram.appId;
request({
url: 'WxLogin',
method: 'post',
data: {
code: result.code,
appid: accInfo,
userInfo: res.detail.userInfo,
rawData: res.detail.rawData,
signature: res.detail.signature,
encryptedData: res.detail.encryptedData,
iv: res.detail.iv
},
isToken: false
}).then(res => {
if (res !== null) {
wx.setStorageSync("token", res.data);
if (res.data) {
this.getUserinfo()
}
} else {
wx.showToast({
title: "获取用户信息失败",
icon: 'none'
})
}
})
}
}
});
本文到处结束啦 如需转载 请备注本文出处