一、会话管理

cookie是服务器发送到浏览器,并保存在浏览器端的一小块数据。浏览器下次访问该服务器时,会自动携带该数据,将其发送给服务器。

image-20220320135243753

    //cookie示例
    @RequestMapping(path = "/cookie/set",method = RequestMethod.GET)
    @ResponseBody
    public String setCookie(HttpServletResponse response){
        //创建cookie
        Cookie cookie = new Cookie("code", CommunityUtil.generateUUID());
        //设置cookie生效的范围
        cookie.setPath("/community/alpha");
        //设置cookie的生存时间
        cookie.setMaxAge(60 * 10);
        //发送cookie
        response.addCookie(cookie);

        return "set cookie";
    }

访问链接:

http://localhost:8080/community/alpha/cookie/set

image-20220320114002208

优点:

  • 极高的扩展性和可用性
  • 通过良好的编程,控制保存在cookie中的session对象的大小。
  • 通过加密和安全传输技术(SSL),减少cookie被破解的可能性。
  • 只在cookie中存放不敏感数据,即使被盗也不会有重大损失。
  • 控制cookie的生命期,使之不会永远有效。偷盗者很可能拿到一个过期的cookie。

缺点:

  • Cookie数量和长度的限制。每个domain最多只能有20条cookie,每个cookie长度不能超过4KB,否则会被截掉。
  • 安全性问题。如果cookie被人拦截了,那人就可以取得所有的session信息。即使加密也与事无补,因为拦截者并不需要知道cookie的意义,他只要原样转发cookie就可以达到目的了。
  • 有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。

②Session

session是通过cookie传一个id给浏览器保存,适用于单体应用。

  • session是JavaEE的标准,用于在服务端记录客户端信息
  • 数据存放在服务端更加安全,但是也会增加服务端的内存压力

image-20220320140155907

image-20220320135819720

image-20220320140117999

优点

  • 如果要在诸多Web页间传递一个变量,那么用Session变量要比通过QueryString传递变量可使问题简化。

  • 要使WEb站点具有用户化,可以考虑使用Session变量。你的站点的每位访问者都有用户化的经验,基于此,随着LDAP和诸如MS Site Server等的使用,已不必再将所有用户化过程置入Session变量了,而这个用户化是取决于用户喜好的。

  • 你可以在任何想要使用的时候直接使用session变量,而不必事先声明它,这种方式接近于在VB中变量的使用。使用完毕后,也不必考虑将其释放,因为它将自动释放。

缺点

  • Session变量和cookies是同一类型的。如果某用户将浏览器设置为不兼容任何cookie,那么该用户就无法使用这个Session变量!

  • 当一个用户访问某页面时,每个Session变量的运行环境便自动生成,这些Session变量可在用户离开该页面后仍保留20分钟!(事实上,这些变量一直可保留至“timeout”。“timeout”的时间长短由Web服务器管理员设定。一些站点上的变量仅维持了3分钟,一些则为10分钟,还有一些则保留至默认值20分钟。)所以,如果在Session中置入了较大的对象(如ADO recordsets,connections, 等等),那就有麻烦了!随着站点访问量的增大,服务器将会因此而无法正常运行!

  • 因为创建Session变量有很大的随意性,可随时调用,不需要开发者做精确地处理,所以,过度使用session变量将会导致代码不可读而且不好维护。

  • 虽然“你可以在任何想要使用的时候直接使用session变量,而不必事先声明它,这种方式接近于在VB中变量的使用。使用完毕后,也不必考虑将其释放,因为它将自动释放”。但是,“谁”想到那儿呢?变量的含义是什么?这些都变得不很清晰。

③使用nginx负载均衡

使用nginx做负载均衡的话,如果访问一个服务器哪只有哪个服务器有session其他的服务器没session,如果轮询到另外的服务器则无法获得session数据所以在负载均衡的时候,我们同城都是在服务器后面搭建一个redis集群服务器用来存储相关的会话数据。

image-20220320141524390

二、验证码生成

①导入kaptcha

<!-- https://mvnrepository.com/artifact/com.github.penggle/kaptcha -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

②编写Configuration

@Configuration
public class KaptchaConfig {

    @Bean
    public Producer kaptchaProducer(){
        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","0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 
        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;
    }
}

③编写接口

编写getKaptcha获取验证码controller

@RequestMapping(path = "/kaptcha",method = RequestMethod.GET)
public void getKaptcha(HttpServletResponse response, HttpSession session){
    //生成验证码
    String text = kaptchaProducer.createText();
    BufferedImage image = kaptchaProducer.createImage(text);

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

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

在global.js中设置全局变量CONTEXT_PATH

var CONTEXT_PATH = "/community"

编写前端代码

<div class="col-sm-4">
			<img th:src="@{/kaptcha}" id="kaptcha" style="width:100px;height:40px;" class="mr-2"/>
			<a href="javascript:refresh_kaptcha();" class="font-size-12 align-bottom">刷新验证码</a>
</div>	

<script>
		function refresh_kaptcha(){
			var path = CONTEXT_PATH + "/kaptcha?p="+ Math.random();
			$("#kaptcha").attr("src",path);
		}
</script>

三、登录功能

①编写LoginTicket实体类存放登录状态信息

@ToString
@Data
@Getter
@Setter
public class LoginTicket {

    private int id;
    private int userId;
    private String ticket;
    private int status;
    private Date expired;	//过期时间
}

②编写LoginTicketMapper接口

@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 id,user_id,ticket,status,expired ",
            "from login_ticket where ticket=#{ticket}"
    })
    LoginTicket selectByTicket(String ticket);

    @Update({
            "<script>",
            "update login_ticket set status=#{status} where ticket=#{ticket} ",
            "<if test=\"ticket!=null\"> ",
            "and 1=1 ",
            "</if>",
            "</script>"
    })
    int updateStatus(String ticket, int status);
}

③编写登录功能

在UserService编写

    public Map<String,Object> login(String username,String password,int expiredSeconds){
        HashMap<String, Object> map = new HashMap<>();

        //空值处理
        if (StringUtils.isBlank(username)){
            map.put("usernameMsg","账号不能为空!");
            return map;
        }
        if (StringUtils.isBlank(username)){
            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 = new LoginTicket();
        loginTicket.setUserId(user.getId());
        loginTicket.setTicket(CommunityUtil.generateUUID());
        loginTicket.setStatus(0);
        loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
        loginTicketMapper.insertLoginTicket(loginTicket);

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

    public void logout(String ticket){
        //1 代表无效
        loginTicketMapper.updateStatus(ticket,1);
    }

④编写controller

@RequestMapping(path = "/login",method = RequestMethod.POST)
public String login(String username,String password,String code,boolean rememberMe,
                    Model model,HttpSession session,HttpServletResponse response){

    //login形参里的值前端可以通过request获取,前端获取username ==》》 param.username
    String kaptcha = (String)session.getAttribute("kaptcha");

    //检查验证码
    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")){
        Cookie cookie = new Cookie("ticket",map.get("ticket").toString());
        cookie.setPath(contextPath);
        cookie.setMaxAge(expiredSeconds);
        response.addCookie(cookie);
        return "redirect:/index";
    }else {
        model.addAttribute("usernameMsg",map.get("usernameMsg"));
        model.addAttribute("passwordMsg",map.get("passwordMsg"));
        return "/site/login";
    }
}

四、退出功能

①编写退出功能

public void logout(String ticket){
    //1 代表无效
    loginTicketMapper.updateStatus(ticket,1);
}

②编写controller功能

在LoginController,前端页面单击退出的时候,会更改数据库中login_ticket表的status字段为1

@RequestMapping(path = "/logout" ,method = RequestMethod.GET)
public String logout (@CookieValue("ticket") String ticket){
    userService.logout(ticket);
    return "redirect:/login";
}

Q.E.D.


窝似嫩叠