如何使用 OAuth2 保护 2 个 Spring Boot 微服务之间的通信?
Posted
技术标签:
【中文标题】如何使用 OAuth2 保护 2 个 Spring Boot 微服务之间的通信?【英文标题】:How to secure communication between 2 Spring Boot microservices using OAuth2? 【发布时间】:2021-02-12 20:34:31 【问题描述】:我正在学习如何使用基本身份验证和 OAuth2 JWT 令牌身份验证来保护微服务。我使用基本身份验证实现了它,现在我想在 OAuth2 身份验证中对其进行转换。
这是使用基本身份验证保护这两个微服务之间通信的实现。
微服务 1 - REST API
@Configuration
@Getter
public class DemoApiConfiguration
@Value("$demo.api.credentials.username")
private String username;
@Value("$demo.api.credentials.password")
private String password;
SecurityConfigurer 类:
@Configuration
@RequiredArgsConstructor
public class SecurityConfigurer extends WebSecurityConfigurerAdapter
private final DemoApiConfiguration apiConfig;
@Override
protected void configure(HttpSecurity http) throws Exception
http
.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder)
UserDetails theUser = User.withUsername(apiConfig.getUsername())
.password(passwordEncoder.encode(apiConfig.getPassword())).roles("USER").build();
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(theUser);
return userDetailsManager;
@Bean
public PasswordEncoder passwordEncoder()
return new BCryptPasswordEncoder();
控制器类:
@RestController
@RequestMapping("/rest/api/v1")
public class HomeController
@GetMapping("/products")
public String home()
return "These are products!";
application.yml:
demo:
api:
credentials:
username: $demo_api_username:john
password: $demo_api_password:test
微服务 2 - REST 消费者
@Configuration
@Getter
public class DemoApiConfiguration
@Value("$demo.api.credentials.username")
private String username;
@Value("$demo.api.credentials.password")
private String password;
@Value("$demo.api.credentials.basePath")
private String basePath;
WebConfigurer 类:
@Configuration
@RequiredArgsConstructor
public class WebConfigurer
private final DemoApiConfiguration apiConfig;
@Bean
public ApiClient restTemplate()
RestTemplate restTemplate = new RestTemplate();
ApiClient apiClient = new ApiClient(restTemplate);
apiClient.setBasePath(apiConfig.getBasePath());
return apiClient;
public String getAuthorization()
return (!StringUtils.isEmpty(apiConfig.getUsername()) &&
!StringUtils.isEmpty(apiConfig.getPassword())) ?
"Basic " + Base64Utils.encodeToString((
apiConfig.getUsername() + ":" + apiConfig.getPassword())
.getBytes()) :
null;
ApiClient 类:
@Getter
@RequiredArgsConstructor
@Slf4j
public class ApiClient
private static final String AUTHORIZATION_HEADER = "Authorization";
private final RestTemplate restTemplate;
private String basePath;
public ApiClient setBasePath(String basePath)
this.basePath = basePath;
return this;
public String invokeApi(String path, String credentials)
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(basePath).path(path);
RequestEntity.BodyBuilder requestBuilder =
RequestEntity.method(HttpMethod.GET, builder.build().toUri());
requestBuilder.contentType(MediaType.APPLICATION_JSON);
requestBuilder.header(AUTHORIZATION_HEADER, credentials);
RequestEntity<Object> requestEntity = requestBuilder.body(null);
return restTemplate
.exchange(requestEntity, String.class).getBody();
ConsumeController 类:
@RestController
@RequiredArgsConstructor
public class ConsumeController
private static final String PATH = "/rest/api/v1/products";
private final WebConfigurer webConfigurer;
private final ApiClient apiClient;
@GetMapping(value = "/products-client")
public String getProductList()
return apiClient.invokeApi(PATH, webConfigurer.getAuthorization());
application.yml:
server:
port: 8090
demo:
api:
credentials:
username: $demo_api_username:john
password: $demo_api_password:test
basePath: $demo_api_path:http://localhost:8080
所以第一个微服务是一个 REST API,第二个微服务是一个 REST 消费者,并且使用基本身份验证来保护通信。
现在我想使用 OAuth2 实现,我想问您如何使用 OAuth2 保护通信?所以我想添加另一个端点,比如“/access-token”,客户端首先会在这个端点上使用用户名和密码进行请求,并获得一个 jwt 令牌。之后,将使用此 jwt 令牌请求带有 Authorization 标头的“/products”端点。你能帮我做这种实施吗?谢谢!
【问题讨论】:
能否请您查看现有答案并提供反馈?谢谢! 嗨,svr,我刚刚查看了答案,一切都很好,而且您的答案也很棒,因为它是关于 OAuth 实施的。我没有时间实现它,因为我工作很忙,我想在周末实现它。谢谢! 【参考方案1】:微服务架构
理想的方式或通常首选的方式是微服务的 API 网关模式,但它可能会根据项目和要求而改变。让我们考虑以下组件
配置服务器: 负责管理微服务的配置,我们可以使用带有 Kafka 或 RabbitMQ 的公共总线接口的 Spring Cloud 功能动态更改配置
API 网关: 这将是管理其他服务的 REST 请求的通用入口点。我们可以在这里使用负载均衡器来管理请求。此外,我们可以从 API 网关提供 UI。
身份验证服务 (UAA):
这应该负责管理用户管理和相关活动。您将在此处添加@EnableAuthorizationServer
并扩展AuthorizationServerConfigurerAdapter
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception
int accessTokenValidity = uaaProperties.getWebClientConfiguration().getAccessTokenValidityInSeconds();
accessTokenValidity = Math.max(accessTokenValidity, MIN_ACCESS_TOKEN_VALIDITY_SECS);
int refreshTokenValidity = uaaProperties.getWebClientConfiguration().getRefreshTokenValidityInSecondsForRememberMe();
refreshTokenValidity = Math.max(refreshTokenValidity, accessTokenValidity);
/*
For a better client design, this should be done by a ClientDetailsService (similar to UserDetailsService).
*/
clients.inMemory()
.withClient(uaaProperties.getWebClientConfiguration().getClientId())
.secret(passwordEncoder.encode(uaaProperties.getWebClientConfiguration().getSecret()))
.scopes("openid")
.autoApprove(true)
.authorizedGrantTypes("implicit","refresh_token", "password", "authorization_code")
.accessTokenValiditySeconds(accessTokenValidity)
.refreshTokenValiditySeconds(refreshTokenValidity)
.and()
.withClient(applicationProperties.getSecurity().getClientAuthorization().getClientId())
.secret(passwordEncoder.encode(applicationProperties.getSecurity().getClientAuthorization().getClientSecret()))
.scopes("web-app")
.authorities("ROLE_GA")
.autoApprove(true)
.authorizedGrantTypes("client_credentials")
.accessTokenValiditySeconds((int) jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSeconds())
.refreshTokenValiditySeconds((int) jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSecondsForRememberMe());
服务 1、服务 2...
这将是管理业务逻辑和需求的微服务,通常称为 Resource Server,可以使用 ResourceServerConfigurerAdapter
Diagram
管理访问和刷新令牌
如前所述,API 网关是请求的通用入口点。我们可以在 API Gateway 中管理登录/注销 API。当用户执行登录时,我们可以使用身份验证服务和OAuth2TokenEndpointClient
使用org.springframework.security.oauth2.common.OAuth2AccessToken
和OAuth2AccessToken sendPasswordGrant(String username, String password);
和 OAuth2AccessToken sendRefreshGrant(String refreshTokenValue);
方法管理授权授予类型。
身份验证服务将根据配置和登录用户提供OAuth2AccessToken
。在OAuth2AccessToken
中,您将获得 access_token、refresh_token、OAuth2、expires_in、scope 。
在身份验证时,将创建两个 JWT - 访问令牌和刷新令牌。刷新令牌将具有更长的有效期。这两个令牌都将写入 cookies 中,以便在每个后续请求中发送它们。
每次调用 REST API 时,都会从 HTTP 标头中检索令牌。如果访问令牌未过期,请检查用户的权限并相应地允许访问。如果访问令牌过期但刷新令牌有效,则重新创建新使用新的到期日期访问令牌和刷新令牌并通过Cookies
发回/**
* Authenticate the user by username and password.
*
* @param request the request coming from the client.
* @param response the response going back to the server.
* @param loginVM the params holding the username, password and rememberMe.
* @return the @link OAuth2AccessToken as a @link ResponseEntity. Will return @code OK (200), if successful.
* If the UAA cannot authenticate the user, the status code returned by UAA will be returned.
*/
public ResponseEntity<OAuth2AccessToken> authenticate(HttpServletRequest request, HttpServletResponse response,
LoginVM loginVM)
try
String username = loginVM.getUsername();
String password = loginVM.getPassword();
boolean rememberMe = loginVM.isRememberMe();
OAuth2AccessToken accessToken = authorizationClient.sendPasswordGrant(username, password);
OAuth2Cookies cookies = new OAuth2Cookies();
cookieHelper.createCookies(request, accessToken, rememberMe, cookies);
cookies.addCookiesTo(response);
if (log.isDebugEnabled())
log.debug("successfully authenticated user ", username);
return ResponseEntity.ok(accessToken);
catch (HttpStatusCodeException in4xx)
throw new UAAException(ErrorConstants.BAD_CREDENTIALS);
catch (ResourceAccessException in5xx)
throw new UAAException(ErrorConstants.UAA_APPLICATION_IS_NOT_RESPONDING);
/**
* Try to refresh the access token using the refresh token provided as cookie.
* Note that browsers typically send multiple requests in parallel which means the access token
* will be expired on multiple threads. We don't want to send multiple requests to UAA though,
* so we need to cache results for a certain duration and synchronize threads to avoid sending
* multiple requests in parallel.
*
* @param request the request potentially holding the refresh token.
* @param response the response setting the new cookies (if refresh was successful).
* @param refreshCookie the refresh token cookie. Must not be null.
* @return the new servlet request containing the updated cookies for relaying downstream.
*/
public HttpServletRequest refreshToken(HttpServletRequest request, HttpServletResponse response, Cookie
refreshCookie)
//check if non-remember-me session has expired
if (cookieHelper.isSessionExpired(refreshCookie))
log.info("session has expired due to inactivity");
logout(request, response); //logout to clear cookies in browser
return stripTokens(request); //don't include cookies downstream
OAuth2Cookies cookies = getCachedCookies(refreshCookie.getValue());
synchronized (cookies)
//check if we have a result from another thread already
if (cookies.getAccessTokenCookie() == null) //no, we are first!
//send a refresh_token grant to UAA, getting new tokens
String refreshCookieValue = OAuth2CookieHelper.getRefreshTokenValue(refreshCookie);
OAuth2AccessToken accessToken = authorizationClient.sendRefreshGrant(refreshCookieValue);
boolean rememberMe = OAuth2CookieHelper.isRememberMe(refreshCookie);
cookieHelper.createCookies(request, accessToken, rememberMe, cookies);
//add cookies to response to update browser
cookies.addCookiesTo(response);
else
log.debug("reusing cached refresh_token grant");
//replace cookies in original request with new ones
CookieCollection requestCookies = new CookieCollection(request.getCookies());
requestCookies.add(cookies.getAccessTokenCookie());
requestCookies.add(cookies.getRefreshTokenCookie());
return new CookiesHttpServletRequestWrapper(request, requestCookies.toArray());
微服务之间的安全通信
我们可以使用FeignClient 在服务之间进行通信,并且可以通过自定义配置来保护通信。见Class<?>[] configuration() default OAuth2UserClientFeignConfiguration.class;
这里我们增强了默认@FeignClient
和AuthorizedUserFeignClient
接口,其中包含自定义配置OAuth2UserClientFeignConfiguration
,其中包含@Bean
和UserFeignClientInterceptor
,使用标头管理身份验证
AuthorizedUserFeignClient.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@FeignClient
public @interface AuthorizedUserFeignClient
@AliasFor(annotation = FeignClient.class, attribute = "name")
String name() default "";
/**
* A custom @code @Configuration for the feign client.
*
* Can contain override @code @Bean definition for the pieces that make up the client, for instance @link
* feign.codec.Decoder, @link feign.codec.Encoder, @link feign.Contract.
*
* @see FeignClientsConfiguration for the defaults.
*/
@AliasFor(annotation = FeignClient.class, attribute = "configuration")
Class<?>[] configuration() default OAuth2UserClientFeignConfiguration.class;
/**
* An absolute URL or resolvable hostname (the protocol is optional).
*/
String url() default "";
/**
* Whether 404s should be decoded instead of throwing FeignExceptions.
*/
boolean decode404() default false;
/**
* Fallback class for the specified Feign client interface. The fallback class must implement the interface
* annotated by this annotation and be a valid Spring bean.
*/
Class<?> fallback() default void.class;
/**
* Path prefix to be used by all method-level mappings. Can be used with or without @code @RibbonClient.
*/
String path() default "";
UserFeignClientInterceptor.java
public class UserFeignClientInterceptor implements RequestInterceptor
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_TOKEN_TYPE = "Bearer";
@Override
public void apply(RequestTemplate template)
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails)
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
template.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER_TOKEN_TYPE, details.getTokenValue()));
可能会有帮助
Architecture Overview
Managing the authentication service
【讨论】:
【参考方案2】:有必要区分基于 JWT 令牌的身份验证,这似乎是您想要实现的目标,以及 OAuth2 身份验证,一个更复杂的主题。
对于 OAuth2 身份验证,Spring 框架通过 Spring Security OAuth project 提供支持,但我最好的建议是,如果您的项目中确实需要 OAuth2,最好使用第三方 OAuth2 提供程序,例如 Okta 或 @987654323 @,或云中提供的提供商之一 - 例如,GCP OAuth 客户端、AWS Cognito、Azure AD 应用程序等,或类似 Keycloak 的产品。所有这些产品都将为您提供强大的 OAuth2 实现以及帮助您与它们集成的库和机制。
但是对于您问题的最后几段,您实际需要的是使用 JWT 令牌对您的微服务进行身份验证。
我们先说一下服务器端的要求。
要完成此任务,您首先需要一个生成和验证 JWT 令牌的服务。可能是这样的:
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
// ...
@Component
public class JWTService
// Get itfrom a configuration property, for instance
@Value("$secretKey")
private String secretKey;
@Value("$tokenValidityInMillis")
private Long tokenValidityInMillis;
public String createToken(Authentication authentication)
long now = (new Date()).getTime();
Date validity = new Date(now + this.tokenValidityInMillis);
// Modify it as per your needs, defining claims, etcetera. For instance
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
return Jwts.builder()
.setSubject(authentication.getName())
.claim("authorities", authorities)
// The signature algorithm you consider appropriate
.signWith(SignatureAlgorithm.HS256, secretKey)
.setExpiration(validity)
.compact();
public Authentication getAuthentication(String token)
try
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
// Get the authorities back
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get("authorities").toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new PreAuthenticatedAuthenticationToken(principal, token, authorities);
catch (Exception e)
// Handle exceptions (expiration, invalid signature, etcetera) as you wish
return null;
您有多个库来处理实际的 JWT 令牌内容。该示例使用jjwt。
然后,定义一个Controller
,将提供的凭据交换为访问令牌:
import org.springframework.security.authentication.AuthenticationManager;
//...
@RestController
public class AuthController
private final JWTService jwtService;
private final AuthenticationManager authenticationManager;
public AuthRestController(final JWTService jwtService, final AuthenticationManager authenticationManager)
this.jwtService = jwtService;
this.authenticationManager = authenticationManager;
@PostMapping("/access-token")
public ResponseEntity<JWTToken> swapAccessToken(@RequestBody LoginDTO loginDTO)
// Note we are passing a JSON object with two fields, username and password,
// not actual HTTP parameters. Modify it according to your needs
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginDTO.getUsername(), loginDTO.getPassword());
Authentication authentication = authenticationManager.authenticate(authenticationToken);
String jwt = jwtService.createToken(authentication);
return new ResponseEntity.ok(new JWTToken(jwt));
LoginDTO
是一个简单的 POJO,用于存储 username
和 password
:
public class LoginDTO
private String username;
private String password;
// Getters and setters omitted for brevity
而JWTToken
只是将生成的令牌返回为 JSON 而不是纯文本的便捷方式:
public class JWTToken
private String idToken;
JWTToken(String idToken)
this.idToken = idToken;
@JsonProperty("id_token")
String getIdToken()
return idToken;
接下来你需要的是一些在必要时验证令牌的机制。我认为实现这一点的最佳方法是实现一个自定义过滤器,通过检查 JWT 令牌来执行用户身份验证。例如:
public class JWTFilter extends GenericFilterBean
private final JWTService jwtService;
public JWTFilter(final JWTService jwtService)
this.jwtService = jwtService;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwt = getTokenFromHttpRequest(httpServletRequest);
if (jwt != null)
// We have a token, perform actual authentication
Authentication authentication = this.jwtService.getAuthentication(jwt);
// If success
if (authentication != null)
SecurityContextHolder.getContext().setAuthentication(authentication);
// Unsuccesful authentication, let the spring security chain continue and fail if necessary
filterChain.doFilter(servletRequest, servletResponse);
// Look for token in an Authorization Bearer header
private String getTokenFromHttpRequest(HttpServletRequest request)
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer"))
return bearerToken.substring(7, bearerToken.length());
return null;
所有这些组件都必须为 Spring Security 配置。它可能需要进一步调整,但请理解:
@Configuration
@RequiredArgsConstructor
public class SecurityConfigurer extends WebSecurityConfigurerAdapter
private final DemoApiConfiguration apiConfig;
private final JWTService jwtService;
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception
// Probably you need to handle more stuff like configuring exception
// handling endpoints for access denied, stateless sessions, CORS, think about it...
http
.csrf().disable()
.authorizeRequests()
// Allow to swap the credentials for access token
.antMatchers("/access-token").permitAll()
// Require authentication for the rest of your API
.anyRequest().authenticated();
// Include your filter somewhere the Spring Security filter chain
final JWTFilter jwtFilter = new JWTFilter(jwtService);
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
// This is an important step: as we are providing both username an
// password and preauthenticated credentials, so we need to configure
// AuthenticationManager that actually supports both authentication types
// It will use your userDetailsService for validating
// the original provided credentials
@Bean
@Override
public AuthenticationManager authenticationManager()
// Username and password validation
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
PreAuthenticatedAuthenticationProvider preAuthProvider = new PreAuthenticatedAuthenticationProvider();
preAuthProvider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(userDetailsService()));
return new ProviderManager(Arrays.<AuthenticationProvider> asList(daoAuthenticationProvider, preAuthProvider));
@Bean
public UserDetailsService userDetailsService()
if (userDetailsService == null)
userDetailsService = this.initUserDetailsService(passwordEncoder());
return userDetailsService;
@Bean
public PasswordEncoder passwordEncoder()
return new BCryptPasswordEncoder();
private UserDetailsService initUserDetailsService(PasswordEncoder passwordEncoder)
UserDetails theUser = User.withUsername(apiConfig.getUsername())
.password(passwordEncoder.encode(apiConfig.getPassword())).roles("USER").build();
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(theUser);
return userDetailsManager;
您的客户端微服务只需将配置的凭据交换为访问令牌,并在调用受保护的端点时使用返回的 JWT 作为 Bearer
HTTP Authorization
标头的值。它应该很简单,但如果您需要进一步的帮助,请告诉我。
【讨论】:
【参考方案3】:概述
您将需要客户端凭据授予类型的流来在应用之间进行通信。 Spring 内置了对 facebook、google 等知名提供商的支持。在我们的例子中,我们提供了自己的授权服务器。
注意 - 客户端凭据不会按照规范返回刷新令牌 - 因此请确保在当前访问令牌过期时请求新的访问令牌。
客户
应用程序属性
security.basic.enabled=false
server.port=8082
spring.security.oauth2.client.registration.server.client-id=first-client
spring.security.oauth2.client.registration.server.client-secret=noonewilleverguess
spring.security.oauth2.client.registration.server.client-authentication-method=basic
spring.security.oauth2.client.registration.server.authorization-grant-type=client_credentials
spring.security.oauth2.client.registration.server.scope=read
spring.security.oauth2.client.provider.server.token-uri=http://server:8080/oauth/token
主类
@SpringBootApplication
public class App
public static void main(String[] args)
SpringApplication.run(App.class, args);
@Bean
RestTemplate restTemplate(RestTemplateBuilder builder)
return builder.build();
凭据客户端授予流程配置
@Configuration
public class OauthClientCredentialConfig
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository)
OAuth2AuthorizedClientService service =
new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, service);
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
pom 依赖项
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
休息客户端
@Getter
@RequiredArgsConstructor
@Slf4j
@Component
public class ApiClient
private static final String AUTHORIZATION_HEADER = "Authorization";
private final RestTemplate restTemplate;
private final OAuth2AuthorizedClientManager authorizedClientManager;
public String invokeApi(String path)
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://server:8080").path(path);
RequestEntity.BodyBuilder requestBuilder =
RequestEntity.method(HttpMethod.GET, builder.build().toUri());
requestBuilder.contentType(MediaType.APPLICATION_JSON);
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
OAuth2AuthorizeRequest oAuth2AuthorizeRequest =
OAuth2AuthorizeRequest.withClientRegistrationId("server")
.principal(principal.getName())
.build();
requestBuilder.header(AUTHORIZATION_HEADER, "Bearer " + authorizedClientManager.authorize(oAuth2AuthorizeRequest).getAccessToken().getTokenValue());
RequestEntity<Object> requestEntity = requestBuilder.body(null);
return restTemplate.exchange(requestEntity, String.class).getBody();
授权和资源服务器
注意授权和资源服务器我们使用的是旧版本,因为不支持在新的 spring security oauth2 模块中创建授权服务器。
配置
@EnableWebSecurity
public class Security extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception
http.requestMatchers()
.antMatchers("/oauth/token")
.and()
.authorizeRequests()
.anyRequest().authenticated();
@EnableAuthorizationServer
@EnableResourceServer
@SpringBootApplication
public class App
public static void main(String[] args)
SpringApplication.run(App.class, args);
验证服务器配置
@Import(AuthorizationServerEndpointsConfiguration.class)
@Configuration
@Order(2)
@RequiredArgsConstructor
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter
private final TokenStore tokenStore;
private final AccessTokenConverter accessTokenConverter;
@Bean
public PasswordEncoder passwordEncoder()
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception
clients
.inMemory()
.withClient("first-client")
.secret(passwordEncoder().encode("noonewilleverguess"))
.scopes("read")
.authorizedGrantTypes("client_credentials")
.scopes("resource-server-read", "resource-server-write");
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
endpoints
.accessTokenConverter(accessTokenConverter)
.tokenStore(tokenStore);
Jwt 配置
@Configuration
public class JwtTokenConfig
@Bean
public KeyPair keyPair() throws NoSuchAlgorithmException
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
gen.initialize(2048);
KeyPair keyPair = gen.generateKeyPair();
return keyPair;
@Bean
public TokenStore tokenStore() throws NoSuchAlgorithmException
return new JwtTokenStore(accessTokenConverter());
@Bean
public JwtAccessTokenConverter accessTokenConverter() throws NoSuchAlgorithmException
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(keyPair());
return converter;
pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.4.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>8.6</version>
</dependency>
我在
添加了一个工作示例https://github.com/saagar2000/oauth2_server
https://github.com/saagar2000/oauth2_client
使用有效的访问令牌响应
更多解释可见here
【讨论】:
以上是关于如何使用 OAuth2 保护 2 个 Spring Boot 微服务之间的通信?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 OAuth2 和社交登录保护 Spring Boot RESTful 服务
如何使用 Spring Security OAuth2 提供程序配置指定要保护的资源
如何使用 Spring Security OAuth2 和 MITREID Connect Introspect 保护资源?
如何在使用 OAuth2 社交登录保护 REST API 的同时配置 Spring Boot 登录页面