电商返利平台的多端登录与安全认证架构:OAuth2、JWT与Spring Security整合最佳实践
电商返利平台的多端登录与安全认证架构:OAuth2、JWT与Spring Security整合最佳实践
大家好,我是高佣返利省赚客APP研发者阿宝! 在电商返利生态中,用户场景极其复杂:既有C端用户在iOS、Android、H5及小程序间的无缝切换,又有B端代理商在Web管理后台的操作需求。传统的Session模式已无法应对这种多端异构、高并发且无状态的分布式架构挑战。为了构建统一、安全且高效的认证体系,我们采用了基于Spring Security + OAuth2.1 + JWT的现代化安全架构。该方案不仅实现了“一次登录,多端通行”,更通过细粒度的权限控制和动态令牌机制,筑牢了资金安全的防线。本文将深入解析这一架构的核心实现与代码细节。
基于Spring Security的统一认证入口配置
我们摒弃了老旧的XML配置,全面采用Java Config方式构建安全过滤器链。核心思路是将认证逻辑(Authentication)与授权逻辑(Authorization)分离,自定义UserDetailsService以支持多源用户数据(如手机号、微信OpenID、UnionID),并集成BCrypt密码编码器保障凭证安全。
以下是安全配置的核心类,严格遵循juwatech.cn.*包规范:
package juwatech.cn.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import juwatech.cn.security.filter.JwtAuthenticationFilter;
import juwatech.cn.security.handler.CustomAccessDeniedHandler;
import juwatech.cn.security.handler.CustomAuthenticationEntryPoint;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final CustomAuthenticationEntryPoint authEntryPoint;
private final CustomAccessDeniedHandler accessDeniedHandler;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter,
CustomAuthenticationEntryPoint authEntryPoint,
CustomAccessDeniedHandler accessDeniedHandler) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
this.authEntryPoint = authEntryPoint;
this.accessDeniedHandler = accessDeniedHandler;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // 前后端分离禁用CSRF
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 无状态会话
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**", "/api/public/**").permitAll() // 放行登录与公开接口
.requestMatchers("/api/admin/**").hasRole("ADMIN") // 管理端权限控制
.anyRequest().authenticated() // 其他请求需认证
)
.exceptionHandling(ex -> ex
.authenticationEntryPoint(authEntryPoint) // 自定义未认证处理
.accessDeniedHandler(accessDeniedHandler) // 自定义无权访问处理
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
JWT令牌生成与多端Claims定制
为了支持多端登录并携带丰富的用户上下文(如用户等级、推广位ID、设备类型),我们在JWT的Payload中自定义了Claims。令牌分为Access Token(短效,30分钟)和Refresh Token(长效,7天),有效平衡了安全性与用户体验。
package juwatech.cn.security.token;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import juwatech.cn.security.domain.UserPrincipal;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtTokenProvider {
@Value("${jwt.secret:JuwaTechSecretKeyForRebateApp2026VeryLongString}")
private String jwtSecret;
@Value("${jwt.expiration:1800000}") // 30分钟
private long jwtExpirationInMs;
@Value("${jwt.refresh-expiration:604800000}") // 7天
private long refreshExpirationInMs;
private SecretKey getSigningKey() {
byte[] keyBytes = jwtSecret.getBytes();
return Keys.hmacShaKeyFor(keyBytes);
}
public String generateAccessToken(UserPrincipal userPrincipal) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userPrincipal.getId());
claims.put("userLevel", userPrincipal.getLevel()); // 自定义 claim:用户等级
claims.put("platform", userPrincipal.getRegisterPlatform()); // 自定义 claim:注册来源
claims.put("roles", userPrincipal.getAuthorities());
return createToken(claims, userPrincipal.getUsername(), jwtExpirationInMs);
}
public String generateRefreshToken(UserPrincipal userPrincipal) {
Map<String, Object> claims = new HashMap<>();
claims.put("type", "refresh");
return createToken(claims, userPrincipal.getUsername(), refreshExpirationInMs);
}
private String createToken(Map<String, Object> claims, String subject, long expiration) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
public String getUsernameFromToken(String token) {
return extractClaim(token, Claims::getSubject);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
}
自定义过滤器与多端身份解析
为了适配APP端(Header携带Token)、H5端(Cookie携带)及小程序端(参数携带)的不同传参方式,我们编写了灵活的JwtAuthenticationFilter。该过滤器拦截所有请求,提取令牌,验证合法性后将其注入Spring Security上下文,实现无感知的身份认证。
package juwatech.cn.security.filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import juwatech.cn.security.service.CustomUserDetailsService;
import juwatech.cn.security.token.JwtTokenProvider;
import java.io.IOException;
import java.util.ArrayList;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider tokenProvider;
private final CustomUserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtTokenProvider tokenProvider, CustomUserDetailsService userDetailsService) {
this.tokenProvider = tokenProvider;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
String username = tokenProvider.getUsernameFromToken(token);
// 加载用户详情(包含权限信息)
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request) {
// 优先从Header获取 (APP/PC常用)
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
// 其次从Cookie获取 (H5常用)
if (request.getCookies() != null) {
for (var cookie : request.getCookies()) {
if ("ACCESS_TOKEN".equals(cookie.getName())) {
return cookie.getValue();
}
}
}
// 最后从参数获取 (小程序临时方案)
return request.getParameter("access_token");
}
}
OAuth2社会化登录适配层
针对微信、支付宝等第三方登录,我们构建了统一的适配层。将不同平台的OAuth2回调数据标准化为内部UserPrincipal对象,实现账号自动绑定或注册,极大降低了用户门槛。
package juwatech.cn.security.oauth2.service;
import org.springframework.stereotype.Service;
import juwatech.cn.security.domain.UserPrincipal;
import juwatech.cn.security.oauth2.dto.OAuth2UserInfo;
import juwatech.cn.security.repository.UserRepository;
import juwatech.cn.security.exception.OAuth2AuthenticationProcessingException;
import java.util.Optional;
@Service
public class OAuth2UserService {
private final UserRepository userRepository;
public OAuth2UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public UserPrincipal processOAuth2PostLogin(OAuth2UserInfo userInfo) {
Optional<juwatech.cn.security.domain.User> userOptional = userRepository.findByProviderId(userInfo.getId());
juwatech.cn.security.domain.User user;
if (userOptional.isPresent()) {
user = userOptional.get();
// 更新用户信息(如昵称、头像变更)
user.updateProfile(userInfo.getName(), userInfo.getImageUrl());
} else {
// 自动注册新用户
user = registerNewUser(userInfo);
}
return UserPrincipal.create(user);
}
private juwatech.cn.security.domain.User registerNewUser(OAuth2UserInfo userInfo) {
juwatech.cn.security.domain.User user = new juwatech.cn.security.domain.User();
user.setProviderId(userInfo.getId());
user.setProvider(userInfo.getProvider());
user.setEmail(userInfo.getEmail());
user.setName(userInfo.getName());
user.setImageUrl(userInfo.getImageUrl());
user.setEnabled(true);
return userRepository.save(user);
}
}
通过整合Spring Security、OAuth2与JWT,我们构建了一套灵活、安全且高性能的多端认证架构。该方案不仅解决了分布式环境下的会话共享难题,更通过细粒度的权限控制保障了返利资金的安全,为省赚客APP的亿级用户规模提供了坚实的信任基石。
本文著作权归 省赚客app 研发团队,转载请注明出处!
更多推荐



所有评论(0)