前端视角 Java Web 入门手册 5.9:真实世界 Web 开发—— 对话管理与授权

163 阅读5分钟

Servlet 操作 Cookie & Session

在 Web 开发中,Session 和 Cookie 是用于管理用户会话状态的重要机制,使用 Servlet 操作 Cookie 和 Session 非常简单

操作 Cookie

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/cookieOperations")
public class CookieOperationsServlet extends HttpServlet {
    private void createCookie(HttpServletRequest req, HttpServletResponse resp) {
        // 创建一个名为 user 的 Cookie,值为 John
        Cookie cookie = new Cookie("user", "John");
        // 设置 Cookie 的最大存活时间为 3600 秒(1 小时)
        cookie.setMaxAge(3600);
        // 将 Cookie 添加到响应中
        resp.addCookie(cookie);
    }

    private void readCookie(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 获取请求中的所有 Cookie
        Cookie[] cookies = req.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("user".equals(cookie.getName())) {
                    // ...
                    return;
                }
            }
        }
        out.println("<p>未找到指定的 Cookie!</p>");
    }

    private void modifyCookie(HttpServletRequest req, HttpServletResponse resp) {
        Cookie[] cookies = req.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("user".equals(cookie.getName())) {
                    // 修改 Cookie 的值为 Tom
                    cookie.setValue("Tom");
                    // 设置 Cookie 的最大存活时间为 3600 秒(1 小时)
                    cookie.setMaxAge(3600);
                    // 将修改后的 Cookie 添加到响应中
                    resp.addCookie(cookie);
                    break;
                }
            }
        }
    }

    private void deleteCookie(HttpServletRequest req, HttpServletResponse resp) {
        Cookie[] cookies = req.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("user".equals(cookie.getName())) {
                    // 将 Cookie 的最大存活时间设置为 0,即删除该 Cookie
                    cookie.setMaxAge(0);
                    // 将修改后的 Cookie 添加到响应中
                    resp.addCookie(cookie);
                    break;
                }
            }
        }
    }
}

操作 Session

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet("/sessionCookieOperations")
public class SessionCookieOperationsServlet extends HttpServlet {
    private void createSession(HttpServletRequest req, HttpServletResponse resp) {
        // 获取当前会话,如果不存在则创建一个新的会话
        HttpSession session = req.getSession(true);
        // 向会话中存储数据
        session.setAttribute("username", "John");
    }

    private void readSession(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 获取当前会话,如果不存在则返回 null
        HttpSession session = req.getSession(false);
        if (session != null) {
            String username = (String) session.getAttribute("username");
            // ...
        }
    }

    private void modifySession(HttpServletRequest req, HttpServletResponse resp) {
        HttpSession session = req.getSession(false);
        if (session != null) {
            // 修改会话中的数据
            session.setAttribute("username", "Tom");
        }
    }

    private void deleteSession(HttpServletRequest req, HttpServletResponse resp) {
        HttpSession session = req.getSession(false);
        if (session != null) {
            // 使会话失效
            session.invalidate();
        }
    }
}    

Spring Session

Spring Session 是 Spring 提供的一个会话管理库,通过将会话存储从应用内存转移到外部存储(数据库、Redis 等),实现了会话的集中管理、集群化和持久化

  • 会话共享:在分布式系统中,多个应用实例可以共享会话数据,解决传统基于内存的会话存储在负载均衡环境中的局限性。
  • 会话持久化:将会话数据持久化到外部存储,确保会话在服务器重启或故障后依然可用。

看一下如何在 SpringBoot 中使用 Spring Session,并把 Session 信息存储到 Redis

添加依赖

pom.xml 中添加以下依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

配置 Redis

application.properties 中配置 Redis 连接信息:

spring.redis.host=localhost
spring.redis.port=6379

启用 Spring Session

在 Spring Boot 主应用类上添加 @EnableRedisHttpSession 注解:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@SpringBootApplication
@EnableRedisHttpSession
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

开始使用

在控制器中使用 HttpSession 来管理会话:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;

@RestController
public class SessionController {
    @GetMapping("/setSession")
    public String setSession(HttpSession session) {
        session.setAttribute("username", "John");
        return "Session set successfully";
    }

    @GetMapping("/getSession")
    public String getSession(HttpSession session) {
        String username = (String) session.getAttribute("username");
        return "Username in session: " + username;
    }
}

JWT

JWT(JSON Web Token)是一种用于在网络应用中安全传输信息的开放标准,通常用于身份验证和授权。服务器在用户登录后生成 JWT 并返回给客户端,客户端在后续请求中携带该令牌,服务器通过验证 JWT 来确认用户身份。

JWT 通过自包含的令牌机制实现无状态认证,服务端无需存储会话信息,客户端只需在请求头携带令牌即可完成身份验证,显著降低服务器存储压力,尤其适合分布式系统,在单点登录 SSO、OAuth 三方授权等场景有应用。

工作流程

  • 生成令牌:用户登录成功后,服务器生成 JWT 并返回给客户端。
  • 携带令牌:客户端在后续请求通过 HTTP HeaderAuthorization: Bearer <token>携带令牌。
  • 验证令牌:服务器接收到请求后,验证 JWT 的签名和有效性,并提取用户信息。

简单应用

添加依赖

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-api</artifactId>
  <version>0.11.5</version>
</dependency>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-impl</artifactId>
  <version>0.11.5</version>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-jackson</artifactId>
  <version>0.11.5</version>
  <scope>runtime</scope>
</dependency>

生成 JWT

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;

public class JwtUtil {
    private static final String SECRET_KEY = "your-secret-key-keep-it-safe";
    private static final long EXPIRATION_TIME = 86400000; // 24小时(毫秒)

    public static String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }
}

验证 JWT

public static boolean validateToken(String token) {
    try {
        Claims claims = Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
        return !claims.getExpiration().before(new Date());
    } catch (Exception e) {
        return false;
    }
}

OAuth2.0

OAuth 2.0 是一种开放授权协议,允许第三方应用在用户授权后访问其存储在资源服务器上的数据,而无需共享用户凭据(如密码)。它通过令牌(Token,可能就是 JWT 格式的)机制实现安全的权限委托,是互联网服务间数据共享的通用标准

关键角色

角色职责描述典型示例
资源所有者数据的实际拥有者,决定是否授权第三方访问资源用户
授权服务器验证用户身份并颁发访问令牌(Access Token)微信 OAuth 服务
客户端请求访问资源的第三方应用(需预先在授权服务器注册)电商网站
资源服务器存储受保护资源并根据令牌权限响应请求订单列表

  • 很多时候 Client 和资源服务器都是我们自己的 Web 应用
  • 授权服务器返回的令牌可以是 JWT 格式

简单应用

许多网站提供使用 Google、Facebook、微信等第三方账号登录的功能,就是基于 OAuth 2.0 实现的,Spring 对 OAuth 2.0 提供了全面的支持,在应用中集成 OAuth 2.0 变得非常简便

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

配置 Client

application.yml 中配置 OAuth 2.0 客户端信息,使用 GitHub 作为授权服务器

spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: YOUR_GITHUB_CLIENT_ID
            client-secret: YOUR_GITHUB_CLIENT_SECRET
            scope: read:user
        provider:
          github:
            authorization-uri: https://github.com/login/oauth/authorize
            token-uri: https://github.com/login/oauth/access_token
            user-info-uri: https://api.github.com/user
            user-name-attribute: id

配置 Spring Security

创建一个安全配置类,配置 OAuth2 登录流程

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll() // 允许匿名访问的路径
                .anyRequest().authenticated() // 其他路径需要认证
                .and()
            .oauth2Login() // 启用 OAuth2 登录
                .loginPage("/login"); // 自定义登录页面(可选)
    }
}

创建控制器

创建一个简单的控制器,用于演示 OAuth2 登录

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @GetMapping("/")
    public String home() {
        return "home"; // 返回主页视图
    }

    @GetMapping("/login")
    public String login() {
        return "login"; // 返回登录页视图
    }

    @GetMapping("/user")
    public String user(@AuthenticationPrincipal OAuth2User principal, Model model) {
        model.addAttribute("name", principal.getAttribute("name"));
        model.addAttribute("email", principal.getAttribute("email"));
        return "user"; // 返回用户信息视图
    }
}