Spring: Securing REST API with BasicAuth

23 07 2016

The simpliest, yet effective way to secure Spring REST API is to use Basic Auth. I’m going to show how to do it with Spring Security.

Note

Basic Auth is stateless (no need to manage sessions) and simple to implement. However it should be used with https only if outside of a trusted network. Also be aware that there are issues with Basic Auth (http://security.stackexchange.com/questions/988/is-basic-auth-secure-if-done-over-https)

Source Code

As usual, get it from my GitHub: https://github.com/yacekmm/looksok/tree/HttpBasicAuthDemo/Spring/SecurityBasicAuth

1. Create a project

Use your favorite tool. I choose Spring Boot Initializr in IntelliJ. Add web and security starter poms:

Przechwytywanie

Note that right after you add the security starter pom to your classpath, the auto configuration kicks in and your app is secured by default (the default password is logged on the application startup). I will override that default configuration and customize it later on.

2. Create Controller with urls to be secured

The most straight forward one as possible like mine below:

@RestController
@RequestMapping("/api")
public class SecuredController {

    @RequestMapping(value = "/secured", method = GET, produces = TEXT_PLAIN_VALUE)
    public String securedGet(){
        return "You have access to secured API!";
    }

    @RequestMapping(value = "/admin", method = GET, produces = TEXT_PLAIN_VALUE)
    public String adminGet(){
        return "Welcome, Admin";
    }

    @RequestMapping(value = "/open", method = GET, produces = TEXT_PLAIN_VALUE)
    public String openedGet(){ return "this API does not require authentication"; }
}

It contains 3 endpoints. Each of them I will secure to be accessible on various levels.

3. Create Basic Auth security configuration for selected endpoints

As in one of my previous tutorials (Spring Security Tutorial: Authorization and user roles), you need to extend the WebSecurityConfigurerAdapter:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    ...
}

and configure the HttpSecurity in there:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .httpBasic()
        .and()
            .authorizeRequests()
            .antMatchers("/api/secured").authenticated()
            .antMatchers("/api/admin").hasRole("ADMIN")
            .antMatchers("/api/open").permitAll()
            .anyRequest().authenticated();
}

What just happened here?

  • first of all with httpBasic() you say that BasicAuth will be in use
  • then you say that want to authorizeRequests() so that:
    • url matching “/api/secured” is accessible for all authenticated users
    • url matching “/api/admin” is accessible for ADMIN users only
    • url matching “/api/open” is accessible with no authentication required
    • any other request is accessible for all authenticated() users

4. Create security configuration: usernames and passwords

Here I create two user accounts with USER and ADMIN role. That method is also in my WebsecurityConfig class. Using those accounts you can log in to the application:

@Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
            .withUser("admin").password("superhard").roles("ADMIN")
        .and()
            .withUser("user").password("easyone").roles("USER");
}

Normaly you would use the AuthenticationProvider, probably connected with your database or LDAP. This is separate big subject not to be cevered in this article.

5. Go ahead and try it!

Run the app and:

  1. head over to url 127.0.0.1:8080/api/open – you should access it without logging in
  2. head over http://127.0.0.1:8080/api/secured – the browser will ask you for username and password: type ‘user’ and ‘easyone’. Then you should access the site
  3. head over to url http://127.0.0.1:8080/api/admin – you will get 403 Forbidden, since browser cached your auth data and reused it. Only admin is permitted to access that url.
  4. To log in as another user you need to restart your browser and open the url once again. You can also use private browsing.




Apache DefaultHttpClient Tutorial: execute HTTP GET request with params and BasicAuthentication

11 05 2013

When dealing with REST webservices, the HTTP GET request is commonly used (very often with params). Apache’s DefaultHttpClient has convenient methods that utilize making such requests. This is how I do it:

Request data

the URL that I am going to execute is:

http://www.example.org:8001/rest/sendMessage

GET request params are as follows:

  • body: message body (content)
  • from: sender’s name
  • to: recipient’s name

so the URL with params attached will look like this:

http://www.example.org:8001/rest/sendMessage?body=hello&from=jack&to=bill

Prerequisities

If your JRE is not equipped with Apache DefaultHttpClient, then you need to download two jars (I recommend to use version no older than 4.2.1):

  • httpclient-4.2.1.jar
  • httpcore-4.2.1.jar

1. Create URL

You can do it in two ways: hardcoded or objective.

Hardcoded version is simply creating String variable like this:

String url = "http://www.example.org:8001/rest/sendMessage?body=hello&from=jack&to=bill";

It is not convenient, because You have to converts this String to URI object, and encode it. The url above does not need encoding, but it would, if for example message content would contain spaces (The space is encoded to %20). So if body would be:

hello world

then the param would be encoded to:

body=hello%20world

More about ecoding you will find on wiki page here.

Objective version requires httpclient in version 4.2.1 or higher. That release has URIBuilder:

URIBuilder builder = new URIBuilder();
builder.setScheme("http").setHost("www.example.org").setPort(8001).setPath("/rest/sendMessage")
.setParameter("msg", "hello")
.setParameter("from", "jack")
.setParameter("to", "bill");
URI url = builder.build();

the build method is encoding the url automatically, so the result uri is ready to execute. In other words you can write:

.setParameter("msg", "hello world")

without taking care about encoding – the builder will do it for you. If you do not need params, then you simply omit the setParameter() methods.

2. Create HttpGet instance

HttpGet request object  is created with url:

HttpGet httpget = new HttpGet(url);

3. Create Http Client

The DefaultHttpClient is powerful enough in most cases. The client is responsible for executing requests:

DefaultHttpClient httpClient = new DefaultHttpClient();

4. Provide BasicAuthentication credentials (Optional)

If your REST API is protected with BasicAuthentication you can add an Authentication Header to your HttpClient:

Credentials credentials = new UsernamePasswordCredentials("username", "password");
httpClient.getCredentialsProvider().setCredentials(newAuthScope("www.example.org",8001), 
		credentials);

5. Execute GET request

Now it’s time to execute request on client and retrieve the response:

HttpResponse response = httpClient.execute(httpget);

6. Handle all exceptions

There are several exceptions thrown by methods above. Your IDE will enforce you to handle them. Please see my implementation in snippet below.

7. See my complete source code :)

If you have problems building it up all together, here is my full implementation:

import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.DefaultHttpClient;

public void sendTestMessage() {
    try{
        // build URL
	URIBuilder builder =newURIBuilder();
	builder.setScheme("http").setHost("www.example.org").setPort(8001).setPath("/rest/sendMessage").setParameter("msg","hello").setParameter("from","jack").setParameter("to","bill");
	URI url = builder.build();

	HttpGet httpget =newHttpGet(url);// Execute HTTP Get Request
	DefaultHttpClient httpClient =new DefaultHttpClient();
	Credentials credentials =new UsernamePasswordCredentials("username", "password");
	httpClient.getCredentialsProvider().setCredentials(newAuthScope("www.example.org",8001), 
				credentials);

	HttpResponse response = httpClient.execute(httpget);
    }catch(ClientProtocolException e) {
	e.printStackTrace();
    }catch(IOException e) {
	e.printStackTrace();
    }catch(URISyntaxException e) {
	e.printStackTrace();
    }
}

Did I help you?
I manage this blog and share my knowledge for free sacrificing my time. If you appreciate it and find this information helpful, please consider making a donation in order to keep this page alive and improve quality

Donate Button with Credit Cards

Thank You!





Android push notification tutorial: C2DM 3rd party Application server implementation

26 05 2012

Here is how to implement C2DM server part in java for Google C2DM service using apache http-client and http-impl apache libs for REST webservice calls:

Server Authentication in C2DM Service

In order to send message to C2DM Server, first you have to authenticate your server with valid google account. Here is how to do it.

Params:

private static final String PARAM_VALUE_SERVICE = "ac2dm";
private static final String PARAM_AUTH_SERVICE = "service";
private static final String PARAM_VALUE_ACCOUNT_TYPE = "GOOGLE";
private static final String PARAM_AUTH_ACCOUNT_TYPE = "accountType";
private static final String PARAM_AUTH_PASSWD = "Passwd";
private static final String PARAM_AUTH_EMAIL = "Email";
private static final String PARAM_AUTH_SOURCE = "source";

Authentication method (try-catch omitted):

public static String getAuthToken(String email, String password){
	HttpClient httpClient = new DefaultHttpClient();
	HttpPost httppost = new HttpPost("https://www.google.com/accounts/ClientLogin");
	// Add your data
	List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
	nameValuePairs.add(new BasicNameValuePair(PARAM_AUTH_EMAIL, email));
	nameValuePairs.add(new BasicNameValuePair(PARAM_AUTH_PASSWD, password));
	nameValuePairs.add(new BasicNameValuePair(PARAM_AUTH_ACCOUNT_TYPE, PARAM_VALUE_ACCOUNT_TYPE));
	nameValuePairs.add(new BasicNameValuePair(PARAM_AUTH_SOURCE, "your-app-name"));
	nameValuePairs.add(new BasicNameValuePair(PARAM_AUTH_SERVICE, PARAM_VALUE_SERVICE));

	httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
	httppost.setHeader("Content-Type", "application/x-www-form-urlencoded");

	// Execute HTTP Post Request
	HttpResponse response = httpClient.execute(httppost);

	// Read the response
	BufferedReader reader = new BufferedReader(
			new InputStreamReader((response.getEntity().getContent())));
	String line = null;
	String auth_key = null;
	while ((line = reader.readLine()) != null) {
		if (line.startsWith("Auth=")) {
			auth_key = line.substring(5);
		}
	}
}

Authentication method call:

String auth_token = AuthenticationUtil.getAuthToken(ConstantsC2dm.C2DM_SENDER_EMAIL, ConstantsC2dm.C2DM_SENDER_EMAIL_PASSWORD);

Sending message to C2DM Server

To send push notification to C2DM Server you just need to make Http Post REST request to given URL. Some parameters are necessary to add (specified here):

public static final String PARAM_REGISTRATION_ID = "registration_id";
public static final String PARAM_DELAY_WHILE_IDLE = "delay_while_idle";
public static final String PARAM_COLLAPSE_KEY = "collapse_key";
private static final String DATA_PREFIX = "data.";

Here is the message sender method code. In my message I pass two parameters: questionId and validUntil. App reads questionId and performs further actions (download data from App server based on given questionId. Note that Google C2DM service does not take part in further downloads. It just triggers an app to perform some action):

public static int sendMessage(String auth_token, String registrationId,
		String questionId, String validUntil) throws ClientProtocolException, IOException{
	// Create a new HttpClient and Post Header
	HttpClient httpClient = new DefaultHttpClient();
	HttpPost httppost = new HttpPost("https://android.clients.google.com/c2dm/send");

	// Add your data
	List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
	nameValuePairs.add(new BasicNameValuePair(PARAM_REGISTRATION_ID, registrationId));
	nameValuePairs.add(new BasicNameValuePair(PARAM_COLLAPSE_KEY, "0"));
	nameValuePairs.add(new BasicNameValuePair(DATA_PREFIX + ConstantsC2dm.PARAM_QUESTION_ID, questionId));
	nameValuePairs.add(new BasicNameValuePair(DATA_PREFIX + ConstantsC2dm.PARAM_VALID_UNTIL, validUntil));

	httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
	httppost.addHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
	httppost.addHeader("Authorization", "GoogleLogin auth="+ auth_token);

	// Execute HTTP Post Request
	HttpResponse response = httpClient.execute(httppost);

	return response.getStatusLine().getStatusCode();
}

Hope that helps.

Summary

This code can be used as a servlet, standalone java application, in jsp page or in Bean. Message sender can be triggered by some event (like incoming email in GMail) or by hand (after clicking a button on a webpage).

If any more help is needed – do not hesitate to ask.

Did I help you?
I manage this blog and share my knowledge for free sacrificing my time. If you appreciate it and find this information helpful, please consider making a donation in order to keep this page alive and improve quality

Donate Button with Credit Cards

Thank You!








%d bloggers like this: