SpringBoot 集成 微信绑定 微信登录

SpringBoot 集成 微信登录

重写一个认证逻辑 实现AuthenticationProvider

import com.hzjtcl.commons.security.service.HzjtclUserDetailsService;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetails;
/**
 * @author gczjt
 * @date 2018/8/5 手机登录校验逻辑 验证码登录、社交登录
 */
@Slf4j
public class MobileAuthenticationProvider implements AuthenticationProvider {

	private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

	@Getter
	@Setter
	private HzjtclUserDetailsService userDetailsService;

	@Override
	@SneakyThrows
	public Authentication authenticate(Authentication authentication) {
		MobileAuthenticationToken mobileAuthenticationToken = (MobileAuthenticationToken) authentication;

		String principal = mobileAuthenticationToken.getPrincipal().toString();
		// 获取用户信息时进行微信登录认证
		UserDetails userDetails = userDetailsService.loadUserBySocial(principal);
		if (userDetails == null) {
			log.debug("Authentication failed: no credentials provided");
			throw new BadCredentialsException(messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.noopBindAccount", "Noop Bind Account"));
		}

		// 检查账号状态
		//detailsChecker.check(userDetails);

		MobileAuthenticationToken authenticationToken = new MobileAuthenticationToken(userDetails,
				userDetails.getAuthorities());
		authenticationToken.setDetails(mobileAuthenticationToken.getDetails());
		return authenticationToken;
	}

	@Override
	public boolean supports(Class<?> authentication) {
		return MobileAuthenticationToken.class.isAssignableFrom(authentication);
	}

}
import lombok.SneakyThrows;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;

import java.util.Collection;

/**
 * @author gczjt
 * @date 2018/1/9 手机号登录令牌
 */
public class MobileAuthenticationToken extends AbstractAuthenticationToken {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

	private final Object principal;

	public MobileAuthenticationToken(String mobile) {
		super(null);
		this.principal = mobile;
		setAuthenticated(false);
	}

	public MobileAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
		super(authorities);
		this.principal = principal;
		super.setAuthenticated(true);
	}

	@Override
	public Object getPrincipal() {
		return this.principal;
	}

	@Override
	public Object getCredentials() {
		return null;
	}

	@Override
	@SneakyThrows
	public void setAuthenticated(boolean isAuthenticated) {
		if (isAuthenticated) {
			throw new IllegalArgumentException(
					"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
		}

		super.setAuthenticated(false);
	}

	@Override
	public void eraseCredentials() {
		super.eraseCredentials();
	}

}

登录认证及获取用户信息

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

/**
 * @author gczjt
 * @date 2018/8/15
 */
public interface HzjtclUserDetailsService extends UserDetailsService {

	/**
	 * 根据社交登录code 登录
	 * @param code TYPE@CODE
	 * @return UserDetails
	 * @throws UsernameNotFoundException
	 */
	UserDetails loadUserBySocial(String social) throws UsernameNotFoundException;


	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException ;

}
import com.hzjtcl.commons.security.enums.UserStatusEnum;
import com.hzjtcl.commons.security.feign.AccountFeignClient;
import com.hzjtcl.commons.security.service.HzjtclUserDetailsService;
import com.hzjtcl.commons.security.user.UserDetail;
import com.hzjtcl.commons.tools.exception.ErrorCode;
import com.hzjtcl.commons.tools.exception.RenException;
import com.hzjtcl.commons.tools.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 * UserDetailsService
 *
 * @author Mark sunlightcs@gmail.com
 */
@Service
public class RenUserDetailsServiceImpl implements HzjtclUserDetailsService {
    @Autowired(required=false)
    private AccountFeignClient accountFeignClient;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Result<UserDetail> result = accountFeignClient.getByUsername(username);
        UserDetail userDetail = result.getData();
        //账号不存在
        if(userDetail == null){
            throw new RenException(ErrorCode.ACCOUNT_NOT_EXIST);
        }

        //账号不可用
        if(userDetail.getStatus() == UserStatusEnum.DISABLE.value()){
            throw new RenException(ErrorCode.ACCOUNT_DISABLE);
        }

        return userDetail;
    }

    @Override
    public UserDetails loadUserBySocial(String social) throws UsernameNotFoundException {
        Result<UserDetail> result = accountFeignClient.loadUserBySocial(social);
        UserDetail userDetail = result.getData();
        //账号不存在
        if(userDetail == null){
            return null;
        }

        //账号不可用
        if(userDetail.getStatus() == UserStatusEnum.DISABLE.value()){
            return null;
        }

        return userDetail;
    }
}
import com.hzjtcl.commons.security.feign.fallback.AccountFeignClientFallbackFactory;
import com.hzjtcl.commons.security.user.UserDetail;
import com.hzjtcl.commons.tools.constant.ServiceConstant;
import com.hzjtcl.commons.tools.utils.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

/**
 * 账号接口
 *
 * @author Mark sunlightcs@gmail.com
 */
@FeignClient(name = ServiceConstant.HZJTCL_ADMIN_SERVER, contextId = "AccountFeignClient", fallbackFactory = AccountFeignClientFallbackFactory.class)
public interface AccountFeignClient {

    /**
     * 根据用户名,获取用户信息
     * @param username  用户名
     */
    @PostMapping("sys/user/getByUsername")
    Result<UserDetail> getByUsername(@RequestParam("username") String username);


    /**
     *  通过社交账号或手机号查询用户、角色信息
     * @param inStr appid@code
     */
    @PostMapping("sys/user/socialinfo")
    Result<UserDetail> loadUserBySocial(@RequestParam("inStr") String inStr);



}

controller 层自己去实现

service用户信息认证


    @Autowired
    private  Map<String, LoginHandler> loginHandlerMap;

 @Override
    public SysUserDTO getSocialinfoByinStr(String inStr) {
        String[] inStrs = inStr.split(StringPool.AT);
        String type = inStrs[0];
        String loginStr = inStrs[1];
        // 通过前端传递来的参数在map中获取  type为解析出的类的名称 通过@Component注解获取对应类
        return loginHandlerMap.get(type).handle(loginStr);
    }

handler 处理类

/**
 * 登录处理器
 */
public interface LoginHandler {

	/***
	 * 数据合法性校验
	 * @param loginStr 通过用户传入获取唯一标识
	 * @return
	 */
	Boolean check(String loginStr);

	/**
	 * 通过用户传入获取唯一标识
	 * @param loginStr
	 * @return
	 */
	String identify(String loginStr);

	/**
	 * 通过openId 获取用户信息
	 * @param identify
	 * @return
	 */
	SysUserDTO info(String identify);

	/**
	 * 处理方法
	 * @param loginStr 登录参数
	 * @return
	 */
	SysUserDTO handle(String loginStr);

}
public abstract class AbstractLoginHandler implements LoginHandler {

	/***
	 * 数据合法性校验
	 * @param loginStr 通过用户传入获取唯一标识
	 * @return 默认不校验
	 */
	@Override
	public Boolean check(String loginStr) {
		return true;
	}

	/**
	 * 处理方法
	 * @param loginStr 登录参数
	 * @return
	 */
	@Override
	public SysUserDTO handle(String loginStr) {
		if (!check(loginStr)) {
			return null;
		}

		String identify = identify(loginStr);
		return info(identify);
	}

}

mport com.hzjtcl.dto.SysUserDTO;
import com.hzjtcl.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author gczjt
 * @date 2018/11/18
 */
@Slf4j
@Component("SMS")
public class SmsLoginHandler extends AbstractLoginHandler {

	@Autowired
	private SysUserService sysUserService;

	/**
	 * 验证码登录传入为手机号 不用不处理
	 * @param mobile
	 * @return
	 */
	@Override
	public String identify(String mobile) {
		return mobile;
	}

	/**
	 * 通过mobile 获取用户信息
	 * @param identify
	 * @return
	 */
	@Override
	public SysUserDTO info(String identify) {
		SysUserDTO user = sysUserService.getOneByinStr(identify);

		if (user == null) {
			log.info("手机号未注册:{}", identify);
			return null;
		}
		return user;
	}

}
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hzjtcl.commons.security.constant.SecurityConstants;
import com.hzjtcl.commons.tools.utils.ConvertUtils;
import com.hzjtcl.dao.SysSocialDetailsDao;
import com.hzjtcl.dao.SysUserDao;
import com.hzjtcl.dto.SysUserDTO;
import com.hzjtcl.entity.SysSocialDetailsEntity;
import com.hzjtcl.entity.SysUserEntity;
import com.hzjtcl.enums.LoginTypeEnum;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component("WX")  // 通过前端传递来的参数在map中获取
@AllArgsConstructor
public class WeChatLoginHandler extends AbstractLoginHandler {


	private final SysUserDao sysUserDao;

	private final SysSocialDetailsDao sysSocialDetailsDao;

	/**
	 * 微信登录传入code
	 * <p>
	 * 通过code 调用qq 获取唯一标识
	 * @param code
	 * @return
	 */
	@Override
	public String identify(String code) {
		SysSocialDetailsEntity condition = new SysSocialDetailsEntity();
		condition.setType(LoginTypeEnum.WECHAT.getType());
		SysSocialDetailsEntity socialDetails = sysSocialDetailsDao.selectOne(new QueryWrapper<>(condition));

		String url = String.format(SecurityConstants.WX_AUTHORIZATION_CODE_URL, socialDetails.getAppId(),
				socialDetails.getAppSecret(), code);
		String result = HttpUtil.get(url);
		log.debug("微信响应报文:{}", result);

		Object obj = JSONUtil.parseObj(result).get("openid");
		if (obj == null) {
			return "";
		}
		return obj.toString();
//		return code;
	}

	/**
	 * openId 获取用户信息
	 * @param openId
	 * @return
	 */
	@Override
	public SysUserDTO info(String openId) {
		SysUserEntity user = sysUserDao.getOneByinStr(openId);

		if (user == null) {
			log.info("微信未绑定:{}", openId);
			return null;
		}
		return ConvertUtils.sourceToTarget(user, SysUserDTO.class);
	}

}

工具类

public interface SecurityConstants {

	/**
	 * 编码
	 */
	String UTF8 = "UTF-8";
	/**
	 * 成功标记
	 */
	Integer SUCCESS = 0;

	/**
	 * 失败标记
	 */
	Integer FAIL = 1;


	/**
	 * 刷新
	 */
	String REFRESH_TOKEN = "refresh_token";

	/**
	 * 验证码有效期
	 */
	int CODE_TIME = 60;

	/**
	 * 验证码长度
	 */
	String CODE_SIZE = "4";

	/**
	 * 角色前缀
	 */
	String ROLE = "ROLE_";

	/**
	 * 前缀
	 */
	String GCZJT_PREFIX = "gczjt_";

	/**
	 * oauth 相关前缀
	 */
	String OAUTH_PREFIX = "oauth:";

	/**
	 * 项目的license
	 */
	String GCZJT_LICENSE = "made by gczjt";

	/**
	 * 内部
	 */
	String FROM_IN = "Y";

	/**
	 * 标志
	 */
	String FROM = "from";

	/**
	 * OAUTH URL
	 */
	String OAUTH_TOKEN_URL = "/oauth/token";

	/**
	 * 手机号登录URL
	 */
	String SMS_TOKEN_URL = "/mobile/token/sms";

	/**
	 * 社交登录URL
	 */
	String SOCIAL_TOKEN_URL = "/mobile/token/social";

	/**
	 * 自定义登录URL
	 */
	String MOBILE_TOKEN_URL = "/mobile/token/*";

	/**
	 * 微信获取OPENID
	 */
	String WX_AUTHORIZATION_CODE_URL = "https://api.weixin.qq.com/sns/oauth2/access_token"
			+ "?appid=%s&secret=%s&code=%s&grant_type=authorization_code";

	/**
	 * 微信小程序OPENID
	 */
	String MINI_APP_AUTHORIZATION_CODE_URL = "https://api.weixin.qq.com/sns/jscode2session"
			+ "?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code";

	/**
	 * 码云获取token
	 */
	String GITEE_AUTHORIZATION_CODE_URL = "https://gitee.com/oauth/token?grant_type="
			+ "authorization_code&code=%S&client_id=%s&redirect_uri=" + "%s&client_secret=%s";

	/**
	 * 开源中国获取token
	 */
	String OSC_AUTHORIZATION_CODE_URL = "https://www.oschina.net/action/openapi/token";

	/**
	 * 码云获取用户信息
	 */
	String GITEE_USER_INFO_URL = "https://gitee.com/api/v5/user?access_token=%s";

	/**
	 * 开源中国用户信息
	 */
	String OSC_USER_INFO_URL = "https://www.oschina.net/action/openapi/user?access_token=%s&dataType=json";

	/**
	 * {bcrypt} 加密的特征码
	 */
	String BCRYPT = "{bcrypt}";

	/**
	 * sys_oauth_client_details 表的字段,不包括client_id、client_secret
	 */
	String CLIENT_FIELDS = "client_id, CONCAT('{noop}',client_secret) as client_secret, resource_ids, scope, "
			+ "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
			+ "refresh_token_validity, additional_information, autoapprove";

	/**
	 * JdbcClientDetailsService 查询语句
	 */
	String BASE_FIND_STATEMENT = "select " + CLIENT_FIELDS + " from sys_oauth_client_details";

	/**
	 * 按条件client_id 查询
	 */
	String DEFAULT_SELECT_STATEMENT = BASE_FIND_STATEMENT + " where client_id = ? and del_flag = 0 and tenant_id = %s";

	/**
	 * 资源服务器默认bean名称
	 */
	String RESOURCE_SERVER_CONFIGURER = "resourceServerConfigurerAdapter";

	/**
	 * 客户端模式
	 */
	String CLIENT_CREDENTIALS = "client_credentials";

	/**
	 * 用户ID字段
	 */
	String DETAILS_USER_ID = "id";

	/**
	 * 用户名
	 */
	String DETAILS_USERNAME = "username";

	/**
	 * 信息是否完善
	 */
	String DETAILS_USERISFULL = "isFull";

	/**
	 * 用户基本信息
	 */
	String DETAILS_USER = "user_info";

	/**
	 * 用户名phone
	 */
	String DETAILS_PHONE = "phone";

	/**
	 * 头像
	 */
	String DETAILS_AVATAR = "avatar";

	/**
	 * 用户部门字段
	 */
	String DETAILS_DEPT_ID = "deptId";

	/**
	 * 租户ID 字段
	 */
	String DETAILS_TENANT_ID = "tenantId";

	/**
	 * 协议字段
	 */
	String DETAILS_LICENSE = "license";

	/**
	 * 激活字段 兼容外围系统接入
	 */
	String ACTIVE = "active";

	/**
	 * AES 加密
	 */
	String AES = "aes";

}

微信绑定

import com.hzjtcl.commons.tools.utils.Result;
import com.hzjtcl.service.SysSocialDetailsService;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 系统社交登录账号表
 *
 * @author gczjt
 * @date 2018-08-16 21:30:41
 */
@RestController
@RequestMapping("/social")
@AllArgsConstructor
@Api(value = "social", tags = "三方账号管理模块")
public class SysSocialDetailsController {

	private final SysSocialDetailsService sysSocialDetailsService;


	/**
	 * 绑定社交账号
	 * @param state 类型
	 * @param code code
	 * @return
	 */
	@PostMapping("/bind")
	public Result bindSocial(String state, String code) {
		Boolean aBoolean = sysSocialDetailsService.bindSocial(state, code);
		if (aBoolean){
			return new Result().ok(true);
		}else{
			return new Result().error("绑定失败!");
		}

	}

}
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.hzjtcl.commons.mybatis.service.BaseService;
import com.hzjtcl.entity.SysSocialDetailsEntity;

/**
 * 系统社交登录账号表
 *
 * @author gczjt
 * @date 2018-08-16 21:30:41
 */
public interface SysSocialDetailsService extends BaseService<SysSocialDetailsEntity> {

	/**
	 * 绑定社交账号
	 * @param state 类型
	 * @param code code
	 * @return
	 */
	Boolean bindSocial(String state, String code);

}

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.hzjtcl.commons.mybatis.service.impl.BaseServiceImpl;
import com.hzjtcl.commons.security.user.SecurityUser;
import com.hzjtcl.dao.SysSocialDetailsDao;
import com.hzjtcl.entity.SysSocialDetailsEntity;
import com.hzjtcl.entity.SysUserEntity;
import com.hzjtcl.enums.CacheConstants;
import com.hzjtcl.enums.LoginTypeEnum;
import com.hzjtcl.handler.LoginHandler;
import com.hzjtcl.service.SysSocialDetailsService;
import com.hzjtcl.service.SysUserService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;

import java.util.Map;

/**
 * @author gczjt
 * @date 2018年08月16日
 */
@Slf4j
@AllArgsConstructor
@Service
public class SysSocialDetailsServiceImpl extends BaseServiceImpl<SysSocialDetailsDao, SysSocialDetailsEntity>
		implements SysSocialDetailsService {

	private final Map<String, LoginHandler> loginHandlerMap;

	private final CacheManager cacheManager;

	private final SysUserService sysUserService;

	/**
	 * 绑定社交账号
	 * @param type type
	 * @param code code
	 * @return
	 */
	@Override
	public Boolean bindSocial(String type, String code) {
		LoginHandler loginHandler = loginHandlerMap.get(type);
		String identify = loginHandler.identify(code);
		if (StringUtils.isNotBlank(identify)) {
			SysUserEntity user = sysUserService.selectById(SecurityUser.getUserId());


//		if (LoginTypeEnum.GITEE.getType().equals(type)) {
//			sysUser.setGiteeLogin(identify);
//		}
//		else if (LoginTypeEnum.OSC.getType().equals(type)) {
//			sysUser.setOscId(identify);
//		}
			if (LoginTypeEnum.WECHAT.getType().equals(type)) {
				user.setWxOpenid(identify);
			}
//		else if (LoginTypeEnum.QQ.getType().equals(type)) {
//			sysUser.setQqOpenid(identify);
//		}
			else if (LoginTypeEnum.MINI_APP.getType().equals(type)) {
				user.setMiniOpenid(identify);
			}

			sysUserService.updateById(user);
			// 更新緩存
			cacheManager.getCache(CacheConstants.USER_DETAILS).evict(user.getUsername());
			return Boolean.TRUE;
		}else{
			return Boolean.FALSE;
		}

	}
}

微信绑定和登录思路总结:
该系统使用微信登录之前 必须先进行微信绑定, 如果未绑定则提示用户进行微信绑定;
使用微信登录和微信绑定需要公司在微信开放平台进行注册,得到注册后的专属url和appId
前端通过appId和url进行微信接口访问得到 返回值 code
通过该code 和 登录类型 对用户进行 openID 的绑定,
openId需要 appId 和 code参数 再次对微信平台进行访问得到返回值 openId
信息绑定成功之后

登录时前端传入特定的参数, 进行参数的解析, 同样需要微信平台的code值
通过该code值和appId再次获取 openId ,通过openId来获取用户信息进行登录.

核心逻辑就是对微信接口访问得到code值和openId, 将openId与用户进行绑定,
登录时通过该openId获取用户信息.

前端代码

 wxlogin() {
      // ElMessage.success("测试中,敬请期待");

// 该路径为微信访问成功之后回调路径, 需在微信开放平台进行注册
      const redirect_uri = encodeURIComponent("http://oa.hebhzjt.com:7879/#/authredirect");
      // appid
      const appid = "wx549f52db7fc1da3b";
      // 该路由参数参照 微信开放平台api
      let url = `https://open.weixin.qq.com/connect/qrconnect?appid=${appid}&redirect_uri=${redirect_uri}&state=WX-LOGIN&response_type=code&scope=snsapi_login#wechat_redirect`;
      window.open(url);
    },
    // 登录接口调用
    onLoginBySocial(code) {
      baseService
        .post(
          "/auth/mobile/token/social",
          // 登录参数 code 为微信调用回调值 wx@用于后台通过 '@'进行参数解析获取登录类型
          { mobile: `WX@${code}`, grant_type: "mobile" },
          {
            "content-type": "application/x-www-form-urlencoded",
            Authorization: "Basic aHpqdGNsOmh6anRjbA=="
          }
        )
        .then((res) => {
          if (res.code === 0) {
            setCache(CacheToken, res, true);
            ElMessage.success(this.$t("ui.login.loginOk"));
            this.$router.push("/");
          } else {
            ElMessage.error(res.msg);
            this.onRefreshCode();
          }
        })
        .catch(() => {
          ElMessage.error("未绑定登录账号,请使用密码登录后绑定");
          this.loading = false;
        });
    },
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值