Community项目--用户的登录

用户的登录

1. 验证码的生成

1.1. 生成验证码工具的介绍

官网地址: https://code.google.com/archive/p/kaptch(需科学上网)
Kaptcha是一个容易并且安全的生成验证码的模块化框架,用户可以通过几个配置选项来改变验证码输出的外观。

1.2. 导入jar包
		<dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>
1.3. 编写Kaptcha配置类
@Configuration
public class KaptchaConfig {

    @Bean
    public Producer kaptchaProducer(){
        Properties properties = new Properties();
        // 图片宽
        properties.setProperty("kaptcha.img.width", "100");
        // 图片高
        properties.setProperty("kaptcha.img.height", "40");
        // 字体大小
        properties.setProperty("kaptcha.textproducer.font.size", "32");
        // 字体颜色
        properties.setProperty("kaptcha.textproducer.font.color", "black");
        // 文本集合
        properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGJHIJKLMNOPQRSTUVWXYZ");
        // 验证码长度
        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.4. 生成随机字符和图片

LoginController

    @Autowired
    private Producer kaptchaProducer;

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

    @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");
        try {
            OutputStream os = response.getOutputStream();
            ImageIO.write(image, "png", os); // ImageIO 用于定位图片的读和写的工具类,并执行简单的编码和解码
        } catch (IOException e) {
            logger.error("响应验证码失败: " + e.getMessage());
        }
    }

login.html

<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?p=" + Math.random()  --让浏览器认为地址改变*/
        $("#kaptcha").attr("src", path);
    }
</script>

结果展示
在这里插入图片描述

2. 登录和退出功能实现

在这里插入图片描述

2.1. 登录
2.1.1. 业务和需求

用户登录时,服务端会验证从客户端接受的账号,密码和验证码是否正确.如果成功,标记账户状态为有效,生成登录凭证,并返回给客户端,失败时则跳回登录页重新登录.同时,如果用户选择记住我,则用户可以在较长的一段时间不需要重新登录.

2.1.2. 功能实现

util类:

    /**
     * 默认状态凭证登录的超时时间
     */
    int DEFAULT_EXPIRED_SECONDS = 3600 * 12;

    /**
     * 记住状态下登录凭证的超时时间
     */
    int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;

LoginController:

	@Value("${server.servlet.context-path}")
    private String contextPath;
 	/**
     * 登录
     * @param username 用户名
     * @param password 密码
     * @param code 验证码
     * @param remember 是否记住账户,记住则销毁日期延长
     * @param model
     * @param session
     * @param response
     * @return
     */
	@PostMapping("/login")
    public String login(String username, String password, String code, boolean remember,
                        Model model, HttpSession session, HttpServletResponse response) {
        // 判断验证码
        String kaptcha = (String) session.getAttribute("kaptcha");
        if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {
            model.addAttribute("codeMsg", "验证码不正确");
            return "site/login";
        }

        // 检查账号密码
        int expiredSeconds = remember ? 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";
        }
    }

UserService

	/**
     * 登录业务
     *
     * @param username
     * @param password
     * @param expiredSeconds 过期的时间
     * @return
     */
    public Map<String, Object> login(String username, String password, long 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 u = userMapper.selectByName(username);
        if (u == null) {
            map.put("usernameMsg", "该账号不存在");
            return map;
        }
        // 验证是否激活
        if (u.getStatus() == 0) {
            map.put("usernameMsg", "该账号未激活");
            return map;
        }
        // 验证密码
        password = CommunityUtil.md5(password + u.getSalt());
        if (!u.getPassword().equals(password)) {
            map.put("passwordMsg", "密码不正确");
            return map;
        }

        // 生成登录凭证
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(u.getId()).setTicket(CommunityUtil.generateUUID()).setStatus(0).setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000)); // generateUUID()自定义工具方法,可以生成随机字符串
        loginTicketMapper.insertLoginTicket(loginTicket);

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

        return map;
    }

UserMapper

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

login.html(截取)

<div class="col-sm-10">
	<input type="text" th:class="|form-control ${usernameMsg!=null?'is-invalid':''}|"
		th:value="${param.username}"
		id="username" name="username" placeholder="请输入您的账号!" required>
    <div class="invalid-feedback" th:text="${usernameMsg}">
          xxxxxxx!
     </div>
</div>
2.2. 退出

标记账户状态为无效(略)

3. 显示登录信息

3.1. 业务和需求

用户可以在客户端看到当前登录的信息
在这里插入图片描述

3.2. 拦截器(Interceptor)
  • 拦截器是动态拦截Action调用的对象,它提供了一种机制,可以使开发者自定义在一个action的前后执行指定代码,也可以在一个action执行之前阻止其执行。同时,拦截器还可以将通用的代码模块化并作为可重用的类。
  • 拦截器中的3个方法
    preHandler:在进入Handler方法之前执行此方法,适用于身份认证、身份授权、登陆校验等,比如:在身份认证时,判断到用户没有登陆,则不再向下执行,返回值为false,即可实现拦截,否则,返回true时,不执行拦截;
    postHandler:进入Handler方法之后执行此方法,返回ModelAndView之前执行,适用场景从ModelAndView参数出发,比如:将公用的模型数据在这里传入到视图,也可以统一指定显示的视图等;
    afterCompletion:在执行Handler完成后执行此方法,适用于统一的异常处理、日志处理等。
    (摘自:作者:luckgem 链接:https://www.jianshu.com/p/86a7243a78c9 来源:简书)
3.3. ThreadLocal
  • 多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
  • ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。
    (摘自:https://www.cnblogs.com/fsmly/p/11020641.html)
3.4. 功能实现
3.4.1. 拦截器的应用
  • 在请求开始时查询登录用户
  • 在本次请求中持有用户数据
  • 在模板视图上显示用户数据
  • 在请求结束时清理用户数据
3.4.2. 定义拦截器

HostHolder

/**
 * 容器作用:持有用户的信息,用于代替session对象
 * @author XH
 * @date 2022/2/9 20:04
 */
@Component
public class HostHolder {

    private ThreadLocal<User> users = new ThreadLocal();

    public void setUser(User user){
        users.set(user);
    }
    public User getUser(){
        return users.get();
    }

    public void clear(){
        users.remove();
    }
}

LoginTicketInterceptor

/**
 * @author XH
 * @date 2022/2/9 19:39
 */
@Component
public class LoginTicketInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService userService;

    @Autowired
    private HostHolder hostHolder;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从cookie中获取凭证
        String ticket = CookieUtil.getValue(request, "ticket");
        if (ticket != null) {
            // 查询凭证
            LoginTicket loginTicket = userService.findLoginTicket(ticket);
            // 检查凭证是否有效
            if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {
                // 根据凭证查询用户
                User user = userService.findUserById(loginTicket.getUserId());
                // 在本次请求中持有用户 考虑到多线程并发 线程隔离用ThreadLocal
                hostHolder.setUser(user);
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        User user = hostHolder.getUser();
        if (user != null && modelAndView != null) {
            modelAndView.addObject("loginUser", user);
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        hostHolder.clear();
    }
}
3.4.3. 配置拦截器

WebMvcConfig

 	 @Override
    public void addInterceptors(InterceptorRegistry registry) {
    	/*注入拦截器,静态资源随意访问,静态资源的路径被排除*/
        registry.addInterceptor(loginTicketInterceptor)
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
    }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值