1.搭建环境:JDK1.8+IDEA2017+MySQL8.0
2.功能要求:
登录功能:
1. 用户名为6-20位字符串,支持字母、数字;
2. 密码为8-20位字符串,支持字母、数字、特殊字符;
3. 登录失败有原因提示;
4. 5分钟内5次登录失败冻结账号10分钟;
注册功能:
1. 注册信息包括用户名、密码、手机号、出生年月、性别;
2. 用户名、手机号不可重复;
3. 用户名为6-20位字符串,支持字母、数字;
4. 密码为8-20位字符串,支持字母、数字、特殊字符;
5. 验证手机号格式;
6. 注册失败有原因提示;
3.登录实现
3.1编写UserService类
package com.example.shop.service;
import com.example.shop.model.User;
public interface UserService {
Integer checkLoginLog(User user);
User login(User user, String ip);
Integer reg(User user);
}
3.2编写实现类
package com.example.shop.service.impl;
import com.example.shop.dao.LoginLogDAO;
import com.example.shop.dao.LoginLogSpecDAO;
import com.example.shop.dao.UserDAO;
import com.example.shop.model.LoginLog;
import com.example.shop.model.LoginLogExample;
import com.example.shop.model.User;
import com.example.shop.model.UserExample;
import com.example.shop.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import java.util.Date;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
/* @Autowired
private LoginLogDAO loginLogDAO;
@Autowired
private LoginLogSpecDAO loginLogSpecDAO;*/
/**
* 检查登录失败次数
*/
/* @Override
public Integer checkLoginLog(User user){
//判断该账号是否可以登录
LoginLog loginLog = new LoginLog();
loginLog.setUsername(user.getUsername());
return loginLogSpecDAO.countByUsername(loginLog);
}*/
/**
* 登录验证用户名和密码
* @param user
* @return
*/
@Override
public User login(User user, String ip) {
//判断用户名密码是否存在
UserExample userExample = new UserExample();
userExample.createCriteria().andUsernameEqualTo(user.getUsername()).andPasswordEqualTo(DigestUtils.md5DigestAsHex(user.getPassword().getBytes()));
List<User> users = userDAO.selectByExample(userExample);
/* //增加登录日志
int flag = !CollectionUtils.isEmpty(users) ? 1 : 0;
LoginLog loginLog = new LoginLog();
loginLog.setUsername(user.getUsername());
loginLog.setIp(ip);
loginLog.setLoginTime(new Date());
loginLog.setLoginSuccess(flag);
loginLogDAO.insertSelective(loginLog);*/
return !CollectionUtils.isEmpty(users) ? users.get(0) : null;
}
/**
* 新用户注册
* @param user
* @return
*/
@Override
public Integer reg(User user){
//判断手机号和用户名是否重复
UserExample ue = new UserExample();
ue.createCriteria().andUsernameEqualTo(user.getUsername());
ue.or().andPhoneEqualTo(user.getPhone());
List<User> users = userDAO.selectByExample(ue);
if(!CollectionUtils.isEmpty(users)){
return -1;
}
//密码加密,增加创建时间,注册用户信息
user.setPassword(DigestUtils.md5DigestAsHex(user.getPassword().getBytes()));
user.setCreatedTime(new Date());
int rs = userDAO.insertSelective(user);
//如果添加成功,获取刚插入的id
if(rs > 0){
return user.getId();
}
return rs;
}
}
3.3校验功能有两种方式:第一种使用正则表达式
package com.example.shop_test.controller;
import com.example.shop_test.common.util.JwtUtils;
import com.example.shop_test.common.util.ResultModel;
import com.example.shop_test.model.User;
import com.example.shop_test.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
/**
* 用户登录
* @param user
* @return
*/
@PostMapping("/login")
@ResponseBody
public ResultModel login(@RequestBody User user){
//验证参数,也可以使用validated
if(null == user.getUsername()){
return ResultModel.error("请输入用户名");
}
String userExp = "^[A-Za-z0-9]{6,20}$";
if(!user.getUsername().matches(userExp)) {
return ResultModel.error("用户名为6-20位字母、数字");
}
if(null == user.getPassword()){
return ResultModel.error("请输入密码");
}
String pwdExp = "^[A-Za-z0-9_!@#$%&*]{8,20}$";
if(!user.getPassword().matches(pwdExp)) {
return ResultModel.error("密码为8-20位字母、数字、特殊字符");
}
//调用登录方法
User rs = userService.login(user);
if(null == rs){
return ResultModel.error("用户名密码错误");
}
//jwt 封装token返回给前端 后续接口用token验证身份
String token = JwtUtils.createToken(rs.getId(), rs.getUsername());
Map resultMap = new HashMap();
resultMap.put("id", rs.getId());
resultMap.put("token", token);
return ResultModel.success(resultMap);
}
/**
* 用户注册
* @param user
* @return
*/
@PostMapping("reg")
@ResponseBody
public ResultModel reg(@RequestBody User user){
Integer rs = userService.reg(user);
if(0 == rs){
return ResultModel.error("注册失败");
}
//jwt 封装token返回给前端 后续接口用token验证身份
String token = JwtUtils.createToken(rs, user.getUsername());
Map resultMap = new HashMap();
resultMap.put("id", rs);
resultMap.put("token", token);
return ResultModel.success(resultMap);
}
}
第二种使用注解@Validated:
步骤如下:
(1)先创建两个接口Reg和Login
package com.example.shop.common.validation.user;
/**
* 注册校验
*/
public interface Login {
}
package com.example.shop.common.validation.user;
/**
* 注册校验
*/
public interface Reg {
}
(2)使用这两个接口绑定@Validated注解上(@Validated(Reg.class))
(3)在model模型中绑定属性值
package com.example.shop.model;
import com.example.shop.common.validation.user.Login;
import com.example.shop.common.validation.user.Reg;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import java.util.Date;
public class User {
private Integer id;
@NotBlank(message = "用户名不能为空",groups = {Reg.class, Login.class})
@Pattern(regexp = "^[A-Za-z0-9]{6,20}$", message = "用户名格式错误", groups = {Reg.class, Login.class})
private String username;
@NotBlank(message = "密码不能为空",groups = {Reg.class, Login.class})
@Pattern(regexp = "^[A-Za-z0-9_!@#$%&*]{8,20}$", message = "密码格式错误", groups = {Reg.class, Login.class})
private String password;
private String nickname;
private Date birthday;
private Integer sex;
private String phone;
private String idcardNum;
private Integer status;
private Date createdTime;
private Date modifiedTime;
private Integer isDelete;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getIdcardNum() {
return idcardNum;
}
public void setIdcardNum(String idcardNum) {
this.idcardNum = idcardNum;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
public Date getModifiedTime() {
return modifiedTime;
}
public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}
public Integer getIsDelete() {
return isDelete;
}
public void setIsDelete(Integer isDelete) {
this.isDelete = isDelete;
}
}
(4)导入ValidationExceptionHandler类(监听格式是否出错)
package com.example.shop.config;
import com.example.shop.common.util.ResultModel;
import com.example.shop.exception.LoginException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.stream.Collectors;
/**
* validation
*/
@Slf4j
@RestControllerAdvice(basePackages = {"com.example"})
public class ValidationExceptionHandler {
public ValidationExceptionHandler() {
}
@ExceptionHandler({MethodArgumentNotValidException.class})
public ResultModel<?> validationErrorHandler(MethodArgumentNotValidException e) {
List<String> errorInfo = e
.getBindingResult()
.getAllErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
log.error(errorInfo.toString());
return new ResultModel(401, errorInfo.toString());
}
@ExceptionHandler({ConstraintViolationException.class})
public ResultModel validationParamErrorHandler(ConstraintViolationException e) {
List<String> errorInfo = e
.getConstraintViolations()
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
log.error(errorInfo.toString());
return new ResultModel(402, errorInfo.toString());
}
}
3.4编写controller类
package com.example.shop.controller;
import com.example.shop.common.util.IpUtils;
import com.example.shop.common.util.JwtUtils;
import com.example.shop.common.validation.user.Login;
import com.example.shop.common.validation.user.Reg;
import com.example.shop.model.User;
import com.example.shop.service.UserService;
import com.example.shop.common.util.ResultModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
/**
* 用户登录
* @param user
* @return
*/
@PostMapping("/login")
@ResponseBody
public ResultModel login(@Validated(Login.class) @RequestBody User user, HttpServletRequest request){
/* //验证是否可以登录
Integer count = userService.checkLoginLog(user);
if(count > 4){
return ResultModel.error("用户禁止登录");
}*/
//调用登录方法
String ip = IpUtils.getIpAddr(request);
User rs = userService.login(user, ip);
if(null == rs){
return ResultModel.error("用户名密码错误");
}
//jwt 封装token返回给前端 后续接口用token验证身份
String token = JwtUtils.createToken(rs.getId(), rs.getUsername());
Map resultMap = new HashMap();
resultMap.put("id", rs.getId());
resultMap.put("token", token);
return ResultModel.success(resultMap);
}
/**
* 用户注册
* @param user
* @return
*/
@PostMapping("reg")
@ResponseBody
public ResultModel reg(@Validated(Reg.class) @RequestBody User user){
Integer rs = userService.reg(user);
if(0 == rs){
return ResultModel.error("注册失败");
}else if(-1 == rs){
return ResultModel.error("用户名或手机已存在");
}
//jwt 封装token返回给前端 后续接口用token验证身份
String token = JwtUtils.createToken(rs, user.getUsername());
Map resultMap = new HashMap();
resultMap.put("id", rs);
resultMap.put("token", token);
return ResultModel.success(resultMap);
}
}
3.5拦截器的实现
(1)编写拦截器LoginInterceptor
package com.example.shop.common.interceptor;
import com.example.shop.common.util.JwtUtils;
import com.example.shop.exception.LoginException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Configuration
public class LoginInterceptor implements HandlerInterceptor {
/**
* 验证登录状态
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
if (StringUtils.isBlank(token)) {
log.error("token为空或token已过期");
throw new LoginException(400,"token为空或token已过期");
}
if (!JwtUtils.verify(token)) {
log.error("token不正确");
throw new LoginException(400, "token不正确");
}
String id = JwtUtils.getClaimByName(token, "id");
String username = JwtUtils.getClaimByName(token, "username");
request.getSession().setAttribute("id", id);
request.getSession().setAttribute("username", username);
return true;
}
}
(2)定义自己的配置类 MyMvcConfig,加入拦截器
package com.example.shop.config;
import com.example.shop.common.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 列举不需要验证登录状态的接口
*/
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/user/login")
.excludePathPatterns("/user/reg");
}
}
(3)定义一个异常类LoginException
package com.example.shop.exception;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginException extends RuntimeException{
private int code;
private String message;
public LoginException(String message) {
super(message);
}
public LoginException(String message, Throwable cause) {
super(message, cause);
}
}
3.6冻结登录实现
(1)自定义一个DAO类,LoginLogSpecDAO
package com.example.shop.dao;
import com.example.shop.model.LoginLog;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface LoginLogSpecDAO {
int countByUsername(LoginLog loginLog);
}
(2)model类LoginLog自动生成Mybatis
package com.example.shop.model;
import java.util.Date;
public class LoginLog {
private Integer id;
private String username;
private String ip;
private Integer loginSuccess;
private Date loginTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Integer getLoginSuccess() {
return loginSuccess;
}
public void setLoginSuccess(Integer loginSuccess) {
this.loginSuccess = loginSuccess;
}
public Date getLoginTime() {
return loginTime;
}
public void setLoginTime(Date loginTime) {
this.loginTime = loginTime;
}
}
<table tableName="login_log" domainObjectName="LoginLog" mapperName="LoginLogDAO" catalog="shop">
<property name="ignoreQualifiersAtRuntime" value="true"/>
<generatedKey column="id" sqlStatement="MySql" identity="true"/>
</table>
(3)编写LoginLogSpecDAO.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.shop.dao.LoginLogSpecDAO">
<!-- 根据用户id查询角色名称 -->
<select id="countByUsername" parameterType="com.example.shop.model.LoginLog" resultType="java.lang.Integer">
select count(id)
from login_log
where username = #{username}
and login_time > DATE_SUB(NOW(),INTERVAL 5 MINUTE)
and login_success = 0
</select>
</mapper>
(4)重写实现类
package com.example.shop.service.impl;
import com.example.shop.dao.LoginLogDAO;
import com.example.shop.dao.LoginLogSpecDAO;
import com.example.shop.dao.UserDAO;
import com.example.shop.model.LoginLog;
import com.example.shop.model.LoginLogExample;
import com.example.shop.model.User;
import com.example.shop.model.UserExample;
import com.example.shop.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import java.util.Date;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
@Autowired
private LoginLogDAO loginLogDAO;
@Autowired
private LoginLogSpecDAO loginLogSpecDAO;
/**
* 检查登录失败次数
*/
@Override
public Integer checkLoginLog(User user){
//判断该账号是否可以登录
LoginLog loginLog = new LoginLog();
loginLog.setUsername(user.getUsername());
return loginLogSpecDAO.countByUsername(loginLog);
}
/**
* 登录验证用户名和密码
* @param user
* @return
*/
@Override
public User login(User user, String ip) {
//判断用户名密码是否存在
UserExample userExample = new UserExample();
userExample.createCriteria().andUsernameEqualTo(user.getUsername()).andPasswordEqualTo(DigestUtils.md5DigestAsHex(user.getPassword().getBytes()));
List<User> users = userDAO.selectByExample(userExample);
//增加登录日志
int flag = !CollectionUtils.isEmpty(users) ? 1 : 0;
LoginLog loginLog = new LoginLog();
loginLog.setUsername(user.getUsername());
loginLog.setIp(ip);
loginLog.setLoginTime(new Date());
loginLog.setLoginSuccess(flag);
loginLogDAO.insertSelective(loginLog);
return !CollectionUtils.isEmpty(users) ? users.get(0) : null;
}
/**
* 新用户注册
* @param user
* @return
*/
@Override
public Integer reg(User user){
//判断手机号和用户名是否重复
UserExample ue = new UserExample();
ue.createCriteria().andUsernameEqualTo(user.getUsername());
ue.or().andPhoneEqualTo(user.getPhone());
List<User> users = userDAO.selectByExample(ue);
if(!CollectionUtils.isEmpty(users)){
return -1;
}
//密码加密,增加创建时间,注册用户信息
user.setPassword(DigestUtils.md5DigestAsHex(user.getPassword().getBytes()));
user.setCreatedTime(new Date());
int rs = userDAO.insertSelective(user);
//如果添加成功,获取刚插入的id
if(rs > 0){
return user.getId();
}
return rs;
}
}
3.7postman测试
3.8工具类
IpUtils
package com.example.shop.common.util;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class IpUtils {
public static String getIpAddr(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1")) {
// 根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
// = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress="";
}
// ipAddress = this.getRequest().getRemoteAddr();
return ipAddress;
}
}
JwtUtils
package com.example.shop.common.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
/**
* @author dhl
* @datetime 2021/8/17 13:47
*/
public class JwtUtils {
/**
* 超时时间
*/
private static final long TIME_OUT_MS = 1000 * 60 * 60 * 24;
/**
* 加密方式
*/
private static final String ENCRYPT ="wqw123";
/**
* 算法
*/
private static final Algorithm ALGORITHM = Algorithm.HMAC256(ENCRYPT);
/**
* 生成签名,30min后过期
* @param id
* @return
*/
public static String createToken(Integer id, String username){
Date date = new Date(System.currentTimeMillis() + TIME_OUT_MS);
return JWT.create()
// 设置荷载 payload
.withClaim("id", id)
.withClaim("username", username)
// 设置过期时间
.withExpiresAt(date)
.sign(ALGORITHM);
}
/**
* 验证token是否正确
* @param token
* @return
*/
public static boolean verify(String token) {
try {
JWTVerifier verifier = JWT.require(ALGORITHM).build();
verifier.verify(token);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 获取token中的信息
* @param token
* @param name
* @return
*/
public static String getClaimByName(String token, String name) {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim(name).asString();
}
}
ResultModel
package com.example.shop.common.util;
import lombok.Data;
@Data
public class ResultModel<T> {
private int code;
private String message="";
private T content;
public ResultModel(){
}
public ResultModel(int code,String message){
this.code = code;
this.message = message;
}
public ResultModel(int code,String message,T content){
this.code = code;
this.message = message;
this.content = content;
}
public static <T> ResultModel<T> success(String message,T content){
return new ResultModel<T>(0,message,content);
}
public static <T> ResultModel<T> success(T content){
return new ResultModel<T>(0,"success",content);
}
public static <T> ResultModel<T> error(int code,String message){
return new ResultModel<T>(code,message);
}
public static <T> ResultModel<T> error(String message){
return new ResultModel<T>(-1,message);
}
public static <T> ResultModel<T> error(){
return new ResultModel<T>(-1,"error");
}
}