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