spring boot实现微信的网页授权
使用spring boot实现简单微信授权登录
这是第一篇在csdn发表的博客
一,添加maven依赖
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>2.8.0</version>
</dependency>
二,添加微信相关的配置信息
这里使用的是第三方sdk来MP_OAuth2网页授权功能,想了解可以看这个
微信的授权登录需要用到公众号的appid和secret,我用的是测试号,将这些配置信息写到application.yml中
wechat:
appid: wx6*************6b
secret: 0******************************6
创建一个bean来接收配置信息,set和get方法这里省略了
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatAccountConfig {
/**公众号appid*/
private String appid;
/**公众号secret*/
private String secret;
用户在微信客户端中访问第三方网页,公众号就可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑,使用该sdk获取用户信息的步骤是首先构造网页授权url,当用户同意授权后,会回调所设置的url并把authorization code传过来,然后用这个code获得access token,其中也包含用户的openid等信息,代码如下:
首先将appid和secret放进一个WxMpService,创建一个WxMpService对象
@Component
public class WechatMpConfig {
@Autowired
private WechatAccountConfig accountConfig;
@Bean
public WxMpService wxMpService(){
//创建WxMpService实例并设置appid和sectret
WxMpService wxMpService = new WxMpServiceImpl();
//这里的设置方式是跟着这个sdk的文档写的
wxMpService.setWxMpConfigStorage(wxConfigProvider());
return wxMpService;
}
@Bean
public WxMpConfigStorage wxConfigProvider(){
WxMpInMemoryConfigStorage wxConfigProvider = new WxMpInMemoryConfigStorage();
wxConfigProvider.setAppId(accountConfig.getAppid());
wxConfigProvider.setSecret(accountConfig.getSecret());
return wxConfigProvider;
}
}
三,编写微信授权代码获取openid
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import wang.linge.config.ProjectUrlConfig;
import wang.linge.enums.ResultEnum;
import wang.linge.exception.MyException;
import java.net.URLEncoder;
/**
* Created by linge on 2017/10/5.
*/
@Controller
@RequestMapping("/wechat")
@Slf4j
public class WechatController {
@Autowired
private WxMpService wxMpService;
@Autowired
private WxMpService wxOpenService;
@Autowired
private ProjectUrlConfig projectUrlConfig;
/**
* 访问这个时便会发起微信的网页授权
* @param returnUrl 发起授权是可携带的一个参数,我这里用的是下面将要用到的login()的地址,将获取到的openid传递过去
* @return
*/
@GetMapping("/authorize")
public String authorize(@RequestParam("returnUrl") String returnUrl) {
//设置回调地址
String url = "http://localhost/wechat/userInfo";
String redirectUrl = wxMpService.oauth2buildAuthorizationUrl(url, WxConsts.OAUTH2_SCOPE_BASE, URLEncoder.encode(returnUrl));
return "redirect:" + redirectUrl;
}
//微信回调时访问的地址 这里获得code和之前所设置的returnUrl
@GetMapping("/userInfo")
public String userInfo(@RequestParam("code") String code,
@RequestParam("state") String returnUrl) {
WxMpOAuth2AccessToken wxMpOAuth2AccessToken = new WxMpOAuth2AccessToken();
try {
wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code);
} catch (WxErrorException e) {
log.error("【微信网页授权】{}", e);
//抛出异常 自定义的 方便处理 可自己定义
throw new MyException(ResultEnum.WECHAT_MP_ERROR, e.getError().getErrorMsg());
}
String openId = wxMpOAuth2AccessToken.getOpenId();
return "redirect:" + returnUrl + "?openid=" + openId;
}
}
四,编写login方法将当前登录用户的信息存入当前会话中
@GetMapping("login")
public ModelAndView login(@RequestParam("openid") String openid,
HttpServletResponse response,
Map<String,Object> map){
//1,openid 去和数据库里面的数据匹配验证 这里只是演示就删掉没有了
//2,设置token至cookie 并设置过期时间
CookieUtil.set(response,CookieConstant.TOKEN,token,CookieConstant.EXPIRE);
return new ModelAndView("redirect:http://localhost/index"));
}
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Cookie工具类
* Created by linge on 2017/10/17.
*/
public class CookieUtil {
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;
}
}
/**
* 将cookie封装成map
* @param request
* @return
*/
public static Map<String,Cookie> readCookieMap(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
Map<String,Cookie> map = new HashMap<>();
if(cookies != null){
for(Cookie cookie : cookies){
map.put(cookie.getName(),cookie);
}
}
return map;
}
}
/**
* Cookie常量
* Created by linge on 2017/10/17.
*/
public interface CookieConstant {
String TOKEN = "token";
Integer EXPIRE = 7200 ;
}
五,使用切面拦截所有请求验证是否已验证登录
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import wang.linge.constant.CookieConstant;
import wang.linge.constant.RedisConstant;
import wang.linge.exception.AuthorizeException;
import wang.linge.utils.CookieUtil;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
/**
* Created by linge on 2017/10/17.
*/
@Aspect
@Component
@Slf4j
public class AuthorizeAspect {
@Autowired
private StringRedisTemplate redisTemplate;
@Pointcut("execution(public * wang.linge.controller.*.*(..))"+
"&& !execution(public * wang.linge.controller.UserController.*(..))")
public void verify(){
}
@Before("verify()")
public void doVerify(){
ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
if(cookie == null){
log.warn("【登录校验】,无法在cookie中查找到token的值");
throw new AuthorizeException();
}
String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
if(StringUtils.isEmpty(tokenValue)){
log.warn("【登录校验】,无法在redis中查找到token的值");
throw new AuthorizeException();
}
}
}
六,处理未登录的异常,引导到登陆链接
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import wang.linge.config.ProjectUrlConfig;
import wang.linge.exception.Exception;
import wang.linge.exception.AuthorizeException;
/**
* Created by linge on 2017/10/18.
*/
@ControllerAdvice
public class ExceptionHandler {
//为了方便将一些url写到配置里去 这里就不贴出来 可不用这个直接写地址或者仿照之前微信appid的配置写这个配置
@Autowired
private ProjectUrlConfig urlConfig;
@ExceptionHandler(AuthorizeException.class)
public ModelAndView handlerAuthorizeException(){
return new ModelAndView("redirect:"
.concat(urlConfig.getWechatOpenAuthorize())
.concat("/wechat/qrAuthorize")
.concat("?returnUrl=")
.concat("http://localhost/login"));
}
}