Secure Spring REST API using Basic Authentication
What is Basic Authentication?
Traditional authentication approaches like login pages or session identification are good for web based clients involving human interaction but does not really fit well when communicating with [REST] clients which may not even be a web application. Think of an API over a server which tries to communicate with another API on a totally different server, without any human intervention.
Basic Authentication provides a solution for this problem, although not very secure. With Basic Authentication, clients send it’s Base64 encoded credentials with each request, using HTTP [Authorization] header . That means each request is independent of other request and server may/does not maintain any state information for the client, which is good for scalability point of view.
Shown below is the sample code for preparing the header.
String plainClientCredentials="myusername:mypassword";
String base64ClientCredentials = new String(Base64.encodeBase64(plainClientCredentials.getBytes())); HttpHeaders headers = getHeaders();
headers.add("Authorization", "Basic " + base64ClientCredentials);
which may in turn produce something like:Authorization : Basic bXktdHJ1c3RlZC1jbGllbnQ6c2VjcmV0...
This header will be sent with ech request. Since Credentials [Base 64 encoded, not even encrypted] are sent with each request, they can be compromised. One way to prevent this is using HTTPS in conjunction with Basic Authentication.
Basic Authentication & Spring Security
With two steps, you can enable the Basic Authentication in Spring Security Configuration.
1. Configure httpBasic
: Configures HTTP Basic authentication. [http-basic in XML]
2. Configure authentication entry point with BasicAuthenticationEntryPoint
: In case the Authentication fails [invalid/missing credentials], this entry point will get triggered. It is very important, because we don’t want [Spring Security default behavior] of redirecting to a login page on authentication failure [ We don't have a login page].
Shown below is the complete Spring Security configuration with httpBasic and entry point setup.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy; @Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private static String REALM="MY_TEST_REALM"; @Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("bill").password("abc123").roles("ADMIN");
auth.inMemoryAuthentication().withUser("tom").password("abc123").roles("USER");
} @Override
protected void configure(HttpSecurity http) throws Exception { http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/**").hasRole("ADMIN")
.and().httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint())
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//We don't need sessions to be created.
} @Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint(){
return new CustomBasicAuthenticationEntryPoint();
} /* To allow Pre-flight [OPTIONS] request from browser */
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
}
And the actual Entry point, which will get triggerd if authentication failed. You can customize it to send custom content in response.
import java.io.IOException;
import java.io.PrintWriter; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; public class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint { @Override
public void commence(final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException) throws IOException, ServletException {
//Authentication failed, send error response.
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.addHeader("WWW-Authenticate", "Basic realm=" + getRealmName() + ""); PrintWriter writer = response.getWriter();
writer.println("HTTP Status 401 : " + authException.getMessage());
} @Override
public void afterPropertiesSet() throws Exception {
setRealmName("MY_TEST_REALM");
super.afterPropertiesSet();
}
}
That’s all you need to configure basic security. Now let’s see everything in action, with our good old REST API
REST API
Simple Spring REST API, which serves user(s). A client can perform CRUD operations using Standard HTML verbs, compliant with REST style.
import java.util.List; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder; import com.websystique.springmvc.model.User;
import com.websystique.springmvc.service.UserService; @RestController
public class HelloWorldRestController { @Autowired
UserService userService; //Service which will do all data retrieval/manipulation work //-------------------Retrieve All Users-------------------------------------------------------- @RequestMapping(value = "/user/", method = RequestMethod.GET)
public ResponseEntity<List<User>> listAllUsers() {
List<User> users = userService.findAllUsers();
if(users.isEmpty()){
return new ResponseEntity<List<User>>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND
}
return new ResponseEntity<List<User>>(users, HttpStatus.OK);
} //-------------------Retrieve Single User-------------------------------------------------------- @RequestMapping(value = "/user/{id}", method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON_VALUE,MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<User> getUser(@PathVariable("id") long id) {
System.out.println("Fetching User with id " + id);
User user = userService.findById(id);
if (user == null) {
System.out.println("User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<User>(user, HttpStatus.OK);
} //-------------------Create a User-------------------------------------------------------- @RequestMapping(value = "/user/", method = RequestMethod.POST)
public ResponseEntity<Void> createUser(@RequestBody User user, UriComponentsBuilder ucBuilder) {
System.out.println("Creating User " + user.getName()); if (userService.isUserExist(user)) {
System.out.println("A User with name " + user.getName() + " already exist");
return new ResponseEntity<Void>(HttpStatus.CONFLICT);
} userService.saveUser(user); HttpHeaders headers = new HttpHeaders();
headers.setLocation(ucBuilder.path("/user/{id}").buildAndExpand(user.getId()).toUri());
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
} //------------------- Update a User -------------------------------------------------------- @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
public ResponseEntity<User> updateUser(@PathVariable("id") long id, @RequestBody User user) {
System.out.println("Updating User " + id); User currentUser = userService.findById(id); if (currentUser==null) {
System.out.println("User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
} currentUser.setName(user.getName());
currentUser.setAge(user.getAge());
currentUser.setSalary(user.getSalary()); userService.updateUser(currentUser);
return new ResponseEntity<User>(currentUser, HttpStatus.OK);
} //------------------- Delete a User -------------------------------------------------------- @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public ResponseEntity<User> deleteUser(@PathVariable("id") long id) {
System.out.println("Fetching & Deleting User with id " + id); User user = userService.findById(id);
if (user == null) {
System.out.println("Unable to delete. User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
} userService.deleteUserById(id);
return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
} //------------------- Delete All Users -------------------------------------------------------- @RequestMapping(value = "/user/", method = RequestMethod.DELETE)
public ResponseEntity<User> deleteAllUsers() {
System.out.println("Deleting All Users"); userService.deleteAllUsers();
return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
} }
Running the application
Build and deploy the application [on tomcat e.g]. Run it and test it using two different clients.
Using Client 1: Postman
Send a request to get the list of users. You would get a 401 instead.
Now select type as ‘Basic Auth’ from dropdown, fill in username/password [bill/abc123], click on ‘update request’.
Click on Headers tab. You should see the new header. Let’s add ‘accept’ header as well to enforce json response.
Now send the request. You should see the list of users in response this time.
Using Client 2: RestTemplate based Java Application
Let’s use a full fledged Java client to access our REST API. We will be sending request using Spring RestTemplate
. Take special note about how we are setting up the headers for each request, before sending the request.
import java.net.URI;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List; import org.apache.commons.codec.binary.Base64;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate; import com.websystique.springmvc.model.User; public class SpringRestClient { public static final String REST_SERVICE_URI = "http://localhost:8080/SecureRESTApiWithBasicAuthentication"; /*
* Add HTTP Authorization header, using Basic-Authentication to send user-credentials.
*/
private static HttpHeaders getHeaders(){
String plainCredentials="bill:abc123";
String base64Credentials = new String(Base64.encodeBase64(plainCredentials.getBytes())); HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Credentials);
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
return headers;
} /*
* Send a GET request to get list of all users.
*/
@SuppressWarnings("unchecked")
private static void listAllUsers(){
System.out.println("\nTesting listAllUsers API-----------");
RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeaders());
ResponseEntity<List> response = restTemplate.exchange(REST_SERVICE_URI+"/user/", HttpMethod.GET, request, List.class);
List<LinkedHashMap<String, Object>> usersMap = (List<LinkedHashMap<String, Object>>)response.getBody(); if(usersMap!=null){
for(LinkedHashMap<String, Object> map : usersMap){
System.out.println("User : id="+map.get("id")+", Name="+map.get("name")+", Age="+map.get("age")+", Salary="+map.get("salary"));;
}
}else{
System.out.println("No user exist----------");
}
} /*
* Send a GET request to get a specific user.
*/
private static void getUser(){
System.out.println("\nTesting getUser API----------");
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> request = new HttpEntity<String>(getHeaders());
ResponseEntity<User> response = restTemplate.exchange(REST_SERVICE_URI+"/user/1", HttpMethod.GET, request, User.class);
User user = response.getBody();
System.out.println(user);
} /*
* Send a POST request to create a new user.
*/
private static void createUser() {
System.out.println("\nTesting create User API----------");
RestTemplate restTemplate = new RestTemplate();
User user = new User(,"Sarah",,);
HttpEntity<Object> request = new HttpEntity<Object>(user, getHeaders());
URI uri = restTemplate.postForLocation(REST_SERVICE_URI+"/user/", request, User.class);
System.out.println("Location : "+uri.toASCIIString());
} /*
* Send a PUT request to update an existing user.
*/
private static void updateUser() {
System.out.println("\nTesting update User API----------");
RestTemplate restTemplate = new RestTemplate();
User user = new User(,"Tomy",, );
HttpEntity<Object> request = new HttpEntity<Object>(user, getHeaders());
ResponseEntity<User> response = restTemplate.exchange(REST_SERVICE_URI+"/user/1", HttpMethod.PUT, request, User.class);
System.out.println(response.getBody());
} /*
* Send a DELETE request to delete a specific user.
*/
private static void deleteUser() {
System.out.println("\nTesting delete User API----------");
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> request = new HttpEntity<String>(getHeaders());
restTemplate.exchange(REST_SERVICE_URI+"/user/3", HttpMethod.DELETE, request, User.class);
} /*
* Send a DELETE request to delete all users.
*/
private static void deleteAllUsers() {
System.out.println("\nTesting all delete Users API----------");
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> request = new HttpEntity<String>(getHeaders());
restTemplate.exchange(REST_SERVICE_URI+"/user/", HttpMethod.DELETE, request, User.class);
} public static void main(String args[]){ listAllUsers(); getUser(); createUser();
listAllUsers(); updateUser();
listAllUsers(); deleteUser();
listAllUsers(); deleteAllUsers();
listAllUsers();
}
}
And the output is :
Testing listAllUsers API-----------
User : id=, Name=Sam, Age=, Salary=70000.0
User : id=, Name=Tom, Age=, Salary=50000.0
User : id=, Name=Jerome, Age=, Salary=30000.0
User : id=, Name=Silvia, Age=, Salary=40000.0 Testing getUser API----------
User [id=, name=Sam, age=, salary=70000.0] Testing create User API----------
Location : http://localhost:8080/SecureRESTApiWithBasicAuthentication/user/5 Testing listAllUsers API-----------
User : id=, Name=Sam, Age=, Salary=70000.0
User : id=, Name=Tom, Age=, Salary=50000.0
User : id=, Name=Jerome, Age=, Salary=30000.0
User : id=, Name=Silvia, Age=, Salary=40000.0
User : id=, Name=Sarah, Age=, Salary=134.0 Testing update User API----------
User [id=, name=Tomy, age=, salary=70000.0] Testing listAllUsers API-----------
User : id=, Name=Tomy, Age=, Salary=70000.0
User : id=, Name=Tom, Age=, Salary=50000.0
User : id=, Name=Jerome, Age=, Salary=30000.0
User : id=, Name=Silvia, Age=, Salary=40000.0
User : id=, Name=Sarah, Age=, Salary=134.0 Testing delete User API---------- Testing listAllUsers API-----------
User : id=, Name=Tomy, Age=, Salary=70000.0
User : id=, Name=Tom, Age=, Salary=50000.0
User : id=, Name=Silvia, Age=, Salary=40000.0
User : id=, Name=Sarah, Age=, Salary=134.0 Testing all delete Users API---------- Testing listAllUsers API-----------
No user exist----------
Secure Spring REST API using Basic Authentication的更多相关文章
- Web API: Security: Basic Authentication
原文地址: http://msdn.microsoft.com/en-us/magazine/dn201748.aspx Custom HttpModule code: using System; u ...
- 一个HTTP Basic Authentication引发的异常
这几天在做一个功能,其实很简单.就是调用几个外部的API,返回数据后进行组装然后成为新的接口.其中一个API是一个很奇葩的API,虽然是基于HTTP的,但既没有基于SOAP规范,也不是Restful风 ...
- Web API 基于ASP.NET Identity的Basic Authentication
今天给大家分享在Web API下,如何利用ASP.NET Identity实现基本认证(Basic Authentication),在博客园子搜索了一圈Web API的基本认证,基本都是做的Forms ...
- HTTP Basic Authentication认证(Web API)
当下最流行的Web Api 接口认证方式 HTTP Basic Authentication: http://smalltalllong.iteye.com/blog/912046 什么是HTTP B ...
- Basic Authentication in ASP.NET Web API
Basic authentication is defined in RFC 2617, HTTP Authentication: Basic and Digest Access Authentica ...
- HTTP Basic Authentication
Client端发送请求, 要在发送请求的时候添加HTTP Basic Authentication认证信息到请求中,有两种方法:1. 在请求头中添加Authorization: Authoriz ...
- HTTP Basic Authentication认证的各种语言 后台用的
访问需要HTTP Basic Authentication认证的资源的各种语言的实现 无聊想调用下嘀咕的api的时候,发现需要HTTP Basic Authentication,就看了下. 什么是HT ...
- Setting Up Swagger 2 with a Spring REST API
Last modified: August 30, 2016 REST, SPRING by baeldung If you're new here, join the next webinar: & ...
- HTTP Basic Authentication认证
http://smalltalllong.iteye.com/blog/912046 ******************************************** 什么是HTTP Basi ...
随机推荐
- oracle 性能优化建议
原则一:注意WHERE子句中的连接顺序: ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHER ...
- windows 配置环境变量快捷方式
在 Windows 设置环境变量 在环境变量中添加软件A的目录: 在命令提示框中(cmd) : 输入 path %path%;C:\A, 按下"Enter". 注意: C:\A是软 ...
- 16.同步类容器Collections.synchronized
voctor动态数组.同步类容器,底层实现基于:Collections.synchronized package demo5; import java.util.ArrayList; import j ...
- 本地环境 XAMPP+phpStorm+XDebug+chrome配置和断点调试
不明白phpStorm+XAMPP+chrome组合的phpStorm配置XDebug的断点调试,很多种网页办法都看过了,可用,但没达预期.QQ群问,一个大牛很奇怪我都配置了怎么还不正确,很干脆地说远 ...
- 一步一步学RenderMonkey(3)——改良Phong光照模型 【转】
转载请注明出处: http://blog.csdn.net/tianhai110 改良后的Phong光照模型: 上一节实现的phong镜面光照模型,如果固定光源,移动视点(及matView 关联为ma ...
- Python 面试中 8 个必考问题(转载)
Python 面试中 8 个必考问题 1.下面这段代码的输出结果是什么?请解释. def extendList(val, list=[]): list.append(val) return list ...
- Python绘制直方图 Pygal模拟掷骰子
#coding=utf-8 from random import randint class Die(): """骰子类""" def __ ...
- 猫猫学iOS之UITextField右边设置图片,以及UITextField全解
猫猫分享,必须精品 原创文章.欢迎转载.转载请注明:翟乃玉的博客 地址:http://blog.csdn.net/u013357243 效果: 封装好的方法: 猫猫封装的一个小方法,简单共享出来,方便 ...
- 求函数 y=x^2-2x-3/2x^2+2x+1 的极值
解:展开函数式得到2yx2+2xy+y=x2-2x-3 继而得到(2y-1)x2+(2y+2)x+(y+3)=0 将上式看作x的二次方程,y组成了方程的系数. 只有Δ>=0,x才有实值. Δ=( ...
- Java补漏(一)
第一章前言 在学长的建议下,为了弥补之前学Java漏下的或者不是非常清楚的知识点,买了本蛮好的教科书-<Java学习笔记(JDK6)>,正式又一次学习.为了记下一些让我恍然大悟的知识 ...