15-web实战-登录认证

1. 登录校验思路 ?

登录标记
用户登录成功后,后续的每次请求,都可以获取到这个标记。

会话技术,维护浏览器状态的方法。

服务器需要识别多次请求是否来自同一个浏览器,便于同一次会话,的多次请求的数据共享。

会话跟踪技术:

跨域:

2. 会话跟踪技术

响应头里的

Set-Cookie:name=value

请求头里的

Cookie:name=value
@GetMapping("/c1")  
public Result cookie1(HttpServletResponse response){  
	//Set-Cookie:login_username:tom
    response.addCookie(new Cookie("login_username","tom")); 
    return Result.success();  
}  
  
//获取Cookie  
@GetMapping("/c2")  
public Result cookie2(HttpServletRequest request){ 
	//Cookie:login_username:tom
    Cookie[] cookies = request.getCookies();  
    for (Cookie cookie : cookies) {  
        if(cookie.getName().equals("login_username")){  
            log.info("login_username: {}",cookie.getValue());
        }  
    }  
    return Result.success();  
}

优点:

缺点:

2.3. 会话跟踪技术 Session 的原理?

底层基于 Cookie

@GetMapping("/s1")  
public Result session1(HttpSession session){  
    log.info("id:{}",session.getId());  
    log.info("HttpSession-s1: {}", session.hashCode());  
    session.setAttribute("loginUser", "tom"); //往session中存储数据  
    return Result.success();  
}  
  
@GetMapping("/s2")  
public Result session2(HttpServletRequest request){  
    HttpSession session = request.getSession();  
    log.info("HttpSession-s2: {}", session.hashCode());  
  
    Object loginUser = session.getAttribute("loginUser"); //从session中获取数据  
    log.info("loginUser: {}", loginUser);  
    return Result.success(loginUser);  
}

2.4. 会话跟踪技术Session 的优缺点?

优点:

缺点:

3. 会话跟踪技术令牌 Token

3.1. 优缺点

优点:

缺点:

@Override  
public LoginInfo login(Emp emp) {  
    Emp empLogin = mapper.getUsernameAndPassword(emp);  
    if(empLogin != null){  
        //1. 生成JWT令牌  
        Map<String,Object> dataMap = new HashMap<>();  
        dataMap.put("id", empLogin.getId());  
        dataMap.put("username", empLogin.getUsername());  
  
        String jwt = JwtUtils.generateJwt(dataMap);  
        LoginInfo loginInfo = new LoginInfo(empLogin.getId(), empLogin.getUsername(), empLogin.getName(), jwt);  
        return loginInfo;  
    }  
    return null;  
}

assets/03-web/15-web实战-登录认证/IMG-20250531-215144-539.png

3.2. JWT 有几个组成部分?

每个部分存的内容是什么?

assets/03-web/15-web实战-登录认证/IMG-20250531-220024-983.png

Base 64

是一种基于 64 个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的编码方式。

3.3. 怎么生成令牌?

jjwt
0.9.1
map.put("id",101);
map.put("username","tom")
String jwt = Jwts.builder()
  .signWith(SignatureAlgorithm.HS256, "xxx")
  .addClaims(map)
  .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME_MS))

3.4. JWT 的生成和校验?

3.5. 解析令牌什么情况报错?

@Test  
public void test() {  
    Map<String, Object> claims = new HashMap<>();  
    claims.put("id", 10);  
    claims.put("username", "tom");  
    String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256, "666888").addClaims(claims)  
            //.setExpiration(new Date(System.currentTimeMillis() +  100))  
            .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 1000)).compact();  
    System.out.println(jwt);  
    //io.jsonwebtoken.ExpiredJwtException:  
    Claims claims2 = Jwts.parser().setSigningKey("666888").parseClaimsJws(jwt).getBody();  
    System.out.println(claims2);  
    //io.jsonwebtoken.MalformedJwtException  
    String s = jwt.replaceAll("\\d", "0");  
    System.out.println("s = " + s);  
    Claims claims3 = Jwts.parser().setSigningKey("666888").parseClaimsJws(s).getBody();  
    System.out.println(claims3);  
}

4. 过滤器

4.1. Filter 的开发步骤?

Important

过滤器放行:chian. doFilter (req, resp);

过滤器如果不执行放行操作,

过滤器拦截到请求后,就不后访问到对应的资源。

4.2. 过滤器执行流程?

过滤器链

注意过滤器链

assets/03-web/15-web实战-登录认证/IMG-20250531-222216-607.png

assets/03-web/15-web实战-登录认证/IMG-20250531-232945-249.png

4.3. 过滤器拦截路径例子?

4.4. 过滤器-登录校验逻辑

登录和 token 解析成功,是放行。

token 获取或解析失败,是 401。

5. 拦截器

5.1. 拦截器使用步骤?

5.2. 拦截路径例子?

@Override  
public void addInterceptors(InterceptorRegistry registry) {  
    //注册自定义拦截器对象  
    registry.addInterceptor(demoInterceptor)  
            .addPathPatterns("/**")//设置拦截器拦截的请求路径( /** 表示拦截所有请求)  
            .excludePathPatterns("/login");//设置不拦截的请求路径  
}

5.3. 拦截器和过滤器区别?

assets/03-web/15-web实战-登录认证/IMG-20250531-223807-390.png

5.4. 拦截器-登录校验逻辑

和过滤器逻辑一样。

放行方式不同。

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
    System.out.println("preHandle .... ");  
    return true; //true表示放行  
}
import com.jun.utils.JwtUtils;  
import jakarta.servlet.*;  
import jakarta.servlet.annotation.WebFilter;  
import jakarta.servlet.http.HttpServletRequest;  
import jakarta.servlet.http.HttpServletResponse;  
import lombok.extern.slf4j.Slf4j;  
import org.apache.http.HttpStatus;  
import org.springframework.util.StringUtils;  
import java.io.IOException;  
  
@Slf4j  
@WebFilter(urlPatterns = "/*")  
public class TokenFilter implements Filter {  
    @Override  
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {  
        HttpServletRequest request = (HttpServletRequest) req;  
        HttpServletResponse response = (HttpServletResponse) resp;  
        String url = request.getRequestURL().toString();  
        if (url.contains("login")) {  
            log.info("登录请求 , 直接放行");  
            chain.doFilter(request, response);  
            return;        }  
        String jwt = request.getHeader("token");  
        if (!StringUtils.hasLength(jwt)) { //jwt为空  
            log.info("获取到jwt令牌为空, 返回错误结果");  
            response.setStatus(HttpStatus.SC_UNAUTHORIZED);  
            return;        }  
        try {  
            JwtUtils.parseJWT(jwt);  
        } catch (Exception e) {  
            e.printStackTrace();  
            log.info("解析令牌失败, 返回错误结果");  
            response.setStatus(HttpStatus.SC_UNAUTHORIZED);  
            return;        }  
        log.info("令牌合法, 放行");  
        chain.doFilter(request, response);  
    }  
}
import com.jun.utils.JwtUtils;  
import jakarta.servlet.http.HttpServletRequest;  
import jakarta.servlet.http.HttpServletResponse;  
import lombok.extern.slf4j.Slf4j;  
import org.apache.http.HttpStatus;  
import org.springframework.stereotype.Component;  
import org.springframework.util.StringUtils;  
import org.springframework.web.servlet.HandlerInterceptor;  
  
@Slf4j  
@Component  
public class TokenInterceptor implements HandlerInterceptor {  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        String url = request.getRequestURL().toString();  
        if (url.contains("login")) { //登录请求  
            log.info("登录请求 , 直接放行");  
            return true;        }  
        String jwt = request.getHeader("token");  
        if (!StringUtils.hasLength(jwt)) { //jwt为空  
            log.info("获取到jwt令牌为空, 返回错误结果");  
            response.setStatus(HttpStatus.SC_UNAUTHORIZED);  
            return false;        }  
        try {  
            JwtUtils.parseJWT(jwt);  
        } catch (Exception e) {  
            e.printStackTrace();  
            log.info("解析令牌失败, 返回错误结果");  
            response.setStatus(HttpStatus.SC_UNAUTHORIZED);  
            return false;        }  
        log.info("令牌合法, 放行");  
        return true;    }  
}
Important

@Order 指定拦截器优先级,数字小,优先级高。

适合拦截器是 Spring Bean 的场景(@Order 只有在拦截器以 Bean 的方式存在于 Spring 容器中时才生效)

例子:

@Component
@Order(1)
public class FirstInterceptor implements HandlerInterceptor {
    // ...
}

@Component
@Order(2)
public class SecondInterceptor implements HandlerInterceptor {
    // ...
}

AFilter init
BFilter init
CFilter init
# 服务器启动的时候,filter的初始化执行一次 ----------------------


AFilter 放行前
BFilter 放行前
CFilter 放行前


TokenInterceptor preHandle 请求进入 Controller 之前执行

AController print...

TokenInterceptor postHandle Controller 执行后,视图渲染前执行
TokenInterceptor afterHandle 请求完成后执行(包括视图渲染完成之后)

CFilter 放行后
BFilter 放行后
AFilter 放行后

# 服务器关闭的时候,filter的销毁执行一次  ----------------------
CFilter destory
BFilter destory
AFilter destory

视图渲染完成理解:

上一节:14-web实战-学员管理-自

下一节:16-web实战-aop