Java implements WeChat scan code login

1. Authorization process description

WeChat OAuth2.0 authorized login allows users to securely log in to third-party applications or websites using WeChat identities. After the WeChat user authorizes to log in to a third-party application that has access to WeChat OAuth2.0, the third party can obtain the user's interface call credentials (access_token) , through the access_token, the authorization relationship interface of the WeChat open platform can be called, so as to obtain the basic open information of WeChat users and help users realize basic open development functions, etc.

官方文档:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html

WeChat OAuth2.0 authorization login currently supports authorization_code mode, which is suitable for application authorization with server side. The overall process of this mode is:

  1. The third party initiates a WeChat authorization login request. After the WeChat user allows the authorization of the third-party application, WeChat will launch the application or redirect to the third-party website with the authorization temporary ticket code parameter;
  2. Through the code parameter plus AppID and AppSecret, etc., the access_token is exchanged through the API;
  3. Use the access_token to make interface calls to obtain basic user data resources or help users implement basic operations.
    insert image description here

Step 1: Request code

Before the third party uses the website application authorization to log in, it needs to pay attention to the corresponding web page authorization scope (scope = snsapi_login), then you can open the following link on the PC side:Remember to replace with your own parameters
https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

return instructions

After the user permits authorization, it will be redirected to the redirect_uri URL with code and state parameters

redirect_uri?code=CODE&state=STATE

If the user prohibits authorization, the code parameter will not be carried after redirection, only the state parameter will be carried

redirect_uri?state=STATE

For example:
log in to the Yihaodian website application: https://passport.yhd.com/wechat/login.do
After opening, Yihaodian will generate a state parameter and jump to:
https://open.weixin.qq.com /connect/qrconnect?appid=wxbdc5610cc59c1631&redirect_uri=https%3A%2F%2Fpassport.yhd.com%2Fwechat%2Fcallback.do&response_type=code&scope=snsapi_login&state=3d6be0a4035d839573b04816624a
415e#wechat_redirect WeChat users use WeChat to scan the QR code and confirm login, the PC terminal will Jump to: https://passport.yhd.com/wechat/callback.do?code=CODE&state=3d6be0a4035d839573b04816624a415e

Step 2: Obtain access_token through code

Get access_token through code

官方文档:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

return instructions

correctly returns:

{
    
     
"access_token":"ACCESS_TOKEN", 
"expires_in":7200, 
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID", 
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

Error return example:

{
    
    "errcode":40029,"errmsg":"invalid code"}
  • AppSecret is the key used by the application interface. If it is leaked, it may lead to high-risk consequences such as application data leakage and application user data leakage; stored on the client, it is very likely to be maliciously stolen (such as decompiling to obtain AppSecret);
  • The access_token is the credential for the user to authorize the third-party application to initiate the interface call (equivalent to the user login state), which is stored on the client, and there may be behaviors such as user data leakage caused by maliciously obtaining the access_token, and user WeChat-related interface functions being maliciously initiated;
  • The refresh_token is a long-term certificate for the user to authorize the third-party application. It is only used to refresh the access_token, but if it is leaked, it is equivalent to the leak of the access_token, and the risk is the same as above.

It is recommended to put Secret and user data (such as access_token) on the App cloud server, and the cloud will transfer the interface to call the request.

Step 3: Call the interface through access_token

After obtaining the access_token, the interface call must have the following prerequisites:

1. The access_token is valid and has not timed out;
2. The WeChat user has authorized the corresponding interface scope of the third-party application account (scope)

For the interface scope (scope), the interfaces that can be called are as follows:
insert image description here

2. Authorization process code

Because the AppID and AppSecret of the WeChat open platform and the WeChat public platform are different, the following configurations are required:

# 开放平台
wechat.open-app-id = wx6ad144e54af67d87
wechat.open-app-secret = 91a2ff6d38a2bbccfb7e9f9079108e2e
@Data
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatAccountConfig {
    
    

    //公众号appid
    private String mpAppId;

    //公众号appSecret
    private String mpAppSecret;

    //商户号
    private String mchId;

    //商户秘钥
    private String mchKey;
    
    //商户证书路径
    private String keyPath;

    //微信支付异步通知
    private String notifyUrl;

    //开放平台id
    private String openAppId;

    //开放平台秘钥
    private String openAppSecret;
}
@Configuration
public class WechatOpenConfig {
    
    

    @Autowired
    private WechatAccountConfig accountConfig;

    @Bean
    public WxMpService wxOpenService() {
    
    
        WxMpService wxOpenService = new WxMpServiceImpl();
        wxOpenService.setWxMpConfigStorage(wxOpenConfigStorage());
        return wxOpenService;
    }

    @Bean
    public WxMpConfigStorage wxOpenConfigStorage() {
    
    
        WxMpInMemoryConfigStorage wxMpInMemoryConfigStorage = new WxMpInMemoryConfigStorage();
        wxMpInMemoryConfigStorage.setAppId(accountConfig.getOpenAppId());
        wxMpInMemoryConfigStorage.setSecret(accountConfig.getOpenAppSecret());
        return wxMpInMemoryConfigStorage;
    }
}
@Controller
@RequestMapping("/wechat")
@Slf4j
public class WeChatController {
    
    
    @Autowired
    private WxMpService wxMpService;

    @Autowired
    private WxMpService wxOpenService;

    @GetMapping("/qrAuthorize")
    public String qrAuthorize() {
    
    
        //returnUrl就是用户授权同意后回调的地址
        String returnUrl = "http://heng.nat300.top/sell/wechat/qrUserInfo";

        //引导用户访问这个链接,进行授权
        String url = wxOpenService.buildQrConnectUrl(returnUrl, WxConsts.QRCONNECT_SCOPE_SNSAPI_LOGIN, URLEncoder.encode(returnUrl));
        return "redirect:" + url;
    }

    //用户授权同意后回调的地址,从请求参数中获取code
    @GetMapping("/qrUserInfo")
    public String qrUserInfo(@RequestParam("code") String code) {
    
    
        WxMpOAuth2AccessToken wxMpOAuth2AccessToken = new WxMpOAuth2AccessToken();
        try {
    
    
            //通过code获取access_token
            wxMpOAuth2AccessToken = wxOpenService.oauth2getAccessToken(code);
        } catch (WxErrorException e) {
    
    
            log.error("【微信网页授权】{}", e);
            throw new SellException(ResultEnum.WECHAT_MP_ERROR.getCode(), e.getError().getErrorMsg());
        }
        //从token中获取openid
        String openId = wxMpOAuth2AccessToken.getOpenId();

        //这个地址可有可无,反正只是为了拿到openid,但是如果没有会报404错误,为了好看随便返回一个百度的地址
        String  returnUrl = "http://www.baidu.com";

        log.info("openid={}", openId);

        return "redirect:" + returnUrl + "?openid="+openId;
    }
}

Request path: Open
https://open.weixin.qq.com/connect/qrconnect?appid=wx6ad144e54af67d87&redirect_uri=http%3A%2F%2Fsell.springboot.cn%2Fsell%2Fqr%2FoTgZpwenC6lwO2eTDDf_-UYyFtqI&response_ in the browser type=code&scope=snsapi_login&state =http%3a%2f%2fheng.nat300.top%2fsell%2fwechat%2fqrUserInfo

Obtained openid:openid = o9AREv7Xr22ZUk6BtVqw82bb6AFk
insert image description here

3. User login and logout

@Controller
@RequestMapping("/seller")
public class SellerUserController {
    
    

    @Autowired
    private SellerService sellerService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ProjectUrlConfig projectUrlConfig;

    @GetMapping("/login")
    public ModelAndView login(@RequestParam("openid") String openid,                               HttpServletResponse response,                               Map<String, Object> map) {
    
    

        //1. openid去和数据库里的数据匹配
        SellerInfo sellerInfo = sellerService.findSellerInfoByOpenid(openid);
        if (sellerInfo == null) {
    
    
            map.put("msg", ResultEnum.LOGIN_FAIL.getMessage());
            map.put("url", "/sell/seller/order/list");
            return new ModelAndView("common/error");
        }

        //2. 设置token至redis
        String token = UUID.randomUUID().toString();
        //设置token的过期时间
        Integer expire = RedisConstant.EXPIRE;

        redisTemplate.opsForValue().set(String.format(RedisConstant.TOKEN_PREFIX, token), openid, expire, TimeUnit.SECONDS);

        //3. 设置token至cookie
        CookieUtil.set(response, CookieConstant.TOKEN, token, expire);

        return new ModelAndView("redirect:" + "http://heng.nat300.top/sell/seller/order/list");
    }

    @GetMapping("/logout")
    public ModelAndView logout(HttpServletRequest request,                        HttpServletResponse response,                        Map<String, Object> map) {
    
    
        //1. 从cookie里查询
        Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
        if (cookie != null) {
    
    
            //2. 清除redis
            redisTemplate.opsForValue().getOperations().delete(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));

            //3. 清除cookie
            CookieUtil.set(response, CookieConstant.TOKEN, null, 0);
        }

        map.put("msg", ResultEnum.LOGOUT_SUCCESS.getMessage());
        map.put("url", "/sell/seller/order/list");
        return new ModelAndView("common/success", map);
    }
}
  • Store the openID obtained above into the database
    insert image description here
  • Change the redirected address after authorization to the login address:
	//用户授权同意后回调的地址,从请求参数中获取code
    @GetMapping("/qrUserInfo")
    public String qrUserInfo(@RequestParam("code") String code) {
    
    
        WxMpOAuth2AccessToken wxMpOAuth2AccessToken = new WxMpOAuth2AccessToken();
        try {
    
    
            //通过code获取access_token
            wxMpOAuth2AccessToken = wxOpenService.oauth2getAccessToken(code);
        } catch (WxErrorException e) {
    
    
            log.error("【微信网页授权】{}", e);
            throw new SellException(ResultEnum.WECHAT_MP_ERROR.getCode(), e.getError().getErrorMsg());
        }
        //从token中获取openid
        String openId = wxMpOAuth2AccessToken.getOpenId();

        //授权成功后跳转到卖家系统的登录地址
        String  returnUrl = "http://heng.nat300.top/sell/seller/login";

        log.info("openid={}", openId);

        return "redirect:" + returnUrl + "?openid="+openId;
    }
  • Request this link in the browser:
    https://open.weixin.qq.com/connect/qrconnect?appid=wx6ad144e54af67d87&redirect_uri=http%3A%2F%2Fsell.springboot.cn%2Fsell%2Fqr%2FoTgZpwenC6lwO2eTDDf_-UYyFtqI&response_ type=code&scope=snsapi_login&state =http%3a%2f%2fheng.nat300.top%2fsell%2fwechat%2fqrUserInfo
    The third-party application requests to use the WeChat scan code to log in instead of using the password of this website:
    insert image description here
    After the user agrees to authorize, log in to the background management system of the third-party application:
    insert image description here
    insert image description here

4. Spring AOP checks whether the user is logged in

@Aspect
@Component
@Slf4j
public class SellerAuthorizeAspect {
    
    

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Pointcut("execution(public * com.hh.controller.Seller*.*(..))" +
    "&& !execution(public * com.hh.controller.SellerUserController.*(..))")
    public void verify() {
    
    }

    @Before("verify()")
    public void doVerify() {
    
    
        
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        //查询cookie
        Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
        //如果cookie中没有token说明已经登出或者根本没有登录
        if (cookie == null) {
    
    
            log.warn("【登录校验】Cookie中查不到token");
            //校验不通过,抛出异常
            throw new SellerAuthorizeException();
        }

        //去redis里查询
        String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
        //如果redis中没有对应的openid,同样表示登出或者根本没有登录
        if (StringUtils.isEmpty(tokenValue)) {
    
    
            log.warn("【登录校验】Redis中查不到token");
            throw new SellerAuthorizeException();
        }
    }
}

5. Intercept the exception thrown when the login verification fails

Intercept and fail the login verification exception, let it jump to the login page, scan the code to log in

@ControllerAdvice
public class SellExceptionHandler {
    
    
    //拦截登录异常
    @ExceptionHandler(value = SellerAuthorizeException.class)     
    public ModelAndView handlerAuthorizeException() {
    
    
        //拦截异常后,跳转到登录界面
        return new ModelAndView("redirect:".concat("https://open.weixin.qq.com/connect/qrconnect?" +
                "appid=wx6ad144e54af67d87" +
                "&redirect_uri=http%3A%2F%2Fsell.springboot.cn%2Fsell%2Fqr%2F" +
                "oTgZpwenC6lwO2eTDDf_-UYyFtqI" +
                "&response_type=code&scope=snsapi_login" +
                "&state=http%3a%2f%2fheng.nat300.top%2fsell%2fwechat%2fqrUserInfo"));
    }
    
    @ExceptionHandler(value = SellException.class)     
    @ResponseBody     
    public ResultVO handlerSellerException(SellException e) {
    
    
        return ResultVOUtil.error(e.getCode(), e.getMessage());
    }
    
    @ExceptionHandler(value = ResponseBankException.class)     
    @ResponseStatus(HttpStatus.FORBIDDEN)     
    public void handleResponseBankException() {
    
    }
}

The law of good things: Everything will be a good thing in the end, if it is not a good thing, it means that it is not the end yet.

Guess you like

Origin blog.csdn.net/Cike___/article/details/128901949