前言
实际开发中,使用第三方登录是非常常见的业务。
比如很多微信的网页中,就会有网页向用户申请使用权限。
通过实现网页授权并获取用户基本信息是一种比较好的选择。
比如如下就是申请微信授权,获取微信用户的openId
正文
准备
- 准备公众号和测试环境
微信公众平台接口测试帐号申请 - 网页授权获取用户基本信息配置
申请完测试账号后, 会获得一个appId和appSecret, 随后会用到
页面下方有接口权限,设置网页回调地址
不要带http,此处地址填写本地ip即可。
到此公众号配置已经完成
实现网页授权(微信登陆)
简易流程图:
第一步:用户同意授权,获取code
在确保微信公众账号拥有授权作用域(scope参数)的权限的前提下(一种是需要用户授权,可以获取昵称、头像、openId, 一种是不需要授权, 只能获取openId),引导关注者打开如下页面:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
这个地址需要 appid , redirect_uri, response_type, scope 等几个参数。
而这个地址就给交给后台构建,前台负责访问该地址。
而微信授权回调后,就会设置 header的 location 属性 为 redirect_uri?code = xxxxx。 使得我们会跳转到这个地址
例如, 我填写的redirect_uri 为 http://192.168.3.234:8009/oauth
。 微信根据这个回调地址进行携带code回跳。
跳转前:
跳转后
具体代码
这里引用了weixin-java-mp提供的包, 可以很便捷地与微信请求,
com.github.binarywang
weixin-java-mp
4.4.0
这里需要将appId和appsecret配置给wxMpService,配置过程具体可以看该包的文档,简单来说就是继承WxMpServiceImpl类,并调用setWxMpConfigStorage方法设置config
@Service
public class WeChatMpServiceImpl extends WxMpServiceImpl {
@Autowired
private WxMpService wxMpService;
@Autowired
private WxMpConfig wxMpConfig;
@PostConstruct
public void init() {
final WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
// 设置微信公众号的appId
config.setAppId(this.wxMpConfig.getAppid());
// 设置微信公众号的app corpSecret
config.setSecret(this.wxMpConfig.getAppSecret());
// 设置微信公众号的token
config.setToken(this.wxMpConfig.getToken());
// 设置消息加解密密钥
config.setAesKey(this.wxMpConfig.getAesKey());
super.setWxMpConfigStorage(config);
}
如下的buildAuthorizationUrl方法可以很快速的生成授权连接的地址
@Autowired
private WxMpService wxMpService;
/**
* 构建网页授权链接
*
* @return
*/
public String getOauthUrl() {
return wxMpService.getOAuth2Service().buildAuthorizationUrl(this.wxMpConfig.getRedirectUri(), WxConsts.OAuth2Scope.SNSAPI_BASE, null);
}
第二步:通过code换取网页授权access_token
这时候,微信已经回调到我们的redirect_uri, 并且前端可以从 url 的参数中获取到code
这时候我们就可以用code向后台登录
(这里在向后台请求的header中传或者在param中传都可以)
ts code:
/**
* 使用code登录
* @param code
*/
loginWithCode(code: string): Observable> {
Assert.isString(code, "code must be string")
let headers = new HttpHeaders();
// 添加code作为 header认证
headers = headers.append('wechat-code', code)
return this.httpClient.get('wechat/login', {
headers,
observe: 'response'
});
}
后台:
通过getAccessToken(code)方法可以获取到 accessToken,此时已经可以获取到用户的OpenId
WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(code);
String openId = accessToken.getOpenId();
当前需求获取到openId已足够, 若想要获取到用户信息(昵称、头像等)则需要再用accessToken去获取 用户信息
// 获取用户信息
wxMpUser = wxMpService.getUserService().userInfo(accessToken.getOpenId());
log.info("wxMpUser={}", JSONUtil.toJsonStr(wxMpUser));
此时就可以根据用户唯一的OpenId去登录了。具体看各个项目的实现。
当前项目是定义一个过滤器,用spring security来登录, 设置安全上下文的认证。
@Component
public class WechatAuthFilter extends OncePerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 微信第一次认证需要的code信息
*/
public static final String codeKey = "wechat-code";
private final WeChatMpServiceImpl weChatMpService;
public WechatAuthFilter(WeChatMpServiceImpl weChatMpService) {
this.weChatMpService = weChatMpService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String code = request.getHeader(codeKey);
if (code != null) {
try {
WeChatUser wechatUser = this.weChatMpService.oauth(code);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken;
// 设置认证用户:微信用户、安全令牌设置为openid、认证权限为空(后期可变更为正确的微信权限名称)
usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
wechatUser,
wechatUser.getOpenid(),
wechatUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
} catch (WxErrorException exception) {
this.logger.warn("虽然接收到了code,但是没有通过code换取有效的微信数据: " + exception.getMessage());
exception.printStackTrace();
}
}
filterChain.doFilter(request, response);
}
}