大家好,今天我们来聊聊一个让无数程序员又爱又恨的话题——登录认证机制。
你是否也遇到过这些场景:
前端同事问你:“登录接口返回什么?Cookie还是Token?”产品经理说:“我们要支持微信登录!”安全审计说:“你们的Token安全性不够!”用户抱怨:“换个浏览器又要重新登录?”老板问:“为什么我们的系统不支持单点登录?”别慌!今天我就把这套登录机制五兄弟的关系、区别和实战应用全掏出来,手把手教你玩转各种登录认证方式!
在开始正题之前,先聊聊为什么登录认证如此重要:
安全防护:防止未授权访问,保护用户数据用户体验:提供便捷的登录方式,提升用户满意度系统架构:影响整个系统的架构设计和扩展性合规要求:满足各种安全标准和法规要求业务发展:支持第三方登录,拓展用户渠道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使用示例
@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使用示例
@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(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使用示例
@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);
}
);
*/
// 后端安全措施
@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;
}
}
// 双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);
}
}
@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);
}
}
// 用户信息脱敏
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];
}
}
// 安全日志记录
@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:适用于第三方登录和授权场景,标准化安全记住:好的认证系统不是一次到位的,而是在实践中不断优化的。从满足基本需求开始,根据实际情况逐步完善,最终你也能构建出安全可靠的认证体系!
认证机制只是系统安全的一部分,后续我们还会分享更多安全和优化的实战技巧,记得关注我们的公众号"服务端技术精选"!
觉得这篇文章对你有帮助吗?欢迎点赞、在看、转发三连,你的支持是我们持续创作的最大动力!
服务端技术精选 | 专注分享实用的后端技术干货