在用H5开发微信公众号页面应用时,往往需要获取微信的用户信息,H5页面在微信属于访问第三方网页,因此通过微信网页授权机制,来获取用户基本信息,此处需要用户确认授权才能获取,用户确认授权后,我们可以认为用户已经登录了,这时候就需要保留用户登录的凭证,此处可以通过记录cookie来达成目的。

具体流程就是:用户通过微信打开H5页面时,判断用户是否已经在cookie中记录信息,如果已经登录未过期,则直接跳转到业务访问页面,如果未登录或者凭证已过期,则弹出微信网页授权页面,引导用户完成用户授权,待用户确认授权后,后台保存用户信息同时往cookie写入登录凭证,最后跳转到业务访问页面即可。

其中,微信网页授权可参照官方的 微信网页授权 ,下面介绍下我的具体实现和代码:

Controller:

    @RequestMapping("/login")public String login(Model model){RequestCache requestCache = new HttpSessionRequestCache();// 重定向前一URLString url = "/index";SavedRequest savedRequest = requestCache.getRequest(request,response);if(savedRequest != null){url = savedRequest.getRedirectUrl();}model.addAttribute("url","/authorize?url="+ url);}/*** authorize?url=index* @param url* @return*/@RequestMapping("/authorize")public String authorize(@RequestParam(name="url",required=false)String url){Cookie cookie = CookieUtils.get(request, cookieName);if (cookie == null) {String backUrl = apiDomain + "wxlogin?url="+url;String redirectUrl = wxService.oauth2AuthorizationUrl(backUrl);return "redirect:" + redirectUrl;//必须重定向,否则不能成功}else{return "redirect:" + url;}}@RequestMapping("/wxlogin")public String wxLogin(@RequestParam(name="code",required=false)String code,@RequestParam(name="state",required=false)String state,@RequestParam(name="url",required=false)String url) throws AuthenticationException{WxUserInfo wxUser = wxService.oauth2getUserInfo(code);if (wxUser != null) {// 生成tokenWebAccessToken accessToken = new WebAccessToken();accessToken.setUuid(IDUtils.getUuid());accessToken.setExpiresIn(cookieMaxAge);accessToken.setOpenid(wxUser.getOpenid());// 保存tokenConstants.LOG.info("Save web access token is [{}]",accessToken);wxService.newWebAccessToken(accessToken);CookieUtils.set(response, cookieName, accessToken.getUuid(), cookieMaxAge);UserInfo user = userService.getUserByName(wxUser.getOpenid());if ( user == null) {// 新用户user = new UserInfo();user.setUserName(wxUser.getOpenid());user.setNickName( EmojiParser.parseToAliases(wxUser.getNickname()) ); //EmojiParser.parseToUnicode(string)user.setUserPhoto(wxUser.getHeadimgurl());user.setUserPassword(Constants.DEFAULT_USER_PASSWORD);user.setOpenid(wxUser.getOpenid());int userId = userService.newUser(user);user.setUserId(userId);Constants.LOG.info("New user from openid [{}], save id is [{}]",wxUser.getOpenid(), userId);}}else{Constants.LOG.error("WxUserInfo is empty.");throw new AuthenticationServiceException("登录失败: 未获取到微信用户信息");}return "redirect:" + url;}

wxService:

    /***********************************************oauth2 api*********************************************************/public String oauth2AuthorizationUrl(String uri){String redirectUrl = "";try {redirectUrl = oauth2Authorize.replace("APPID", appid).replace("REDIRECT_URI", URLEncoder.encode(uri, "UTF-8")).replace("SCOPE", "snsapi_userinfo") // snsapi_base.replace("STATE", "1");}catch (UnsupportedEncodingException ex){Constants.LOG.error(ex.getMessage());}Constants.LOG.info("Oauth2 authorization url is [{}]",redirectUrl);return redirectUrl;}/*** oauth2 拉取用户信息* @param code* @return*/public WxUserInfo oauth2getUserInfo(String code){WxUserInfo userInfo = null;String requestUrl = oauth2AccessToken.replace("APPID", appid).replace("SECRET",secret).replace("CODE",code);try {Constants.LOG.info("begin oauth2 access token [{}]", requestUrl);JSONObject jsonObject = restTemplate.getForObject(requestUrl, JSONObject.class);Constants.LOG.info("finish oauth2 access token [{}], json [{}]", requestUrl, jsonObject);if(jsonObject.containsKey("errcode")){Constants.LOG.error("Obtain access token failed [{}]", jsonObject.getString("errmsg"));}else{synchronized (this){WebAccessToken webAccessToken = jsonObject.toJavaObject(WebAccessToken.class);// 判断access_token是否有效requestUrl = oauth2Validate.replace("ACCESS_TOKEN", webAccessToken.getAccessToken()).replace("OPENID",webAccessToken.getOpenid());Constants.LOG.info("begin oauth2 validate [{}]", requestUrl);jsonObject = restTemplate.getForObject(requestUrl, JSONObject.class);Constants.LOG.info("finish oauth2 validate [{}], json [{}]", requestUrl, jsonObject);if ( !jsonObject.getString("errcode").equals("0") ){// 刷新access_tokenrequestUrl = oauth2RefreshToken.replace("ACCESS_TOKEN", webAccessToken.getAccessToken()).replace("OPENID",webAccessToken.getOpenid());Constants.LOG.info("begin oauth2 refresh token [{}]", requestUrl);jsonObject = restTemplate.getForObject(requestUrl, JSONObject.class);Constants.LOG.info("finish oauth2 refresh token [{}], json [{}]", requestUrl, jsonObject);if(jsonObject.containsKey("errcode")){Constants.LOG.error("Refresh access token failed [{}]", jsonObject.getString("errmsg"));}else{webAccessToken = jsonObject.toJavaObject(WebAccessToken.class);}}// 获取userinforequestUrl = oauth2UserInfo.replace("ACCESS_TOKEN", webAccessToken.getAccessToken()).replace("OPENID",webAccessToken.getOpenid());Constants.LOG.info("begin oauth2 user info [{}]", requestUrl);jsonObject = restTemplate.getForObject(requestUrl, JSONObject.class);Constants.LOG.info("finish oauth2 user info [{}], json [{}]", requestUrl, jsonObject);if(jsonObject.containsKey("errcode")){Constants.LOG.error("Obtain user info failed [{}]", jsonObject.getString("errmsg"));}else{userInfo = jsonObject.toJavaObject(WxUserInfo.class);boolean flag = this.saveUserInfo(userInfo);Constants.LOG.info("Save wx user info result is [{}]", flag);}}}} catch (Exception ex){Constants.LOG.error(ex.getMessage());}return userInfo;}

CookieUtils:

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;public class CookieUtils {public static void set(HttpServletResponse response,String name, String value,int maxAge){Cookie cookie = new Cookie(name, value);cookie.setPath("/");cookie.setMaxAge(maxAge);response.addCookie(cookie);}/*** 获取Cookie* @param request* @param name* @return*/public static Cookie get(HttpServletRequest request,String name){Map<String, Cookie> map = readCookieMap(request);if(map.containsKey(name)){return map.get(name);}else{return null;}}
}

"/authorize?url=URL"是处理用户授权的页面,首先会从cookie中获取登录凭证,即获取键值为cookieName(值是token)的cookie,看是是否存在和过期,如果能获取得到,证明用户不是首次登录,会自动重定向到授权页面前访问的页面URL,如果cookie为空,则重定向到https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=/wxLogin?url=URL&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect,弹出微信的授权页面等待用户确认,如用户确认登录之后,则会重定向redirect_uri,即我所写的/wxLogin?url=URL,此处带上URL的原因就是我想让授权登录后还是返回授权前的页面,让登录的用户体验好点(如果是已登录的用户则直接跳转,做到无感知);

“/wxLogin?url=URL”是“通过code换取网页授权access_token,并做后续的处理”,对应官方的第二到第四步,同时再处理完之后,其中换取网页授权access_token,判断access_token是否过期,刷新access_token,并通过access_token来获取用户的基本信息,都在wxService.oauth2getUserInfo(code)里面,其中的变量参数如下:

    @Value("${wx.oauth2.authorize}")private String oauth2Authorize;@Value("${wx.oauth2.access_token}")private String oauth2AccessToken;@Value("${wx.oauth2.refresh_token}")private String oauth2RefreshToken;@Value("${wx.oauth2.userinfo}")private String oauth2UserInfo;@Value("${wx.oauth2.validate}")private String oauth2Validate;//springboot配置文件中
wx:oauth2:authorize: https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirectaccess_token: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_coderefresh_token: https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKENuserinfo: https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CNvalidate: https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID

获取到用户的基本信息之后,就可以保存一下access_token,同时往cookie里面写入登录凭证,此处写入cookie的格式“token=openid”,以微信的openid来做用户的标识(如果是微信公众H5和微信小程序公用的,微信是建议用UnionID),同时判断用户是否已存在,新用户需要创建用户信息,写入数据库用户表,而老用户则不需要,最后重定向业务访问URL。

到此,则完成微信的网页授权流程,也完成用户的登录操作,至于springboot security的登录认证机制,可以沿用WebSecurity配置,另增加OncePerRequestFilter来识别用户是否登录,代码如下:

SecurityConfig:

@Configuration
@EnableWebSecurity
public class SecurityConfig  extends WebSecurityConfigurerAdapter {// Spring会自动寻找实现接口的类注入,会找到我们的 UserDetailsServiceImpl  类@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate LogoutHandler logoutHandler;@Autowiredprivate AuthenticationTokenFilter authenticationTokenFilter;@Autowiredpublic void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {authenticationManagerBuilder// 设置UserDetailsService.userDetailsService(this.userDetailsService)// 使用BCrypt进行密码的hash.passwordEncoder(this.passwordEncoder);}@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {httpSecurity// 取消csrf.csrf().disable()// 基于token,所以不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 允许对于网站静态资源的无授权访问.antMatchers(HttpMethod.GET,"/","/*.html").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login").and().logout().logoutSuccessHandler(logoutHandler).logoutUrl("/logout");
;httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);}
}

authenticationTokenFilter:


@Component
public class AuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate WxService wxService;@Value("${cookie.name}")private String cookieName;@Overrideprotected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {if ( request.getServletPath().contains("login")|| request.getServletPath().contains("logout")|| request.getServletPath().contains("authorize")) {return true;}else{return super.shouldNotFilter(request);}}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {Cookie cookie = CookieUtils.get(request,cookieName);String token = cookie != null ? cookie.getValue() : "";if(StringUtils.isNotBlank(token)){WebAccessToken accessToken = wxService.getToken(token);if (accessToken != null){UserDetails userDetails = userDetailsService.loadUserByUsername(accessToken.getOpenid());if (userDetails == null){userDetailsService.loadUserByUsername(accessToken.getCellNo().toString());}if (userDetails == null){throw new AuthenticationServiceException("登录失败:用户不存在");}Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authentication);}}super.doFilter(request, response, chain);}
}

完!!!

H5页面使用微信网页授权实现登录认证相关推荐

  1. 微信公众号开发-使用微信网页授权进行登录并加上过滤器判断是否已登录

    首先你需要阅读一下这篇文档 微信开放文档 你需要去搞一个本地内网穿透,这样你就可以在本地进行调试你的代码. 我用的穿透工具是这个:natapp 你还需要下载一个微信开发者工具 开发者工具下载,因为从手 ...

  2. 【WEB开发】微信网页授权第三方登录接口(WEB登录)

    https://www.cnblogs.com/xuzhengzong/p/8513269.html 本文链接至:http://blog.csdn.net/hxker/article/details/ ...

  3. Vue 微信网页授权自动登录

    项目需求:微信公众号(服务号)访问网页,第一次登录后,再次访问默认自动登录,执行退出后,可重新手动登录. 具体实现如下: 微信公众号 - 设置 - 公众号设置 - 功能设置 - 配置网页授权域名,用于 ...

  4. 微信授权Java重定向前端地址_微信网页授权+分享踩过的坑

    页面用浏览器自带返回和安卓物理返回死循环的话,直接看高潮部分 背景 折磨我两个工作日加周末一天的问题,我觉得还是有必要记录一下,为什么程序员总是加班,就是遇到这些意想不到的问题 需求 领导:我想做两个 ...

  5. 微信公众号H5【微信网页授权快照页】复现情况,以及解决方法(详细,成功,forcePopup,forceSnapShot,is_snapshotuse)

    (上班时间写的!!,大哥们看完记得点赞) 1.官方回答(稀碎) 快照页将会默认对用户屏蔽网页授权弹窗,用户在快照页中仅可进行滑动浏览操作,其他交互将被限制,并提示用户 "该网页需获取个人信息 ...

  6. ajax 微信code获取_ajax 实现微信网页授权登录的方法

    项目背景 因为项目采用前后端完全分离方案,所以,无法使用常规的微信授权登录作法,需要采用 ajax 实现微信授权登录. 需求分析 因为本人是一个phper ,所以,微信开发采用的是 EasyWeCha ...

  7. 微信公众号开发(一) 微信网页授权登录

    微信网页授权登录 前期准备 授权登录 获取微信数据 处理授权拒绝 前期准备 1.微信公众号开发,首先要搞一个公众号,开发阶段可以申请一个公众平台测试账号. (进入到微信公众公众平台,找到开发者工具,点 ...

  8. 公众号H5页面接入微信登录流程

    起步 首先创建一个项目,我们采用uni-app来作为我们的前端框架 环境安装 全局安装vue-cli npm install -g @vue/cli 创建uni-app 使用正式版(对应HBuilde ...

  9. php微信授权ajax,ajax 实现微信网页授权登录

    项目背景 因为项目采用前后端完全分离方案,所以,无法使用常规的微信授权登录作法,需要采用 ajax 实现微信授权登录. 需求分析 因为本人是一个PHPer ,所以,微信开发采用的是 EasyWeCha ...

最新文章

  1. DDD 领域驱动设计:贫血模型、充血模型的深入解读!
  2. 深入实践Spring Boot1.3.2 一个简单的实例
  3. 《机器学习实践应用》书中源代码
  4. npm 与 package.json 快速入门
  5. 跨平台2D/3D游戏开发框架libGDX发布1.2.0更新
  6. 记录下Lambda常用的表现形式
  7. 分布式多级缓存中间件引导实践
  8. 【linux】修改某一行
  9. javaweb前后台中文参数乱码
  10. houdini 常用
  11. Matlab根据广播星历表计算卫星坐标
  12. 如何在软件UI设计中运用格式塔心理学5项法则?
  13. 服务器sni协议,SNI协议分析
  14. Android NDK-EGL 初级
  15. ECC椭圆曲线算法(1)阿贝尔群
  16. XSSF 导入导出excel.xlsx 解决获取空白单元格自动跳过问题,校验excel表头是否符合需求
  17. Ubuntu 20.04 离线安装podman
  18. ubuntu18.04 RoboCup实物
  19. Fine-Grained Classification之车型识别
  20. 桌面宠物:天选姬官方下载指南和游玩体验

热门文章

  1. hdu1176 免费馅饼 nyoj613 免费馅饼
  2. Models Genesis: Generic Autodidactic Models for 3D Medical Image Analysis精读
  3. [51单片机学习笔记TWO]----蜂鸣器
  4. 使用Python训练好的决策树模型生成C++代码
  5. STM32基础学习笔记寄存器之GPIO(1)
  6. 联想服务器看硬盘,联想服务器SSD硬盘
  7. python代码学习——类与对象提升(继承、超继承,类的例题,魔术方法)
  8. 湖北专升本-湖师计科
  9. Qlcomm Android 开发环境,编译
  10. TypeScript 全面介绍