Spring Security, Rest Authentication and CSRF - java

I would like use authentication with my application.
I hava a Spring MVC app and Spring Security applied. Against browser, it is working fine.
It means, I authenticate a user to my app and use web page.
Now, I want to use rest. I added on my unsecure controller method #ResponseBody and I receive response in json.
But how to connect to my application with user and password with RestTemplate ?
My code in RestClient is (for test) :
public void unsecureProfileTest() {
String url = articleServiceUrl + "unsecure/profile/test.json";
url = articleServiceUrl + "secure/profile/wiew.json";
HttpEntity<Object> entity = new HttpEntity<Object>(getHeaders("user:userpassword"));
Object s = restTemplate.exchange(url, HttpMethod.GET, entity, Object.class);
}
static HttpHeaders getHeaders(String auth) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON,
MediaType.TEXT_HTML));
byte[] encodedAuthorisation = Base64.encode(auth.getBytes());
headers.add("Authorization", "Basic "
+ new String(encodedAuthorisation));
return headers;
}
My SecurityConfig :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/*").permitAll().and()
.formLogin().successHandler(successHandler)
.defaultSuccessUrl("/").failureHandler(failureHandler)
.failureUrl("/login?error=true").permitAll().and().logout()
.permitAll();
http.authorizeRequests().antMatchers("/resources/**").permitAll();
http.authorizeRequests().antMatchers("/welcome").permitAll();
http.authorizeRequests().antMatchers("/unsecure/**").permitAll();
http.authorizeRequests().antMatchers("/secure/*").authenticated();
http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated();
}
The result is : Access is denied.
I guess the problem comming from authentication from restTemplate but how can I authenticate ?
My second question is regarding csrf who is disabled but I want to enable it (my forms use it)
I'm using Spring 4.0 and Spring Security 3.2
EDIT
I updated my code with
String url = articleServiceUrl + "unsecure/profile/test.json";
url = articleServiceUrl + "secure/profile/wiew.json";
HttpEntity<Object> entity = new HttpEntity<Object>(getHeaders("{user:userpassword, password:userpassword}"));
Object s = restTemplate.exchange(url, HttpMethod.GET, entity, Object.class);
I receive a code 302
EDIT 18022014 - 16:46
I updated to
String url = articleServiceUrl + "login?username=user&password=userpassword";
HttpEntity entity restTemplate;exchange(url, HTTPMethod.POST,null, HttpEntity.class)
system.out.println(entity);
In log of web server, I received a success message (see userdetails on "user").
Now, I would like use authentication to access to other url ("secure/profile/view.json")
How to keep authentication ?
Thank you

I have been playing with spring security and spring boot REST application and I created my own MapCsrfTokenRepository that I used instead of default HttpSessionCsrfTokenRepository.
Then you can enable csrf for your rest URIs with
http.csrf().csrfTokenRepository(tokenRepository)
The main idea is to return new CSRF_TOKEN when client access /login resource with GET, because no csrf token is needed for GET. And then client has to use this token in next calls.
Example is on github

Related

How to add multiple authorization headers in spring boot resttemplate

I have the following common configuration in my Spring Boot application:
private RestTemplate getRestTemplate(String username, String pwd){
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(username, pwd));
return restTemplate;
}
Right now I am using BasicAuthenticationInterceptor to add basic auth credentials to the Http 'Authorization' headers.
My new requirement asks to add another Http 'Authorization' header with a OAuth/JWT token. So I added something like this below:
headers.set(HttpHeaders.AUTHORIZATION, escape(token));
But as I was using Spring's BasicAuthenticationInterceptor when I add token as "Authorization" header because of the if condition in the spring BasicAuthenticationInterceptor class it is not adding the basic auth credentials. Please find below if condition for more info:
public class BasicAuthenticationInterceptor implements ClientHttpRequestInterceptor {
private final String encodedCredentials;
public BasicAuthenticationInterceptor(String username, String password) {
this(username, password, (Charset)null);
}
public BasicAuthenticationInterceptor(String username, String password, #Nullable Charset charset) {
this.encodedCredentials = HttpHeaders.encodeBasicAuth(username, password, charset);
}
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpHeaders headers = request.getHeaders();
if (!headers.containsKey("Authorization")) { /* here it will not add if I already have an Authorization header*/
headers.setBasicAuth(this.encodedCredentials);
}
return execution.execute(request, body);
}
}
So I was thinking may be I need to write my own custom Interceptor but before doing so I wanted to see if there is already a existing Interceptor that can fulfill my request. BTW, I was hoping to use BasicAuthorizationInterceptor but is deprecated in 5.3.9(my current spring version).
And in a sidenote, if I am going to write a new interceptor (if that is what you suggest.) then I also wanted to add the token auth header in that custom interceptor.
Any input or suggestion is appreciated.

create a Rest API with customized header request

I'm falling into a problem this morning with a custom request between two application, what i need to do is to let application able to talk eachother with two Rest API cause i need to do some actions on the first application by the second. The two applications are developed with springboot.
Suppose to call this two applications admin and superadmin
superadmin send a request with a RestAPI and a customized header -> name = key value = 1234
admin recieve the request and first of all check if the header is present or not, after that the header is finded it can proceed to do all the task.
Here's the code that i've developed :
SUPERADMIN :
#PostMapping(value="/test_api_header")
public ResponseEntity<String> test_API(#RequestParam String url) {
RestTemplate template = new RestTemplate();
URI targetUrl = UriComponentsBuilder.fromUriString(url) // Build the base link
.path("/test_API") // Add path
.build() // Build the URL
.encode() // Encode any URI items that need to be encoded
.toUri(); // Convert to URI
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Content-Type", "application/json");
headers.add("superadminKey", "123456abc");
// build the request
ResponseEntity<String> entity = template.exchange(targetUrl, HttpMethod.GET, new HttpEntity<String>(headers), String.class);
return entity;
}
ADMIN :
#Value("123456abc")
private String saKey;
#GetMapping(value = "/superadmin/test_API")
public String test_API(HttpServletRequest request) {
if (request.getHeader("superadminKey") == saKey) {
return "Finally";
} else {
return "Nothing to do, header not present";
}
}
The SUPERADMIN is able to communicate with the RESTApi in the ADMIN application, in fact on postman i received the answer : Nothing to do, header not present, but i really cannot be able to set that customized header in the superadmin request cause i cannot found it also on postman request in the section "headers".
I've seen that i could also create a customized API Key for this special case, but really don't know how it works, if someone could help me I would be very grateful!

Different handling of basic authentication in rest client and web app in browser

I have angular frontend and spring backend. I'm using spring security to hande http basic authentication.
I noticed strange difference in behaviour using advanced rest client(or any other) and angular web app.
For tests I disabled my httpInterceptor so it is not including "Authorisation: Basic foobardoofab==" header. So spring backend should response with Unauthorised error right? Well... Sometimes it does and sometimes in doesn't.
Flow is like this:
I use my authenticate method to log in:
authenticate(credentials, callback) {
const headers = new HttpHeaders(credentials ? {
authorization : 'Basic ' + btoa(credentials.username + ':' + credentials.password)
} : {});
this.http.get('http://localhost:8080/login', {headers: headers}).subscribe(response => {
if (response['name']) {
let authString = 'Basic ' + btoa(credentials.username + ':' + credentials.password);
sessionStorage.setItem('basicauth', authString);
sessionStorage.setItem('username', response['name']);
sessionStorage.setItem('role', response['authorities']);
} else {
// this.authenticated = false;
}
return callback && callback();
});
}
Those values are stored only for getting access inside angular app.
Now I go to http://localhost:4200/events
In this component I have GET request in ngOninit
ngOnInit() {
this.http.get<Array<MyEvent>>('http://localhost:8080/events').subscribe((response) => {
this.events = response;});
}
Because my httpinterceptor is disabled i dont add this "basicauth" stored in session so spring should respond with unathorised. And it does:
{"timestamp":"2019-04-26T13:08:19.167+0000","status":401,"error":"Unauthorized","message":"Unauthorized","path":"/events"}
And I think it is good and expected behaviour.(Since i'm not sending basicauth Im not able to go through spring security.
But there is this second case when im testing it with some rest clients (advanced rest client)
I send GET request to http://localhost:8080/login with "Authorisation: Basic foobar" added to headers and I got 200 OK response from spring.
Now i send get request to http://localhost:8080/events WITHOUT any headers included and I still got 200 OK response and access to returned objects. (While through angular app it respondend with Unauthorised error)
To get unathorised error i have to sent GET request to http://localhost:8080/logout (through angular app im not sending it anywhere)
So my question is why those two cases are different in behaviour?
I will also show my WebSecurityConifg:
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().httpBasic()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/index.html", "/", "/home", "/login").permitAll()
.anyRequest().authenticated().and()
.logout().invalidateHttpSession(true).deleteCookies("JSESSIONID").and()
.csrf().disable();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new
UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}

Spring boot consuming rest api in Gradle

I'm completely new to Java and trying to consume a rest API with Spring Boot in Gradle, so far I've managed to make a very basic get request to display a message like below
#RestController
public class HelloController {
#RequestMapping(value = "/hello", method = RequestMethod.GET)
public String printWelcome(ModelMap model) {
model.addAttribute("message", "Hello");
return "hello";
}
}
Now, how to extend this get request to make HTTP requests consume an endpoint based on RestTemplate, assuming this is my endpoint that i want to consume like below:
RestTemplate restTemplate = new RestTemplate(); ResponseEntity response = restTemplate.getForEntity("http://aws.services.domain.com/country/id", String.class);
Finally, I want to achieve authorized HTTP GET requests by adding a token Bearer in the Authorization header.
Thank you for answers and suggestions in advance
If you want to add a header, you have to use exchange or execute method.
So, in your case:
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Your Bearer Token");
HttpEntity entity = new HttpEntity(headers);
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.GET, entity, String.class, param);

spring mvc Proxy server - maintaining url context (contextpath)

I have the below piece spring REST controller class.
#RestController
#RequestMapping("/global")
public class ProxyController extends BaseController{
#RequestMapping(value = "/**")
public ResponseEntity<String> proxy(HttpServletRequest request, HttpServletResponse response ) throws Exception {
try {
String restOfTheUrl = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
URL uri = new URL("https://myrealserver" +
restOfTheUrl);
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> httpEntity = new HttpEntity<>(headers);
RestTemplate restTemplate = new RestTemplate();
return resp;
} catch (Exception e) {
logger.error("Error ", e);
return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
What I am trying to achieve here is to hide a server behind a proxy, which blindly forwards requests to the server.
This piece of code is invoked with url
https://myproxyserver/myapp1/end/point1
which in turn returns an html page with few clickable links. Now when the user clicks I am expecting the link to be invoked as
https://myproxyserver/myapp1/end/point2
Where as actually the endpoint invoked is
https://myproxyserver/end/point2
In the html page returned by the actual server, the path is end/point2 and has no mention of myapp1. So on click on those links my context changes to https://myproxyserver/end/point2 instead of https://myproxyserver/myapp1/end/point2
How do I ensure that the root context is always https://myproxyserver/myapp1 and not https://myproxyserver ?
You want to get your server context path. this is sample code.
like this :
public static String getServerNameAndContextPath(HttpServletRequest req) {
return "https://" + req.getServerName() + req.getContextPath();
}
Finally I resolved the problem by taking what D D suggested. I scanned through the whole response body, fortunately I had a pattern that I could use to scan and appended the context of the url to where ever required. That resolved the problem for me this problem.

Categories