- 基础登录功能:根据提供的用户名和密码判断是否存在于数据库
LoginController.java
@RestController
@Slf4j
public class LoginController {@Autowiredprivate UserService userService;@PostMapping("/login")public Result login(@RequestBody User user) {log.info("user: {}", user);User u = userService.login(user);return u != null ? Result.success(u) : Result.error("用户名或密码错误");}
}
UserController.java
@Service
public class UserServiceImpl implements com.diaryback.Service.UserService {@Autowiredprivate UserMapper userMapper;public User login(User user) {return userMapper.getUserByIdAndPassword(user);}
}
登录校验
在用户未登录情况下访问需要登录才能使用的业务,会跳转到登录界面
会话技术
- 会话:用户通过浏览器访问服务器资源时建立会话,直到一方断开连接会话结束。一次会话中可以包含多次请求和响应
- 会话跟踪:服务器需要识别多个请求是否来自同一个浏览器,以便在同一个会话的多个请求之间共享数据
Cookie
服务器端存储Cookie,响应时自动加上Cookie到客户端,客户端下次请求会自动加上cookie
不允许跨域请求
Session
Session技术基于cookie,首次访问服务端时创建session,并将sessionID附加到cookie
令牌
令牌中存储用户的身份信息以及需要共享的数据,存储在客户端
JWT令牌
jwt:JSON Web Token,将原始JSON数据进行安全封装
jwt三组成部分:
- Header:记录令牌类型,签名算法
- Payload:有效载荷,携带自定义信息,默认信息和有效期等
- Signature:签名,确保安全性。将header和payload加密计算而来
引入依赖:
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.12.3</version></dependency>
生成jwt字符串:需要指定签名算法、密钥以及自定义内容和过期时间
/*** 测试生成JWT*/@Testpublic void testGenJWT(){Map<String, Object> claims = new HashMap<>();claims.put("id", 1);claims.put("name", "maria");String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256, "AlexandarHamiltonWeAreWaitingInTheWingsForYou")//设置加密方式和加密密钥.claims(claims)//设置自定义内容.expiration(new Date(System.currentTimeMillis() + 3600 * 1000))//设置有效期为一个小时.compact();//生成字符串System.out.println(jwt);}
解析时需要指定签名的密钥
/*** 测试解析JWT*/@Testpublic void testParseJWT() {Claims claims = Jwts.parser().setSigningKey("AlexandarHamiltonWeAreWaitingInTheWingsForYou")//设置解密密钥.build().parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoibWFyaWEiLCJpZCI6MSwiZXhwIjoxNzM3NzExMjQ3fQ.AD2hdUzHdzq9qA0ZulvOyWA857tuRUWChnEX2P1ebcI").getBody();System.out.println(claims);}
JWT工具类:jwtUtil
package com.diaryback.Utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;import java.util.Date;
import java.util.Map;public class JwtUtil {private static String signKey = "AlexandarHamiltonWeAreWaitingInTheWingsForYou";private static Long expire = 432000000L;/*** 生成jwt令牌*/public static String generateJwt(Map<String, Object> claims){String jwt = Jwts.builder().addClaims(claims).signWith(SignatureAlgorithm.HS256, signKey).setExpiration(new Date(System.currentTimeMillis() + expire)).compact();return jwt;}/*** 解析jwt令牌*/public static Claims parseJwt(String jwt){Claims claims = Jwts.parser().setSigningKey(signKey).build().parseClaimsJws(jwt).getBody();return claims;}
}
Filter过滤器
可以拦截对资源的请求,通常用于登录校验、统一编码等
- 定义Filter:定义一个类,实现Filter接口,重写所有方法(init(), destroy(), doFilter())
- 配置Filter:Filter类加上@WebFilter注解,配置拦截资源的路径,同时启动类加上@ServletComponentScan开启Servlet组件支持
拦截路径的配置:
过滤器链:一个web应用中可以配置多个过滤器行程过滤器链
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;import javax.servlet.*;
import java.io.IOException;@WebFilter(urlPatterns = "/*")
public class DemoFilter implements jakarta.servlet.Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {
// Filter.super.init(filterConfig);System.out.println("初始化过滤器");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("Demo 执行过滤操作...放行前逻辑");//放行filterChain.doFilter(servletRequest, servletResponse);System.out.println("Demo 执行过滤操作...放行后逻辑");}@Overridepublic void destroy() {
// Filter.super.destroy();System.out.println("销毁过滤器");}
}
登录校验的Filter流程:
LoginFilter.java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONPObject;
import com.diaryback.Pojo.Result;
import com.diaryback.Utils.JwtUtil;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.http.HttpRequest;@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest) servletRequest;//强制类型转换,将ServletRequest转换为其子类HttpServletRequestHttpServletResponse resp = (HttpServletResponse) servletResponse;String url = req.getRequestURL().toString();log.info("请求的url:{}", url);// 如果是登录请求则放行if(url.contains("login")){log.info("登录请求,放行");filterChain.doFilter(servletRequest, servletResponse);return;}//获取请求头中的令牌String jwt = req.getHeader("token");//判断请求头中是否有令牌if(!StringUtils.hasLength(jwt)){log.info("未登录,不允许访问");Result error = Result.error("NOT_LOGIN");//将Result转换为JSON格式传递给前端,使用fastJson包String notLogin = JSON.toJSONString(error);resp.getWriter().write(notLogin);return;}//解析token校验令牌try {JwtUtil.parseJwt(jwt);} catch (Exception e) {//jwt解析失败e.printStackTrace();log.info("解析失败,不允许访问");Result error = Result.error("NOT_LOGIN");String notLogin = JSON.toJSONString(error);resp.getWriter().write(notLogin);return;}//放行log.info("已登录,放行");filterChain.doFilter(servletRequest, servletResponse);}
}
Interceptor拦截器
Interceptor:作用类似于Filter,拦截请求,在指定方法调用前后执行预先设定的代码
- 定义拦截器,实现HandlerInterceptor接口,并重写所有方法(preHandler, postHandle, afterCompletion)
- 注册拦截器,需要在配置类中注册
LoginCheckInterceptor.java
package com.diaryback.Interceptor;import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;@Component //将拦截器交给Spring容器管理
public class LoginCheckInterceptor implements HandlerInterceptor {@Override //请求处理前调用,返回false则请求不会被处理public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle执行");return true;}@Override //请求处理后调用,但在视图渲染前public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle执行");}@Override //请求处理后调用,视图渲染后public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion执行");}
}
WebConfig.java
package com.diaryback.config;import com.diaryback.Interceptor.LoginCheckInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration //声明是配置类
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginCheckInterceptor loginCheckInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//添加拦截器registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");}
}
拦截路径:addPathPatterns()定义需要拦截哪些资源,excludePathPatterns()定义不需要拦截哪些资源
拦截器执行流程:
登录校验拦截器
```java
package com.diaryback.Interceptor;import com.alibaba.fastjson.JSONObject;
import com.diaryback.Pojo.Result;
import com.diaryback.Utils.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;@Slf4j
@Component //将拦截器交给Spring容器管理
public class LoginCheckInterceptor implements HandlerInterceptor {@Override //请求处理前调用,返回false则请求不会被处理public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String url = request.getRequestURL().toString();log.info("preHandle执行,url: {}", url);if(url.contains("login")){log.info("放行");return true;}String jwt = request.getHeader("token");//检查是否有令牌if(!StringUtils.hasLength(jwt)){log.info("未登录");Result error = Result.error("NOT_LOGIN");String notLogin = JSONObject.toJSONString(error);response.getWriter().write(notLogin);return false;}//解析令牌try {JwtUtil.parseJwt(jwt);} catch (Exception e) {e.printStackTrace();log.info("令牌无效");Result error = Result.error("NOT_LOGIN");String notLogin = JSONObject.toJSONString(error);response.getWriter().write(notLogin);return false;}//放行log.info("放行");return true;}@Override //请求处理后调用,但在视图渲染前public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle执行");}@Override //请求处理后调用,视图渲染后public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion执行");}
}
异常处理
定义全局异常处理器
import com.diaryback.Pojo.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;/*** 全局异常处理*/
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)//指定处理的异常类型public Result ex(Exception ex){ex.printStackTrace();return Result.error("服务器异常");}
}