Github->GitHub - noahlin27/Astera: Astera project for Spring Boot
Gitee->Astera: Astera project for Spring Boot
注册登录功能在 service 包创建 RegloginService,接着创建它的实现类,然后在 controller 包创建 RegloginController。尽量在 service 实现逻辑功能,而 controller 处理外部请求时只负责调用 service,以及封装数据并把数据返回给前端。
注册要对用户注册时设置的用户名、密码等做一些规定,比如用户名的合法性:允许使用的字符、允许的长度、不允许重复、不允许出现敏感词;密码的安全性:长度要求、字符要求等。在保存密码时,使用加密后的密文保存,以防万一。为了防止批量注册,可以增加邮箱/手机验证。
这里我只简单地对用户名做了非空和去重判断,密码做了非空判断,而密码的保存用“加盐”的方式保证数据的安全性。
生成默认头像,可以在静态资源文件中预先添加若干图片,在用户注册的时候将 headUrl 的值设置为随机的图片 url 即可。
@Override public Map登录register(String username, String password) { Map map = new HashMap<>(); if (StringUtils.isBlank(username)) { map.put("msg", "用户名不能为空"); return map; } if (StringUtils.isBlank(password)) { map.put("msg", "密码不能为空"); return map; } if (userDAO.selectByName(username) != null) { map.put("msg", "用户名已存在"); return map; } User user = new User(); Random random = new Random(); user.setName(username); user.setSalt(UUID.randomUUID().toString().substring(0, 5)); user.setHeadUrl(String.format("/images/img/%d.png", random.nextInt(36))); user.setPassword(MD5Util.Encode(password + user.getSalt(), "utf-8")); userDAO.insert(user); map.put("success", "注册成功! 请登录"); return map; }
如果用户登录时输入的用户名或密码不正确,可以适当地给用户反馈一些提示信息;在密码校验成功后,发放 token 令牌给用户,并进行登记,token 可以有多种实现方式,而 cookie 是在浏览器客户端被广泛使用的一种。从浏览器请求中获取 cookie 会用到 HttpServletRequest 中的 getcookies() 方法,而添加 cookie 用到的是 HttpServletResponse 中的 addcookies() 方法。
服务端将令牌存储在数据库中,与用户 id 相关联。在数据库创建 login_ticket表,model 包创建 LoginTicket类,完成对应的 LoginTicketDAO,之后完成登录的业务逻辑。
@Override public Maplogin(String username, String password) { Map map = new HashMap<>(); User user = userDAO.selectByName(username); if (StringUtils.isBlank(username)) { map.put("msg", "用户名不能为空"); } if (StringUtils.isBlank(password)) { map.put("msg", "密码不能为空"); } if (user == null) { map.put("msg", "用户名不存在"); return map; } if (!MD5Util.Encode(password + user.getSalt(), "utf-8").equals(user.getPassword())) { map.put("msg", "用户名或密码错误"); return map; } String ticket = addTicket((user.getId())); map.put("ticket", ticket); return map; } private String addTicket(int userId) { LoginTicket loginTicket = new LoginTicket(); loginTicket.setUserId(userId); Date now = new Date(); now.setTime(now.getTime()+3600*1000*24); loginTicket.setExpired(now); loginTicket.setStatus(0); loginTicket.setTicket(UUID.randomUUID().toString().replaceAll("-","")); loginTicketDAO.insert(loginTicket); return loginTicket.getTicket(); }
@PostMapping("/login")
public String login(HttpServletRequest request, HttpServletResponse response,
@RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam("next") String next) {
try {
Map map = loginService.login(username, password);
if (map.containsKey("ticket")) {
cookie cookie = new cookie("ticket", map.get("ticket"));
cookie.setPath("/");
response.addcookie(cookie);
if (StringUtils.isNotBlank(next)) {
return "redirect:" + next;
}
return "redirect:/";
} else {
request.setAttribute("msg", map.get("msg"));
return "login";
}
} catch (Exception e) {
System.out.println("登录异常" + e.getMessage());
return "login";
}
}
登录状态验证 - 拦截器
当用户密码验证通过后,服务端给用户发放一个 token 令牌,保存在浏览器的 cookie 中,通常这个令牌只有一段时间的有效期。当客户端通过浏览器访问时,服务端就可以通过验证令牌来确认客户端的身份,只有在令牌存在且处于有效状态才可以通过验证,用拦截器 interceptor 可以实现 token 的校验。
新建 interceptor 包,创建 PassportInterceptor 类,继承 HandlerInterceptor,重写 preHandle(), postHandle(), afterCompletion() 方法。
@Component
public class PassportInterceptor implements HandlerInterceptor {
@Resource
LoginTicketDAO loginTicketDAO;
@Resource
UserDAO userDAO;
@Resource
HostHolder hostHolder;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String ticket = null;
if (request.getcookies() != null) {
for (cookie cookie : request.getcookies()){
if (cookie.getName().equals("ticket")){
ticket = cookie.getValue();
break;
}
}
}
if (ticket != null) {
LoginTicket loginTicket = loginTicketDAO.selectByTicket(ticket);
if (loginTicket == null || loginTicket.getExpired().before(new Date()) || loginTicket.getStatus() != 0) {
return true;
}
User user = userDAO.selectById(loginTicket.getUserId());
hostHolder.setUser(user);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (modelAndView != null) {
modelAndView.addObject("user", hostHolder.getUser());
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
hostHolder.clear();
}
}
新建 config 包,创建 WebConfig 类,继承 WebMvcConfigurer,重写 addInterceptors() 方法注册拦截器。
public class WebConfig implements WebMvcConfigurer {
@Resource
PassportInterceptor passportInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(passportInterceptor);
WebMvcConfigurer.super.addInterceptors(registry);
}
}
要同时处理多个用户的请求,就要用到多线程,在 model 包建一个 User 多线程的类,成员变量为 ThreadLocal
@Component
public class HostHolder {
private static final ThreadLocal users = new ThreadLocal<>();
public User getUser() {
return users.get();
}
public void setUser(User user) {
users.set(user);
}
public void clear() {
users.remove();
}
}
如此,只要成功验证了浏览器 cookie 中的令牌,客户端就可以显示为登录状态了。
参考博客Spring 过滤器 拦截器 AOP区别
filter、interceptor、aop实现与区别
Filter、Interceptor、Aop实现与区别



