5_会话管理实现登录功能

一 会话管理登录功能

前置了解

5_会话管理实现登录功能_第1张图片

初识cookie

如下图,浏览器初次访问服务器时,服务器生成数据将数据存放在浏览器的cookie中,当浏览器再次访问服务器时将会携带该cookie,此时服务器就可以确定该浏览器的身份了。

5_会话管理实现登录功能_第2张图片

session的使用

如下图,session的实现需要借助cookie,不同cookie的是,session的数据是存放在服务器端。而此时的cookie只是用于传递数据。

5_会话管理实现登录功能_第3张图片

二 分布式部署项目

在分布式项目中使用session不是一个好的解决方案,下面是几种实现方案。

  1. 使用粘性的化

  2. 使用同步

  3. 存在数据库或者rdis中

5_会话管理实现登录功能_第4张图片

而本项最终是存放在redis中

三 生成验证码

生成验证码使用 Kaptcha

  • 导入jar包
  • 编写 Kaptcha 配置类
  • 生成随机字符,生成图片

官网地址


<dependency>
    <groupId>com.github.pengglegroupId>
    <artifactId>kaptchaartifactId>
    <version>2.3.2version>
dependency>

image-20221004191413942

controller

@Autowired
private Producer kaptchaProducer;

 private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
/**
     * 发送验证码
     * @param response  向浏览器响应图片
     * @param session 用于存放验证码
     */
    @GetMapping("/kaptcha")
    public void getKaptcha(HttpServletResponse response, HttpSession session) {
        // 生成验证码
        String text = kaptchaProducer.createText();
        BufferedImage image = kaptchaProducer.createImage(text);

        //将验证码存入到session中
        session.setAttribute("kaptcha", text);

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

工具包

package com.wjiangquan.community.config;

import com.google.code.kaptcha.Producer;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;

/**
 * @author weijiangquan
 * @date 2022/10/4 -19:34
 * @Description
 */
@Configuration
public class KaptchaConfig {


    @Bean
    public Producer kaptchProducer(){
        Properties properties = new Properties();
        properties.setProperty("kaptcha.image.width","100");  //设置图片的宽
        properties.setProperty("kaptcha.image.height","40");
        properties.setProperty("kaptcha.textproducer.font.size","32");  //单位像素
        properties.setProperty("kaptcha.textproducer.font.color","0,0,0");  //单位像素
        properties.setProperty("kaptcha.textproducer.char.string","9876543210qwertyuiopasdfghjklzxcfvbnm");  //单位像素
        properties.setProperty("kaptcha.textproducer.char.length","4");
        properties.setProperty("kaptcha.noise.impl","com.google.code.kaptcha.impl.NoNoise");  //生成干容东西(比如阴影)


        DefaultKaptcha kaptcha = new DefaultKaptcha();
        Config config = new Config(properties);
        kaptcha.setConfig(config);
        return kaptcha;
    }

}

页面实现的代码

<div class="col-sm-4">
<img  id="kaptcha" th:src="@{/kaptcha}" style="width:100px;height:40px;" class="mr-2"/>
<a href="javascript:refresh_kaptcha();" class="font-size-12 align-bottom">刷新验证码a>
div>
<script>
		CONTEXT_PATH = "/community";
		function refresh_kaptcha() {
			// /kaptcha?p="+Math.random() 加?是为了欺骗浏览器的作用,达到刷新的作用
			var path =  CONTEXT_PATH + "/kaptcha?p="+Math.random();
			$("#kaptcha").attr("src",path);
		}
	</script>

四 登录和退出的功能

5_会话管理实现登录功能_第5张图片

下面是具体的实现步骤

  1. 书写实体类 LoginTicket

  2. 数据访问层 dao

  • 插入数据

    对于插入可以 通过 @Options(userGenerateKeys = true,keyProperty="id") 自动注入属性

  • 通过 ticket查询数据

  • 修改 ticket 修改状态 status

    在注解中也可以像 mapper.xml 文件中一样实现动态 sql

    5_会话管理实现登录功能_第6张图片

  1. 测试dao层的增删改查
  2. 业务层 service

整体框架

public Map login(String username,String password,int expiredSecond(过期秒数)){

​ Map map = new HashMap();

  1. 处理空值
  2. 验证
  • 验证账号是否为空
  • 验证账号是否激活
  • 验证密码
  1. 生成登录凭证
  • 检查验证码

​ retrun null;

}

具体的代码实现

页面主要代码

	<form class="mt-5" method="post" th:action="@{/login}">
					<div class="form-group row">
						<label for="username" class="col-sm-2 col-form-label text-right">账号:label>
						<div class="col-sm-10">
							<input type="text" th:class="|form-control ${usernameMsg!=null?'is-invalid':''}|" id="username" name="username" th:value="${param.username}" placeholder="请输入您的账号!" required>
							<div class="invalid-feedback" th:text="${usernameMsg}">
								该账号不存在!
							div>
						div>
					div>
					<div class="form-group row mt-4">
						<label for="password" class="col-sm-2 col-form-label text-right">密码:label>
						<div class="col-sm-10">
							<input type="password" th:class="|form-control ${passwordMsg!=null?'is-invalid':''}|" id="password"  name="password"  th:value="${param.password}" placeholder="请输入您的密码!" required>
							<div class="invalid-feedback" th:text="${passwordMsg}">
								密码长度不能小于8位!
							div>							
						div>
					div>
					<div class="form-group row mt-4">
						<label for="verifycode" class="col-sm-2 col-form-label text-right">验证码:label>
						<div class="col-sm-6">
							<input type="text" th:class="|form-control ${codeMsg!=null?'is-invalid':''}|" id="verifycode"  name="code"  placeholder="请输入验证码!">
							<div class="invalid-feedback" th:text="${codeMsg}">
								验证码不正确!
							div>
						div>
						<div class="col-sm-4">
							<img  id="kaptcha" th:src="@{/kaptcha}" style="width:100px;height:40px;" class="mr-2"/>
							<a href="javascript:refresh_kaptcha();" class="font-size-12 align-bottom">刷新验证码a>
						div>
					div>				
					<div class="form-group row mt-4">
						<div class="col-sm-2">div>
						<div class="col-sm-10">
							<input type="checkbox" id="remember-me" th:checked="${param.rememberMe}" name="rememberMe">
							<label class="form-check-label"  for="remember-me">记住我label>
							<a href="forget.html" class="text-danger float-right">忘记密码?a>
						div>
					div>				
					<div class="form-group row mt-4">
						<div class="col-sm-2">div>
						<div class="col-sm-10 text-center">
							<button type="submit" class="btn btn-info text-white form-control">立即登录button>
						div>
					div>
				form>				

controller的代码

/**
     * @param username 用户名
     * @param password 密码
     * @param code  验证码
     * @param rememberMe 记住我
     * @param model 传给模板的数据
     * @param session 用户获取存入的验证码用于验证
     * @param response 将 ticket 通过该对象放入session中
     * @return 处理完之后前往的页面(成功前往首页,失败重定向回登录界面)
     */
    @RequestMapping(path = "/login",method = RequestMethod.POST)
    public String login(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        @RequestParam(value = "code",required = false) String code,
                        @RequestParam(value = "rememberMe",defaultValue = "false") boolean rememberMe,
                        Model model,
                        HttpSession session,
                        HttpServletResponse response){
        //1.检测验证码是否正确
        String kaptcha = (String)session.getAttribute("kaptcha");
        if(StringUtils.isBlank(code)||StringUtils.isBlank(kaptcha)||!code.equals(kaptcha)){
            model.addAttribute("codeMsg","验证码不正确");
            return "/site/login";
        }
        //2.检查账号密码(交给业务层进行管理)
        //设置超时的秒数
        int expiredSecond = rememberMe ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
        Map<String, Object> map = userService.login(username, password, expiredSecond);
        //成功前往首页,不成功继续在登录界面
        if(map.containsKey("ticket")){
            Cookie cookie = new Cookie("ticket",map.get("ticket").toString());
            cookie.setPath(contextPath);
            cookie.setMaxAge(expiredSecond);
            response.addCookie(cookie);
            return "redirect:/index";//直接重定向到首页
        }else {
            model.addAttribute("usernameMsg",map.get("usernameMsg"));
            model.addAttribute("passwordMsg",map.get("passwordMsg"));
            return "/site/login";
        }
    }

service

 @Override
    public Map<String,Object> login(String username,String password,int expiredSeconds){
        Map<String,Object> map = new HashMap<>();
        //1.验证空值
        if(StringUtils.isBlank(username)){
            map.put("usernameMsg","用户名不能为空");
            return map;
        }
        if(StringUtils.isBlank(password)){
            map.put("passwordMsg","密码不能为空");
            return map;
        }
        //2.验证
        // 验证账号
        User userByName = userMapper.getUserByName(username);
        if(userByName==null){
            map.put("usernameMsg","用户名不能为空");
            return map;
        }
        //验证是否激活
        if(userByName.getStatus() == 0){
            map.put("usernameMsg","账号没有激活!");
            return map;
        }

        // 验证密码
        String salt = userByName.getSalt();
        String password1 = CommunityUtil.MD5(salt + password);
        if(!userByName.getPassword().equals(password1)){
            map.put("passwordMsg","密码不正确");
            return map;
        }
        //3 登录成功(将ticket放入到login_ticket表中) 生成登录凭证
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(userByName.getId());
        loginTicket.setStatus(0);
        loginTicket.setTicket(CommunityUtil.generateUUID());
        loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds*1000));
        loginTicketMapper.insertLoginTicket(loginTicket);

        map.put("ticket",loginTicket.getTicket());
        // loginTicketMapper.insertLoginTicket()
        return map;
    }

dao

@Mapper
public interface LoginTicketMapper {


    @Insert({"insert into login_ticket(user_id,ticket,status,expired) ",
    "values(#{userId},#{ticket},#{status},#{expired})"})
    @Options(useGeneratedKeys = true,keyProperty = "id")
    int insertLoginTicket(LoginTicket loginTicket);

    @Select({"select * from login_ticket where ticket = #{ticket}"})
    LoginTicket getByTicket(String ticket);

    @Update({"update login_ticket set status = #{status} where ticket = #{ticket}"})
    int updateByTicket(@Param("status") int status,@Param("ticket") String ticket);
}

技术要点总结

  1. 对于标记中的SpringMcv不会帮忙放到 model对象中,只有是对象时,spingMvc才会将其放入到model对象中

5_会话管理实现登录功能_第7张图片

因此在数据进行会回显时,由于在一个请求中,即在一个 request中,在页面中可以通过 param.username方式获取 request域中的东西。如下所示当想让用户名回显的时的使用方式

5_会话管理实现登录功能_第8张图片

你可能感兴趣的:(#,仿牛客网,java,springboot项目,项目,thymeleaf,bootstrap)