Implementation of login mode

The introduction

Think about the login methods used in previous projects and summarize briefly

1. General login

  • The implementation of common login: according to the user name and password entered by the user, the user submits the information to the background. The background determines whether the information entered by the user exists in the database, and returns data to the front end if it does.
  • Problems: As long as the database has user information, you can log in at any time, so there are security problems, you need to consider permission control, security authentication, prevent CSRF attacks and other issues.

The front-end code

$.ajax({
            url: '/login'.type: 'POST'.dataType: "json".data: {
                "username": username,
                "password": password,
            },
            success: function (result1) {
                // Get background data result1
                if ("true" === result1.flag) {
                    // If the information is correct, go to home page
                    window.location.href = "/common/index";
                } else if ("false" === result1.flag) {
                    $("#tip2").html("The user does not exist!"); }},async: true.error: function () {
                // Failed to redirect to login
                window.location.href = "/tologin"; }})Copy the code

Back-end Controller code

@RequestMapping("/login")
    @ResponseBody
    public Map<String, String> userLogin(@RequestParam("username") String username,
                                         @RequestParam("password") String password,
                                         HttpServletRequest request) {
        Users users = userService.userLogin(username, password);
        Map<String, String> result = new HashMap<String, String>();
        if(users ! =null) {
            result.put("flag"."true");
        } else {
            result.put("flag"."false");
        }
        return result;
    }

Copy the code

Back-end Service code

public Users userLogin(String username, String password) {
        return usermapper.userLogin(username, password);
 }

Copy the code

2. Token verification

  • What is a Token

    It is a string of strings generated in the background, that is, the server, used to authenticate the front end. If the front end encounters a very frequent request for background data, each time it needs to compare the current login user information with the database to determine whether it is correct before returning data, which will undoubtedly increase the pressure on the server

  • The role of the Token

    Avoid CSRF attacks

    Tokens are stateless and can be shared across multiple services

  • Realization in the project: If the Token is not empty, it generates a Token and passes the Token to the front end. After receiving the Token, the front end saves it to the Local Storage. Then an AXIos interceptor can be created. Check whether the Local Storage contains a Token to ensure login security

The front-end code

async success() {
      // Initiate a login request
      const { data: res } = await this.$http.post(
        "api/system/user/login".this.userLoginForm
      );
      if (res.success) {
        this.$message({
          title: "Login successful".message: "Welcome to the system.".type: "success"
        });
        // Save the token information returned in the background to LocalStorage
        LocalStorage.set(LOCAL_KEY_XINGUAN_ACCESS_TOKEN, res.data);
        // Obtain the information about the current login user
        await this.getUserInfo();
      } else {
        this.$message.error({
          title: "Login failed".message: res.data.errorMsg,
          type: "error"
      });
}
Copy the code

Back-end Controller code

    @PostMapping("/login")
public ResponseBean<String> login(@RequestBody UserLoginDTO userLoginDTO, HttpServletRequest request) throws SystemException {
       log.info(userLoginDTO.getUsername()+userLoginDTO.getPassword()+userLoginDTO.getImageCode());
String token=
userService.login(userLoginDTO.getUsername(),userLoginDTO.getPassword(),userLoginDTO.getImageCode());
       loginLogService.add(request);
       return ResponseBean.success(token);
}
Copy the code

Back-end Service code

 @Override
    public String login(String username, String password,String code) throws SystemException {
        String token;
        // Get a random verification code
        String verifyCode = (String) redisTemplate.opsForValue().get("imageCode");
        if(code.equals(verifyCode)){
            User user = apiUserService.findUserByName(username);
            if(user ! =null) {
                // Encrypt the password with salt
                String salt = user.getUSalt();
                // The key is salt
                String target = MD5Utils.md5Encryption(password, salt);
                / / Token is generated
                token = JWTUtils.sign(username, target);
                JWTToken jwtToken = new JWTToken(token);
                try {
                    SecurityUtils.getSubject().login(jwtToken);
                } catch (AuthenticationException e) {
                    throw newSystemException(SystemCodeEnum.PARAMETER_ERROR,e.getMessage()); }}else {
                throw new SystemException(SystemCodeEnum.PARAMETER_ERROR,"User does not exist"); }}else{
            throw new SystemException(SystemCodeEnum.PARAMETER_ERROR,"Verification code error");
        }
        return token;
    }
Copy the code

3. Wechat login

Wechat login is also a secure login method. It is a wechat OAuth 2.0 authorized login system constructed based on OAuth 2.0 protocol standard. The sequence diagram is as follows

The official documentation

Developers.weixin.qq.com/doc/oplatfo…

The front-end code

// Background interface
const api_name = `/api/ucenter/wx`
export default {
  getLoginParam() {
    return request({
      url: `${api_name}/getLoginParam`.method: `get`}}})Copy the code
weixinApi.getLoginParam().then(response= > {
        console.log(response);
        let REDIRECT_URI = encodeURIComponent(response.data.redirectUri);
        var obj = new WxLogin({
          self_redirect: true.id: "weixinLogin".// The container ID to display
          appid: response.data.appid, // Appid wx*******
          scope: response.data.scope, // The default page is ok
          redirect_uri: REDIRECT_URI, // The url to call back after successful authorization
          state: response.data.state, // Can be set to a simple random number plus session for verification
          style: "black".// Provide "black" and "white" options. The style of two-dimensional code
          href: "" // External CSS file url, HTTPS required
        });
});
Copy the code

The back-end code

Application. Properties file configuration

App_id = // wechat development platform appsecret wx.open.app_secret= // wechat development platform redirection address wx.open.redirect_url= // Configure the front-end domain name address baseUrl=Copy the code

Back-end Controller code

// Wechat scan code
@GetMapping("getLoginParam")
    @ResponseBody
    public Result genQrConnect(a) {
        try {
            Map<String, Object> map = new HashMap<>();
            map.put("appid", ConstantWxPropertiesUtils.WX_OPEN_APP_ID);
            map.put("scope"."snsapi_login");
            String wxOpenRedirectUrl = ConstantWxPropertiesUtils.WX_OPEN_REDIRECT_URL;
            wxOpenRedirectUrl = URLEncoder.encode(wxOpenRedirectUrl, "utf-8");
            map.put("redirect_uri",wxOpenRedirectUrl);
            map.put("state",System.currentTimeMillis()+"");
            return Result.ok(map);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null; }}// The method of wechat callback after scanning
    @GetMapping("callback")
    public String callback(String code,String state) {
        // The first step is to get the temporary ticket code
        System.out.println("code:"+code);
        // The second step is to take the code and wechat ID and secret key, request wechat fixed address, get two values
        // Use code and appID and appscrect for access_token
        // %s placeholder
        StringBuffer baseAccessTokenUrl = new StringBuffer()
                .append("https://api.weixin.qq.com/sns/oauth2/access_token")
                .append("? appid=%s")
                .append("&secret=%s")
                .append("&code=%s")
                .append("&grant_type=authorization_code");
        String accessTokenUrl = String.format(baseAccessTokenUrl.toString(),
                ConstantWxPropertiesUtils.WX_OPEN_APP_ID,
                ConstantWxPropertiesUtils.WX_OPEN_APP_SECRET,
                code);
        // Use httpClient to request this address
        try {
            String accesstokenInfo = HttpClientUtils.get(accessTokenUrl);
            System.out.println("accesstokenInfo:"+accesstokenInfo);
            // Get two values openID and access_token from the return string
            JSONObject jsonObject = JSONObject.parseObject(accesstokenInfo);
            String access_token = jsonObject.getString("access_token");
            String openid = jsonObject.getString("openid");

            // Check whether there is wechat scanning information in the database
            // According to openID
            UserInfo userInfo = userInfoService.selectWxInfoOpenId(openid);
            if(userInfo == null) { // There is no wechat information in database
                // The third step is to request the wechat address with openID and access_token and get the scanned person information
                String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                        "? access_token=%s" +
                        "&openid=%s";
                String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);
                String resultInfo = HttpClientUtils.get(userInfoUrl);
                System.out.println("resultInfo:"+resultInfo);
                JSONObject resultUserInfoJson = JSONObject.parseObject(resultInfo);
                // Parse user information
                // User name
                String nickname = resultUserInfoJson.getString("nickname");
                // User profile picture
                String headimgurl = resultUserInfoJson.getString("headimgurl");

                // Get the scanned person information and add the database
                userInfo = new UserInfo();
                userInfo.setNickName(nickname);
                userInfo.setOpenid(openid);
                userInfo.setStatus(1);
                userInfoService.save(userInfo);
            }
            // Return the name and token strings
            Map<String,String> map = new HashMap<>();
            String name = userInfo.getName();
            if(StringUtils.isEmpty(name)) {
                name = userInfo.getNickName();
            }
            if(StringUtils.isEmpty(name)) {
                name = userInfo.getPhone();
            }
            map.put("name", name);

            // Check if userInfo has a phone number. If the phone number is empty, return openID
            // If the phone number is not empty, the openID value is returned as an empty string
            // Front-end judgment: If openID is not empty, bind the mobile phone number. If openID is empty, do not bind the mobile phone number
            if(StringUtils.isEmpty(userInfo.getPhone())) {
                map.put("openid", userInfo.getOpenid());
            } else {
                map.put("openid"."");
            }
            // Generate token strings using JWT
            String token = JwtHelper.createToken(userInfo.getId(), name);
            map.put("token", token);
            // Jump to the front-end page
            return "redirect:" + ConstantWxPropertiesUtils.BASE_URL + "/weixin/callback? token="+map.get("token") +"&openid="+map.get("openid") +"&name="+URLEncoder.encode(map.get("name"),"utf-8");
        } catch (Exception e) {
            e.printStackTrace();
            return null; }}Copy the code

4. Login with your mobile phone number

The login of mobile phone number is as follows: According to the user to enter a phone number, when submitted after the login, the background will determine whether a mobile phone number is empty, if not null, the method of using a random authentication code can be generated, keep the authentication code to the Redis, and set up the effective time, then the configuration parameter information including the authentication code generated, submitted to the ali cloud and determine whether the configuration information is correct, if correct, SMS verification code is sent to the user’s mobile phone number. After the user enters the verification code, the verification code is compared with the verification code in Redis. If the verification code is the same, the data is returned to the front end

Introduction of depend on

<dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>aliyun-java-sdk-core</artifactId>
</dependency>

Copy the code

Application. The properties configuration

// Configure ali cloud API key
aliyun.sms.regionId=default
aliyun.sms.accessKeyId=
aliyun.sms.secret=


Copy the code

The front-end code

<div class="operate-view" v-if="dialogAtrr.showLoginType === 'phone'"> <div class="wrapper" style="width: 100%"> <div class="mobile-wrapper" style="position: static; width: 70%"> <span class="title">{{ dialogAtrr.labelTips }}</span> <el-form> <el-form-item> <el-input v-model="dialogAtrr.inputValue" :placeholder="dialogAtrr.placeholder" :maxlength="dialogAtrr.maxlength" class="input v-input" > <span slot="suffix" class="sendText v-link" v-if="dialogAtrr.second > 0" >{{ dialogAtrr.second }}s</span> <span slot="suffix" class="sendText v-link highlight clickable selected" v-if="dialogAtrr.second == 0" @click="getCodeFun()" > </span> </el-input> </el-form-item> </el-form> <div class="send-button v-button" @click="btnClick()">{{ dialogAtrr.loginBtn }}</div> </div> <div class="bottom"> <div class="wechat-wrapper" @click="weixinLogin()"> <span class="iconfont icon"></span> </div> </div> </div>Copy the code
// Background interface
const api_name = `/api/sms`
export default {
  sendCode(mobile) {
    return request({
      url: `${api_name}/send/${mobile}`.method: `get`}}})Copy the code
	// Get the verification code
    getCodeFun() {
      if (!/^1[34578]\d{9}$/.test(this.userInfo.phone)) {
        this.$message.error("Incorrect cell phone number");
        return;
      }

      // Initializes the captcha attributes
      this.dialogAtrr.inputValue = "";
      this.dialogAtrr.placeholder = "Please enter the verification code.";
      this.dialogAtrr.maxlength = 6;
      this.dialogAtrr.loginBtn = "Log in now.";

      // Control the retransmission
      if (!this.dialogAtrr.sending) return;
      // Send the SMS verification code
      this.timeDown();
      this.dialogAtrr.sending = false;
      smsApi
        .sendCode(this.userInfo.phone)
        .then(response= > {
          this.timeDown();
        })
        .catch(e= > {
          this.$message.error("Send failed, resend");
          // Failed to send the verification code. The page for obtaining the verification code again is displayed
          this.showLogin();
        });
    },

    / / the countdown
    timeDown() {
      if (this.clearSmsTime) {
        clearInterval(this.clearSmsTime);
      }
      this.dialogAtrr.second = 60;
      this.dialogAtrr.labelTips = "Verification code has been sent to" + this.userInfo.phone;
      this.clearSmsTime = setInterval(() = >{-this.dialogAtrr.second;
        if (this.dialogAtrr.second < 1) {
          clearInterval(this.clearSmsTime);
          this.dialogAtrr.sending = true;
          this.dialogAtrr.second = 0; }},1000);
    },

Copy the code

Back-end Controller code

	// User's mobile phone number login interface
    @PostMapping("login")
    public Result login(@RequestBody LoginVo loginVo) {
        Map<String,Object> info = userInfoService.loginUser(loginVo);
        return Result.ok(info);
   }

Copy the code
	// Send the mobile verification code
    @GetMapping("send/{phone}")
    public Result sendCode(@PathVariable String phone) {
        // Get the captcha from redis. If so, return OK
        // key Mobile phone number value Verification code
        String code = redisTemplate.opsForValue().get(phone);
        if(! StringUtils.isEmpty(code)) {return Result.ok();
        }
        // If you can't get it from redis,
        // Generate a captcha,
        code = RandomUtil.getSixBitRandom();
        // Call the service method to send the message by integrating the SMS service
        boolean isSend = msmService.send(phone,code);
        // Generate a verification code and put it in redis, set the validity time
        if(isSend) {
            redisTemplate.opsForValue().set(phone,code,1, TimeUnit.MINUTES);
            return Result.ok();
        } else {
            return Result.fail().message("Sending SMS failed"); }}Copy the code

Back-end Service code

	// Log in to service with mobile phone number
	@Override
    public Map<String, Object> loginUser(LoginVo loginVo) {
        // Get the phone number and verification code from loginVo
        String phone = loginVo.getPhone();
        String code = loginVo.getCode();

        // Check whether the mobile phone number and verification code are empty
        if(StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) {
            throw new Exception(ResultCodeEnum.PARAM_ERROR);
        }

        // Check whether the mobile verification code is consistent with the input verification code
        String redisCode = redisTemplate.opsForValue().get(phone);
        if(! code.equals(redisCode)) {throw new Exception(ResultCodeEnum.CODE_ERROR);
        }

        // Bind the mobile phone number
        UserInfo userInfo = null;
        if(! StringUtils.isEmpty(loginVo.getOpenid())) { userInfo =this.selectWxInfoOpenId(loginVo.getOpenid());
            if(null! = userInfo) { userInfo.setPhone(loginVo.getPhone());this.updateById(userInfo);
            } else {
                throw newException(ResultCodeEnum.DATA_ERROR); }}// If userInfo is empty, perform normal mobile login
        if(userInfo == null) {
            // Check whether you have logged in for the first time: Query the database based on the mobile phone number. If there is no same mobile phone number, the database is logged in for the first time
            QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
            wrapper.eq("phone",phone);
            userInfo = baseMapper.selectOne(wrapper);
            if(userInfo == null) { // Log in using this mobile number for the first time
                // Add information to the database
                userInfo = new UserInfo();
                userInfo.setName("");
                userInfo.setPhone(phone);
                userInfo.setStatus(1); baseMapper.insert(userInfo); }}// Whether verification is disabled
        if(userInfo.getStatus() == 0) {
            throw new Exception(ResultCodeEnum.LOGIN_DISABLED_ERROR);
        }

        // Not the first time, direct login
        // Returns the login information
        // Returns the login user name
        // Returns token information
        Map<String, Object> map = new HashMap<>();
        String name = userInfo.getName();
        if(StringUtils.isEmpty(name)) {
            name = userInfo.getNickName();
        }
        if(StringUtils.isEmpty(name)) {
            name = userInfo.getPhone();
        }
        map.put("name",name);

        // JWT generates a token string
        String token = JwtHelper.createToken(userInfo.getId(), name);
        map.put("token",token);
        return map;
    }

Copy the code
	// Submit the verification code
	@Override
    public boolean send(String phone, String code) {
        // Check if the phone number is empty
        if(StringUtils.isEmpty(phone)) {
            return false;
        }
        // Integrate Aliyun SMS service
        // Set related parameters
        DefaultProfile profile = DefaultProfile.
                getProfile(ConstantPropertiesUtils.REGION_Id,
                        ConstantPropertiesUtils.ACCESS_KEY_ID,
                        ConstantPropertiesUtils.SECRECT);
        IAcsClient client = new DefaultAcsClient(profile);
        CommonRequest request = new CommonRequest();
        //request.setProtocol(ProtocolType.HTTPS);
        request.setMethod(MethodType.POST);
        request.setDomain("dysmsapi.aliyuncs.com");
        request.setVersion("2017-05-25");
        request.setAction("SendSms");

        / / cell phone number
        request.putQueryParameter("PhoneNumbers", phone);
        // Signature name
        request.putQueryParameter("SignName"."* * * *");
        / / template code
        request.putQueryParameter("TemplateCode"."* * * * * *");

        request.putQueryParameter("TemplateCode"."* * * * *");
        {"code":"123456"}
        Map<String,Object> param = new HashMap();
        param.put("code",code);
        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));

        // Call the method to send SMS messages
        try {
            CommonResponse response = client.getCommonResponse(request);
            System.out.println(response.getData());
            return response.getHttpResponse().isSuccess();
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (ClientException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean send(MsmVo msmVo) {
        if(! StringUtils.isEmpty(msmVo.getPhone())) {boolean isSend = this.send(msmVo.getPhone(), msmVo.getParam());
            return isSend;
        }
        return false;
}

Copy the code
@Data
@apiModel (description = "SMS entity ")
public class MsmVo {

    @ApiModelProperty(value = "phone")
    private String phone;

    @apiModelProperty (value = "SMS template code")
    private String templateCode;

    @apiModelProperty (value = "SMS template parameter ")
    private Map<String,Object> param;
}

Copy the code