Java 用户验证
爱嘤斯塔 人气:0接着上期内容超详细讲解Java秒杀项目登陆模块的实现
一、用户验证
未登录的用户不能进入首页
根据上期内容,我未登录也能进入首页:
1、在方法内添加请求与反应
①、IUserService
package com.example.seckill.service; import com.example.seckill.pojo.User; import com.baomidou.mybatisplus.extension.service.IService; import com.example.seckill.util.response.ResponseResult; import com.example.seckill.vo.UserVo; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * <p> * 用户信息表 服务类 * </p> * * @author lv * @since 2022-03-15 */ public interface IUserService extends IService<User> { ResponseResult<?> findByAccount(UserVo userVo, HttpServletRequest request, HttpServletResponse response); }
②、UserServiceImpl
③、UserController
package com.example.seckill.controller; import com.example.seckill.service.IUserService; import com.example.seckill.util.response.ResponseResult; import com.example.seckill.vo.UserVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; /** * <p> * 用户信息表 前端控制器 * </p> * * @author lv * @since 2022-03-15 */ @RestController @RequestMapping("/user") public class UserController { @Autowired private IUserService userService; // 用户登录 @RequestMapping("/login") public ResponseResult<?> login(@Valid UserVo userVo, HttpServletRequest request, HttpServletResponse response){ // 调用service的登录验证 return userService.findByAccount(userVo,request,response); } }
2、cookie操作的封装
package com.example.seckill.util; import lombok.extern.slf4j.Slf4j; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; @Slf4j public class CookieUtils { /** * @Description: 得到Cookie的值, 不编码 */ public static String getCookieValue(HttpServletRequest request, String cookieName) { return getCookieValue(request, cookieName, false); } /** * @Description: 得到Cookie的值 */ public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) { Cookie[] cookieList = request.getCookies(); if (cookieList == null || cookieName == null) { return null; } String retValue = null; try { for (int i = 0; i < cookieList.length; i++) { if (cookieList[i].getName().equals(cookieName)) { if (isDecoder) { retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8"); } else { retValue = cookieList[i].getValue(); } break; } } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return retValue; } /** * @Description: 得到Cookie的值 */ public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) { Cookie[] cookieList = request.getCookies(); if (cookieList == null || cookieName == null) { return null; } String retValue = null; try { for (int i = 0; i < cookieList.length; i++) { if (cookieList[i].getName().equals(cookieName)) { retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString); break; } } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return retValue; } /** * @Description: 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码 */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue) { setCookie(request, response, cookieName, cookieValue, -1); } /** * @Description: 设置Cookie的值 在指定时间内生效,但不编码 */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage) { setCookie(request, response, cookieName, cookieValue, cookieMaxage, false); } /** * @Description: 设置Cookie的值 不设置生效时间,但编码 * 在服务器被创建,返回给客户端,并且保存客户端 * 如果设置了SETMAXAGE(int seconds),会把cookie保存在客户端的硬盘中 * 如果没有设置,会默认把cookie保存在浏览器的内存中 * 一旦设置setPath():只能通过设置的路径才能获取到当前的cookie信息 */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, boolean isEncode) { setCookie(request, response, cookieName, cookieValue, -1, isEncode); } /** * @Description: 设置Cookie的值 在指定时间内生效, 编码参数 */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) { doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode); } /** * @Description: 设置Cookie的值 在指定时间内生效, 编码参数(指定编码) */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, String encodeString) { doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString); } /** * @Description: 删除Cookie带cookie域名 */ public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) { doSetCookie(request, response, cookieName, null, -1, false); } /** * @Description: 设置Cookie的值,并使其在指定时间内生效 */ private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) { try { if (cookieValue == null) { cookieValue = ""; } else if (isEncode) { cookieValue = URLEncoder.encode(cookieValue, "utf-8"); } Cookie cookie = new Cookie(cookieName, cookieValue); if (cookieMaxage > 0) cookie.setMaxAge(cookieMaxage); if (null != request) {// 设置域名的cookie String domainName = getDomainName(request); log.info("========== domainName: {} ==========", domainName); if (!"localhost".equals(domainName)) { cookie.setDomain(domainName); } } cookie.setPath("/"); response.addCookie(cookie); } catch (Exception e) { e.printStackTrace(); } } /** * @Description: 设置Cookie的值,并使其在指定时间内生效 */ private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, String encodeString) { try { if (cookieValue == null) { cookieValue = ""; } else { cookieValue = URLEncoder.encode(cookieValue, encodeString); } Cookie cookie = new Cookie(cookieName, cookieValue); if (cookieMaxage > 0) cookie.setMaxAge(cookieMaxage); if (null != request) {// 设置域名的cookie String domainName = getDomainName(request); log.info("========== domainName: {} ==========", domainName); if (!"localhost".equals(domainName)) { cookie.setDomain(domainName); } } cookie.setPath("/"); response.addCookie(cookie); } catch (Exception e) { e.printStackTrace(); } } /** * @Description: 得到cookie的域名 */ private static final String getDomainName(HttpServletRequest request) { String domainName = null; String serverName = request.getRequestURL().toString(); if (serverName == null || serverName.equals("")) { domainName = ""; } else { serverName = serverName.toLowerCase(); serverName = serverName.substring(7); final int end = serverName.indexOf("/"); serverName = serverName.substring(0, end); if (serverName.indexOf(":") > 0) { String[] ary = serverName.split("\\:"); serverName = ary[0]; } final String[] domains = serverName.split("\\."); int len = domains.length; if (len > 3 && !isIp(serverName)) { // www.xxx.com.cn domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1]; } else if (len <= 3 && len > 1) { // xxx.com or xxx.cn domainName = "." + domains[len - 2] + "." + domains[len - 1]; } else { domainName = serverName; } } return domainName; } public static String trimSpaces(String IP) {//去掉IP字符串前后所有的空格 while (IP.startsWith(" ")) { IP = IP.substring(1, IP.length()).trim(); } while (IP.endsWith(" ")) { IP = IP.substring(0, IP.length() - 1).trim(); } return IP; } public static boolean isIp(String IP) {//判断是否是一个IP boolean b = false; IP = trimSpaces(IP); if (IP.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")) { String s[] = IP.split("\\."); if (Integer.parseInt(s[0]) < 255) if (Integer.parseInt(s[1]) < 255) if (Integer.parseInt(s[2]) < 255) if (Integer.parseInt(s[3]) < 255) b = true; } return b; } }
3、UserServiceImpl
// 最初版本(session),但Session内数据是服务器共用的 // 所有不让名字重复 String ticket=UUID.randomUUID().toString().replace("-",""); request.getSession().setAttribute(ticket,user); // 将生成的ticket给用户,用户如何验证?使用cookie CookieUtils.setCookie(request,response,"ticket",ticket);
点击登录时,查看应用程序,cookie内有值
4、跳转界面PathController
跳转方法:
// 跳所有二级页面 @RequestMapping("/{dir}/{path}") public String toPath(@PathVariable("dir") String dir, @PathVariable("path") String path, HttpServletRequest request){ String ticket= CookieUtils.getCookieValue(request,"ticket"); if(ticket==null){ throw new BusinessException(ResponseResultCode.TICKET_ERROR); } // 去session中取到ticket对应的用户,判断是否有值 Object obj=request.getSession().getAttribute(ticket); if(obj==null){ throw new BusinessException(ResponseResultCode.TICKET_ERROR); } return dir+"/"+path; }
未登录时访问主界面失败:只有登录成功后才能访问
二、全局session
根据以上操作完后,session仍然有问题,,session只能在当前某一台服务器中,
需使用第三者完成数据共享,此处使用redis方式,将需要缓存的数据丢到缓存内
1、导入依赖
此处全局session是spring session中的一个,spring session就是spring中的一个文件
<!--commons-pool2--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!--spring-session将session借助于第三方存储(redis/mongodb等等),默认redis--> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>
2、配置yml文件redis
spring:
redis:
host: 47.100.191.44
password: xiaoli_redis
database: 0
port: 6379
3、开启虚拟机
现在开启虚拟机
三、自定义redis实现功能
完成全局session的作用
1、新建RedisConfig文件
用于操作redis
package com.example.seckill.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { public RedisTemplate redisTemplate(RedisConnectionFactory factory){ // 新建一个 RedisTemplate redisTemplate=new RedisTemplate(); //设置一下使用连接工厂 redisTemplate.setConnectionFactory(factory); // 额外设置 // 将key序列化操作,转化为string redisTemplate.setKeySerializer(new StringRedisSerializer()); // value被转化为json redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 额外设置(hash 就是 map集合) redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); // 让设置生效 redisTemplate.afterPropertiesSet(); return redisTemplate; } }
2、实现全局session
①、写个帮助类,用于调用
IRedisService接口:
package com.example.seckill.service; import com.example.seckill.pojo.User; public interface IRedisService { void putUserByTicket(String ticket, User user); User getUserByTicket(String ticket); }
实现接口:
package com.example.seckill.service.impl; import com.example.seckill.pojo.User; import com.example.seckill.service.IRedisService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; @Service public class RedisServiceImpl implements IRedisService { @Autowired private RedisTemplate redisTemplate; @Override public void putUserByTicket(String ticket, User user) { redisTemplate.opsForValue().set("user:"+ticket,user,2L, TimeUnit.HOURS); } @Override public User getUserByTicket(String ticket) { Object obj=redisTemplate.opsForValue().get("user:"+ticket); if(obj==null || !(obj instanceof User)){ return null; } return (User)obj; } }
②、redisService到缓存中拿元素
@Autowired private IRedisService redisService; // 跳所有二级页面 @RequestMapping("/{dir}/{path}") public String toPath(@PathVariable("dir") String dir, @PathVariable("path") String path, HttpServletRequest request){ // 获取用户的ticket String ticket= CookieUtils.getCookieValue(request,"ticket"); if(ticket==null){ throw new BusinessException(ResponseResultCode.TICKET_ERROR); } // 去session中取到ticket对应的用户,判断是否有值 // Object obj=request.getSession().getAttribute(ticket); // 去缓存中拿元素 User user=redisService.getUserByTicket(ticket); if(user==null){ throw new BusinessException(ResponseResultCode.TICKET_ERROR); } return dir+"/"+path; }
③、放到缓存中
UserServiceImpl:
// 放到缓存中去 redisService.putUserByTicket(ticket,user);
package com.example.seckill.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.example.seckill.exception.BusinessException; import com.example.seckill.pojo.User; import com.example.seckill.mapper.UserMapper; import com.example.seckill.service.IRedisService; import com.example.seckill.service.IUserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.seckill.util.CookieUtils; import com.example.seckill.util.MD5Utils; import com.example.seckill.util.ValidatorUtils; import com.example.seckill.util.response.ResponseResult; import com.example.seckill.util.response.ResponseResultCode; import com.example.seckill.vo.UserVo; import com.sun.deploy.nativesandbox.comm.Request; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.RequestBody; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.Session; import java.util.Date; import java.util.UUID; /** * <p> * 用户信息表 服务实现类 * </p> * * @author lv * @since 2022-03-15 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Autowired private IRedisService redisService; @Override public ResponseResult<?> findByAccount(UserVo userVo, HttpServletRequest request, HttpServletResponse response) { // 先判断信息是否符合(账号是否是手机号码,密码是不是空) // if(!ValidatorUtils.isMobile(userVo.getMobile())){ // throw new BusinessException(ResponseResultCode.USER_ACCOUNT_NOT_MOBLIE); // } // if(StringUtils.isBlank(userVo.getPassword())){ // throw new BusinessException(ResponseResultCode.USER_PASSWORD_NOT_MATCH); // } // 再去数据库查出对应的用户(mobile) User user=this.getOne(new QueryWrapper<User>().eq("id",userVo.getMobile())); if(user==null){ throw new BusinessException(ResponseResultCode.USER_ACCOUNT_NOT_FIND); } // 比较密码 // 二重加密(前端->后端,后端->数据库) String salt=user.getSalt(); // 将前台的加密密码和后端的盐再次进行加密 String newPassword=MD5Utils.formPassToDbPass(userVo.getPassword(),salt); if(!newPassword.equals(user.getPassword())){ throw new BusinessException(ResponseResultCode.USER_PASSWORD_NOT_MATCH); } // 修改最后的登录时间 this.update(new UpdateWrapper<User>().eq("id",userVo.getMobile()).set("last_login_date",new Date()).setSql("login_count=login_count+1")); // 最初版本(session),但Session内数据是服务器共用的 // 所有不让名字重复 String ticket=UUID.randomUUID().toString().replace("-",""); // 放到全局或者当服务器的session // request.getSession().setAttribute(ticket,user); // 放到缓存中去 redisService.putUserByTicket(ticket,user); // 将生成的ticket给用户,用户如何验证?使用cookie CookieUtils.setCookie(request,response,"ticket",ticket); return ResponseResult.success(); } }
④、LocalDateTime无法构造实例
LocalDateTime是Java8推荐使用的时间类,我此处无法转化,需要去配置解析器,我就改变实体类中的LocalDateTime改为时间戳Timestamp
四、使用参数解析器
在controller类中方法内加上User实体类参数,查询出用户,自动将信息赋值
1、新建WebConfig文件
package com.example.seckill.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @Configuration //打开mvc的功能 @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Autowired private UserArgumentResolvers userArgumentResolvers; @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(userArgumentResolvers); } // 使静态资源仍然可使用 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { //静态资源访问映射 映射路径 -> 本地资源路径 registry.addResourceHandler("/static/**") .addResourceLocations("classpath:/static/"); } }
2、定义参数解析器
UserArgumentResolvers :
package com.example.seckill.config; import com.example.seckill.exception.BusinessException; import com.example.seckill.pojo.User; import com.example.seckill.service.IRedisService; import com.example.seckill.util.CookieUtils; import com.example.seckill.util.response.ResponseResultCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.stereotype.Component; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import javax.servlet.http.HttpServletRequest; @Component public class UserArgumentResolvers implements HandlerMethodArgumentResolver { @Autowired private IRedisService redisService; // supportsParameter方法判断参数是否需要解析,决定了resolveArgument方法是否运行 @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.getParameterType() == User.class; } // 解析参数 @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { // 参数解析User,因为很多地方需要做登录验证 HttpServletRequest request=(HttpServletRequest) nativeWebRequest.getNativeRequest(); // 获取用户的ticket String ticket= CookieUtils.getCookieValue(request,"ticket"); if(ticket==null){ throw new BusinessException(ResponseResultCode.TICKET_ERROR); } // 去session中取到ticket对应的用户,判断是否有值 // Object obj=request.getSession().getAttribute(ticket); // 去缓存中拿元素 User user=redisService.getUserByTicket(ticket); if(user==null){ throw new BusinessException(ResponseResultCode.TICKET_ERROR); } return user;//经过了参数解析后,参数会变成你这个地方返回的值 } }
3、PathController
package com.example.seckill.controller; import com.example.seckill.exception.BusinessException; import com.example.seckill.pojo.User; import com.example.seckill.service.IRedisService; import com.example.seckill.util.CookieUtils; import com.example.seckill.util.response.ResponseResultCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; @Controller public class PathController { // 登录跳首页 @RequestMapping("/") public String toPath(){ return "login"; } // 跳所有二级页面 @RequestMapping("/{dir}/{path}") public String toPath(@PathVariable("dir") String dir, @PathVariable("path") String path, User user){ return dir+"/"+path; } }
4、访问主界面得到相关信息:
本期内容结束~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
加载全部内容