paulwong

          My Links

          Blog Stats

          常用鏈接

          留言簿(67)

          隨筆分類(1393)

          隨筆檔案(1151)

          文章分類(7)

          文章檔案(10)

          相冊

          收藏夾(2)

          AI

          Develop

          E-BOOK

          Other

          養生

          微服務

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          60天內閱讀排行

          SPRING BOOT OAUTH2 + KEYCLOAK - service to service call

          employee-service調用department-service,如果要按OAUTH2.0流程,只需要提供client-id和client-secrect即可。在KEYCLOAK中引入service-account,即配置該employee-service時,取消standard-flow,同時激活service-account。
          employee-service的application.yaml文件,其中的public-key要從KEYCLOAK中取
          server:
             port: 8090
          # Can be set to false to disable security during local development
          rest:
             security:
                enabled: true
                #issuer-uri: http://localhost:8080/auth/realms/dev
                api-matcher: /api/**
                cors:
                   allowed-origins: '*'
                   allowed-headers: '*'
                   allowed-methods: GET,POST,PUT,PATCH,DELETE,OPTIONS
                   max-age: 3600

          security:
             oauth2:
                resource:
                   filter-order: 3
                   id: test-employee-service
                   token-info-uri: ${rest.security.issuer-uri}/protocol/openid-connect/token/introspect
                   user-info-uri: ${rest.security.issuer-uri}/protocol/openid-connect/userinfo
                   jwt:
                      key-value: | 
                         -----BEGIN PUBLIC KEY-----
                         MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB
                         -----END PUBLIC KEY-----

          # To access another secured micro-service
                client:
                   client-id: test-employee-service
                   #client-secret: 25c33006-e1b9-4fc2-a6b9-c43dbc41ecd0
                   user-authorization-uri: ${rest.security.issuer-uri}/protocol/openid-connect/auth
                   access-token-uri: ${rest.security.issuer-uri}/protocol/openid-connect/token
                   scope: openid
                   grant-type: client_credentials
                   is-client-only: true

          #Logging Configuration
          logging:
             level:
                org.springframework.boot.autoconfigure.logging: INFO
                org.springframework.security: DEBUG
                org.arun: DEBUG
                root: INFO

          application-dev.yaml
          rest:
             security:
                issuer-uri: http://10.80.27.69:8180/auth/realms/quickstart

          department-service:
             url: http://10.80.27.69:8095/api/departments/1

          security:
             oauth2:
                client:
                   client-secret: db25cdbd-605b-429d-bd92-96705bdf1474

          department-service的application.yaml
          server:
             port: 8095
          # Can be set to false to disable security during local development
          rest:
             security:
                enabled: true
                #issuer-uri: http://localhost:8080/auth/realms/dev
                api-matcher: /api/**
                cors:
                   allowed-origins: '*'
                   allowed-headers: '*'
                   allowed-methods: GET,POST,PUT,PATCH,DELETE,OPTIONS
                   max-age: 3600

          security:
             oauth2:
                resource:
                   filter-order: 3
                   id: test-department-service
                   token-info-uri: ${rest.security.issuer-uri}/protocol/openid-connect/token/introspect
                   user-info-uri: ${rest.security.issuer-uri}/protocol/openid-connect/userinfo
                   jwt:
                      key-value: | 
                         -----BEGIN PUBLIC KEY-----
                         MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB
                         -----END PUBLIC KEY-----

          #Logging Configuration
          logging:
             level:
                org.springframework.boot.autoconfigure.logging: INFO
                org.springframework.security: DEBUG
                org.arun: DEBUG
                root: INFO

          application-dev.yaml
          rest:
             security:
                issuer-uri: http://10.80.27.69:8180/auth/realms/quickstart

          employee-service的pom.xml
          <?xml version="1.0" encoding="UTF-8"?>
          <project xmlns="http://maven.apache.org/POM/4.0.0"
              xmlns:xsi
          ="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation
          ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
              <modelVersion>4.0.0</modelVersion>
              <parent>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-parent</artifactId>
                  <version>2.1.18.RELEASE</version>
                  <relativePath /> <!-- lookup parent from repository -->
              </parent>
              <groupId>org.arun.springoauth</groupId>
              <artifactId>spring-oauth2-employee-service</artifactId>
              <version>1.0.0</version>
              <name>spring-oauth2-employee-service</name>
              <description>Employee Service</description>

              <properties>
                  <java.version>1.8</java.version>
                  <spring-boot.version>2.1.18.RELEASE</spring-boot.version>
              </properties>

              <dependencies>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-security</artifactId>
                  </dependency>

                  <dependency>
                      <groupId>org.springframework.security.oauth.boot</groupId>
                      <artifactId>spring-security-oauth2-autoconfigure</artifactId>
                      <!-- <version>2.1.18.RELEASE</version> -->
                      <version>${spring-boot.version}</version>
                  </dependency>

                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-web</artifactId>
                  </dependency>

                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-devtools</artifactId>
                      <scope>runtime</scope>
                  </dependency>

                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-configuration-processor</artifactId>
                      <optional>true</optional>
                  </dependency>

                  <dependency>
                      <groupId>org.projectlombok</groupId>
                      <artifactId>lombok</artifactId>
                      <optional>true</optional>
                  </dependency>

                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-test</artifactId>
                      <scope>test</scope>
                  </dependency>

                  <dependency>
                      <groupId>org.springframework.security</groupId>
                      <artifactId>spring-security-test</artifactId>
                      <scope>test</scope>
                  </dependency>
              </dependencies>

              <build>
                  <plugins>
                      <plugin>
                          <groupId>org.springframework.boot</groupId>
                          <artifactId>spring-boot-maven-plugin</artifactId>
                          <configuration>
                              <layout>ZIP</layout>
                              <excludes>
                                  <exclude>
                                      <groupId>*</groupId>
                                      <artifactId>*</artifactId>
                                  </exclude>
                              </excludes>
                              <includes>
                                  <include>
                                      <groupId>com.paul</groupId>
                                  </include>
                              </includes>
                          </configuration>
                      </plugin>
                  </plugins>
              </build>

          </project>

          將jwt格式的access_token轉成Authentication的類JwtAccessTokenCustomizer
          package org.arun.springoauth.employee.config;

          import com.fasterxml.jackson.databind.JsonNode;
          import com.fasterxml.jackson.databind.ObjectMapper;
          import java.util.HashSet;
          import java.util.List;
          import java.util.Map;
          import java.util.Set;
          import org.slf4j.Logger;
          import org.slf4j.LoggerFactory;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.boot.autoconfigure.security.oauth2.resource.JwtAccessTokenConverterConfigurer;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
          import org.springframework.security.core.Authentication;
          import org.springframework.security.core.GrantedAuthority;
          import org.springframework.security.core.authority.AuthorityUtils;
          import org.springframework.security.oauth2.provider.OAuth2Authentication;
          import org.springframework.security.oauth2.provider.OAuth2Request;
          import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
          import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

          @Configuration
          public class JwtAccessTokenCustomizer extends DefaultAccessTokenConverter implements JwtAccessTokenConverterConfigurer {

              private static final Logger LOG = LoggerFactory.getLogger(JwtAccessTokenCustomizer.class);

              private static final String CLIENT_NAME_ELEMENT_IN_JWT = "resource_access";

              private static final String ROLE_ELEMENT_IN_JWT = "roles";

              private ObjectMapper mapper;

              @Autowired
              public JwtAccessTokenCustomizer(ObjectMapper mapper) {
                  this.mapper = mapper;
                  LOG.info("Initialized {}", JwtAccessTokenCustomizer.class.getSimpleName());
              }

              @Override
              public void configure(JwtAccessTokenConverter converter) {
                  converter.setAccessTokenConverter(this);
                  LOG.info("Configured {}", JwtAccessTokenConverter.class.getSimpleName());
              }

              /**
               * Spring oauth2 expects roles under authorities element in tokenMap, but
               * keycloak provides it under resource_access. Hence extractAuthentication
               * method is overriden to extract roles from resource_access.
               *
               * 
          @return OAuth2Authentication with authorities for given application
               
          */
              @Override
              public OAuth2Authentication extractAuthentication(Map<String, ?> tokenMap) {
                  LOG.debug("Begin extractAuthentication: tokenMap = {}", tokenMap);
                  JsonNode token = mapper.convertValue(tokenMap, JsonNode.class);
                  Set<String> audienceList = extractClients(token); // extracting client names
                  List<GrantedAuthority> authorities = extractRoles(token); // extracting client roles

                  OAuth2Authentication authentication = super.extractAuthentication(tokenMap);
                  OAuth2Request oAuth2Request = authentication.getOAuth2Request();

                  OAuth2Request request = new OAuth2Request(oAuth2Request.getRequestParameters(), oAuth2Request.getClientId(),
                          authorities, true, oAuth2Request.getScope(), audienceList, nullnullnull);

                  Authentication usernamePasswordAuthentication = new UsernamePasswordAuthenticationToken(
                          authentication.getPrincipal(), "N/A", authorities);
                  LOG.debug("End extractAuthentication");
                  return new OAuth2Authentication(request, usernamePasswordAuthentication);
              }

              private List<GrantedAuthority> extractRoles(JsonNode jwt) {
                  LOG.debug("Begin extractRoles: jwt = {}", jwt);
                  Set<String> rolesWithPrefix = new HashSet<>();

                  jwt.path(CLIENT_NAME_ELEMENT_IN_JWT).elements().forEachRemaining(e -> e.path(ROLE_ELEMENT_IN_JWT).elements()
                          .forEachRemaining(r -> rolesWithPrefix.add("ROLE_" + r.asText())));

                  final List<GrantedAuthority> authorityList = AuthorityUtils
                          .createAuthorityList(rolesWithPrefix.toArray(new String[0]));
                  LOG.debug("End extractRoles: roles = {}", authorityList);
                  return authorityList;
              }

              private Set<String> extractClients(JsonNode jwt) {
                  LOG.debug("Begin extractClients: jwt = {}", jwt);
                  if (jwt.has(CLIENT_NAME_ELEMENT_IN_JWT)) {
                      JsonNode resourceAccessJsonNode = jwt.path(CLIENT_NAME_ELEMENT_IN_JWT);
                      final Set<String> clientNames = new HashSet<>();
                      resourceAccessJsonNode.fieldNames().forEachRemaining(clientNames::add);

                      LOG.debug("End extractClients: clients = {}", clientNames);
                      return clientNames;

                  } else {
                      throw new IllegalArgumentException(
                              "Expected element " + CLIENT_NAME_ELEMENT_IN_JWT + " not found in token");
                  }

              }

          }



          Reference
          https://medium.com/@bcarunmail/securing-rest-api-using-keycloak-and-spring-oauth2-6ddf3a1efcc2



          posted on 2021-10-26 17:06 paulwong 閱讀(580) 評論(0)  編輯  收藏 所屬分類: OAUTH2

          主站蜘蛛池模板: 嵊泗县| 三门县| 嘉黎县| 平顺县| 堆龙德庆县| 济宁市| 和林格尔县| 晴隆县| 乌兰浩特市| 留坝县| 萨迦县| 祁连县| 睢宁县| 高雄市| 南投市| 敦煌市| 永胜县| 钟祥市| 晋中市| 西充县| 天台县| 阿克陶县| 大理市| 铜陵市| 罗定市| 泊头市| 桐梓县| 兴和县| 驻马店市| 内丘县| 临朐县| 筠连县| 曲松县| 杂多县| 天镇县| 平山县| 房产| 密山市| 茶陵县| 唐河县| 砀山县|