用户登录模块Java后端实现

05月05日 收藏 0 评论 3 java开发

用户登录模块Java后端实现

转载声明:https://blog.csdn.net/qq_41847894/article/details/125870538

一、Util工具

public interface CommunityConstant {

// 默认的登录凭证超时时间 (12小时)
int DEFAULT_EXPIRED_SECONDS = 3600 * 12;

// “记住我”状态下凭证超时时间 (100天)
int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;
}
public class CommunityUtil {

/**
* 生成随机字符串
* @return
*/
public static String generateUUID() {
// 去除生成的随机字符串中的 ”-“
return UUID.randomUUID().toString().replaceAll("-", "");
}

/**
* md5 加密
* @param key 要加密的字符串
* @return
*/
public static String md5(String key) {
if (StringUtils.isBlank(key)) {
return null;
}
return DigestUtils.md5DigestAsHex(key.getBytes());
}

}
public class RedisKeyUtil {

private static final String SPLIT = ":";
private static final String PREFIX_KAPTCHA = "kaptcha"; // 验证码
private static final String PREFIX_TICKET = "ticket"; // 登录凭证

/**
* 登录验证码(指定这个验证码是针对哪个用户的)
* @param owner 用户进入登录页面的时候,由于此时用户还未登录,无法通过 id 标识用户
* 随机生成一个字符串,短暂的存入 cookie,使用这个字符串来标识这个用户
* @return
*/
public static String getKaptchaKey(String owner) {
return PREFIX_KAPTCHA + SPLIT + owner;
}

/**
* 登录凭证
* @param ticket
* @return
*/
public static String getTicketKey(String ticket) {
return PREFIX_TICKET + SPLIT + ticket;
}

}

二、Dao层(数据访问层:CRUD操作)

创建com.jodie.xxx.dao.UserMapper:

@Mapper
public interface UserMapper {
/**
* 根据 username 查询用户
* @param username
* @return
*/
User selectByName(String username);
}

在resources/mappers下创建user-mapper.xml写sql查询语句:

<mapper namespace="com.jodie.xxx.dao.UserMapper">
<sql id = "selectFields">
id, username, password, salt, email, type, status, activation_code, header_url, create_time
</sql>
<!--根据 Username 查询用户信息-->
<select id="selectByName" resultType="User">
select <include refid="selectFields"></include>
from user
where username = #{username}
</select>
</mapper>

三、Sevice层(业务逻辑层:条件判断)

@Service
public class UserService implements CommunityConstant {

@Autowired
private UserMapper userMapper;

/**
* 用户登录(为用户创建凭证)
* @param username
* @param password
* @param expiredSeconds 多少秒后凭证过期
* @return Map<String, Object> 返回错误提示消息以及 ticket(凭证)
*/
public Map<String, Object> login(String username, String password, int expiredSeconds) {
Map<String, Object> map = new HashMap<>();

// 参数判断:判断用户名和密码是否为空
if (StringUtils.isBlank(username)) {
map.put("usernameMsg", "账号不能为空");
return map;
}
if (StringUtils.isBlank(password)) {
map.put("passwordMsg", "密码不能为空");
return map;
}

// 判断用户对象是否存在
User user = userMapper.selectByName(username);
if (user == null) {
map.put("usernameMsg", "该账号不存在");
return map;
}

// 判断用户对象是否激活
if (user.getStatus() == 0) {
map.put("usernameMsg", "该账号未激活");
return map;
}

// 将客户端传递的密码加密,再与数据库中查询的密码比较
password = CommunityUtil.md5(password + user.getSalt());
if (!user.getPassword().equals(password)) {
map.put("passwordMsg", "密码错误");
return map;
}

// 用户名和密码均正确,为该用户生成唯一的登录凭证实体类对象 LoginTicket
// LoginTicket包含用户id、登录凭证字符串ticket、是否有效、过期时间
LoginTicket loginTicket = new LoginTicket();
loginTicket.setUserId(user.getId());
loginTicket.setTicket(CommunityUtil.generateUUID()); // 随机凭证
loginTicket.setStatus(0); // 设置凭证状态为有效(当用户登出的时候,设置凭证状态为无效)
loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000)); // 设置凭证到期时间

// 将登录凭证存入 redis
String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
// 自动把loginTicket序列化成一个json字符串
redisTemplate.opsForValue().set(redisKey, loginTicket);

map.put("ticket", loginTicket.getTicket());

return map;
}
}

四、controller层(控制层:接收请求、响应结果)

先随机生成当前页面暂时的用户id→cookie

随机生成验证码→redis

输入账号密码验证码,账号密码正确后,比较redis中的验证码与输入的code

验证码正确,登录成功,生成登录凭证ticket→redis/cookie

下次用户想免输入登录时,直接判断cookie中是否含有该用户ticket,有则直接登录,否则重新输入用户名和密码

@Controller
public class LoginController implements CommunityConstant {

private static final Logger logger = LoggerFactory.getLogger(LoginController.class);

@Autowired
private Producer kaptchaProducer;

@Autowired
private RedisTemplate redisTemplate;

@Value("${server.servlet.context-path}")
private String contextPath;

/**
* 生成验证码, 并存入 Redis
* @param response
*/
@GetMapping("/kaptcha")
public void getKaptcha(HttpServletResponse response) {
// 生成验证码
String text = kaptchaProducer.createText(); // 生成随机字符
System.out.println("验证码:" + text);
BufferedImage image = kaptchaProducer.createImage(text); // 生成图片

// 验证码的归属者
String kaptchaOwner = CommunityUtil.generateUUID(); // 登录前随机生成用户暂时的id
Cookie cookie = new Cookie("kaptchaOwner", kaptchaOwner); //将暂时id存入cookie中
cookie.setMaxAge(60);
cookie.setPath(contextPath);
response.addCookie(cookie);
// 将id对应的验证码存入 redis
String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);

// 将图片输出给浏览器
response.setContentType("image/png");
try {
ServletOutputStream os = response.getOutputStream();
ImageIO.write(image, "png", os);
} catch (IOException e) {
logger.error("响应验证码失败", e.getMessage());
}
}

/**
* 用户登录
* @param username 用户名
* @param password 密码
* @param code 验证码
* @param rememberMe 是否记住我(点击记住我后,凭证的有效期延长)
* @param model
* @param kaptchaOwner 通过@CookieValue注解获取cookie中的暂时的id
* @param response
* @return
*/
@PostMapping("/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam("code") String code,
@RequestParam(value = "rememberMe", required = false) boolean rememberMe,
Model model, HttpServletResponse response,
@CookieValue("kaptchaOwner") String kaptchaOwner) {
// 检查redis中id对应的验证码
String kaptcha = null;
if (StringUtils.isNotBlank(kaptchaOwner)) {
String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
kaptcha = (String) redisTemplate.opsForValue().get(redisKey);
}
// 判断redis中的验证码是否和输入的验证码code一致
if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {
model.addAttribute("codeMsg", "验证码错误");
return "/site/login";
}

// 凭证过期时间(是否记住我)
int expiredSeconds = rememberMe ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
// 验证用户名和密码
Map<String, Object> map = userService.login(username, password, expiredSeconds);
if (map.containsKey("ticket")) {
// 账号和密码均正确,则服务端会生成 ticket,浏览器通过 cookie 存储 ticket
Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
cookie.setPath(contextPath); // cookie有效范围
cookie.setMaxAge(expiredSeconds); //cookie存在时间
response.addCookie(cookie); // 服务器返回给浏览器cookie以便下次判断
return "redirect:/index"; // 重定向
}
else {
model.addAttribute("usernameMsg", map.get("usernameMsg"));
model.addAttribute("passwordMsg", map.get("passwordMsg"));
return "/site/login";
}
}
}



C 3条回复 评论
C李要控制李寄几

涨知识了

发表于 2023-09-10 21:00:00
0 0
呵呵

只有懂得基本原理和协议规范的程序员才能摆脱搬砖码农这个束缚。

发表于 2023-09-07 23:00:00
0 0
你是闰土我是猹

收藏不息,战斗不止

发表于 2023-06-03 23:00:00
0 0