6

I have implemented Password Grant for my Authentication Server using Spring Security OAuth 2.0 and JWT. I create the Server by extending the AuthorizationServerConfigurerAdapter. I am able to give the server a username/password and get back a JWT Token. I use a ResourceConfiguration which extends the ResourceServerConfigurerAdapter class on the other services to verify the JWT any time a web service call is made. Pasted below is my code.

I would like to give my Native Mobile app the ability to login using Facebook, Gmail, Linked etc... I would like to follow the steps in the article attached:

https://ole.michelsen.dk/blog/social-signin-spa-jwt-server.html

  1. User does the OAuth dance on the Mobile side and sends me an Access Token for the Social Service they are using.
  2. I receive the Access Token, and use it to call the respective Social Service.
  3. If the Token is valid, the user cannot log in, an error is thrown.
  4. If the Token is valid, I get User Details from the Social Service and use it to create a "Social User" in my data store that will be tied to an existing or new System User.
  5. Once the System User is created with a Social User, or the Social User is tied to an existing System User, a JWT token gets sent back to the Mobile App.
  6. This JWT token should resemble the JWT Token created by the Spring Security OAuth 2.0 Password Grant Flow, and should be accepted by my ResourceServerConfiguration when authorization the user.

I have searched online for a solution that meets my criteria, but I cannot find any. Are my requirements reasonable? Is there an easier way to do this, allow users to sign in via username/password and social media authentication while getting back a JWT token. One example I have found uses the OAuth2ClientAuthenticationProcessingFilter to do the logic I mentioned above, but I have no idea how OAuth2ClientAuthenticationProcessingFilter works, and cannot find any documentation on it. If some one has had to implement similar requirements using a similar tech stack, please let me know what techniques you used to implement this solution.

On the Authentication Server:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;

    @Value("${clientId}")
    private String clientId;

    @Value("${clientSecret}")
    private String clientSecret;

    @Value("${jwtSigningKey}")
    private String jwtSigningKey;

    @Value("${accessTokenValiditySeconds}")
    private String accessTokenValiditySeconds;

    @Value("${refreshTokenValiditySeconds}")
    private String refreshTokenValiditySeconds;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter(){
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        accessTokenConverter.setSigningKey(jwtSigningKey);
        return accessTokenConverter;
    }

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

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

    // Added for refresh token capability
    @Bean
    @Primary
    public DefaultTokenServices tokenServices(){
        final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    .withClient(clientId)
                    .secret(clientSecret)
                    .authorizedGrantTypes("password", "refresh_token")
                    .scopes("read","write")
                    .accessTokenValiditySeconds(Integer.valueOf(accessTokenValiditySeconds)) // 1 hour
                    .refreshTokenValiditySeconds(Integer.valueOf(refreshTokenValiditySeconds));// 30 days
    }

    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {

        // Add the JWT token enhancer to the token enhancer chain then add to endpoints
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));

        endpoints.tokenStore(tokenStore())
                .tokenEnhancer(tokenEnhancerChain)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST)
                .accessTokenConverter(accessTokenConverter());
    }

    @Override
    public void configure(final AuthorizationServerSecurityConfigurer securityConfigurer) throws Exception {
        securityConfigurer.checkTokenAccess("permitAll()");
        super.configure(securityConfigurer);
    }
}

public class JWTTokenEnhancer implements TokenEnhancer {

    @Override
    public OAuth2AccessToken enhance(final OAuth2AccessToken accessToken,
                                     final OAuth2Authentication authentication) {

        Map<String, Object> additionalInfo = new HashMap<>();

        // Get the user detail implementation
        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();

        // add userId and roles to the JWT token
        additionalInfo.put("user_id", userDetails.getUserId());
        additionalInfo.put("email", userDetails.getEmail());
        additionalInfo.put("user_name", userDetails.getUsername());
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);

        return accessToken;
    }
}

On each Microservice:

@Configuration
@EnableResourceServer
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@ComponentScan("com.test.security")
@Profile({"prod", "qa", "dev"})
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

  @Value("${jwtSigningKey}")
  private String jwtSigningKey;

  // http security concerns
  @Override
  public void configure(final HttpSecurity http) throws Exception {

    http.authorizeRequests()
        .antMatchers("/swagger-ui.html").permitAll()
        .antMatchers("/hystrix/**").permitAll()
        .antMatchers("/admin/hystrix.stream/**").permitAll()
        .antMatchers("/admin/health/**").permitAll()
        .antMatchers("/admin/info/**").permitAll()
        .antMatchers("/admin/**").authenticated()
        .antMatchers("/greetings/**").authenticated()
        .and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and().csrf().disable();
  }

  @Override
  public void configure(final ResourceServerSecurityConfigurer config) {
    config.tokenServices(tokenServices());
  }

  @Bean
  public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
    accessTokenConverter.setSigningKey(jwtSigningKey);
    return accessTokenConverter;
  }

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

  // Added for refresh token capability
  @Bean
  @Primary
  public DefaultTokenServices tokenServices() {
    final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    defaultTokenServices.setTokenStore(tokenStore());
    defaultTokenServices.setSupportRefreshToken(true);
    return defaultTokenServices;
  }
}

0 Answers0