Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Spring Security OAuth2 with Spring Social integration

Custom Spring security OAuth2 is working fine and now would like to add Spring Social integration(facebook login, google login etc), When the user clicks on Facebook login(user would not provide any username/password), Facebook will return an access_token, but this access_token we can not use to query my application web services, to get my application access_token we need to pass username and password with grant_type as password. Below are my configuration files

AuthorizationServerConfiguration.java

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    DataSource dataSource;

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(
            AuthorizationServerSecurityConfigurer oauthServer) 
                    throws Exception {
        oauthServer.allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) 
            throws Exception {
        clients.jdbc(dataSource);
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setAccessTokenValiditySeconds(86400000);
        tokenServices.setRefreshTokenValiditySeconds(86400000);
        return tokenServices;
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) 
            throws Exception {
        endpoints
        .tokenServices(tokenServices())
        .authenticationManager(authenticationManager);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }
}

ResourceServerConfiguration.java

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    private String resourceId = "rest_api";

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        // @formatter:off
        resources.resourceId(resourceId);
        // @formatter:on
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
        .antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
        .antMatchers(HttpMethod.GET, "/**/login").permitAll()
        .antMatchers(HttpMethod.GET, "/**/callback").permitAll()
        .anyRequest().authenticated()
        .and()
        .formLogin().permitAll();
    }
}

and finally WebSecurityConfigurerAdapter.java

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) 
      throws Exception {
        auth.userDetailsService(userDetailsService);
    }

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

    @Override
    public void configure(HttpSecurity http) throws Exception {
         http.csrf().disable()
         .authorizeRequests()
         .antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
         .antMatchers(HttpMethod.GET, "/**/login").permitAll()
         .antMatchers(HttpMethod.GET, "/**/callback").permitAll()
         .anyRequest().authenticated()
         .and()
         .formLogin().permitAll();
    }
}

Have read different posts in SO, but couldn't get any working example, please guide me on this. Thanks in advance.!

like image 601
Ramesh Kotha Avatar asked Oct 29 '22 15:10

Ramesh Kotha


2 Answers

    String redirectURL = messages.getProperty(Constant.REDIRECT_URI.getValue());
    String clientSecret = messages.getProperty(Constant.CLIENT_SECRET.getValue());
    HttpHeaders header = new HttpHeaders();
    header.setContentType(org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED);

    String req = "client_id=myas&" + "client_secret=" + clientSecret + "&grant_type=authorization_code&"
            + "scope=user_profile&" + "code=" + loginReqeust.getCode() + "&redirect_uri="
            + loginReqeust.getRedirectURL();

    HttpEntity<String> body = new HttpEntity<String>(req, header);
    Map<Object, Object> mapRes = new LinkedHashMap<Object, Object>();

    // call to get access token
    mapRes = getEndpoint("https://auth.mygov.in/oauth2/token", null, body, null);
    String accessToken = mapRes.get("access_token").toString();


    // Call for getting User Profile

    String userUrl = "https://auth.mygov.in/myasoauth2/user/profile";

    HttpHeaders head = new HttpHeaders();
    head.add("Authorization", "Bearer " + accessToken);

    HttpEntity<String> ent = new HttpEntity<String>(head);
    Map<Object, Object> mapResponse = new LinkedHashMap<Object, Object>();
    mapResponse.put("userProfile", getEndpoint(userUrl, null, ent, null));

    //In my case userKey represents the username basically the email of the user using which he/she logged into facebook/google

    String userKey = (String) ((LinkedHashMap<Object, Object>) mapResponse.get("userProfile")).get("mail");

    // Store the user profile in your database with basic info like username & an autogenerated password for the time being and other basic fields.

    userService.save(userprofileInfo);                  

                mapResponse.put("username", "retrieved from facebook/google user's profile");
                mapResponse.put("password", "autogenerated by your application");   

    //send back this response (mapResponse) to your UI and then from there make a call by passing this username and pwd to retrieve the access_token from your own applicatioon.
like image 177
Pushpendra Pal Avatar answered Nov 26 '22 00:11

Pushpendra Pal


I had a similar requirement to get an access token from facebook and generate own JWT token by validating the facebook token on the server side.

I modified the project mentioned here: https://github.com/svlada/springboot-security-jwt

My customizations are as follows(I am assuming you already have a facebook access token):

LoginRequest.java

public class LoginRequest {
private String token;

@JsonCreator
public LoginRequest(@JsonProperty("token") String token) {
    this.token = token;
}

public String getToken() {
    return token;
}

public void setToken(String token) {
    this.token = token;
}
}

AjaxLoginProcessingFilter.java

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException, IOException, ServletException {
    if (!HttpMethod.POST.name().equals(request.getMethod()) || !WebUtil.isAjax(request)) {
        if(logger.isDebugEnabled()) {
            logger.debug("Authentication method not supported. Request method: " + request.getMethod());
        }
        throw new AuthMethodNotSupportedException("Authentication method not supported");
    }

    LoginRequest loginRequest = objectMapper.readValue(request.getReader(), LoginRequest.class);

    if (StringUtils.isBlank(loginRequest.getToken())) {
        throw new AuthenticationServiceException("token not provided");
    }

    UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginRequest.getToken(), null);

    return this.getAuthenticationManager().authenticate(token);
}

AjaxAuthenticationProvider.java

@Component
public class AjaxAuthenticationProvider implements AuthenticationProvider {
@Autowired private BCryptPasswordEncoder encoder;
@Autowired private DatabaseUserService userService;


@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    Assert.notNull(authentication, "No authentication data provided");
    String username = null;
    try {
        username = getUsername(authentication.getPrincipal());
    } catch (UnsupportedOperationException e) {

    } catch (IOException e) {

    }

    //You can either register this user by fetching additional data from facebook or reject it.        
    User user = userService.getByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found"));

    if (user.getRoles() == null) throw new InsufficientAuthenticationException("User has no roles assigned");

    List<GrantedAuthority> authorities = user.getRoles().stream()
            .map(authority -> new SimpleGrantedAuthority(authority.getRole().authority()))
            .collect(Collectors.toList());

    UserContext userContext = UserContext.create(user.getUsername(), authorities);

    return new UsernamePasswordAuthenticationToken(userContext, null, userContext.getAuthorities());
}

private String getUsername(Object principal) throws UnsupportedOperationException, IOException {
    HttpClient client = new DefaultHttpClient();
    //I am just accessing the details. You can debug whether this token was granted against your app.
    HttpGet get = new HttpGet("https://graph.facebook.com/me?access_token=" + principal.toString());
    HttpResponse response = client.execute(get);

    BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

    StringBuffer result = new StringBuffer();
    String line = "";
    while ((line = rd.readLine()) != null) {
        result.append(line);
    }

    JSONObject o = new JSONObject(result.toString());

    //This is just for demo. You should use id or some other unique field.
    String username = o.getString("first_name");
    return username;
}

@Override
public boolean supports(Class<?> authentication) {
    return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}

Apart from this, I also had to add custom BeanPostProcessor to override the default behavior of UsernamePasswordAuthenticationFilter to accept only token as a field instead of a username and a password.

UserPassAuthFilterBeanPostProcessor.java

public class UserPassAuthFilterBeanPostProcessor implements BeanPostProcessor {

private String usernameParameter;
private String passwordParameter;

@Override
public final Object postProcessAfterInitialization(final Object bean,
    final String beanName) {
    return bean;
}

@Override
public final Object postProcessBeforeInitialization(final Object bean,
    final String beanName) {
    if (bean instanceof UsernamePasswordAuthenticationFilter) {
        final UsernamePasswordAuthenticationFilter filter =
            (UsernamePasswordAuthenticationFilter) bean;
        filter.setUsernameParameter(getUsernameParameter());
        filter.setPasswordParameter(getPasswordParameter());
    }

    return bean;
}

public final void setUsernameParameter(final String usernameParameter) {
    this.usernameParameter = usernameParameter;
}

public final String getUsernameParameter() {
    return usernameParameter;
}

public final void setPasswordParameter(final String passwordParameter) {
    this.passwordParameter = passwordParameter;
}

public final String getPasswordParameter() {
    return passwordParameter;
}

Configuration:

@Bean
public UserPassAuthFilterBeanPostProcessor  userPassAuthFilterBeanPostProcessor(){
    UserPassAuthFilterBeanPostProcessor bean = new UserPassAuthFilterBeanPostProcessor();
    bean.setUsernameParameter("token");
    bean.setPasswordParameter(null);
    return bean;
}

postman output

like image 40
amant singh Avatar answered Nov 26 '22 00:11

amant singh