簡體   English   中英

如何使用 OAuth2 保護 2 個 Spring Boot 微服務之間的通信?

[英]How to secure communication between 2 Spring Boot microservices using OAuth2?

我正在學習使用基本身份驗證和 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;
}

安全配置器類:

@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!";
    }
}

應用程序.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();
    }
}

消費控制器類:

@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());
    }
}

應用程序.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”端點進行請求。 你能幫我做這種實現嗎? 謝謝!

微服務架構

理想的方式或通常首選的方式是微服務的 API 網關模式,但它可能會根據項目和需求而變化。 讓我們考慮以下組件

配置服務器:負責管理微服務的配置,我們可以使用帶有 Kafka 或 RabbitMQ 公共總線接口的 Spring Cloud 特性動態更改配置

API 網關:這將是管理其他服務的 REST 請求的通用入口點。 我們可以在這里使用負載均衡器管理請求。 此外,我們可以從 API 網關提供 UI。

認證服務(UAA):這應該負責管理用戶管理和相關活動。 您將在@EnableAuthorizationServer添加@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());
    }

Service 1, Service 2...這將是管理業務邏輯和需求的微服務,通常稱為資源服務器,可以使用ResourceServerConfigurerAdapter進行配置

圖表

在此處輸入圖片說明


管理訪問和刷新令牌

如前所述,API 網關是請求的公共入口點。 我們可以在 API Gateway 中管理登錄/注銷 API。 當用戶執行登錄時,我們可以使用身份驗證服務和OAuth2TokenEndpointClientorg.springframework.security.oauth2.common.OAuth2AccessToken使用OAuth2AccessToken sendPasswordGrant(String username, String password);管理授權授予類型OAuth2AccessToken sendPasswordGrant(String username, String password); OAuth2AccessToken sendRefreshGrant(String refreshTokenValue); 方法。

身份驗證服務將根據配置和登錄用戶提供OAuth2AccessToken OAuth2AccessToken您將獲得access_tokenrefresh_tokenOAuth2expires_inscope

在身份驗證時,將創建兩個 JWT - access tokenrefresh token 刷新令牌將具有更長的有效期 這兩個令牌都將寫入cookie,以便在每個后續請求中發送它們。

在每個 REST API 調用中,將從 HTTP 標頭中檢索令牌。 如果訪問令牌未過期,請檢查用戶的權限並相應地允許訪問。 如果訪問令牌已過期刷新令牌有效,則重新創建新的訪問令牌和具有新到期日期的刷新令牌,並通過Cookie發回

/**
     * 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;

在這里,我們增強的默認@FeignClientAuthorizedUserFeignClient接口,包括自定義配置作為OAuth2UserClientFeignConfiguration它由@BeanUserFeignClientInterceptor其管理使用標頭中的autehication

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()));
        }
    }
}

可能會有所幫助

架構概覽

管理認證服務

有必要區分基於 JWT 令牌的身份驗證,這似乎是您要實現的目標,而 OAuth2 身份驗證是一個更復雜的主題。

對於 OAuth2 身份驗證,Spring 框架提供了Spring Security OAuth 項目的支持,但我最好的建議是,如果您的項目中確實需要 OAuth2,最好使用第三方 OAuth2 提供程序,例如OktaAuth0 ,或其中之一雲中提供的提供商 - 例如,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是一個用於存儲usernamepassword的簡單 POJO:

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標頭的值。 這應該很簡單,但如果您需要進一步的幫助,請告訴我。

概述

您將需要客戶端憑據授予類型流來在應用程序之間進行通信。 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 安全 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;
    }
}

絨球

<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

在此處輸入圖片說明

具有有效訪問令牌的響應

在此處輸入圖片說明

更多解釋可以在這里找到

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM