🌕开发社区登录注册模块
4.开发登录、退出功能
步骤: 重点说明:
生成登录凭证,最终发送一个key给客户端,让它记录。下次再提交给服务端能够根据key 登录凭证来识别 你。 但是登录凭证中国包含了一些敏感数据,包括用户的id,用户名,密码;这些数据不能发送给客户端,要存在服务端,可以用session或者数据库来存,我这存到数据库里。以后,对它进行一个重构,存到Redis里。
查看数据库表login_ticket 最重要的就是ticket 字段,也是整个表的核心数据 。 ticket是一个凭证 ,根据凭证(核心数据)查询,最终把ticket这个字符串发送给浏览器让它保存,客户端再次访问浏览器时就把ticket给我们,我们就能通过ticket查到到全部信息,就知道哪个用户正在登录;
4.1 登录实现
4.1.1 LoginTicket
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class LoginTicket {
private int id;
private int userId;
private String ticket;
private int status;
private Date expired;
}
4.1.2 LoginTicketMapper
通过注解方式写sql,而不是xml方式。
@Mapper
public interface LoginTicketMapper {
@Insert({
"insert into login_ticket (user_id,ticket,status,expired)",
"values(#{userId},#{ticket},#{status},#{expired})"
})
@Options(useGeneratedKeys = true,keyProperty = "id")
public int insertLoginTicket(LoginTicket loginTicket);
@Select({
"select user_id,ticket,status,expired ",
"from login_ticket where ticket = #{ticket}"
})
public LoginTicket selectByTicket(String ticket);
@Update({
"update login_ticket set status = #{status} where ticket = #{ticket}"
})
public int updateStatus(String ticket,int status);
}
4.1.2.1 MapperTest进行测试
@Resource
private LoginTicketMapper loginTicketMapper;
@Test
public void testInsertLoginTicket(){
LoginTicket loginTicket = new LoginTicket();
loginTicket.setUserId(101);
loginTicket.setTicket("abc");
loginTicket.setStatus(0);
loginTicket.setExpired(new Date(System.currentTimeMillis()+60*1000*10));
int i = loginTicketMapper.insertLoginTicket(loginTicket);
if (i <= 0){
System.out.println("数据添加失败!");
}
System.out.println(new Date()+" 数据添加成功!"+ loginTicket.getExpired());
}
@Test
public void testSelectByTicket(){
LoginTicket loginTicket = loginTicketMapper.selectByTicket("abc");
if (loginTicket == null){
System.out.println("未查到对应的用户!无效凭证");
}
System.out.println(loginTicket);
}
@Test
public void testUpdateStatus(){
int i = loginTicketMapper.updateStatus("abc", 1);
System.out.println(i+" :数据修改成功!");
}
结果: 1.测试插入功能 数据库表的时间也和12:01:15一样。成功!但是一旦手动修改数据,expired的未来时间立马变成当前时间,为什么我也不知道。 2.查询 3.更新 这个贼简单,懂都懂,浪费时间!
??4.1.3 UserService
@Resource
private LoginTicketMapper loginTicketMapper;
public Map<String,Object> login(String username,String password,Long expiredSeconds){
HashMap<String, Object> map = new HashMap<>();
if (StringUtils.isBlank(username)){
map.put("usernameMsg","用户名不能为空!");
return map;
}
if (StringUtils.isBlank(password)){
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;
}
String md5Password = CommunityUtil.md5(password + user.getSalt());
if (!user.getPassword().equals(md5Password)){
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;
}
??4.1.4 LoginController
@Value("${server.servlet.context-path}")
private String contextPath;
......
@RequestMapping(path = "/login",method = RequestMethod.POST)
public String login(String username, String password, String code, boolean rememberme,
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";
}
long 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((int) expiredSeconds);
response.addCookie(cookie);
return "redirect:/index";
}else {
model.addAttribute("usernameMsg",map.get("usernameMsg"));
model.addAttribute("passwordMsg",map.get("passwordMsg"));
return "/site/login";
}
}
4.1.5 login.html页面改造
…
4.1.6 测试
验证码错误 账号或密码错误 同时数据库的login_ticket表也数据添加了 完成!
4.2 退出登录
其中凭证改为失效,数据访问层已经写好了,只需要写业务层逻辑。
4.2.1 LoginTicketMapper
4.2.2 UserService
public void logout(String ticket){
int i = loginTicketMapper.updateStatus(ticket,1);
}
??4.2.3 LoginController
@RequestMapping(path = "/logout",method = RequestMethod.GET)
public String logout(@CookieValue("ticket") String ticket){
userService.logout(ticket);
return "redirect:/login";
}
4.2.4 配置页面’退出登录’的链接
4.2.5 测试
点击确实回退到登录页面: 查看数据库底层逻辑,ticket由0生效变成1无效了。
5. 显示登录信息
比如用户没有登录,头部就要显示首页、登录、注册; 用户已近登录,就要显示首页、消息、用户头像、显示用户名,不用显示登录注册;
每一次请求都要通过这种方式来显示用户信息,太麻烦了;直接通过拦截器来完美实现
5.1 LoginTicketInterceptor ——拦截器类
要实现HandlerInterceptor 接口 即:拦截器要实现HandlerInterceptor接口,而WebMvcConfigurer接口是MVC配置类要实现的接口。
@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 {
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.findByUserId(loginTicket.getUserId());
hostHolder.setUsers(user);
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
User user = hostHolder.getUsers();
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();
}
}
5.1.1 CookieUtil —— 获取cookie的工具类
以后有用到,需要灵活改变参数,道理都是一样的!
public class CookieUtil {
public static String getValue(HttpServletRequest request, String name){
if (request == null || name == null){
throw new IllegalArgumentException("参数不能为空!");
}
Cookie[] cookies = request.getCookies();
if (cookies != null){
for (Cookie cookie : cookies) {
if (cookie.getName().equals(name)){
return cookie.getValue();
}
}
}
return null;
}
}
5.1.2 HostHolder —— 持有用户信息,用于代替session
@Component
public class HostHolder {
private ThreadLocal<User> users = new ThreadLocal<>();
public void setUsers(User user){
users.set(user);
}
public User getUsers(){
return users.get();
}
public void clear(){
users.remove();
}
}
5.1.3 UserService
5.2 WebMvcConfig —— MVC配置类
要实现WebMvcConfigurer接口 即:拦截器要实现HandlerInterceptor接口,而WebMvcConfigurer接口是MVC配置类要实现的接口。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AlphaInterceptor alphaInterceptor;
@Autowired
private LoginTicketInterceptor loginTicketInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(alphaInterceptor)
.excludePathPatterns("/**/*.css","/**/*.js","/**/*.png","/**/*.jpg","/**/*.jpeg")
.addPathPatterns("/register","/login");
registry.addInterceptor(loginTicketInterceptor)
.excludePathPatterns("/**/*.css","/**/*.js","/**/*.png","/**/*.jpg","/**/*.jpeg");
}
}
5.3 测试
未登录: 登录:
|