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

    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">
    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.18.RELEASE
         
    

    org.arun.springoauth
    spring-oauth2-employee-service
    1.0.0
    spring-oauth2-employee-service
    Employee Service

    
        1.8
        2.1.18.RELEASE
    


    
        
            org.springframework.boot
            spring-boot-starter-security
        


        
            org.springframework.security.oauth.boot
            spring-security-oauth2-autoconfigure
            
            ${spring-boot.version}
        


        
            org.springframework.boot
            spring-boot-starter-web
        


        
            org.springframework.boot
            spring-boot-devtools
            runtime
        


        
            org.springframework.boot
            spring-boot-configuration-processor
            true
        


        
            org.projectlombok
            lombok
            true
        


        
            org.springframework.boot
            spring-boot-starter-test
            test
        


        
            org.springframework.security
            spring-security-test
            test
        

    


    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    ZIP
                    
                        
                            *
                            *
                        

                    

                    
                        
                            com.paul
                        

                    

                

            

        

    



将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 tokenMap) {
        LOG.debug("Begin extractAuthentication: tokenMap = {}", tokenMap);
        JsonNode token = mapper.convertValue(tokenMap, JsonNode.class);
        Set audienceList = extractClients(token); // extracting client names
        List 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, null, null, null);

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

    private List extractRoles(JsonNode jwt) {
        LOG.debug("Begin extractRoles: jwt = {}", jwt);
        Set 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 authorityList = AuthorityUtils
                .createAuthorityList(rolesWithPrefix.toArray(new String[0]));
        LOG.debug("End extractRoles: roles = {}", authorityList);
        return authorityList;
    }

    private Set 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 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





paulwong 2021-10-26 17:06 发表评论

你可能感兴趣的:(spring,boot,oauth2)