SpringBoot 整合shiro 之 实现微信小程序登录

SpringBoot 整合shiro 之 实现微信小程序登录

之前也有 发过去 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.代码编写,数据库连接和实体类我就不写了,如果看过我之前那篇博客,就会发现流程一样,只不过代码编写变了而已

  1. 还是先自定义一个 Realm
    CustomWxRealm.java

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());
		}
	}
}

  1. 配置 ShiroConfig

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;
	}
}
  1. 自定义 WxMiniToken 类
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 的配置差不多接结束了,接下来是微信相关的配置的了

  1. 在 application.yml 里配置 微信小程序 相关信息
wx:
  miniapp:
    configs:
      - appid: ******
        secret: *********	
        token:
        aesKey:
        msgDataFormat: JSON

  1. WxMaConfiguration 配置,主要是读取application.yml 里相关配置,校验前端微信小程序 配置的APPID 和 application.yml 里配置的微信APPID是否一致

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));
	}



}

  1. WxMaProperties 获取application.yml 配置中wx.miniapp的参数值

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;
	}
}

  1. WxLoginVo 用于接收前端的传入的参数

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;
	}

}

  1. WxUserLoginController

/**
 * @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'
                })
              }

            })
          }
        }
      });

在这里插入图片描述

本文到处结束啦 如需转载 请备注本文出处

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值