JavaWeb开发--简易论坛--1首页--1.4登录,登出功能

简易论坛–登录功能

1、思路

字符校验,验证码登录,登录成功生成登录凭证发送给客户端进入用户首页并显示登录信息。登录失败就提示。若点击记住密码,还需保留登录状态。再次登录查询在cookie里面查询登录凭证,登录凭证有效登录用户首页,显示用户信息,登出时将登陆凭证变为失效状态,并跳到默认首页。
拦截器应用:
a.请求时开始查询登录用户
b.在本次请求中持有用户数据
c.在模板视图上显示用户数据
d.在请求结束时清理用户数据

2、使用技术或方法

mybatis,依赖注入,SpringMVC,MVC,thymeleaf,Kaptcha,session,Cookie,拦截器
自定义注解

3、关键代码

3.1、关键类

Producer生成验证码,
DefaultKaptcha是Producer的实现类之一,
Properties配置属性,
BufferedImage生成图片的类,
HostHolder持有当前线程的用户信息,
ModelAndView用于存放前后端交互的数据,
HandlerInterceptor拦截器接口,
{HandlerInterceptor三个重要的方法:
1、preHandle() 在controller之前执行
2、postHandle() 在controller之后,TemplateEngine之前执行
3、aftercompletion()}在controller之后,TemplateEngine之后执行
}

3.2、关键逻辑

//验证码配置
   @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", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYAZ");
        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;
    }
// 获取验证码存进session

    @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("kaptcha", text);

        // 将突图片输出给浏览器
        response.setContentType("image/png");
        try {
            OutputStream os = response.getOutputStream();
            ImageIO.write(image, "png", os);
        } catch (IOException e) {
            logger.error("响应验证码失败:" + e.getMessage());
        }
    }
//验证码刷新逻辑
function refresh_kaptcha() {
	var path = CONTEXT_PATH + "/kaptcha?p=" + Math.random();
	$("#kaptcha").attr("src", path);
}
//添加cookie
Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
cookie.setPath(contextPath);
cookie.setMaxAge(expiredSeconds);
response.addCookie(cookie);
//定义拦截的路径和注册拦截器-->即拦截和保留各种资源信息
 public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(alphaInterceptor)//注册拦截器类
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg")
                .addPathPatterns("/register", "/login");

/**
 * ThreadLocal实现了线程隔离
 * 存数据是根据线程为key来存的
 * 持有用户信息,用于代替session对象.
 */
@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();
    }

}
//自定义注解,以下表示该注解的作用范围为拦截方法,拦截策略是在运行时拦截
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {

}
@Component
public class LoginRequiredInterceptor implements HandlerInterceptor {

    @Autowired
    private HostHolder hostHolder;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //如果拦截的是一个方法则handler是HandlerMethod类型
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            //直接获取拦截的Method对象
            Method method = handlerMethod.getMethod();
            //取该方法的注解
            LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);
            //表示不再登录状态时,重定向至登录
            if (loginRequired != null && hostHolder.getUser() == null) {
                response.sendRedirect(request.getContextPath() + "/login");
                return false;
            }
        }
        return true;
    }
}

4、扩展

使用拦截器(
a.在方法前标注自定义注解 
b.拦截所有请求,只处理带有该注解的方法),
自定义注解:(
元注解:@target,@Rentention,@Document,@Inherited
读取注解:
Method.getDeclareAnnotations,
Method.getAnnotation(Class))

5、注意点

  • 注意使用cookie保存登录凭证
  • 注意使用拦截器对不同的登录状态进行处理

6、后续优化

6.1、优化思路

  • 使用Redis存储验证码

    验证码需要频繁的访问与刷新,对性能要求较高。
    验证码不需永久保存,通常在很短的时间后就会失效。
    分布式部署时,存在Session共享的问题。

  • 使用Redis存储登录凭证

    处理每次请求时,都要查询用户的登录凭证,访问的频率非常高。

  • 使用Redis缓存用户信息

    处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高。

6.2、优化点

  //验证码优化
    @RequestMapping(path = "/kaptcha", method = RequestMethod.GET)
    public void getKaptcha(HttpServletResponse response) {
        // 生成验证码
        String text = kaptchaProducer.createText();
        BufferedImage image = kaptchaProducer.createImage(text);
        //验证码的归属
        String kaptchaOwner = BbsUtil.generateUUID();
        Cookie cookie = new Cookie("kaptchaOwner",kaptchaOwner);
        cookie.setMaxAge(60);
        cookie.setPath(contextPath);
        response.addCookie(cookie);
        //将验证码存入redis
        String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
        redisTemplate.opsForValue().set(redisKey,text, 60, TimeUnit.SECONDS);
        // 将突图片输出给浏览器
        response.setContentType("image/png");
        try {
            OutputStream os = response.getOutputStream();
            ImageIO.write(image, "png", os);
        } catch (IOException e) {
            logger.error("响应验证码失败:" + e.getMessage());
        }
    }
//登录优化
@RequestMapping(path = "/login", method = RequestMethod.POST)
    public String login(String username, String password, String code, boolean rememberme,
                        Model model, HttpServletResponse response,@CookieValue("kaptchaOwner") String kaptchaOwner) {
        // 检查验证码
        String kaptcha = null;
        if (StringUtils.isNotBlank(kaptchaOwner)){
            String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
            kaptcha =(String)redisTemplate.opsForValue().get(redisKey);
        }

        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";
        }
    }

其他登录凭证与用户信息的优化只做相应的替换与取值即可。

你可能感兴趣的:(JavaWeb开发)