Login issue-single sign-on (SSO), WeChat scan login

This article mainly explains the login problem of the online education system.
Main introduction-single sign-on concept, WeChat scan login (Java backend)


Article Directory

1. Single sign-on

1.1 Introduction

  • Single Sign On (Single Sign On-SSO)
  • In multiple application systems, you only need to log in once to access other mutually trusted application systems

1.2 Three common single sign-on methods

1.2.1 Implementation of session broadcast mechanism

  1. After the login is successful, put the user data in the session;
  2. Determine whether to log in, get data from the session, you can get the login
session.setAttribute("user",user);
session.getAttribute("user");

Log in to any module in the project, after logging in, put the data in two places

 redis:在 key 生成唯一随机值(ip、用户id等等);在 value 中存储用户数据
 cookie:把 redis 里面生成的 key 值放到 cookie 里面

Visit other modules in the project, send the request with a cookie, and get the redis value

 把 cookie 获取值,到 redis 进行查询, 根据 key 进行查询,如果查询到数据就是登录

1.2.3 Implementation using token

Log in to a module in the project. After logging in, a string is generated according to certain rules, and the user information after logging in is included in the generated string, and the string is returned

 可以把字符串通过 cookie 返回;
 把字符串通过地址栏返回

Then visit other modules of the project, each time you visit with a generated string in the address bar, you can get user information based on the string in the address bar

1.2.4 JWT

  • token is a string generated according to certain rules, including user information
  • What is the rule, not necessarily
  • Generally adopt official rules (JWT)

The JWT generated string consists of three parts:

  1. JWT header information;
  2. Payload, including subject information (user information);
  3. Signature hash (anti-counterfeiting mark)

2. Log in

  • Implementation interface

2.1 Ordinary login

  • Get login mobile phone number and password
  • Determine whether the mobile phone number and password are empty
  • Determine whether the phone number is correct-compare with the phone number in the database
  • Determine whether the password seems to be correct-encrypt the entered password and compare it with the database password
  • Use JWT tool class to generate and return token
    //登录的方法
    @Override
    public String login(UcenterMember member) {
        //获取登录手机号和密码
        String mobile = member.getMobile();
        String password = member.getPassword();

        //手机号和密码非空判断
        if(StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
            throw new GuliException(20001,"登录失败");
        }

        //判断手机号是否正确
        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
        wrapper.eq("mobile",mobile);
        UcenterMember mobileMember = baseMapper.selectOne(wrapper);
        //判断查询对象是否为空
        if(mobileMember == null) {//没有这个手机号
            throw new GuliException(20001,"登录失败");
        }

        //判断密码
        //因为存储到数据库密码肯定加密的
        //把输入的密码进行加密,再和数据库密码进行比较
        //加密方式 MD5
        if(!MD5.encrypt(password).equals(mobileMember.getPassword())) {
            throw new GuliException(20001,"登录失败");
        }

        //登录成功
        //生成token字符串,使用jwt工具类
        String jwtToken = JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname());
        return jwtToken;
    }

2.2 WeChat scan login

2.2.1 OAuth2

  1. OAuth2 is a solution to specific problems
  2. Mainly solve two problems: 1.Open system authorization 2.Distributed access problem

Authorization method between open systems:

  • Password username copy,
  • Universal developer key (master key),
  • Way token

Distributed access (single sign-on)

1. 登陆成功之后,按照一定的规则生成字符串,字符串包含用户信息
2. 把生成的字符串通过路径传播,或者cookie
3. 后面再发送请求的时候,每次带着字符串进行发送;获取字符串,从字符串中获取用户信息登录

OAuth2 solution: token mechanism, according to certain rules to generate a string, the string contains user information

2.2.2 Preparation

In short: get app_id with app_secret

# 微信开放平台 -- appid
wx.open.app_id=wxed9954c01bb89b47
# 微信开放平台 -- appsecret
wx.open.app_secret=a7482517235173ddb4083788de60b90e
# 微信开放平台 -- 重定向url
wx.open.redirect_url=http://guli.shop/api/ucenter/wx/callback

2.2.3 Generate WeChat Scan QR Code

  • Directly request WeChat to provide a fixed address (splicing parameters to the back of the address)
  • Return address (the QR code is in the address)
    // 生成微信扫描二维码
    @GetMapping("login")
    public String getWxCode() {
        //固定地址,后面拼接参数
//        String url = "https://open.weixin.qq.com/" +
//                "connect/qrconnect?appid="+ ConstantWxUtils.WX_OPEN_APP_ID+"&response_type=code";

        // 微信开放平台授权baseUrl  %s相当于?代表占位符
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=%s" +
                "&redirect_uri=%s" +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=%s" +
                "#wechat_redirect";

        //对redirect_url进行URLEncoder编码
        String redirectUrl = ConstantWxUtils.WX_OPEN_REDIRECT_URL;
        try {
            redirectUrl = URLEncoder.encode(redirectUrl, "utf-8");
        }catch(Exception e) {
        }

        //设置%s里面值
        String url = String.format(
                baseUrl,
                ConstantWxUtils.WX_OPEN_APP_ID,
                redirectUrl,
                "kc"
        );

        //重定向到请求微信地址里面
        System.out.println(url);
        return "redirect:"+url;
    }

2.2.4 Scan the QR code

Steps :

After scanning, execute the local callback method, and pass two values ​​(state-passed as is) when jumping (code-similar to mobile phone verification code);

Get the code of the first step, request WeChat to provide a fixed address, and get two values;

 https://api.weixin.qq.com/sns/oauth2/access_token
  1. access_token: Access credentials
  2. openid: A unique identifier for each WeChat

Get the access_token and openid, and then request a WeChat fixed address, you can finally get the information of the person scanned by WeChat

 https://api.weixin.qq.com/sns/userinfo
    @Autowired
    private UcenterMemberService memberService;

    //2 获取扫描人信息,添加数据
    @GetMapping("callback")
    public String callback(String code, String state) {
        try {
            //1 获取code值,临时票据,类似于验证码
            //2 拿着code请求 微信固定的地址,得到两个值 accsess_token 和 openid
            String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                    "?appid=%s" +
                    "&secret=%s" +
                    "&code=%s" +
                    "&grant_type=authorization_code";
            //拼接三个参数 :id  秘钥 和 code值
            String accessTokenUrl = String.format(
                    baseAccessTokenUrl,
                    ConstantWxUtils.WX_OPEN_APP_ID,
                    ConstantWxUtils.WX_OPEN_APP_SECRET,
                    code
            );
            //请求这个拼接好的地址,得到返回两个值 accsess_token 和 openid
            //使用httpclient发送请求,得到返回结果
            String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);

            //从accessTokenInfo字符串获取出来两个值 accsess_token 和 openid
            //把accessTokenInfo字符串转换map集合,根据map里面key获取对应值
            //使用json转换工具 Gson
            Gson gson = new Gson();
            HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
            String access_token = (String)mapAccessToken.get("access_token");
            String openid = (String)mapAccessToken.get("openid");

            //把扫描人信息添加数据库里面
            //判断数据表里面是否存在相同微信信息,根据openid判断
            UcenterMember member = memberService.getOpenIdMember(openid);
            if(member == null) {//memeber是空,表没有相同微信数据,进行添加

                //3 拿着得到accsess_token 和 openid,再去请求微信提供固定的地址,获取到扫描人信息
                //访问微信的资源服务器,获取用户信息
                String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                        "?access_token=%s" +
                        "&openid=%s";
                //拼接两个参数
                String userInfoUrl = String.format(
                        baseUserInfoUrl,
                        access_token,
                        openid
                );
                //发送请求
                String userInfo = HttpClientUtils.get(userInfoUrl);
                System.out.println(userInfo);
                //获取返回userinfo字符串扫描人信息
                HashMap userInfoMap = gson.fromJson(userInfo, HashMap.class);
                String nickname = (String)userInfoMap.get("nickname");//昵称
                String headimgurl = (String)userInfoMap.get("headimgurl");//头像

                member = new UcenterMember();
                member.setOpenid(openid);
                member.setNickname(nickname);
                member.setAvatar(headimgurl);
                memberService.save(member);   // 保存新用户
            }

            //使用jwt根据member对象生成token字符串
            String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());
            //最后:返回首页面,通过路径传递token字符串
            return "redirect:http://localhost:3000?token="+jwtToken;
        }catch(Exception e) {
            throw new GuliException(20001,"登录失败");
        }
    }