具体思路
FIlter拦截器拦截前端发送过来的请求,然后通过检查请求的cookie然后进行匹配检查,如果没有cookie则证明没有登录过,则需要过滤跳转到查询数据库验证用户名和密码看看是否有此人,如果有则以用户的名字(或者其他信息)通过Jwt生成Token并存入到Cookie里返回给客户端。下次登录的时候拦截器再拦截请求然后通过Jwt工具解码检查里面的cookie并与Redis里存的cookie进行匹配,如果符合了就直接跳转不再需要登录
图解
实现
controller:
此处可以根据自己的实际情况来搞,最主要的是登录成功后对cookie的处理,有些人的业务逻辑是放在service里处理的
@PostMapping("/login") //HttpServletRequest是为了把对应的employee的id存储在session里方便调用,@RequestBody中是网页返回来的数据 public R<Employee> login(HttpServletRequest request, HttpServletResponse response, @RequestBody Employee employee) { //1、将页面提交的密码password进行md5加密处理 String password = employee.getPassword(); //将password转换成bytes这样就会进行md5转换 password = DigestUtils.md5DigestAsHex(password.getBytes()); //2、根据页面提交的用户名username查询数据库 LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<Employee>(); queryWrapper.eq(Employee::getUsername, employee.getUsername()); //因为Username是唯一的所以可以用getOne方法去查处一个唯一的数据返回给ee Employee emp = employeeService.getOne(queryWrapper); //3、如果没有查询到则返回登录失败结果 if (emp == null) { return R.error("登陆失败"); } //4、密码比对,如果不一致则返回登录失败结果 if (!emp.getPassword().equals(password)) { return R.error("登陆失败"); } //5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果 if (emp.getStatus() == 0) { return R.error("账户已禁用"); } //6、登录成功,返回登录成功结果 //jwt生成token String token = JwtUtils.getJwtToken(emp.getUsername()); //token存入redis,7天有效期 redisTemplate.opsForValue().set(emp.getUsername(), token, Duration.ofDays(7)); //token存入cookie返回给浏览器 ResponseCookie cookie = ResponseCookie.from("token", token) .path("/") // path .maxAge(60 * 60 * 24 * 7) // 有效期 //以下这两项是设置https共享cookie .secure(true) .sameSite("None") .build(); // 设置Cookie Header response.setHeader("Set-Cookie", cookie.toString()); log.info(cookie.toString());//打印看看secure和sameSite是否设置成功 //将员工id存入Session request.getSession().setAttribute("employee", emp.getId()); request.getSession().setAttribute("employeeName", emp.getUsername()); request.getSession().setAttribute("phone",emp.getPhone()); request.getSession().setAttribute("status",emp.getStatus()); log.info("员工登陆为:{}", request.getSession().getAttribute("employee")); log.info("用户登陆为:{}", request.getSession().getAttribute("user")); return R.success(emp); }
service:
@Service @Slf4j public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService { @Autowired RedisTemplate redisTemplate; @Autowired EmployeeService employeeService; //自动登录获取用户信息,并更新token和cookie @Override public R<Employee> getUserMess(String name, HttpServletRequest request, HttpServletResponse response) { LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<Employee>(); queryWrapper.eq(Employee::getUsername, name); //因为Username是唯一的所以可以用getOne方法去查处一个唯一的数据返回给ee Employee emp = employeeService.getOne(queryWrapper); //jwt生成token,包含用户姓名 String token = JwtUtils.getJwtToken(name); //更新token,token存入redis,7天有效期,具体自己设置 redisTemplate.opsForValue().set(name, token, Duration.ofDays(7)); ResponseCookie cookie = ResponseCookie.from("token", token) .path("/") // path .maxAge(60 * 60 * 24 * 7) // 有效期 //以下这两项是设置https共享cookie .secure(true) .sameSite("None") .build(); // 设置Cookie Header response.setHeader("Set-Cookie", cookie.toString()); log.info(cookie.toString());//打印看看secure和sameSite是否设置成功 return R.success(emp); } }
Filter:
下面的代码主要关注用Jwt解析获得出Cookie然后判断是否空然后进行业务处理即可,注意返回处我用了4种方法实现,如果使用ResponseUtil工具类则继续往下看即可。使用方法2则不需要,更简便
@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")//urlPatterns= "/*"是指所有请求都进过滤器 @Slf4j public class LoginCheckFilter implements Filter { @Autowired RedisTemplate redisTemplate; @Autowired EmployeeService employeeService; //路径通配符 public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //整型提升 HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; //1、获取本次请求的URI String requestURI = request.getRequestURI(); log.info("拦截到请求:{}", requestURI); //不需要拦截的请求路径 String[] urls = new String[]{ "/employee/login", "/employee/logout", //此处不拦截backend和front下所有的页面是因为下面的页面可以被查看但是要做的就是不把数据渲染上去 "/backend/**", "/front/**", "/common/**", "/user/sendMsg", "/user/login", "/doc.html", "/webjars/**", "/swagger-resources", "/v2/api-docs" }; // // //2、判断本次请求是否需要处理 boolean check = check(urls, requestURI); //3、如果不需要处理,则直接放行 if (check) { log.info("本次请求{}不需要处理", requestURI); filterChain.doFilter(request, response); return; } Cookie cookie = JwtUtils.getToken(request); //如果没有cookie证明用户还未登陆过,则往下走,要用账号密码登陆 if(cookie==null){ filterChain.doFilter(request,response); }else { boolean isValid = JwtUtils.checkTokenByCookie(cookie,redisTemplate); if(isValid){ // response.sendRedirect("index.html"); String empname = JwtUtils.getUsername(cookie.getValue());//从token中获得用户名 //方法1 // R r = employeeService.getUserMess(empname,request,response); //获取用户信息 //方法2 Employee emp = employeeService.getUserMess(empname);//通过empname获取用户信息 log.info("员工id为{}", request.getSession().getAttribute("employee")); Long empId = (Long) request.getSession().getAttribute("employee"); BaseContext.setCurrentId(empId); //方法1 // ResponseUtil.write(response,r);//使用ResponseUtil工具类返回响应 //方法2 // response.getWriter().write(JSON.toJSONString(R.success(r)));//使用getWriter()但报错 //方法3 // response.getOutputStream().write(JSON.toJSONBytes(R.success(r))); //使用getOutputStream()返回,无报错但因为返回数据不对因而无法正确显示,要测试的话要先注释掉下面4-1的员工代码 //方法4 成功后可以生成一个url发送给login // Create URL object // URL obj = new URL("http://localhost:8080/employee/login"); // // // Open connection // HttpURLConnection con = (HttpURLConnection) obj.openConnection(); // // // Set request method // con.setRequestMethod("POST"); // // // Set request headers // con.setRequestProperty("User-Agent", "Mozilla/5.0"); // con.setRequestProperty("Accept-Language", "en-US,en;q=0.5"); // // // // Enable output and set content type // con.setDoOutput(true); // con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // // // Write data to output stream // OutputStreamWriter wr = new OutputStreamWriter(con.getOutputStream()); // wr.write(String.valueOf(emp)); // wr.flush(); // wr.close(); filterChain.doFilter(request, response); return; }else{ filterChain.doFilter(request,response); } } // //4-1、判断员工登录状态,如果已登录,则直接放行 if (request.getSession().getAttribute("employee") != null) { log.info("员工已登陆,员工id为{}", request.getSession().getAttribute("employee")); Long empId = (Long) request.getSession().getAttribute("employee"); BaseContext.setCurrentId(empId); filterChain.doFilter(request, response); return; } //4-2、判断用户登录状态,如果已登录,则直接放行 if (request.getSession().getAttribute("user") != null) { log.info("用户已登录,用户id为:{}", request.getSession().getAttribute("user")); Long userId = (Long) request.getSession().getAttribute("user"); BaseContext.setCurrentId(userId); filterChain.doFilter(request, response); return; } log.info("用户未登陆"); //5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据 //将NOTLOGIN以json数据流的方式返回给前端,因为前端的拦截器检测到NOTLOGIN就会进行拦截 response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN"))); System.out.println("未登录,返回前端"); return; } /** * 判断本次请求是否需要拦截,与urls里匹配上的就放行 */ public boolean check(String[] urls, String requestURI) { for (String url : urls) { boolean match = PATH_MATCHER.match(url, requestURI); if (match) { return true; } } return false; } }
工具类
JwtUtils:
package com.k1.reggie.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.util.StringUtils; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.Date; /** * jwt 工具类 */ public class JwtUtils { public static final long EXPIRE = 1000 * 60 * 60 * 24; public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; //包含用户id和用户昵称 public static String getJwtToken(String id, String nickname){ String JwtToken = Jwts.builder() .setHeaderParam("typ", "JWT") .setHeaderParam("alg", "HS256") .setSubject("fish-user") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) .claim("id", id) .claim("nickname", nickname) .signWith(SignatureAlgorithm.HS256, APP_SECRET) .compact(); return JwtToken; } //只包含 用户名 public static String getJwtToken(String username){ String JwtToken = Jwts.builder() .setHeaderParam("typ", "JWT") .setHeaderParam("alg", "HS256") .setSubject("fish-user") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) .claim("username", username) .signWith(SignatureAlgorithm.HS256, APP_SECRET) .compact(); return JwtToken; } /** * 从header中取token * 根据token,获取其中的用户名,根据这个用户名查询redis这个token 是否有效 */ public static boolean checkTokenByRequest(HttpServletRequest request, RedisTemplate redisTemplate){ String token = request.getHeader("token"); System.out.println("请求头中的token为:"+token); //如果token为空 if(token==null || token.isEmpty()) return false; //根据token获取用户名 String username = JwtUtils.getUsername(token); System.out.println("token中获取的用户名为:"+username); //判断token是否存储在redis中 Object redisValue = redisTemplate.opsForValue().get(username); if(redisValue == null){ return false; } return true; } /** * 从cookies中取token * 根据token,获取其中的用户名,根据这个用户名查询redis这个token 是否有效 */ public static boolean checkTokenByCookie(HttpServletRequest request, RedisTemplate redisTemplate){ Cookie[] cookies = request.getCookies(); String token = Arrays.stream(cookies).filter(cookie -> cookie.getName().equals( "token")).findFirst().get().getValue(); String username = JwtUtils.getUsername(token); System.out.println("token中获取的用户名为:"+username); //判断token是否存储在redis中 Object redisValue = redisTemplate.opsForValue().get(username); if(redisValue == null){ return false; } return true; } public static boolean checkTokenByCookie(Cookie cookie, RedisTemplate redisTemplate){ try { String username = JwtUtils.getUsername(cookie.getValue()); System.out.println("token中获取的用户名为:"+username); //判断token是否存储在redis中 Object redisValue = redisTemplate.opsForValue().get(username); if(redisValue == null){ return false; } }catch (Exception e){ return false; } return true; } /** * 判断token是否存在与有效 * @param jwtToken * @return */ public static boolean checkToken(String jwtToken) { if(StringUtils.isEmpty(jwtToken)) return false; try { Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * 判断token是否存在与有效 * @param request * @return */ public static boolean checkToken(HttpServletRequest request) { try { String jwtToken = request.getHeader("token"); if(StringUtils.isEmpty(jwtToken)) return false; Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * 根据token获取会员id * @param request * @return */ public static String getMemberIdByJwtToken(HttpServletRequest request) { String jwtToken = request.getHeader("token"); if(StringUtils.isEmpty(jwtToken)) return ""; Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); Claims claims = claimsJws.getBody(); return (String)claims.get("id"); } /** * 根据token获取用户名 */ public static String getUsername(String token){ Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token); Claims claims = claimsJws.getBody(); return (String)claims.get("username"); } /** * 根据cookie获取用户名 * @param request */ public static String getUsernameByCookie(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); for (Cookie cookie:cookies) { if(cookie.getName().equals("token")){ return JwtUtils.getUsername(cookie.getValue()); } } return null; } public static boolean checkTokenByUsername(String username,String token,RedisTemplate redisTemplate) { //判断token是否存储在redis中 Object redisValue = redisTemplate.opsForValue().get(username); return redisValue.equals(token); } //从token中获取用户名,并根据用户名和token去redis中判断该token是否有效 public static boolean checkTokenByToken(String token,RedisTemplate redisTemplate) { String username = getUsername(token); if(StringUtils.isEmpty(username)){ return false; }else { return checkTokenByUsername(username,token,redisTemplate); } } public static Cookie getToken(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if(cookies == null){ return null; }else{ for (Cookie cookie:cookies) { if(cookie.getName().equals("token")){ return cookie; } } } return null; } }
ResponseUtil:
package com.k1.reggie.utils; /** * @Author:kkoneone11 * @name:ResponseUtil * @Date:2023/4/10 20:15 */ import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.databind.ObjectMapper; import com.k1.reggie.common.R; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 用于处理响应(HttpServletResponse)的工具类 */ //public class ResponseUtil { // // public static void out(HttpServletRequest request, HttpServletResponse response, R r) { // ObjectMapper mapper = new ObjectMapper(); // response.setStatus(HttpStatus.OK.value()); // response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); // /** // * 响应跨域配置 // */ // // 响应标头指定 指定可以访问资源的URI路径 // response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin")); // //响应标头指定响应访问所述资源到时允许的一种或多种方法 // response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE"); // //设置 缓存可以生存的最大秒数 // response.setHeader("Access-Control-Max-Age", "3600"); // //设置 受支持请求标头 // response.setHeader("Access-Control-Allow-Headers", "x-requested-with"); // // 指示的请求的响应是否可以暴露于该页面。当true值返回时它可以被暴露 // response.setHeader("Access-Control-Allow-Credentials", "true"); // try { // mapper.writeValue(response.getWriter(), r); // } catch (IOException e) { // e.printStackTrace(); // } // } //} import java.io.IOException; import java.io.PrintWriter; import javax.servlet.http.HttpServletResponse; public class ResponseUtil { public static void write(HttpServletResponse response,Object o) throws IOException{ // ; PrintWriter out = response.getWriter(); out.write(JSON.toJSONString(R.success(o))); out.flush(); out.close(); } }