登录机制五兄弟关系大揭秘:Cookie、Session、Token、JWT、OAuth2实战指南

  • 时间:2025-11-09 18:13 作者: 来源: 阅读:1
  • 扫一扫,手机访问
摘要:大家好,今天我们来聊聊一个让无数程序员又爱又恨的话题——登录认证机制。 你是否也遇到过这些场景: 前端同事问你:“登录接口返回什么?Cookie还是Token?”产品经理说:“我们要支持微信登录!”安全审计说:“你们的Token安全性不够!”用户抱怨:“换个浏览器又要重新登录?”老板问:“为什么我们的系统不支持单点登录?” 别慌!今天我就把这套登录机制五兄弟的关系、区别和实战应用全掏出来,手把手教

大家好,今天我们来聊聊一个让无数程序员又爱又恨的话题——登录认证机制。

你是否也遇到过这些场景:

前端同事问你:“登录接口返回什么?Cookie还是Token?”产品经理说:“我们要支持微信登录!”安全审计说:“你们的Token安全性不够!”用户抱怨:“换个浏览器又要重新登录?”老板问:“为什么我们的系统不支持单点登录?”

别慌!今天我就把这套登录机制五兄弟的关系、区别和实战应用全掏出来,手把手教你玩转各种登录认证方式!

为什么登录认证这么重要?

在开始正题之前,先聊聊为什么登录认证如此重要:

安全防护:防止未授权访问,保护用户数据用户体验:提供便捷的登录方式,提升用户满意度系统架构:影响整个系统的架构设计和扩展性合规要求:满足各种安全标准和法规要求业务发展:支持第三方登录,拓展用户渠道

五兄弟登场:各显神通

Cookie:最古老的大哥

Cookie是Web世界最早的认证机制,诞生于1994年,是网景公司为解决HTTP无状态问题而设计的。


// Cookie使用示例
@RestController
public class LoginController {
    
    @PostMapping("/login")
    public ResponseEntity<String> login(@RequestBody LoginRequest request, 
                                       HttpServletResponse response) {
        // 验证用户身份
        if (authenticate(request.getUsername(), request.getPassword())) {
            // 设置Cookie
            Cookie cookie = new Cookie("sessionId", generateSessionId());
            cookie.setHttpOnly(true);  // 防止XSS攻击
            cookie.setSecure(true);    // 只在HTTPS下传输
            cookie.setPath("/");       // 所有路径可用
            cookie.setMaxAge(3600);    // 1小时过期
            response.addCookie(cookie);
            
            return ResponseEntity.ok("登录成功");
        }
        return ResponseEntity.status(401).body("登录失败");
    }
    
    @GetMapping("/profile")
    public ResponseEntity<UserProfile> getProfile(@CookieValue("sessionId") String sessionId) {
        // 通过SessionID获取用户信息
        UserProfile profile = getUserProfileBySessionId(sessionId);
        if (profile != null) {
            return ResponseEntity.ok(profile);
        }
        return ResponseEntity.status(401).build();
    }
}

Cookie的特点

自动管理:浏览器自动发送和存储大小限制:每个Cookie不超过4KB数量限制:每个域名下最多20个Cookie安全属性:支持HttpOnly、Secure等安全设置

Session:Cookie的黄金搭档

Session是服务器端的状态管理机制,通常与Cookie配合使用。


// Session使用示例
@RestController
public class SessionLoginController {
    
    @Autowired
    private SessionService sessionService;
    
    @PostMapping("/login")
    public ResponseEntity<String> login(@RequestBody LoginRequest request, 
                                       HttpServletRequest httpRequest,
                                       HttpServletResponse httpResponse) {
        // 验证用户身份
        User user = userService.authenticate(request.getUsername(), request.getPassword());
        if (user != null) {
            // 创建Session
            HttpSession session = httpRequest.getSession(true);
            session.setAttribute("userId", user.getId());
            session.setAttribute("username", user.getUsername());
            session.setMaxInactiveInterval(3600); // 1小时过期
            
            // 可选:在数据库中也保存Session信息
            sessionService.saveSession(session.getId(), user.getId());
            
            return ResponseEntity.ok("登录成功");
        }
        return ResponseEntity.status(401).body("登录失败");
    }
    
    @GetMapping("/profile")
    public ResponseEntity<UserProfile> getProfile(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Long userId = (Long) session.getAttribute("userId");
            if (userId != null) {
                UserProfile profile = userService.getUserProfile(userId);
                return ResponseEntity.ok(profile);
            }
        }
        return ResponseEntity.status(401).build();
    }
    
    @PostMapping("/logout")
    public ResponseEntity<String> logout(HttpServletRequest request, 
                                        HttpServletResponse response) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            // 清除Session
            sessionService.removeSession(session.getId());
            session.invalidate();
        }
        
        // 清除Cookie
        Cookie cookie = new Cookie("JSESSIONID", "");
        cookie.setPath("/");
        cookie.setMaxAge(0);
        response.addCookie(cookie);
        
        return ResponseEntity.ok("登出成功");
    }
}

Session的特点

服务端存储:用户状态保存在服务器内存或数据库中安全性高:敏感信息不会暴露给客户端扩展性差:集群环境下需要Session共享内存消耗:每个用户连接都会占用服务器资源

Token:新时代的挑战者

Token是一种无状态的认证机制,服务器不保存用户状态。


// Token使用示例
@RestController
public class TokenLoginController {
    
    @Autowired
    private TokenService tokenService;
    
    @PostMapping("/login")
    public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
        // 验证用户身份
        User user = userService.authenticate(request.getUsername(), request.getPassword());
        if (user != null) {
            // 生成Token
            String accessToken = tokenService.generateToken(user.getId(), 3600); // 1小时过期
            String refreshToken = tokenService.generateToken(user.getId(), 86400); // 24小时过期
            
            LoginResponse response = new LoginResponse();
            response.setAccessToken(accessToken);
            response.setRefreshToken(refreshToken);
            response.setExpiresIn(3600);
            
            return ResponseEntity.ok(response);
        }
        return ResponseEntity.status(401).build();
    }
    
    @GetMapping("/profile")
    public ResponseEntity<UserProfile> getProfile(@RequestHeader("Authorization") String authorization) {
        if (authorization != null && authorization.startsWith("Bearer ")) {
            String token = authorization.substring(7);
            
            // 验证Token
            Long userId = tokenService.validateToken(token);
            if (userId != null) {
                UserProfile profile = userService.getUserProfile(userId);
                return ResponseEntity.ok(profile);
            }
        }
        return ResponseEntity.status(401).build();
    }
    
    @PostMapping("/refresh")
    public ResponseEntity<LoginResponse> refreshToken(@RequestBody RefreshRequest request) {
        // 验证Refresh Token
        Long userId = tokenService.validateToken(request.getRefreshToken());
        if (userId != null) {
            // 生成新的Access Token
            String newAccessToken = tokenService.generateToken(userId, 3600);
            
            LoginResponse response = new LoginResponse();
            response.setAccessToken(newAccessToken);
            response.setExpiresIn(3600);
            
            return ResponseEntity.ok(response);
        }
        return ResponseEntity.status(401).build();
    }
}

Token的特点

无状态:服务器不需要保存用户状态可扩展:天然支持分布式和微服务架构安全性:Token可以包含签名,防止篡改灵活性:可以设置不同的过期时间和权限

JWT:Token界的明星

JWT(JSON Web Token)是目前最流行的Token实现方式。


// JWT使用示例
@Component
public class JwtTokenService {
    
    private final String SECRET_KEY = "mySecretKey";
    private final long ACCESS_TOKEN_EXPIRATION = 3600000; // 1小时
    private final long REFRESH_TOKEN_EXPIRATION = 86400000; // 24小时
    
    public String generateAccessToken(Long userId, String username) {
        Date expiration = new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION);
        
        return Jwts.builder()
                .setSubject(userId.toString())
                .claim("username", username)
                .claim("type", "access")
                .setIssuedAt(new Date())
                .setExpiration(expiration)
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }
    
    public String generateRefreshToken(Long userId) {
        Date expiration = new Date(System.currentTimeMillis() + REFRESH_TOKEN_EXPIRATION);
        
        return Jwts.builder()
                .setSubject(userId.toString())
                .claim("type", "refresh")
                .setIssuedAt(new Date())
                .setExpiration(expiration)
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }
    
    public Claims validateToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(SECRET_KEY)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (JwtException | IllegalArgumentException e) {
            throw new RuntimeException("Token无效或已过期");
        }
    }
    
    public Long getUserIdFromToken(String token) {
        Claims claims = validateToken(token);
        return Long.parseLong(claims.getSubject());
    }
    
    public boolean isTokenExpired(String token) {
        Claims claims = validateToken(token);
        return claims.getExpiration().before(new Date());
    }
}

@RestController
public class JwtLoginController {
    
    @Autowired
    private JwtTokenService jwtTokenService;
    
    @PostMapping("/login")
    public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
        // 验证用户身份
        User user = userService.authenticate(request.getUsername(), request.getPassword());
        if (user != null) {
            // 生成JWT Token
            String accessToken = jwtTokenService.generateAccessToken(user.getId(), user.getUsername());
            String refreshToken = jwtTokenService.generateRefreshToken(user.getId());
            
            LoginResponse response = new LoginResponse();
            response.setAccessToken(accessToken);
            response.setRefreshToken(refreshToken);
            response.setExpiresIn(3600);
            response.setTokenType("Bearer");
            
            return ResponseEntity.ok(response);
        }
        return ResponseEntity.status(401).build();
    }
    
    @GetMapping("/profile")
    public ResponseEntity<UserProfile> getProfile(@RequestHeader("Authorization") String authorization) {
        if (authorization != null && authorization.startsWith("Bearer ")) {
            String token = authorization.substring(7);
            
            try {
                // 验证JWT Token
                Long userId = jwtTokenService.getUserIdFromToken(token);
                UserProfile profile = userService.getUserProfile(userId);
                return ResponseEntity.ok(profile);
            } catch (RuntimeException e) {
                return ResponseEntity.status(401).build();
            }
        }
        return ResponseEntity.status(401).build();
    }
    
    @PostMapping("/refresh")
    public ResponseEntity<LoginResponse> refreshToken(@RequestBody RefreshRequest request) {
        try {
            // 验证Refresh Token
            Long userId = jwtTokenService.getUserIdFromToken(request.getRefreshToken());
            
            // 获取用户信息
            User user = userService.getUserById(userId);
            
            // 生成新的Access Token
            String newAccessToken = jwtTokenService.generateAccessToken(userId, user.getUsername());
            
            LoginResponse response = new LoginResponse();
            response.setAccessToken(newAccessToken);
            response.setExpiresIn(3600);
            response.setTokenType("Bearer");
            
            return ResponseEntity.ok(response);
        } catch (RuntimeException e) {
            return ResponseEntity.status(401).build();
        }
    }
}

JWT的特点

自包含:Token中包含用户信息和权限跨域支持:天然支持前后端分离架构标准化:遵循RFC 7519标准可扩展:可以自定义Claims(声明)

OAuth2:第三方登录专家

OAuth2是专门用于第三方授权的开放标准。


// OAuth2使用示例
@RestController
public class OAuth2Controller {
    
    @Autowired
    private OAuth2Service oauth2Service;
    
    // 微信登录
    @GetMapping("/oauth/wechat")
    public void wechatLogin(HttpServletResponse response) throws IOException {
        // 重定向到微信授权页面
        String redirectUrl = oauth2Service.getWechatAuthUrl();
        response.sendRedirect(redirectUrl);
    }
    
    // 微信回调
    @GetMapping("/oauth/wechat/callback")
    public ResponseEntity<LoginResponse> wechatCallback(@RequestParam("code") String code) {
        try {
            // 通过授权码获取用户信息
            OAuth2User oauth2User = oauth2Service.getWechatUserInfo(code);
            
            // 检查用户是否已存在
            User user = userService.findByOpenId(oauth2User.getOpenId());
            if (user == null) {
                // 创建新用户
                user = userService.createUserFromOAuth2(oauth2User);
            }
            
            // 生成JWT Token
            String accessToken = jwtTokenService.generateAccessToken(user.getId(), user.getUsername());
            String refreshToken = jwtTokenService.generateRefreshToken(user.getId());
            
            LoginResponse response = new LoginResponse();
            response.setAccessToken(accessToken);
            response.setRefreshToken(refreshToken);
            response.setExpiresIn(3600);
            response.setTokenType("Bearer");
            response.setUserId(user.getId());
            
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            return ResponseEntity.status(401).build();
        }
    }
    
    // GitHub登录
    @GetMapping("/oauth/github")
    public void githubLogin(HttpServletResponse response) throws IOException {
        // 重定向到GitHub授权页面
        String redirectUrl = oauth2Service.getGithubAuthUrl();
        response.sendRedirect(redirectUrl);
    }
    
    // GitHub回调
    @GetMapping("/oauth/github/callback")
    public ResponseEntity<LoginResponse> githubCallback(@RequestParam("code") String code) {
        try {
            // 通过授权码获取用户信息
            OAuth2User oauth2User = oauth2Service.getGithubUserInfo(code);
            
            // 检查用户是否已存在
            User user = userService.findByGithubId(oauth2User.getGithubId());
            if (user == null) {
                // 创建新用户
                user = userService.createUserFromOAuth2(oauth2User);
            }
            
            // 生成JWT Token
            String accessToken = jwtTokenService.generateAccessToken(user.getId(), user.getUsername());
            String refreshToken = jwtTokenService.generateRefreshToken(user.getId());
            
            LoginResponse response = new LoginResponse();
            response.setAccessToken(accessToken);
            response.setRefreshToken(refreshToken);
            response.setExpiresIn(3600);
            response.setTokenType("Bearer");
            response.setUserId(user.getId());
            
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            return ResponseEntity.status(401).build();
        }
    }
}

// OAuth2服务实现
@Service
public class OAuth2Service {
    
    private final String WECHAT_APP_ID = "your_wechat_app_id";
    private final String WECHAT_APP_SECRET = "your_wechat_app_secret";
    private final String WECHAT_REDIRECT_URI = "http://localhost:8080/oauth/wechat/callback";
    
    private final String GITHUB_CLIENT_ID = "your_github_client_id";
    private final String GITHUB_CLIENT_SECRET = "your_github_client_secret";
    private final String GITHUB_REDIRECT_URI = "http://localhost:8080/oauth/github/callback";
    
    public String getWechatAuthUrl() {
        return "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=" + WECHAT_APP_ID +
                "&redirect_uri=" + WECHAT_REDIRECT_URI +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=STATE#wechat_redirect";
    }
    
    public OAuth2User getWechatUserInfo(String code) throws Exception {
        // 1. 通过code获取access_token
        String tokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                "?appid=" + WECHAT_APP_ID +
                "&secret=" + WECHAT_APP_SECRET +
                "&code=" + code +
                "&grant_type=authorization_code";
        
        // 发送HTTP请求获取access_token
        JSONObject tokenResponse = HttpClientUtil.get(tokenUrl);
        String accessToken = tokenResponse.getString("access_token");
        String openId = tokenResponse.getString("openid");
        
        // 2. 通过access_token和openid获取用户信息
        String userInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                "?access_token=" + accessToken +
                "&openid=" + openId +
                "&lang=zh_CN";
        
        // 发送HTTP请求获取用户信息
        JSONObject userInfoResponse = HttpClientUtil.get(userInfoUrl);
        
        OAuth2User user = new OAuth2User();
        user.setOpenId(openId);
        user.setNickname(userInfoResponse.getString("nickname"));
        user.setAvatar(userInfoResponse.getString("headimgurl"));
        user.setProvider("wechat");
        
        return user;
    }
    
    public String getGithubAuthUrl() {
        return "https://github.com/login/oauth/authorize" +
                "?client_id=" + GITHUB_CLIENT_ID +
                "&redirect_uri=" + GITHUB_REDIRECT_URI +
                "&scope=user:email" +
                "&state=STATE";
    }
    
    public OAuth2User getGithubUserInfo(String code) throws Exception {
        // 1. 通过code获取access_token
        String tokenUrl = "https://github.com/login/oauth/access_token";
        
        Map<String, String> params = new HashMap<>();
        params.put("client_id", GITHUB_CLIENT_ID);
        params.put("client_secret", GITHUB_CLIENT_SECRET);
        params.put("code", code);
        params.put("redirect_uri", GITHUB_REDIRECT_URI);
        
        // 发送HTTP请求获取access_token
        String tokenResponse = HttpClientUtil.post(tokenUrl, params);
        
        // 解析access_token
        String accessToken = parseAccessToken(tokenResponse);
        
        // 2. 通过access_token获取用户信息
        String userInfoUrl = "https://api.github.com/user";
        
        // 发送HTTP请求获取用户信息
        JSONObject userInfoResponse = HttpClientUtil.get(userInfoUrl, accessToken);
        
        OAuth2User user = new OAuth2User();
        user.setGithubId(userInfoResponse.getString("id"));
        user.setNickname(userInfoResponse.getString("name"));
        user.setAvatar(userInfoResponse.getString("avatar_url"));
        user.setEmail(userInfoResponse.getString("email"));
        user.setProvider("github");
        
        return user;
    }
    
    private String parseAccessToken(String response) {
        // 解析access_token,例如:access_token=xxx&token_type=bearer
        String[] parts = response.split("&");
        for (String part : parts) {
            if (part.startsWith("access_token=")) {
                return part.substring(13);
            }
        }
        return null;
    }
}

OAuth2的特点

标准化:遵循RFC 6749标准安全性:用户不需要向第三方应用提供密码灵活性:支持多种授权方式广泛支持:主流平台都支持OAuth2

五兄弟关系图谱

让我们用一张图来理解五兄弟的关系:


                    +------------------+
                    |     用户浏览器    |
                    +------------------+
                              |
                    +------------------+
                    |     Cookie      |
                    +------------------+
                              |
                    +------------------+
                    |     Session     |
                    +------------------+
                              |
                    +------------------+       +------------------+
                    |      Token      |<----->|      JWT        |
                    +------------------+       +------------------+
                              |
                    +------------------+
                    |     OAuth2      |
                    +------------------+

关系说明

Cookie和Session是传统的认证方式,通常配合使用Token是现代无状态认证的核心概念JWT是Token的一种具体实现方式OAuth2是专门用于第三方授权的协议

实战案例:某电商平台登录系统设计

需求分析

某电商平台需要支持以下登录方式:

传统用户名密码登录手机验证码登录微信扫码登录GitHub第三方登录支持单点登录(SSO)

系统设计


// 统一认证服务
@RestController
@RequestMapping("/api/auth")
public class UnifiedAuthController {
    
    @Autowired
    private AuthService authService;
    
    @Autowired
    private JwtTokenService jwtTokenService;
    
    @Autowired
    private OAuth2Service oauth2Service;
    
    // 传统登录
    @PostMapping("/login")
    public ResponseEntity<AuthResponse> login(@RequestBody @Valid LoginRequest request) {
        try {
            AuthResult result = authService.authenticate(request);
            return buildAuthResponse(result);
        } catch (AuthException e) {
            return ResponseEntity.status(401).body(AuthResponse.error(e.getMessage()));
        }
    }
    
    // 手机验证码登录
    @PostMapping("/mobile/login")
    public ResponseEntity<AuthResponse> mobileLogin(@RequestBody @Valid MobileLoginRequest request) {
        try {
            AuthResult result = authService.mobileAuthenticate(request);
            return buildAuthResponse(result);
        } catch (AuthException e) {
            return ResponseEntity.status(401).body(AuthResponse.error(e.getMessage()));
        }
    }
    
    // 微信登录
    @GetMapping("/wechat/login")
    public void wechatLogin(HttpServletResponse response) throws IOException {
        String redirectUrl = oauth2Service.getWechatAuthUrl();
        response.sendRedirect(redirectUrl);
    }
    
    @GetMapping("/wechat/callback")
    public ResponseEntity<AuthResponse> wechatCallback(@RequestParam("code") String code) {
        try {
            AuthResult result = authService.wechatAuthenticate(code);
            return buildAuthResponse(result);
        } catch (AuthException e) {
            return ResponseEntity.status(401).body(AuthResponse.error(e.getMessage()));
        }
    }
    
    // GitHub登录
    @GetMapping("/github/login")
    public void githubLogin(HttpServletResponse response) throws IOException {
        String redirectUrl = oauth2Service.getGithubAuthUrl();
        response.sendRedirect(redirectUrl);
    }
    
    @GetMapping("/github/callback")
    public ResponseEntity<AuthResponse> githubCallback(@RequestParam("code") String code) {
        try {
            AuthResult result = authService.githubAuthenticate(code);
            return buildAuthResponse(result);
        } catch (AuthException e) {
            return ResponseEntity.status(401).body(AuthResponse.error(e.getMessage()));
        }
    }
    
    // 刷新Token
    @PostMapping("/refresh")
    public ResponseEntity<AuthResponse> refreshToken(@RequestBody RefreshTokenRequest request) {
        try {
            AuthResult result = authService.refreshToken(request.getRefreshToken());
            return buildAuthResponse(result);
        } catch (AuthException e) {
            return ResponseEntity.status(401).body(AuthResponse.error(e.getMessage()));
        }
    }
    
    // 登出
    @PostMapping("/logout")
    public ResponseEntity<Void> logout(@RequestHeader("Authorization") String authorization) {
        if (authorization != null && authorization.startsWith("Bearer ")) {
            String token = authorization.substring(7);
            authService.logout(token);
        }
        return ResponseEntity.ok().build();
    }
    
    private ResponseEntity<AuthResponse> buildAuthResponse(AuthResult result) {
        AuthResponse response = new AuthResponse();
        response.setAccessToken(result.getAccessToken());
        response.setRefreshToken(result.getRefreshToken());
        response.setExpiresIn(result.getExpiresIn());
        response.setTokenType("Bearer");
        response.setUserId(result.getUserId());
        response.setUsername(result.getUsername());
        
        return ResponseEntity.ok(response);
    }
}

// 认证服务实现
@Service
public class AuthService {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private JwtTokenService jwtTokenService;
    
    @Autowired
    private OAuth2Service oauth2Service;
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    public AuthResult authenticate(LoginRequest request) throws AuthException {
        // 验证验证码(如果是验证码登录)
        if (request instanceof MobileLoginRequest) {
            MobileLoginRequest mobileRequest = (MobileLoginRequest) request;
            if (!validateSmsCode(mobileRequest.getMobile(), mobileRequest.getCode())) {
                throw new AuthException("验证码错误");
            }
        }
        
        // 验证用户身份
        User user = userService.authenticate(request.getUsername(), request.getPassword());
        if (user == null) {
            throw new AuthException("用户名或密码错误");
        }
        
        // 生成Token
        return generateAuthResult(user);
    }
    
    public AuthResult mobileAuthenticate(MobileLoginRequest request) throws AuthException {
        return authenticate(request);
    }
    
    public AuthResult wechatAuthenticate(String code) throws AuthException {
        try {
            // 获取微信用户信息
            OAuth2User oauth2User = oauth2Service.getWechatUserInfo(code);
            
            // 查找或创建用户
            User user = userService.findByWechatOpenId(oauth2User.getOpenId());
            if (user == null) {
                user = userService.createUserFromWechat(oauth2User);
            }
            
            return generateAuthResult(user);
        } catch (Exception e) {
            throw new AuthException("微信登录失败: " + e.getMessage());
        }
    }
    
    public AuthResult githubAuthenticate(String code) throws AuthException {
        try {
            // 获取GitHub用户信息
            OAuth2User oauth2User = oauth2Service.getGithubUserInfo(code);
            
            // 查找或创建用户
            User user = userService.findByGithubId(oauth2User.getGithubId());
            if (user == null) {
                user = userService.createUserFromGithub(oauth2User);
            }
            
            return generateAuthResult(user);
        } catch (Exception e) {
            throw new AuthException("GitHub登录失败: " + e.getMessage());
        }
    }
    
    public AuthResult refreshToken(String refreshToken) throws AuthException {
        try {
            // 验证Refresh Token
            Long userId = jwtTokenService.getUserIdFromToken(refreshToken);
            
            // 检查Token是否在黑名单中(已登出)
            if (isTokenBlacklisted(refreshToken)) {
                throw new AuthException("Token已失效");
            }
            
            // 获取用户信息
            User user = userService.getUserById(userId);
            if (user == null) {
                throw new AuthException("用户不存在");
            }
            
            // 生成新的Access Token
            return generateAuthResult(user);
        } catch (RuntimeException e) {
            throw new AuthException("Token刷新失败: " + e.getMessage());
        }
    }
    
    public void logout(String token) {
        try {
            // 将Token加入黑名单
            Long userId = jwtTokenService.getUserIdFromToken(token);
            blacklistToken(token, userId);
        } catch (Exception e) {
            // 忽略Token解析错误
        }
    }
    
    private AuthResult generateAuthResult(User user) {
        String accessToken = jwtTokenService.generateAccessToken(user.getId(), user.getUsername());
        String refreshToken = jwtTokenService.generateRefreshToken(user.getId());
        
        AuthResult result = new AuthResult();
        result.setAccessToken(accessToken);
        result.setRefreshToken(refreshToken);
        result.setExpiresIn(3600);
        result.setUserId(user.getId());
        result.setUsername(user.getUsername());
        
        return result;
    }
    
    private boolean validateSmsCode(String mobile, String code) {
        String key = "sms_code:" + mobile;
        String storedCode = redisTemplate.opsForValue().get(key);
        return code.equals(storedCode);
    }
    
    private void blacklistToken(String token, Long userId) {
        String key = "blacklist:" + token;
        redisTemplate.opsForValue().set(key, userId.toString(), 30, TimeUnit.DAYS);
    }
    
    private boolean isTokenBlacklisted(String token) {
        String key = "blacklist:" + token;
        return redisTemplate.hasKey(key);
    }
}

// 前端使用示例(Vue.js)
/*
// 登录
async login() {
  try {
    const response = await this.$http.post('/api/auth/login', {
      username: this.username,
      password: this.password
    });
    
    // 保存Token到localStorage
    localStorage.setItem('access_token', response.data.access_token);
    localStorage.setItem('refresh_token', response.data.refresh_token);
    
    // 设置Authorization header
    this.$http.defaults.headers.common['Authorization'] = 
      `Bearer ${response.data.access_token}`;
    
    // 跳转到首页
    this.$router.push('/dashboard');
  } catch (error) {
    this.$message.error('登录失败');
  }
}

// 请求拦截器 - 自动添加Token
this.$http.interceptors.request.use(config => {
  const token = localStorage.getItem('access_token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 响应拦截器 - Token过期自动刷新
this.$http.interceptors.response.use(
  response => response,
  async error => {
    if (error.response?.status === 401) {
      // Token过期,尝试刷新
      const refreshToken = localStorage.getItem('refresh_token');
      if (refreshToken) {
        try {
          const refreshResponse = await this.$http.post('/api/auth/refresh', {
            refresh_token: refreshToken
          });
          
          // 更新Token
          const newToken = refreshResponse.data.access_token;
          localStorage.setItem('access_token', newToken);
          
          // 重新发送原请求
          error.config.headers.Authorization = `Bearer ${newToken}`;
          return this.$http.request(error.config);
        } catch (refreshError) {
          // 刷新失败,跳转到登录页
          localStorage.removeItem('access_token');
          localStorage.removeItem('refresh_token');
          this.$router.push('/login');
        }
      }
    }
    return Promise.reject(error);
  }
);
*/

5个核心安全要点,让你的登录系统固若金汤

1. Token安全存储


// 后端安全措施
@Configuration
public class SecurityConfig {
    
    // 设置安全的Cookie属性
    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setCookieName("SESSION");
        serializer.setCookiePath("/");
        serializer.setDomainNamePattern("^.+?.(w+.[a-z]+)$");
        serializer.setUseHttpOnlyCookie(true);   // 防止XSS
        serializer.setUseSecureCookie(true);     // 只在HTTPS下传输
        serializer.setSameSite("Strict");        // 防止CSRF
        return serializer;
    }
}

2. 双Token机制


// 双Token设计
public class TokenService {
    
    public TokenPair generateTokenPair(Long userId) {
        // 短期有效的Access Token
        String accessToken = generateToken(userId, 3600); // 1小时
        
        // 长期有效的Refresh Token
        String refreshToken = generateToken(userId, 86400); // 24小时
        
        return new TokenPair(accessToken, refreshToken);
    }
    
    // Refresh Token存储到数据库,支持主动撤销
    public void storeRefreshToken(String refreshToken, Long userId) {
        RefreshToken token = new RefreshToken();
        token.setToken(refreshToken);
        token.setUserId(userId);
        token.setExpiresAt(new Date(System.currentTimeMillis() + 86400000));
        refreshTokenRepository.save(token);
    }
}

3. 防暴力破解


@Component
public class LoginAttemptService {
    
    private final RedisTemplate<String, Integer> redisTemplate;
    
    public void recordFailedAttempt(String username) {
        String key = "login_attempt:" + username;
        Integer attempts = redisTemplate.opsForValue().get(key);
        if (attempts == null) {
            attempts = 0;
        }
        redisTemplate.opsForValue().set(key, attempts + 1, 15, TimeUnit.MINUTES);
    }
    
    public boolean isBlocked(String username) {
        String key = "login_attempt:" + username;
        Integer attempts = redisTemplate.opsForValue().get(key);
        return attempts != null && attempts >= 5;
    }
    
    public void resetAttempts(String username) {
        String key = "login_attempt:" + username;
        redisTemplate.delete(key);
    }
}

4. 敏感信息脱敏


// 用户信息脱敏
public class UserInfoDesensitizer {
    
    public static String maskMobile(String mobile) {
        if (mobile == null || mobile.length() < 7) {
            return mobile;
        }
        return mobile.substring(0, 3) + "****" + mobile.substring(7);
    }
    
    public static String maskEmail(String email) {
        if (email == null || !email.contains("@")) {
            return email;
        }
        String[] parts = email.split("@");
        String username = parts[0];
        if (username.length() <= 2) {
            return "**@" + parts[1];
        }
        return username.charAt(0) + "**" + username.charAt(username.length() - 1) + "@" + parts[1];
    }
}

5. 日志安全


// 安全日志记录
@Component
public class SecurityLogger {
    
    private static final Logger logger = LoggerFactory.getLogger(SecurityLogger.class);
    
    public void logLoginAttempt(String username, String ip, boolean success) {
        if (success) {
            logger.info("用户登录成功 | username={}, ip={}", username, ip);
        } else {
            logger.warn("用户登录失败 | username={}, ip={}", username, ip);
        }
    }
    
    public void logTokenRefresh(String userId, String ip) {
        logger.info("Token刷新 | userId={}, ip={}", userId, ip);
    }
    
    public void logLogout(String userId, String ip) {
        logger.info("用户登出 | userId={}, ip={}", userId, ip);
    }
}

结语

掌握登录认证机制,核心不是记住所有技术细节,而是理解每种机制的适用场景和优缺点

Cookie/Session:适用于传统Web应用,简单可靠Token/JWT:适用于分布式和微服务架构,无状态扩展性好OAuth2:适用于第三方登录和授权场景,标准化安全

记住:好的认证系统不是一次到位的,而是在实践中不断优化的。从满足基本需求开始,根据实际情况逐步完善,最终你也能构建出安全可靠的认证体系!

认证机制只是系统安全的一部分,后续我们还会分享更多安全和优化的实战技巧,记得关注我们的公众号"服务端技术精选"!

觉得这篇文章对你有帮助吗?欢迎点赞、在看、转发三连,你的支持是我们持续创作的最大动力!


服务端技术精选 | 专注分享实用的后端技术干货

  • 全部评论(0)
手机二维码手机访问领取大礼包
返回顶部