1
votes

I am new in development of micro-services with jwt. Here is my project structure:

First micro-service is used to authenticate users by jwt and Oauth2 using username and password. This microservice is called auth-service.

Login request url is like:

[http://localhost:9092/oauth/[email protected]&password=randomPassword&grant_type=password][1]

By calling this url we got jwt token like:

{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyX2lkIl0sInVzZXJfbmFtZSI6InNAc2F3YWkuY29tIiwic2NvcGUiOlsicmVhZCIsIndyaXRlIl0sInRlbmFudElkIjoic2F3YWkuY29tIiwic3lzdGVtR2VuZXJhdGVkUGFzc3dvcmQiOnRydWUsImlkIjoiNTYzOTFhYzAtZDc4OC00ODEyLThmYWMtODEwZTIyMjdjYmI1IiwiZXhwIjoxNTI0NzMxNzgwLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6ImY0ZTNmNTM5LWRkNDgtNGMxMy05OTg5LTcwM2E1NWYxMjNlYyIsImVtYWlsIjoic0BzYXdhaS5jb20iLCJjbGllbnRfaWQiOiJ0cnVzdGVkLWFwcCJ9.AS1tXpUcPMgEw63FrvPP-xGBz7qCi14Eqe29rDzTXPg","token_type":"bearer","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyX2lkIl0sInVzZXJfbmFtZSI6InNAc2F3YWkuY29tIiwic2NvcGUiOlsicmVhZCIsIndyaXRlIl0sImF0aSI6ImY0ZTNmNTM5LWRkNDgtNGMxMy05OTg5LTcwM2E1NWYxMjNlYyIsInRlbmFudElkIjoic2F3YWkuY29tIiwic3lzdGVtR2VuZXJhdGVkUGFzc3dvcmQiOnRydWUsImlkIjoiNTYzOTFhYzAtZDc4OC00ODEyLThmYWMtODEwZTIyMjdjYmI1IiwiZXhwIjoxNTI0NzQ2MTgwLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6IjI1ZmJlY2IyLWRhODgtNDY1ZS1iM2I2LTFlN2NmYjBlYmVjMiIsImVtYWlsIjoic0BzYXdhaS5jb20iLCJjbGllbnRfaWQiOiJ0cnVzdGVkLWFwcCJ9.jSG5zUBzu9yGqnBueU7fkIZV6XhXD8oCkYCerwHkkOw","expires_in":14399,"scope":"read write","tenantId":"sawai.com","systemGeneratedPassword":true,"id":"56391ac0-d788-4812-8fac-810e2227cbb5","email":"[email protected]","jti":"f4e3f539-dd48-4c13-9989-703a55f123ec"}

On auth service database we just create a single table called users with following fields:

id varchar(255)

created_on datetime

last_modified_date datetime

email varchar(255)

enabled bit(1)

password varchar(255)

role varchar(255)

system_generated_password bit(1)

tenant_id varchar(255)

validate bit(1)

OK.

Now another micro-service is called company and in company service we have list of users(not same as auth service users, because auth service contains users for multiple micro-services like: Company, Candidate etc).

Now we want to maintain last_logged_on for company users. So admin can check when an user logged in last time.

What we try to do is: When user login using auth service and user type is company user then call company service and update users last_logged_on. For calling company service we need jwt-access-token because urls are secure on company side. So how can we get access token on auth service when we request to get jwt token.

Here is configuration for jwt with spring boot on auth side.

package com.cs.je.auth.config.jwt;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
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.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import com.cs.je.auth.enums.Role;
import com.cs.je.auth.model.User;
/**
 * @author sawai
 *
 */

@Configuration
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    @Value("${auth.token.time}")
    private int accessTokenValiditySeconds;

    @Value("${refresh.token.time}")
    private int refreshTokenValiditySeconds;

    @Value("${security.oauth2.resource.id}")
    private String resourceId;

    @Value("${trusted.app.name}")
    private String trustedAppName;

    @Value("${trusted.app.secret}")
    private String trustedAppSecret;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private CustomAccessTokenConverter customAccessTokenConverter;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints
                .authenticationManager(this.authenticationManager)
                .tokenServices(tokenServices())
                .tokenStore(tokenStore())
                .accessTokenConverter(accessTokenConverter());

    }

    @Bean
    public TokenEnhancer tokenEnhancer() {
        return new CustomTokenEnhancer();
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer
        // we're allowing access to the token only for clients with 'ROLE_TRUSTED_CLIENT' authority
                //.tokenKeyAccess("permitAll()")
                .tokenKeyAccess("hasAuthority('ROLE_TRUSTED_CLIENT')")
                .checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient(trustedAppName)
                    .authorizedGrantTypes("client_credentials", "password", "refresh_token")
                    .authorities("ROLE_TRUSTED_CLIENT")
                    .scopes("read", "write")
                    .resourceIds(resourceId)
    //                .accessTokenValiditySeconds(accessTokenValiditySeconds)
    //               .refreshTokenValiditySeconds(refreshTokenValiditySeconds)
                    .secret(trustedAppSecret);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        System.out.println("3333333333333");
        DefaultAccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
        tokenConverter.setUserTokenConverter(new DefaultUserAuthenticationConverter() {
            @Override
            public Authentication extractAuthentication(Map<String, ?> map) {
                Authentication authentication = super.extractAuthentication(map);
                System.out.println("222222222222");
                // User is my custom UserDetails class
                User user = new User();
                user.setTenantId(map.get("tenantId").toString());
                user.setEmail(map.get("email").toString());
                user.setId(map.get("id").toString());
                //user.setPassword(map.get("password").toString());
                //System.out.println("date " + );
                Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
                authorities.addAll(authentication.getAuthorities());
                user.setGrantedAuthorities(authorities);
                user.setRole(Role.valueOf(authorities.iterator().next().toString()));
                //user.setSpecialKey(map.get("specialKey").toString());
                return new UsernamePasswordAuthenticationToken(user,
                        authentication.getCredentials(), authentication.getAuthorities());
            }
        });

        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("key");
        converter.setAccessTokenConverter(customAccessTokenConverter);
        converter.setAccessTokenConverter(tokenConverter);
        return converter;
    }    

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        defaultTokenServices.setAccessTokenValiditySeconds(accessTokenValiditySeconds);
        defaultTokenServices.setRefreshTokenValiditySeconds(refreshTokenValiditySeconds);
        defaultTokenServices.setReuseRefreshToken(false);
        defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
        return defaultTokenServices;
    }    
}

Here is Custom Token Enhance:

package com.cs.je.auth.config.jwt;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;

import com.cs.je.auth.enums.Role;
import com.cs.je.auth.model.User;
import com.cs.je.auth.rest.api.call.CustomRestTemplate;
import com.cs.je.auth.service.UserService;
import com.cs.je.auth.utils.EncTokenUtils;
import com.cs.je.auth.utils.UserUtils;

/**
 * @author sawai
 *
 */

@Component
public class CustomTokenEnhancer implements TokenEnhancer {

    @Autowired
    private UserService userService;

    @Autowired
    private CustomRestTemplate customRestTemplate;

    @Value("${microservice.company.protocol}")
    private String protocol;

    @Value("${microservice.company.port}")
    private String port;

    @Value("${microservice.company.ip}")
    private String ipAddress;

    @Value("${microservice.company.user.api}")
    private String userCreateUrl;

    @Autowired
    private TokenStore tokenStore;

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        Map<String, Object> additionalInfo = new HashMap<>();
        if (authentication != null) {
            User user = (User)authentication.getPrincipal();
            additionalInfo.put("email", user.getEmail());
            additionalInfo.put("tenantId", user.getTenantId());
            additionalInfo.put("id", user.getId());
            if (user.isSystemGeneratedPassword()) {
                additionalInfo.put("systemGeneratedPassword", true);
            }

            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            System.out.println(accessToken.getRefreshToken());
            System.out.println(accessToken.getRefreshToken().getValue());
            System.out.println(accessToken.getTokenType());


            /*if (user.getRole().equals(Role.ROLE_ADMIN) || user.getRole().equals(Role.ROLE_USER)) {
                 String token = accessToken.toString();
                 try {
                    System.out.println("before call");
                    //ResponseEntity<Object> responseEntity = customRestTemplate.exchange(protocol + "://" + ipAddress + ":" + port + userCreateUrl + "/" + user.getId() + "/last-loggedOn", token, EncTokenUtils.getEncToken(user.getEmail()), HttpMethod.PUT, null);
                    System.out.println("successfull");
                 } catch (Exception e) {
                    e.printStackTrace();
                } 
            }*/
            System.out.println("1111111111");
        }
        return accessToken;
    }
}

Security Config

package com.cs.je.auth.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.cs.je.auth.constant.Constants;
import com.cs.je.auth.enums.Role;
import com.cs.je.auth.model.User;
import com.cs.je.auth.repository.UserRepository;
import com.cs.je.auth.service.UserService;
/**
 * @author sawai
 *
 */
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Autowired
    private UserRepository userRepository;

    /*@Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll()
        .antMatchers("/").permitAll()
        .antMatchers(HttpMethod.POST ,"/user/**").hasAnyAuthority("ROLE_SUPER_USER", "ROLE_ADMIN")
        .anyRequest().authenticated();

        httpSecurity.csrf().disable();
        httpSecurity.headers().frameOptions().disable();
        httpSecurity.requestCache().requestCache(new NullRequestCache());
        httpSecurity.httpBasic();
        //httpSecurity.addFilterBefore(CORSFilter, ChannelProcessingFilter.class);
    }*/

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder);

        if (userRepository.count() < 1) {
            User user = new User();
            user.setEmail("[email protected]");
            user.setPassword("jeAdminUser");
            user.setTenantId(Constants.JE_TENANT_ID);
            user.setRole(Role.ROLE_SUPER_USER);
            user.setValidate(true);
            userService.create(user, null);
        }
     }

    @Override
    public void configure(WebSecurity webSecurity) {
        webSecurity.ignoring().antMatchers("/api/candidate/**");
        webSecurity.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
        webSecurity.ignoring().antMatchers("/api/company/**");
        webSecurity.ignoring().antMatchers("/api/forgotPassword/**");
        //webSecurity.ignoring().antMatchers(HttpMethod.POST, "/oauth/**");
    }
}

ResourceConfig.java

package com.cs.je.auth.config.jwt;
/**
 * @author sawai
 *
 */

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
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.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.savedrequest.NullRequestCache;
import com.cs.je.auth.filter.CORSFilter;

@Configuration 
@EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {

    @Value("${security.oauth2.resource.id}")
    private String resourceId;

    // The DefaultTokenServices bean provided at the AuthorizationConfig
    @Autowired
    private DefaultTokenServices tokenServices;

    // The TokenStore bean provided at the AuthorizationConfig
    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private CORSFilter corsFilter;

    // To allow the rResourceServerConfigurerAdapter to understand the token,
    // it must share the same characteristics with AuthorizationServerConfigurerAdapter.
    // So, we must wire it up the beans in the ResourceServerSecurityConfigurer.
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources
                .resourceId(resourceId)
                .tokenServices(tokenServices)
                .tokenStore(tokenStore);
    }


    public void configure(WebSecurity webSecurity) {
        webSecurity.ignoring().antMatchers("/api/candidate/**");
    //  webSecurity.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
    //  webSecurity.ignoring().antMatchers(HttpMethod.POST, "/oauth/**");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http

        .authorizeRequests().antMatchers("/").permitAll().and().authorizeRequests().antMatchers("/console/**")
        .permitAll().and().authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll().and()

                // when restricting access to 'Roles' you must remove the "ROLE_" part role
                // for "ROLE_USER" use only "USER"
                //.antMatchers("/api/hello").access("hasAnyRole('USER')")          
                //.antMatchers("/api/admin").hasRole("ADMIN")
                .authorizeRequests().antMatchers(HttpMethod.POST ,"/api/user/**").hasAnyAuthority("ROLE_SUPER_USER", "ROLE_ADMIN")
                // restricting all access to /api/** to authenticated users
                //.antMatchers("/**")
                //.antMatchers("/api/**").authenticated();
                .anyRequest().authenticated();

        http.csrf().disable();
        http.headers().frameOptions().disable();
        http.requestCache().requestCache(new NullRequestCache());
        http.httpBasic();
        http.addFilterBefore(corsFilter, ChannelProcessingFilter.class);
    }
}

All above config is on auth service side. Now when user request for jwt token on auth, then we want to get access-token value on service layer, so i can call company service by secure url calling.

Please guide me how can we get access-token value when user request for jwt token. If you look in CustomTokenEnhancer then we tried to print access-token there by using following statements:

**System.out.println(accessToken.getRefreshToken());
System.out.println(accessToken.getRefreshToken().getValue());
System.out.println(accessToken.getTokenType());**

But values are similar to: 642e0cf2-9214-42d8-ae85-29e5cdfccef1 But we want actual token here.

Please guide me.

1
regarding your login request url: grant_type=password&username=... should not be passed as url paramter but as a request header.jps
I have the same question and also finding the solution for it? Since it has been 4 months and not go any question on this question. Maybe you figured out the solution. Can you please help me if you got the solution?Akshay Pethani

1 Answers

1
votes

You can use that method

public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String TOKEN_SEPERATOR = " ";

public static String getAccessToken(){
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    if (requestAttributes instanceof ServletRequestAttributes) {
        HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
        return Arrays.asList(request.getHeader(AUTHORIZATION_HEADER).split(TOKEN_SEPERATOR)).get(1);
    }
    return null;
}