Code Monkey home page Code Monkey logo

spring-cloud-demo's People

Contributors

davidfantasy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

spring-cloud-demo's Issues

Gateway SSO - "Spring Cloud notes (5) Spring Cloud Gateway and authorization authentication"

How to focus on protecting a Spring Gateway or Spring Rest API (client before gateway) using single sign-on with IBM Security Verify as the identity provider (free trial).

For this example, use a gateway called order. The application allows a user to log in and manage (detail, modify) their order. Since a users order is highly private data, only the authenticated and authorized user should be able to see their order and nobody else’s.

Note, I found it very interesting **shrio-with-jwt-spring-boot-starter** specifying JWT as the format tells IBM Security Verify to return a JWT token when requesting an access token. Otherwise it will return an Opaque token. A JWT token is required in order to protect the resource servers. The resource servers use the JWT token and validate it.

We include the standard spring-boot-starter-web and spring-boot-starter-thymeleaf dependencies for your basic web app to support the UI and REST functionality within the application. Since we are using the new WebClient api (instead of traditional RestTemplate) we included the spring-webflux dependency.
We add the spring-boot-starter-oauth2-client dependency to provide the Spring magic that will integrate with IBM Security Verify (Oauth2 and OIDC Identity provider) in order to perform the authentication and mint the access tokens.
Once you add the spring-boot-starter-oauth2-client dependencies you will need to configure a few things. We create a class called UISecurityConfig and override the configure(HttpSecurity http) method to configure the filter chain. Here we configure the “/” and “/login” endpoints of the web client app to be open and permitted to all users. This is so all users can get to the main page and the login page of the application without authenticating. The oauth2Login() statement specifies that we want to login using an Oauth2 identity provider, in our case IBM Security Verify (ISV)(we will see shortly how we configure that). Note, that for ISV, we had to specify the tokenEndpoint().accessTokenResponseClient(…). If you drill down futher into this code, you will see that we had to create a CustomAccessTokenResponseConverter class to convert the token response from ISV as the Spring Security default implementation did not work with ISV.

@EnableWebSecurity
public class UiSecurityConfig extends WebSecurityConfigurerAdapter {
	private static final Logger LOG = LoggerFactory.getLogger(UiSecurityConfig.class);
	
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	
		http
			.authorizeRequests().antMatchers("/", "/login**").permitAll()
				.anyRequest().authenticated()
				.and()
			.oauth2Login()
				.tokenEndpoint().accessTokenResponseClient(authorizationCodeTokenResponseClient());

	}
    
	private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenResponseClient() {
		
		OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter =
				new OAuth2AccessTokenResponseHttpMessageConverter();
		
		tokenResponseHttpMessageConverter.setTokenResponseConverter(new CustomAccessTokenResponseConverter());

		RestTemplate restTemplate = new RestTemplate(Arrays.asList(
				new FormHttpMessageConverter(), tokenResponseHttpMessageConverter));
		
		restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

		DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
		tokenResponseClient.setRestOperations(restTemplate);

		return tokenResponseClient;
	}
    
    // This method returns filter function which will log request data
    private static ExchangeFilterFunction logRequest() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
            LOG.info("####### GK WebClient REQUEST: ####### {} {}", clientRequest.method(), clientRequest.url());
            clientRequest.headers().forEach((name, values) -> values.forEach(value -> LOG.info("### GK WebClient HEADER ### {}={}", name, value)));
            return Mono.just(clientRequest);
        });
    }
}    

In order to integrate with IBM Security Verify (ISV), we had to specify tokenEndpoint().accessTokenResponseClient(…). If you drill down futher into this code, you will see that we had to create and use a new class CustomAccessTokenResponseConverter to convert the token response as the Spring Security default implementation did not work with ISV. The method authorizationCodeTokenResponseClient on line 17 is responsible for calling the token endpoint on the Authorization Server (IBM Security Verify) and returning the token response which contains the JWT access token among other things.
Here is an example of a token response from IBM Security Verify. The access_token is a JWT token which contains the claims of the logged in user.

{
   "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNlcnZlciJ9.eyJ1bmlxdWVTZWN1cml0eU5hbWUiOiI2NTIwMDE4SFFUIiwiY2xpZW50X2lkIjoiZjNlOTg4OTktNzU4NC00YTM0LWE4NTUtOTEyNjRiOGRlOTdhIiwicmVhbG1OYW1lIjoiY2xvdWRJZGVudGl0eVJlYWxtIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZ2tvdmFuQGhvdG1haWwuY29tIiwiYXVkIjoiZjNlOTg4OTktNzU4NC00YTM0LWE4NTUtOTEyNjRiOGRlOTdhIiwiZ3JhbnRfdHlwZSI6ImF1dGhvcml6YXRpb25fY29kZSIsImFjciI6InVybjppYm06c2VjdXJpdHk6cG9saWN5OmlkOjEiLCJncmFudF9pZCI6ImVmMjFjNThjLWRiYzktNDgwOC1hODI0LTk2N2RkZjM1YTdhNyIsInVzZXJUeXBlIjoicmVndWxhciIsImF1dGhfdGltZSI6MTYxNjE4NjI3OCwic2NvcGUiOiJyZWFkIGdrIG9wZW5pZCB3cml0ZSIsImp0aSI6InNDQmQ2TDFZaUNlbkJodjJzOHd6NUt4N2lPWEQzVSIsImlzcyI6Imh0dHBzOi8vZ2tvdmFuLnZlcmlmeS5pYm0uY29tL29pZGMvZW5kcG9pbnQvZGVmYXVsdCIsInN1YiI6IjY1MjAwMThIUVQiLCJpYXQiOjE2MTYxODY1NTAsImV4cCI6MTYxNjE5Mzc1MH0.PWJDgTTff5lBrpt7ExOhs5emF8pG1t_3DGihG7k54D7AsxU7otv3QCHm3JlyQ8i9y81LhyurQoKkgAioYr8jK0ET8Wc8mr6k3Dhi502PVAhtLPhIGyy2yI0e_O_HzeWRaWGrdw9gAXx-Al1YX5I6cWVshgfZUCuxOP15XSdXvl28vEP8tHDps7ET4UjVAz_IrM4H6sLIudhIn0JEd3psHRrNdmcWV4Exs_Qk2MLq8T9l0AxG-WZIhuRzba-R-1wlQfLOmgtgBfagtW9124FBxoQGT-FOxBan5un4Nf1Nv1BZjOwJlMJ59owWA4_iV_y-NB_QcBmXpDVbO-Bp068xVg",
"expires_in": 7199,
"grant_id": "ef21c58c-dbc9-4808-a824-967ddf35a7a7",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNlcnZlciJ9.eyJhY3IiOiJ1cm46aWJtOnNlY3VyaXR5OnBvbGljeTppZDoxIiwidXNlclR5cGUiOiJyZWd1bGFyIiwic19oYXNoIjoiRWs0TGNnR3dPSTE4Ql9ReGxMbGtYUSIsInVuaXF1ZVNlY3VyaXR5TmFtZSI6IjY1MjAwMThIUVQiLCJkaXNwbGF5TmFtZSI6IkdlcnJ5IEtvdmFuIiwianRpIjoiSll1Z3ZWY3NBelVZd2tGWDN4S2VKQkdTdHhxdnd3IiwicmVhbG1OYW1lIjoiY2xvdWRJZGVudGl0eVJlYWxtIiwibmFtZSI6IkdlcnJ5IEtvdmFuIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZ2tvdmFuQGhvdG1haWwuY29tIiwibm9uY2UiOiJjeGttY3A2ZmllIiwiZXh0Ijp7InRlbmFudElkIjoiZ2tvdmFuLnZlcmlmeS5pYm0uY29tIn0sImlzcyI6Imh0dHBzOi8vZ2tvdmFuLnZlcmlmeS5pYm0uY29tL29pZGMvZW5kcG9pbnQvZGVmYXVsdCIsImF1ZCI6ImYzZTk4ODk5LTc1ODQtNGEzNC1hODU1LTkxMjY0YjhkZTk3YSIsInN1YiI6IjY1MjAwMThIUVQiLCJpYXQiOjE2MTYxODY1NTAsImV4cCI6MTYxNjE5Mzc1MCwiYXRfaGFzaCI6IjZFOFlCTm9GOHF5SkNPblhpYS15RXcifQ.Ouu1Tt-U0kEzDR8asoMAUQiTqaQL_Vjtn04pICqyCM7rBB3jmOTuAsLIeDsWA7QkKisWTJr5ux4L7unCLf0cRKFuWlEbk-e_XP4QM9boojm9B-GFQue3oDSY4NXTo3dsjnL_IDUhcEWwSwpexpDgrW3wT9vIlvURnj-kqKqaAvX-y3bF89z4nRwBN0R-IzYuw3WuqFdGZLByEVpPl6WdbvXDbQcZWvqoC4tB-n5cN-RZePo2XKbwLZMlpg5fYzOnx6GHinFlrtyXBWcaV7bp5yo_2Y7_ypvnzxAb4_OTUzvLMfXMb_tICLt9dBKllbhruIJIOGz7C6FtbLF-ZlDKMQ",
"scope": "read openid write",
"token_type": "jwt"
}

Notice that the value for the token_type field is “jwt”. The default implementation in the Spring Security token response converter expects this field to have the value of “Bearer” and that is why we needed to create our custom implementation.
Below is the implementation of the CustomAccessTokenResponseConverter.
You will notice that this code does not check/validate the value of the token_type parameter. Instead on line 14, we just hard code the token type to Bearer set the other other fields accordingly and return the token response object.

public class CustomAccessTokenResponseConverter implements Converter<Map<String, String>, OAuth2AccessTokenResponse> {
	private static final Set<String> TOKEN_RESPONSE_PARAMETER_NAMES = Stream.of(
			OAuth2ParameterNames.ACCESS_TOKEN,
			OAuth2ParameterNames.TOKEN_TYPE,
			OAuth2ParameterNames.EXPIRES_IN,
			OAuth2ParameterNames.REFRESH_TOKEN,
			OAuth2ParameterNames.SCOPE).collect(Collectors.toSet());

	@Override
	public OAuth2AccessTokenResponse convert(Map<String, String> tokenResponseParameters) {
		
		String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN);

		OAuth2AccessToken.TokenType accessTokenType = OAuth2AccessToken.TokenType.BEARER;

		long expiresIn = 0;
		if (tokenResponseParameters.containsKey(OAuth2ParameterNames.EXPIRES_IN)) {
			try {
				expiresIn = Long.valueOf(tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN));
			} catch (NumberFormatException ex) { }
		}

		Set<String> scopes = Collections.emptySet();
		if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) {
			String scope = tokenResponseParameters.get(OAuth2ParameterNames.SCOPE);
			scopes = Arrays.stream(StringUtils.delimitedListToStringArray(scope, " ")).collect(Collectors.toSet());
		}

		Map<String, Object> additionalParameters = new LinkedHashMap<>();
		tokenResponseParameters.entrySet().stream()
				.filter(e -> !TOKEN_RESPONSE_PARAMETER_NAMES.contains(e.getKey()))
				.forEach(e -> additionalParameters.put(e.getKey(), e.getValue()));

		return OAuth2AccessTokenResponse.withToken(accessToken)
				.tokenType(accessTokenType)
				.expiresIn(expiresIn)
				.scopes(scopes)
				.additionalParameters(additionalParameters)
				.build();
	}
}

Finally, we need to configure the appropriate Spring Security oauth2 parameters in the application.yml file as shown below:

spring:
  security:
    oauth2:
      client:
        registration:
          custom:
            client-id: ${CLIENT_ID}
            client-secret: ${CLIENT_SECRET}
            scope: read,write,openid
            authorization-grant-type: authorization_code
            redirect-uri: http://<url>:9000/login/oauth2/code/custom
        provider:
          custom:
            authorization-uri: https://<tenantid>.verify.ibm.com/oidc/endpoint/default/authorize
            token-uri: https://<tenantid>.verify.ibm.com/oidc/endpoint/default/token
            user-info-uri: https://<tenantid>.verify.ibm.com/oidc/endpoint/default/userinfo
            user-name-attribute: preferred_username
            issuer-uri: https://<tenantid>.verify.ibm.com/oidc/endpoint/default
            jwk-set-uri: https://<tenantid>.verify.ibm.com/v1.0/endpoint/default/jwks

...

resource server

To configure the resource we extend the Spring Security WebSecurityConfigurerAdapter class as follows:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {// @formatter:off
       
    	http.sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
        .cors()
            .and()
              .authorizeRequests()
                .antMatchers("/open/api/test").permitAll()
                .antMatchers(HttpMethod.GET, "/order-service/**", "/user/info")
                  .hasAuthority("SCOPE_read")
                .antMatchers(HttpMethod.POST, "/api/foos")
                  .hasAuthority("SCOPE_write")
                .anyRequest()
                  .authenticated()
            .and()
              .oauth2ResourceServer()
                .jwt();
    }
}

The application.yml for the resource server configuration is as follows:

# resource server configuration properties
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://<tenatid>.verify.ibm.com/oidc/endpoint/default
          jwk-set-uri: https://<tenatid>.verify.ibm.com/v1.0/endpoint/default/jwks

What am I missing or wrong? Maybe you will help me with this example spring cloud demo with SSO

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.