FusionAuth
    • Home
    • Categories
    • Recent
    • Popular
    • Pricing
    • Contact us
    • Docs
    • Login

    Secure Spring boot Rest Api

    Scheduled Pinned Locked Moved
    General Discussion
    0
    6
    1.1k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • K
      konstantin.dzekov
      last edited by konstantin.dzekov

      Hi all,
      I'm trying to integrate Spring with FusionAuth. Following this tutorial: https://fusionauth.io/blog/2018/10/24/easy-integration-of-fusionauth-and-spring/ I managed to successfully authenticate and authorize different users with different roles for an UI application and it's resources.

      I also try to do the same (appropriate authentication and authorization) for Spring Rest API. Using the same configuration I do not manage to access certain endpoints that are annotated with "@PreAuthorize("hasAuthority('user') or hasAuthority('admin')")". I'm getting "403 Forbidden" error.

      So, I run an Api call using oAuth2 authentication. The client (in my case Postman) generates token after proper authentication into FusionAuth (using credentials form user registered into the app having the required roll). Then this token I set into call's header. However, I'm getting "403 Forbiden". I manage to access the endpoint only then when it is annotated as "@PreAuthorize("permitAll()")".

      Could somebody give a brief explanation about how should I approach and properly configure my Rest Controller ?

      Thanks in advance!

      1 Reply Last reply Reply Quote 0
      • danD
        dan
        last edited by dan

        Hi @konstantin-dzekov ,

        Welcome to the FusionAuth community!

        What does your JWT look like? You can customize it with the JWT populate lambda, documented here.

        This answer indicates you need to add an scp claim (if you are using spring security 5.2): https://stackoverflow.com/questions/59379645/spring-security-5-populating-authorities-based-on-jwt-claims

        This older answer says that the spring framework looks for an authorities claim: https://stackoverflow.com/questions/55609083/how-to-set-user-authorities-from-user-claims-return-by-an-oauth-server-in-spring/56259665

        But in general it appears from some looking around that you need to figure out what claims the JWT is expected to provide and then map the FusionAuth roles (which are assigned to users registered to an application, or in a group and registered to an application) to the correct claims the framework expects.

        Let us know what you find, please!

        --
        FusionAuth - Auth for devs, built by devs.
        https://fusionauth.io

        1 Reply Last reply Reply Quote 0
        • K
          konstantin.dzekov
          last edited by

          HI @dan ,
          So, I reconfigured my application following the solution given in: https://stackoverflow.com/questions/55609083/how-to-set-user-authorities-from-user-claims-return-by-an-oauth-server-in-spring/56259665
          Basically I have the following classes for JWT configuration:

          import org.springframework.beans.factory.annotation.Value;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.context.annotation.Primary;
          import org.springframework.security.config.annotation.web.builders.HttpSecurity;
          import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
          import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
          import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
          import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
          
          @Configuration
          @EnableResourceServer
          public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
          
            @Value("${fusionAuth.accessTokenUri}")
            private String accessTokenUri;
          
            @Value("${fusionAuth.clientId}")
            private String clientId;
          
            @Value("${fusionAuth.clientSecret}")
            private String clientSecret;
          
            @Override
            public void configure(final HttpSecurity http) throws Exception {
              // @formatter:off
              http.authorizeRequests()
                  .anyRequest().access("hasAnyAuthority('Admin')");
            }
            
            @Override
            public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
              resources.resourceId("arawaks");
            }
            
            @Bean
            @Primary
            public RemoteTokenServices tokenServices() {
              final RemoteTokenServices tokenServices = new RemoteTokenServices();
              tokenServices.setClientId(clientId);
              tokenServices.setClientSecret(clientSecret);
              tokenServices.setCheckTokenEndpointUrl(accessTokenUri);
              tokenServices.setAccessTokenConverter(accessTokenConverter());
              return tokenServices;
            }
          
          
            @Bean
            public CustomAccessTokenConverter accessTokenConverter() {
              final CustomAccessTokenConverter converter = new CustomAccessTokenConverter();
              converter.setUserTokenConverter(new CustomUserTokenConverter());
              return converter;
            }
          
          }
          
          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.core.userdetails.UserDetails;
          import org.springframework.security.core.userdetails.UserDetailsService;
          import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
          import org.springframework.util.StringUtils;
          
          import java.util.Collection;
          import java.util.LinkedHashMap;
          import java.util.Map;
          
          public class CustomUserTokenConverter implements UserAuthenticationConverter {
            private Collection<? extends GrantedAuthority> defaultAuthorities;
            private UserDetailsService userDetailsService;
          
            private final String AUTHORITIES = "role";
            private final String USERNAME = "preferred_username";
            private final String USER_IDENTIFIER = "sub";
          
            public CustomUserTokenConverter() {
            }
          
            public void setUserDetailsService(UserDetailsService userDetailsService) {
              this.userDetailsService = userDetailsService;
            }
          
            public void setDefaultAuthorities(String[] defaultAuthorities) {
              this.defaultAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.arrayToCommaDelimitedString(defaultAuthorities));
            }
          
            public Map<String, ?> convertUserAuthentication(Authentication authentication) {
              Map<String, Object> response = new LinkedHashMap();
              response.put(USERNAME, authentication.getName());
              if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
                response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
              }
          
              return response;
            }
          
            public Authentication extractAuthentication(Map<String, ?> map) {
              if (map.containsKey(USER_IDENTIFIER)) {
                Object principal = map.get(USER_IDENTIFIER);
                Collection<? extends GrantedAuthority> authorities = this.getAuthorities(map);
                if (this.userDetailsService != null) {
                  UserDetails user = this.userDetailsService.loadUserByUsername((String)map.get(USER_IDENTIFIER));
                  authorities = user.getAuthorities();
                  principal = user;
                }
          
                return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
              } else {
                return null;
              }
            }
          
            private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
              if (!map.containsKey(AUTHORITIES)) {
                return this.defaultAuthorities;
              } else {
                Object authorities = map.get(AUTHORITIES);
                if (authorities instanceof String) {
                  return AuthorityUtils.commaSeparatedStringToAuthorityList((String)authorities);
                } else if (authorities instanceof Collection) {
                  return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.collectionToCommaDelimitedString((Collection)authorities));
                } else {
                  throw new IllegalArgumentException("Authorities must be either a String or a Collection");
                }
              }
            }
          }
          

          and

          import org.springframework.security.oauth2.provider.OAuth2Authentication;
          import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
          import org.springframework.stereotype.Component;
          
          import java.util.Map;
          
          @Component
          public class CustomAccessTokenConverter extends DefaultAccessTokenConverter {
          
            @Override
            public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
              OAuth2Authentication authentication = super.extractAuthentication(claims);
              authentication.setDetails(claims);
              return authentication;
            }
          }
          
          1 Reply Last reply Reply Quote 0
          • K
            konstantin.dzekov
            last edited by

            An it seems that there is some progress. I'm getting: "401 Anauthorized" response with this body:

            {
                "error": "invalid_token",
                "error_description": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjA5MDkzNmZhZSJ9.eyJhdWQiOiIyYmVhNTQzNS1hZGEwLTQ0Y2QtYTJmMi00OTM1ZDYzMWQ2ZDgiLCJleHAiOjE2MTI4ODY4ODYsImlhdCI6MTYxMjg4MzI4NiwiaXNzIjoiYWNtZS5jb20iLCJzdWIiOiJmZGZlYzExZi02ZGExLTRmN2YtOGIwZi0xMGFhMzhjNTg4ZWQiLCJqdGkiOiJkZTZmNTg2Yi0xNzlhLTRiMTUtOTA1OC1iNWEyNDc5ZmU1ZTYiLCJhdXRoZW50aWNhdGlvblR5cGUiOiJQQVNTV09SRCIsImVtYWlsIjoia29uc3RhbnRpbi5kemVrb3ZAc2VtYW50aWMtd2ViLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhcHBsaWNhdGlvbklkIjoiMmJlYTU0MzUtYWRhMC00NGNkLWEyZjItNDkzNWQ2MzFkNmQ4Iiwicm9sZXMiOlsiYWRtaW4iXX0.82M8lLFe2hjmNuEe3hQ9bdGxXj3WeZkHplUwKuVfR9I"
            }
            

            In order to get the token I use this endpoint: {fusionauth-host}/api/login, with the body:

            {
              "loginId": "konstantin.dzekov@semantic-web.com",
              "password": "fa-kdzekov-21",
              "applicationId": "2bea5435-ada0-44cd-a2f2-4935d631d6d8",
              "noJWT" : false,
              "ipAddress": "localhost"
            }
            

            For which I'm getting this token:

            "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjA5MDkzNmZhZSJ9.eyJhdWQiOiIyYmVhNTQzNS1hZGEwLTQ0Y2QtYTJmMi00OTM1ZDYzMWQ2ZDgiLCJleHAiOjE2MTI4ODY4ODYsImlhdCI6MTYxMjg4MzI4NiwiaXNzIjoiYWNtZS5jb20iLCJzdWIiOiJmZGZlYzExZi02ZGExLTRmN2YtOGIwZi0xMGFhMzhjNTg4ZWQiLCJqdGkiOiJkZTZmNTg2Yi0xNzlhLTRiMTUtOTA1OC1iNWEyNDc5ZmU1ZTYiLCJhdXRoZW50aWNhdGlvblR5cGUiOiJQQVNTV09SRCIsImVtYWlsIjoia29uc3RhbnRpbi5kemVrb3ZAc2VtYW50aWMtd2ViLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhcHBsaWNhdGlvbklkIjoiMmJlYTU0MzUtYWRhMC00NGNkLWEyZjItNDkzNWQ2MzFkNmQ4Iiwicm9sZXMiOlsiYWRtaW4iXX0.82M8lLFe2hjmNuEe3hQ9bdGxXj3WeZkHplUwKuVfR9I".
            

            What bothers me (I also found something on other stack-overflow threads) that:

              @Override
              public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
                resources.resourceId("arawaks");
              }
            

            " resources.resourceId("arawaks")" might causes the issue. To be honest, I don't know how to get appropriate value for the resourceId.

            1 Reply Last reply Reply Quote 1
            • danD
              dan
              last edited by

              Those tokens look similar, and when I decode the token I get:

              {
                "aud": "2bea5435-ada0-44cd-a2f2-4935d631d6d8",
                "exp": 1612886886,
                "iat": 1612883286,
                "iss": "acme.com",
                "sub": "fdfec11f-6da1-4f7f-8b0f-10aa38c588ed",
                "jti": "de6f586b-179a-4b15-9058-b5a2479fe5e6",
                "authenticationType": "PASSWORD",
                "email": "konstantin.dzekov@example.com",
                "email_verified": true,
                "applicationId": "2bea5435-ada0-44cd-a2f2-4935d631d6d8",
                "roles": [
                  "admin"
                ]
              }
              

              (Info in JWTs are public, you might want to hide your email address as I did.)

              Does that have all the claims you expect? From looking at this tutorial and the JWT generated, it appears that spring expects the resourceId to be the audience.

              So could you try

               @Override
                public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
                  resources.resourceId("2bea5435-ada0-44cd-a2f2-4935d631d6d8");
                }
              

              And see if that helps?

              --
              FusionAuth - Auth for devs, built by devs.
              https://fusionauth.io

              1 Reply Last reply Reply Quote 1
              • Z
                zaidmajix06
                last edited by zaidmajix06

                {
                "aud": "2bea5435-ada0-44cd-a2f2-4935d631d6d8",
                "exp": 1612886886,
                "iat": 1612883286,
                "iss": "acme .com",
                "sub": "fdfec11f-6da1-4f7f-8b0f-10aa38c588ed",
                "jti": "de6f586b-179a-4b15-9058-b5a2479fe5e6",
                "authenticationType": "PASSWORD",
                "email": "konstantin.dzekov@example.com",
                "email_verified": true,
                "applicationId": "2bea5435-ada0-44cd-a2f2-4935d631d6d8",
                "roles": [
                "admin"
                ]
                }

                1 Reply Last reply Reply Quote 0
                • First post
                  Last post