项目背景
本项目是基于 OAuth2.0 实现的微服务认证与授权系统,旨在为多个微服务模块提供统一的认证、授权功能。项目采用 UAA 服务 作为认证服务器,负责用户登录、令牌的生成与管理;同时利用网关服务(Gateway)作为资源服务器入口,对各微服务的请求进行安全保护。
OAuth2.0 在本项目中主要采用 密码模式(Password Grant Type) 实现,适用于可信任的客户端与用户直接交互的场景。项目的主要目标是通过分布式认证、动态权限验证以及高效的令牌管理,保证服务之间的安全调用和灵活的权限控制。
UAA 服务的配置
1. 核心配置:AuthorizationServerConfiguration
UAA 服务实现了 OAuth2 的授权服务器扩展功能,配置如下:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
@Qualifier(value = "clientDetailsServiceImpl")
private ClientDetailsService customClientDetailsService;
@Bean
public TokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
@Bean
public ApprovalStore approvalStore() {
return new JdbcApprovalStore(dataSource);
}
@Bean
public TokenEnhancer tokenEnhancer() {
return new OpenTokenEnhancer();
}
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(customClientDetailsService);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.authenticationManager(authenticationManager)
.approvalStore(approvalStore())
.userDetailsService(userDetailsService)
.tokenServices(createDefaultTokenServices())
.accessTokenConverter(OpenHelper.buildAccessTokenConverter())
.authorizationCodeServices(authorizationCodeServices());
}
private DefaultTokenServices createDefaultTokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setTokenEnhancer(tokenEnhancer());
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(true);
tokenServices.setClientDetailsService(customClientDetailsService);
return tokenServices;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
}
2. 关键功能
- RedisTokenStore:将令牌存储在 Redis 中。
- JdbcApprovalStore:基于 JDBC 的用户授权存储。
- 自定义 ClientDetailsService:支持动态管理客户端信息。
- 异常处理:支持自定义授权和错误页面。
3. 登录接口
UAA 服务中登录接口的作用是处理用户登录,并根据用户的身份信息生成访问令牌(Access Token)。以下为接口的详细说明:
/**
* 获取用户访问令牌
* 基于OAuth2密码模式登录
*
* @param username 用户名
* @param password 密码
* @return 返回包含访问令牌的响应
*/
@ApiOperation(value = "登录获取用户访问令牌", notes = "基于OAuth2密码模式登录,无需签名,返回access_token")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", required = true, value = "登录名", paramType = "form"),
@ApiImplicitParam(name = "password", required = true, value = "登录密码", paramType = "form")
})
@PostMapping("/login/token")
public Object getLoginToken(
@RequestParam String username,
@RequestParam String password,
@RequestHeader HttpHeaders httpHeaders) throws Exception {
Map<String, Object> result = getToken(username, password, "admin", httpHeaders);
if (result.containsKey("access_token")) {
return ResultBody.ok().data(result); // 返回成功响应
} else {
return result; // 返回错误信息
}
}
private JSONObject getToken(String username, String password, String type, HttpHeaders headers) {
OpenOAuth2ClientDetails clientDetails = clientProperties.getOauth2().get("admin");
String url = WebUtils.getServerUrl(WebUtils.getHttpServletRequest()) + "/oauth/token";
// 构建请求参数
MultiValueMap<String, Object> postParameters = new LinkedMultiValueMap<>();
postParameters.add("username", username);
postParameters.add("password", password);
postParameters.add("client_id", clientDetails.getClientId());
postParameters.add("client_secret", clientDetails.getClientSecret());
postParameters.add("grant_type", "password"); // 密码模式
postParameters.add("login_type", type);
// 设置请求头
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.remove(HttpHeaders.AUTHORIZATION);
// 发起请求
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(postParameters, headers);
return restTemplate.postForObject(url, request, JSONObject.class);
}
说明
-
getToken()
方法是登录接口的核心逻辑,通过 RestTemplate 发起 HTTP POST 请求,与 OAuth2.0/oauth/token
端点交互。 -
该方法使用客户端的
client_id
和client_secret
,配合用户凭据完成认证。
网关服务的配置
网关服务在整个微服务架构中扮演了重要角色,其主要职责是作为 OAuth2.0 资源服务器,负责对外部请求进行过滤、验证以及路由转发。通过对网关服务的配置,可以实现对所有微服务的统一安全保护和动态权限控制。
本网关服务的职责与核心功能
-
请求过滤
- 对进入网关的请求进行预处理,包括跨域处理、签名验证、访问限制等。
- 防止非法请求进入微服务内网。
-
认证与授权
- 作为资源服务器,验证客户端提交的
access_token
。 - 动态解析权限并对资源访问进行控制,确保只有经过授权的用户能够访问指定的资源。
- 作为资源服务器,验证客户端提交的
-
统一路由转发
- 根据配置的路由规则,将请求转发到对应的微服务模块。
- 支持动态路由更新,便于扩展和维护。
-
安全日志记录
- 记录请求的访问日志、认证日志和异常信息,用于后续分析和审计。
1. 核心配置:ResourceServerConfiguration
@Configuration
public class ResourceServerConfiguration {
private static final String MAX_AGE = "18000L";
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private ResourceLocator apiresourceLocator;
@Autowired
private ApiProperties apiProperties;
// 为跨域请求添加必要的响应头,支持跨域访问
@Bean
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (CorsUtils.isCorsRequest(request)) {
HttpHeaders requestHeaders = request.getHeaders();
ServerHttpResponse response = ctx.getResponse();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(ctx);
};
}
// 为网关服务添加 OAuth2.0 认证功能,验证请求中携带的 `access_token`。
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
// 自定义oauth2 认证, 使用redis读取token,而非jwt方式
JsonAuthenticationEntryPoint entryPoint = new JsonAuthenticationEntryPoint(accessLogService);
JsonAccessDeniedHandler accessDeniedHandler = new JsonAccessDeniedHandler(accessLogService);
AccessManager accessManager = new AccessManager(apiresourceLocator, apiProperties);
// 通过 `RedisTokenStore` 实现令牌的分布式存储与管理。
RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
RedisAuthenticationManager redisAuthenticationManager = new RedisAuthenticationManager(tokenStore);
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(redisAuthenticationManager);
oauth2.setServerAuthenticationConverter(new ServerBearerTokenAuthenticationConverter());
oauth2.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(entryPoint));
oauth2.setAuthenticationSuccessHandler(new ServerAuthenticationSuccessHandler() {
@Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
ServerWebExchange exchange = webFilterExchange.getExchange();
SecurityContextServerWebExchange securityContextServerWebExchange = new SecurityContextServerWebExchange(exchange, ReactiveSecurityContextHolder.getContext().subscriberContext(
ReactiveSecurityContextHolder.withAuthentication(authentication)
));
return webFilterExchange.getChain().filter(securityContextServerWebExchange);
}
});
// 动态管理权限规则,根据请求路径和用户权限动态决定是否放行。
http
.httpBasic().disable()
.csrf().disable()
.authorizeExchange()
.pathMatchers("/").permitAll()
// 动态权限验证
.anyExchange().access(accessManager)
.and().exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(entryPoint).and()
// 日志前置过滤器
.addFilterAt(new PreRequestFilter(baseAppServiceClient), SecurityWebFiltersOrder.FIRST)
// 跨域过滤器
.addFilterAt(corsFilter(), SecurityWebFiltersOrder.CORS)
// 签名验证过滤器
.addFilterAt(new PreSignatureFilter(baseAppServiceClient, apiProperties, new JsonSignatureDeniedHandler(accessLogService)), SecurityWebFiltersOrder.CSRF)
// 签名验证过滤器2
.addFilterAt(new SignatureFilter(baseAppServiceClient, new JsonSignatureDeniedHandler(accessLogService)), SecurityWebFiltersOrder.CSRF)
// 访问验证前置过滤器
.addFilterAt(new PreCheckFilter(tokenStore, accessManager, accessDeniedHandler, apiProperties), SecurityWebFiltersOrder.CSRF)
// oauth2认证过滤器
.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION)
.addFilterAt(new RateLimitFilter(baseAppServiceClient, repository, apiProperties, new RateLimiter(redisTemplate), new JsonRateLimitExceptionHandler(accessLogService), new JsonSignatureDeniedHandler(accessLogService)), SecurityWebFiltersOrder.CSRF)
// 日志过滤器
.addFilterAt(new AccessLogFilter(accessLogService), SecurityWebFiltersOrder.SECURITY_CONTEXT_SERVER_WEB_EXCHANGE);
return http.build();
}
}
2. 关键功能
-
跨域配置 (CORS) :支持跨域请求。
-
令牌存储与验证:基于 Redis 实现令牌存储和验证,使用
RedisAuthenticationManager
进行令牌解析。 -
动态权限验证:通过
AccessManager
实现资源的动态权限管理。 -
AccessManager
:- 从 Redis 或数据库中加载权限信息,与请求路径进行匹配。
- 如果用户的权限不满足要求,则拒绝访问。
网关服务的 OAuth2.0 认证逻辑大致分为以下几个步骤:
-
请求到达网关:
- 对请求进行跨域处理。
- 执行签名验证、访问限制等预处理逻辑。
-
提取并验证令牌:
- 从请求头中提取
Bearer
类型的令牌。 - 使用 Redis 中存储的令牌信息进行验证。
- 从请求头中提取
-
权限校验:
- 根据用户的权限信息和请求路径进行动态匹配。
- 验证通过后,将请求转发至目标微服务。
-
异常处理:
- 如果认证或授权失败,返回对应的错误响应。
OAuth2 工作流程
- 用户通过 UAA 服务登录,使用
/oauth/token
接口获取访问令牌。 - 用户携带访问令牌访问网关服务。
- 网关服务通过
RedisAuthenticationManager
验证令牌。 - 验证通过后,用户请求被转发到对应的微服务。
- 请求被处理后返回响应。
示例: 请求
POST /login/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
username=user1
password=123456
响应
{
"code": 200,
"message": "成功",
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 3600,
"scope": "read write"
}
}