电商项目统一认证方案设计与实战全解析
一、微服务网关整合OAuth2.0设计思路
1.1 网关在认证体系中的角色定位
在微服务架构中,API网关作为统一的入口,在认证授权体系中承担着至关重要的角色。常见的整合方案有两种:
方案一:网关集中式认证(推荐)
-
授权服务器生成令牌
-
所有请求统一在网关层进行令牌验证和权限判断
-
网关解析令牌并将用户信息转发给下游微服务
方案二:资源服务分布式认证
-
网关只负责请求转发
-
各资源服务自行处理令牌验证和权限控制

1.2 网关认证的核心职责
网关在认证授权体系中主要负责两件事:
-
作为OAuth2.0资源服务器角色
-
实现接入方访问权限拦截
-
验证令牌的有效性和权限
-
-
令牌解析与用户信息转发
-
解析令牌获取用户身份和权限信息
-
将明文用户信息转发给下游微服务
-
1.3 微服务端职责
微服务收到网关转发的用户信息后需要:
-
用户授权拦截:检查当前用户是否有权访问该资源
-
用户信息存储:将用户信息存储到当前线程上下文,便于后续业务逻辑随时获取
二、搭建微服务授权中心
2.1 创建授权中心服务
项目结构: wolfmall-authcenter
引入核心依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
2.2 数据库配置
application.yml配置:
yaml
server:
port: 9999
spring:
application:
name: tulingmall-authcenter
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: 6cd8d896-4d19-4e33-9840-26e4bee9a618
datasource:
url: jdbc:mysql://wolf.com:3306/tlmall_oauth?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: root
password: root
druid:
initial-size: 5
min-idle: 10
max-active: 20
2.3 配置授权服务器
基于DB模式存储客户端信息
创建客户端信息表:
sql
CREATE TABLE oauth_client_details ( `client_id` varchar(128) NOT NULL, `resource_ids` varchar(256) DEFAULT NULL, `client_secret` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `authorized_grant_types` varchar(256) DEFAULT NULL, `web_server_redirect_uri` varchar(256) DEFAULT NULL, `authorities` varchar(256) DEFAULT NULL, `access_token_validity` int(11) DEFAULT NULL, `refresh_token_validity` int(11) DEFAULT NULL, `additional_information` varchar(4096) DEFAULT NULL, `autoapprove` varchar(256) DEFAULT NULL, PRIMARY KEY (`client_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
授权服务器配置类:
java
@Configuration
@EnableAuthorizationServer
public class WolfAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 基于DB存储客户端信息
clients.withClientDetails(clientDetails());
}
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
}
基于内存模式配置(开发环境)
java
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client") // 客户端ID
.secret(passwordEncoder().encode("123123")) // 客户端密钥
.authorizedGrantTypes("password", "authorization_code", "refresh_token") // 授权类型
.scopes("all") // 授权范围
.accessTokenValiditySeconds(3600) // 访问令牌有效期
.refreshTokenValiditySeconds(86400); // 刷新令牌有效期
}
2.4 配置Spring Security
安全配置类:
java
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private TulingUserDetailsService wolfUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(tulingUserDetailsService);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
// OAuth2密码模式需要这个Bean
return super.authenticationManagerBean();
}
}
用户详情服务:
java
@Slf4j
@Component
public class WolfUserDetailsService implements UserDetailsService {
@Autowired
private UmsMemberFeignService umsMemberFeignService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 加载用户信息
if (StringUtils.isEmpty(username)) {
throw new UsernameNotFoundException("用户名不能为空");
}
UmsMember umsMember = getByUsername(username);
if (null == umsMember) {
throw new UsernameNotFoundException("用户不存在");
}
// 封装用户信息
return new MemberDetails(umsMember);
}
public UmsMember getByUsername(String username) {
// 通过Feign从会员服务获取用户信息
CommonResult<UmsMember> result = umsMemberFeignService.loadUserByUsername(username);
return result.getData();
}
}
用户详情实现:
java
public class MemberDetails implements UserDetails {
private UmsMember umsMember;
public MemberDetails(UmsMember umsMember) {
this.umsMember = umsMember;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 返回用户权限
return Arrays.asList(new SimpleGrantedAuthority("TEST"));
}
@Override
public String getPassword() {
return umsMember.getPassword();
}
@Override
public String getUsername() {
return umsMember.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return umsMember.getStatus() == 1;
}
public UmsMember getUmsMember() {
return umsMember;
}
}
2.5 测试用户登录
授权码模式(最安全)
适用于第三方应用接入,如微信、QQ登录等。
授权流程:
-
用户访问客户端,客户端将用户导向授权服务器
-
用户选择是否授权
-
用户授权后,授权服务器返回授权码
-
客户端使用授权码换取访问令牌
授权URL示例:
text
http://localhost:9999/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all

密码模式(高度信任)
适用于自家公司内部应用,用户直接提供用户名密码。
获取令牌URL:
text
http://localhost:9999/oauth/token?username=test&password=test&grant_type=password&client_id=client&client_secret=123123&scope=all
2.6 配置资源服务器
java
@Configuration
@EnableResourceServer
public class TulingResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated(); // 所有请求需要认证
}
}
资源接口示例:
java
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/getCurrentUser")
public Object getCurrentUser(Authentication authentication) {
return authentication.getPrincipal(); // 返回当前用户信息
}
}
三、JWT令牌详解与优化
3.1 JWT基础知识
JWT(JSON Web Token) 是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为JSON对象。
核心概念:
-
JWS(JSON Web Signature):签过名的JWT
-
JWK(JSON Web Key):JWT的密钥或公私钥对
3.2 JWT组成结构
一个JWT由三部分组成,用点(.)分隔:
text
header.payload.signature
示例JWT:
text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.khA7TNYc7_0iELcDyTc7gHBZ_xfIcgbfpzUNWwQtzME
1. Header(头部)
描述JWT的基本信息:类型和签名算法。
json
{
"alg": "HS256",
"typ": "JWT"
}
2. Payload(载荷)
存放有效信息的地方,包含三种类型的声明:
标准注册声明:
-
iss:签发者 -
sub:面向的用户 -
aud:接收方 -
exp:过期时间 -
nbf:生效时间 -
iat:签发时间 -
jti:唯一标识
公共声明和私有声明: 可根据业务需求添加自定义字段。
3. Signature(签名)
对header和payload的签名,防止数据篡改。
javascript
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
3.3 JWT应用场景
-
一次性验证:如用户注册邮件激活
-
RESTful API无状态认证:客户端和服务端共享secret
-
单点登录:配合Redis实现会话管理(不推荐纯JWT)
3.4 集成JWT到OAuth2
1. 引入依赖
xml
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
2. 配置JWT Token存储
java
@Configuration
public class JwtTokenStoreConfig {
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123123"); // 对称密钥
return converter;
}
}
3. 授权服务器配置JWT
java
@Autowired
@Qualifier("jwtTokenStore")
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManagerBean)
.tokenStore(tokenStore) // 使用JWT存储策略
.accessTokenConverter(jwtAccessTokenConverter)
.reuseRefreshTokens(false) // 不重复使用刷新令牌
.userDetailsService(tulingUserDetailsService)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
3.5 JWT优化:非对称加密
1. 生成JKS证书文件
bash
keytool -genkeypair \ -alias jwt \ -keyalg RSA \ -keysize 2048 \ -keystore D:/jwt/jwt.jks \ -storepass 123456 \ -keypass 123123 \ -validity 365 \ -dname "CN=fox, OU=tuling, O=tuling, L=changsha, ST=hunan, C=china"
2. 查看公钥信息
bash
keytool -list -rfc -keystore jwt.jks | openssl x509 -inform pem -pubkey
3. JWT配置属性类
java
@ConfigurationProperties(prefix = "tuling.jwt")
public class JwtCAPProperties {
private String keyPairName; // 证书名称
private String keyPairAlias; // 证书别名
private String keyPairSecret; // 证书私钥
private String keyPairStoreSecret; // 证书存储密钥
}
4. 配置非对称加密
java
@Bean
public KeyPair keyPair() {
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
new ClassPathResource(jwtCAPProperties.getKeyPairName()),
jwtCAPProperties.getKeyPairSecret().toCharArray()
);
return keyStoreKeyFactory.getKeyPair(
jwtCAPProperties.getKeyPairAlias(),
jwtCAPProperties.getKeyPairStoreSecret().toCharArray()
);
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(keyPair()); // 使用非对称加密
return converter;
}
5. JWT内容增强
java
public class WolfTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
OAuth2Authentication authentication) {
MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal();
Map<String, Object> additionalInfo = new HashMap<>();
// 添加自定义字段到JWT
additionalInfo.put("memberId", memberDetails.getUmsMember().getId());
additionalInfo.put("nickName", memberDetails.getUmsMember().getNickname());
additionalInfo.put("integration", memberDetails.getUmsMember().getIntegration());
Map<String, Object> retMap = new HashMap<>();
retMap.put("additionalInfo", additionalInfo);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(retMap);
return accessToken;
}
}
6. 配置Token增强器
java
@Autowired
private WolfTokenEnhancer wolfTokenEnhancer;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 配置Token增强器链
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> delegates = new ArrayList<>();
delegates.add(tulingTokenEnhancer);
delegates.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(delegates);
endpoints.authenticationManager(authenticationManagerBean)
.tokenStore(tokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(enhancerChain) // 配置内容增强器
.reuseRefreshTokens(false)
.userDetailsService(tulingUserDetailsService)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
四、接入网关服务
4.1 网关路由配置
application.yml配置:
yaml
server:
port: 8888
spring:
application:
name: wolfmall-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: 80a98d11-492c-4008-85aa-32d889e9b0d0
gateway:
routes:
- id: wolfmall-authcenter
uri: lb://wolfmall-authcenter
predicates:
- Path=/oauth/**
- id: wolfmall-member
uri: lb://wolfmall-member
predicates:
- Path=/member/**
4.2 全局认证过滤器
1. 过滤器核心逻辑
java
@Component
@Order(0)
@EnableConfigurationProperties(value = NotAuthUrlProperties.class)
public class AuthenticationFilter implements GlobalFilter, InitializingBean {
@Autowired
private NotAuthUrlProperties notAuthUrlProperties;
@Autowired
private RestTemplate restTemplate;
private PublicKey publicKey;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String currentUrl = exchange.getRequest().getURI().getPath();
// 1. 过滤不需要认证的URL
if (shouldSkip(currentUrl)) {
return chain.filter(exchange);
}
// 2. 获取Token
String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
if (StringUtils.isEmpty(authHeader)) {
throw new GateWayException(ResultCode.AUTHORIZATION_HEADER_IS_EMPTY);
}
// 3. 校验Token
Claims claims = JwtUtils.validateJwtToken(authHeader, publicKey);
// 4. 存储用户信息到请求头
ServerWebExchange webExchange = wrapHeader(exchange, claims);
return chain.filter(webExchange);
}
@Override
public void afterPropertiesSet() throws Exception {
// 初始化时获取公钥
this.publicKey = JwtUtils.genPublicKey(restTemplate);
}
private boolean shouldSkip(String currentUrl) {
PathMatcher pathMatcher = new AntPathMatcher();
for (String skipPath : notAuthUrlProperties.getShouldSkipUrls()) {
if (pathMatcher.match(skipPath, currentUrl)) {
return true;
}
}
return false;
}
private ServerWebExchange wrapHeader(ServerWebExchange exchange, Claims claims) {
// 从JWT中提取用户信息
String memberId = claims.get("additionalInfo", Map.class).get("memberId").toString();
String nickName = claims.get("additionalInfo", Map.class).get("nickName").toString();
// 添加到请求头
ServerHttpRequest request = exchange.getRequest().mutate()
.header("username", claims.get("user_name", String.class))
.header("memberId", memberId)
.header("nickName", nickName)
.build();
return exchange.mutate().request(request).build();
}
}
2. JWT工具类
java
@Slf4j
public class JwtUtils {
private static final String CLIENT_ID = "wolfmall-gateway";
private static final String CLIENT_SECRET = "123123";
private static final String AUTH_TOKEN_KEY_URL = "http://wolfmall-auth/oauth/token_key";
private static final String AUTH_HEADER = "bearer ";
/**
* 远程调用获取Token Key
*/
private static String getTokenKeyByRemoteCall(RestTemplate restTemplate) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth(CLIENT_ID, CLIENT_SECRET);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(null, headers);
try {
ResponseEntity<Map> response = restTemplate.exchange(
AUTH_TOKEN_KEY_URL, HttpMethod.GET, entity, Map.class);
String tokenKey = response.getBody().get("value").toString();
log.info("获取Token Key: {}", tokenKey);
return tokenKey;
} catch (Exception e) {
log.error("获取Token Key失败", e);
throw new GateWayException(ResultCode.GET_TOKEN_KEY_ERROR);
}
}
/**
* 生成公钥
*/
public static PublicKey genPublicKey(RestTemplate restTemplate) {
String tokenKey = getTokenKeyByRemoteCall(restTemplate);
try {
// 处理公钥字符串
String dealTokenKey = tokenKey
.replaceAll("-*BEGIN PUBLIC KEY-*", "")
.replaceAll("-*END PUBLIC KEY-*", "")
.trim();
Security.addProvider(new BouncyCastleProvider());
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(dealTokenKey));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
log.info("生成公钥成功");
return publicKey;
} catch (Exception e) {
log.error("生成公钥异常", e);
throw new GateWayException(ResultCode.GEN_PUBLIC_KEY_ERROR);
}
}
/**
* 验证JWT Token
*/
public static Claims validateJwtToken(String authHeader, PublicKey publicKey) {
try {
String token = StringUtils.substringAfter(authHeader, AUTH_HEADER);
Jwt<JwsHeader, Claims> parseClaimsJwt = Jwts.parser()
.setSigningKey(publicKey)
.parseClaimsJws(token);
return parseClaimsJwt.getBody();
} catch (Exception e) {
log.error("校验Token异常", e);
throw new GateWayException(ResultCode.JWT_TOKEN_EXPIRE);
}
}
}
3. RestTemplate配置
java
@Configuration
public class RibbonConfig {
@Autowired
private LoadBalancerClient loadBalancer;
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(
Collections.singletonList(new LoadBalancerInterceptor(loadBalancer))
);
return restTemplate;
}
}
4.3 测试验证
1. 获取令牌
text
POST http://localhost:8888/oauth/token?username=test&password=test&grant_type=password&client_id=client&client_secret=123123&scope=all
2. 访问受保护资源
text
GET http://localhost:8888/member/coupons Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
3. 测试结果
-
有效令牌:正常返回数据,网关成功解析用户信息并转发
-
无效令牌:返回401 Unauthorized错误
-
过期令牌:返回Token过期错误
五、方案总结与最佳实践
5.1 方案优势
-
统一认证入口:网关集中处理认证,简化微服务实现
-
无状态架构:JWT支持无状态认证,扩展性好
-
安全性高:非对称加密确保令牌安全
-
性能优化:网关校验减少微服务调用压力
-
灵活扩展:支持多种授权模式和自定义字段
5.2 最佳实践建议
-
令牌管理:
-
设置合理的令牌有效期
-
实现令牌刷新机制
-
记录令牌使用日志
-
-
安全性考虑:
-
使用HTTPS传输令牌
-
定期更换密钥对
-
实现黑名单机制
-
-
性能优化:
-
网关层缓存公钥
-
使用连接池优化远程调用
-
监控认证服务性能
-
-
监控告警:
-
监控认证失败率
-
设置令牌过期告警
-
记录异常访问日志
-
5.3 常见问题解决
| 问题 | 解决方案 |
|---|---|
| 令牌泄露 | 缩短有效期,实现黑名单 |
| 性能瓶颈 | 网关缓存公钥,优化JWT解析 |
| 跨域问题 | 网关统一处理CORS |
| 服务宕机 | 多实例部署,负载均衡 |
5.4 扩展方向
-
多因子认证:集成短信、邮件验证码
-
社交登录:支持微信、QQ等第三方登录
-
权限细化:基于RBAC的细粒度权限控制
-
审计日志:完整的操作审计和追溯
结语
微服务架构下的统一认证方案是保障系统安全的核心组件。本文通过电商项目实战,详细讲解了从OAuth2.0授权服务器搭建、JWT令牌优化到网关认证集成的完整流程。该方案已在生产环境中验证,具备高可用、高性能、易扩展的特点,可为微服务架构的安全认证提供可靠保障。
更多推荐



所有评论(0)