字符校验,验证码登录,登录成功生成登录凭证发送给客户端进入用户首页并显示登录信息。登录失败就提示。若点击记住密码,还需保留登录状态。再次登录查询在cookie里面查询登录凭证,登录凭证有效登录用户首页,显示用户信息,登出时将登陆凭证变为失效状态,并跳到默认首页。
拦截器应用:
a.请求时开始查询登录用户
b.在本次请求中持有用户数据
c.在模板视图上显示用户数据
d.在请求结束时清理用户数据
mybatis,依赖注入,SpringMVC,MVC,thymeleaf,Kaptcha,session,Cookie,拦截器
自定义注解
Producer生成验证码,
DefaultKaptcha是Producer的实现类之一,
Properties配置属性,
BufferedImage生成图片的类,
HostHolder持有当前线程的用户信息,
ModelAndView用于存放前后端交互的数据,
HandlerInterceptor拦截器接口,
{HandlerInterceptor三个重要的方法:
1、preHandle() 在controller之前执行
2、postHandle() 在controller之后,TemplateEngine之前执行
3、aftercompletion()}在controller之后,TemplateEngine之后执行
}
//验证码配置
@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;
}
}
使用拦截器(
a.在方法前标注自定义注解
b.拦截所有请求,只处理带有该注解的方法),
自定义注解:(
元注解:@target,@Rentention,@Document,@Inherited
读取注解:
Method.getDeclareAnnotations,
Method.getAnnotation(Class))
使用Redis存储验证码
验证码需要频繁的访问与刷新,对性能要求较高。
验证码不需永久保存,通常在很短的时间后就会失效。
分布式部署时,存在Session共享的问题。
使用Redis存储登录凭证
处理每次请求时,都要查询用户的登录凭证,访问的频率非常高。
使用Redis缓存用户信息
处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高。
//验证码优化
@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";
}
}
其他登录凭证与用户信息的优化只做相应的替换与取值即可。