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

news2024/11/27 12:35:20

一 会话管理登录功能

前置了解

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 配置类
  • 生成随机字符,生成图片

官网地址

<!-- https://mvnrepository.com/artifact/com.github.penggle/kaptcha -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</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>

四 登录和退出的功能

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<String,Object> login(String username,String password,int expiredSecond(过期秒数)){

​ Map<String,Object> 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对象中

image-20221005193607317

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

image-20221005193804600

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/3988.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

荧光标记氨基酸:荧光标记L-苯丙氨酸乙酯盐酸盐,L-phenylalanine ethylester labeled

产品名称&#xff1a;荧光标记L-苯丙氨酸乙酯盐酸盐&#xff0c;L-phenylalanine ethylester labeled L-苯丙氨酸乙酯盐酸盐是化学物质&#xff0c;分子式是C11H16NO2。别名L-苯基丙氨酸乙酯盐酸盐&#xff0c;熔点: 154-157&ordm;C&#xff0c;比旋光度: 33.7&ordm;(…

使用myCobot 280机械臂校准OAK智能深度相机

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 ▌前言 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是助手…

第三站:函数(第三幕)递归训练

目录 一、编写一个函数实现n的k次方&#xff0c;使用递归实现。&#xff08;k为整数&#xff09; 二、写一个递归函数DigitSum(n)&#xff0c;输入一个非负整数&#xff0c;返回组成它的数字之和 三、编写一个函数 reverse_string(char * string)&#xff08;递归实现&#x…

WebDAV之葫芦儿·派盘+墨阅

墨阅 支持webdav方式连接葫芦儿派盘。 是一款专注于帮助用户离线缓存网页文档图书漫画的免费工具APP。您可以利用墨阅收集来自互联网网站平台的公开文章,图片,漫画等,可以对网页样式进行调整,支持自定义动作,批量离线等功能方便用户日常离线。目前支持小说,markdown,图…

公众号搜题系统

公众号搜题系统 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xff08;点击跳转…

语义分割之RTFormer介绍

语义分割之RTFormer介绍 论文链接&#xff1a;https://arxiv.org/abs/2210.07124 代码地址&#xff1a;https://github.com/PaddlePaddle/PaddleSeg ViT以来&#xff0c;Transformer作为特征提取器在语义分割领域证明了自己&#xff0c;但是由于Transformer的核心 muti-self-…

苹果电脑提高工作效率alfred 5中文

Mac电脑上哪个效率工具好用呢&#xff1f;alfred 5是最强大的效率启动工具之一&#xff0c;丰富的插件资源、简单的上手成本&#xff0c;会是你提高 Mac 使用效率的得力助手。可通过热键&#xff0c;关键字&#xff0c;文本扩展等提高您的效率。搜索您的Mac和网络&#xff0c;并…

Sprint产品待办列表的优先级要怎么排?

在梳理产品待办事项列表的过程中&#xff0c;产品负责人需要先做优先级排列&#xff0c;保证我们在一定的时间盒内能够交付需要优先级最高、最具价值的用户故事。 那这个用户故事的优先级要怎么排列&#xff0c;我们怎样选择用户故事的实现顺序&#xff1f; 有一个实践可以推…

力扣(LeetCode)816. 模糊坐标(C++)

模拟 一次遍历 sss &#xff0c;尝试对遍历到的位置进行分割。对分割点左右字符串 ltltlt , rtrtrt 分别构造子串。 构造子串的规则: 整数不以 000 开头&#xff0c;(特例)单独的 000 可以作为整数。小数不以 000 结尾。小数的整数位是 000 &#xff0c;则整数位不能组成新数…

怎么给PDF添加页面?推荐三个PDF如何插入页面小妙招

在我们生活中&#xff0c;平时接触PDF文件可能没有接触Word文件那么的多。像处理PDF文件这种问题上并没有Word文件那么的容易&#xff0c;毕竟PDF它不易进行内容编辑。这时候如果有小伙伴需要对PDF文件进行修改或者进行插入页面&#xff0c;但是PDF文件是无法直接进行修改的。那…

做期货的阶段(做期货的几个阶段)

如何自己做好期货 学习交易大致有三阶段&#xff1a;直观直觉阶段&#xff0c;看到涨就做多&#xff0c;看到跌就做空&#xff0c;结果半赢半输&#xff1b;学习积累的阶段&#xff0c;看到涨跌不觉涨跌&#xff0c;想得很多&#xff0c;结果赢少赔多&#xff1b;领悟后再次直…

软件架构师考试的真实感受

近1个半月&#xff0c;因准备了软件架构师的考试&#xff0c;停了BLOG&#xff0c;今天想写点东西作为阶段性的沉淀。 节奏紧 和软设比起来&#xff0c;最直观的感受就是节奏紧&#xff0c;软设上午通常能够提前一个小时考完&#xff0c;加上下午2点才开考&#xff0c;中间有…

Java UML 类图

继承 继承&#xff1a;重用父类的某些功能 缺点&#xff1a;强耦合 耦合&#xff1a;修改一方&#xff0c;会影响另一方 应用&#xff1a;是否需要“向上转型” 接口 行为的抽象、可跨越不同的类 不包含实现实现类需完全实现可实现多个接口 是一种“自顶向下”的设计&…

C++多态(1)

目录1. 多态的概念2. 多态的定义和实现2.1 多态的构成条件2.1.1 重写2.2.2 指针或者引用2.2 虚函数重写的两个例外2.3 多态里的一个不规范的地方2.4 重载、重写&#xff08;覆盖&#xff09;、隐藏&#xff08;重定义&#xff09;的对比3. C11 override 和 final1. 多态的概念 …

QSS编辑器QssEditor之三:Ribbon控件与换肤

一.Ribbon控件 Ribbon控件即Office样式UI,Ribbon控件有许多开源实现,例如:Qt优秀开源项目之三:SARibbon SARibbon功能强大,但以学习为目的的话,我们可以自己去实现一个简单的。基本思路是自定义QTabWidget,然后通过QSS调整样式。 如上图所示,我们可以实现一个类QHRi…

IP数据报首部字段、TCP报文段首部字段

文章目录 IP数据报首部字段IP数据报格式TCP报文段首部字段IP数据报首部字段 首部字段功能1.版本是IPv4还是IPv62.首部长度IP数据报首部的长度3.服务类型4.总长度IP数据报的总长度。若总长度>该网段的MTU,则需要分片。分片后IP数据报的总长度必然发生改变。5.标识保证IP分组…

C++--模板

目录 泛型编程 函数模板 函数模板概念 函数模板格式 函数模板的原理 函数模板的实例化 模板参数的匹配原则 类模板 类模板的定义格式 类模板的实例化 非类型模板参数 模板的特化 概念 函数模板特化 类模板特化 泛型编程 不再是针对某种类型&#xff0c;而是能适…

[python]用flask框架搭建微信公众号的后台

用flask框架搭建微信公众号的后台 最近用python写了点爬虫&#xff0c;为了要让爬取的数据能够随时显示在我眼前&#xff0c;并实时根据我的指令返回数据。于是采用微信公众号做这个显示窗口&#xff0c;既能发送指令也能显示简单的相关数据。 准备工具 python3.x环境 pycharm…

vue3与vue2的不同内容

一、main.js入口文件的不同 // 引入的不再是构造函数&#xff0c;引入了一个名为creacteApp的工厂函数 import { createApp } from vue import ./style.css import App from ./App.vue // 创建应用示例对象--->app const app createApp(App) //把组件APP挂载到#app节点上 …

疫情可视化(后续)

前言 这是疫情可视化最开始的文章&#xff0c;有需要了解的可前往查看&#xff1a;https://blog.csdn.net/xi1213/article/details/126824752。 本来说有时间就把这个项目完结了的&#xff0c;结果后面一直有事拖着&#xff0c;直到现在十一月份了才搞完。老样子&#xff0c;先…