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


    一 会话管理登录功能

    前置了解

    image-20221004180613333

    初识cookie

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

    image-20221004175703767

    session的使用

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

    image-20221004184855392

    二 分布式部署项目

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

    1. 使用粘性的化

    2. 使用同步

    3. 存在数据库或者rdis中

    image-20221004191012168

    而本项最终是存放在redis中

    三 生成验证码

    生成验证码使用 Kaptcha

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

    官网地址

    
    <dependency>
        <groupId>com.github.pengglegroupId>
        <artifactId>kaptchaartifactId>
        <version>2.3.2version>
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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());
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    工具包

    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;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    页面实现的代码

    <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>
    
    • 1
    • 2
    • 3
    • 4
    <script>
    		CONTEXT_PATH = "/community";
    		function refresh_kaptcha() {
    			// /kaptcha?p="+Math.random() 加?是为了欺骗浏览器的作用,达到刷新的作用
    			var path =  CONTEXT_PATH + "/kaptcha?p="+Math.random();
    			$("#kaptcha").attr("src",path);
    		}
    	</script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    四 登录和退出的功能

    image-20221004225438813

    下面是具体的实现步骤

    1. 书写实体类 LoginTicket

    2. 数据访问层 dao

    • 插入数据

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

    • 通过 ticket查询数据

    • 修改 ticket 修改状态 status

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

      image-20221004230717571

    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>				
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    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";
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    技术要点总结

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

    image-20221005193607317

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

    image-20221005193804600

  • 相关阅读:
    PyQt5_QScrollArea内容保存成图片
    [附源码]SSM计算机毕业设计疫情状态下病房管理平台JAVA
    基于web的医院预约挂号系统/医院管理系统
    基于Docker的ROS开发
    241.为运算表达式设计优先级
    springboot实现发送邮箱验证码
    nginx 安全加固
    【计算机网络:自顶向下方法】(四)网络层 (IPV4 | IPV6 | 路由算法 )
    Day02 Spring和SpringBoot
    【活动总结】0730-COC深圳社区AI●CMeetup第4期——畅谈AI+智能制造与机器人的现状与未来
  • 原文地址:https://blog.csdn.net/weixin_47994845/article/details/127725411