Get _csrf in spring controller - java

How can I get _csrf object (?!) in spring controller? I've configured Spring Security and can get ${_csrf} request attribute in jsp files.
I've tried:
CsrfToken _csrf = (CsrfToken) session.getAttribute("CsrfToken");
CsrfToken _csrf = (CsrfToken) session.getAttribute("_csrf");
the result is null;
Thanks in advance!

In debug I saw a session attribute with a key "org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN". I viewed the HttpSessionCsrfTokenRepository class. It has a method for loading token from incoming HttpServletRequest object.
Finally this worked for me:
CsrfToken token = new HttpSessionCsrfTokenRepository().loadToken(request);
I will be grateful if someone explains me how this works.

To access the CSRF token in a Spring controller you can simply do this:
#Controller
public class FooController {
#RequestMapping("/foo")
public void foo(CsrfToken token) {
// Do whatever with token
}
}
Spring will automatically detect that you want the token, based on the type of the parameter, and inject it into your method.
This works since at least Spring Security 5.0 and if you are using Spring Boot or have the #EnableWebSecurity annotation in your configuration.
Documentation

Try:
CsrfToken token = (CsrfToken) session.getAttribute(CsrfToken.class.getName());

I think in your earlier attempts, you were mixing up the CSRF parameter name with the session attribute name, and also trying CsrfToken.class.getName() which may or may not have been used in earlier versions. So simply, you had the right idea but the wrong key.
If you look at the source code for HttpSessionCsrfTokenRepository, you'll see it defines the following defaults:
private String parameterName = DEFAULT_CSRF_PARAMETER_NAME;
private String headerName = DEFAULT_CSRF_HEADER_NAME;
private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME;
The first one is the parameter name for when the token comes as a POST parameter, the second is the header name for when it comes in the request header, and the third is the key for storing it in the session. The method loadToken doesn't actually get the token from the request object - it gets the session object from the request and then looks up the token, which it earlier stored with the key defined by sessionAttributeName.

This also works if you want to get it directly from the session
CsrfToken token = (CsrfToken) session.getAttribute("org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN");

You can have HttpServletRequest instance inside Controller's resource method as a parameter. Using this request object you can get the csrf token easily.
#Controller
#RequestMapping("/api/v1/test")
public class TestController {
#GetMapping
public String test(HttpServletRequest request) {
CsrfToken csrfToken =
(CsrfToken)httpServletRequest.getAttribute(CsrfToken.class.getName());
if(csrfToken != null)
return csrfToken.getToken();
return "Token Not Found";
}
}
Csrf Token value used to create using java.util.UUID class, as following:-
UUID.randomUUID().toString();
Check org.springframework.security.web.csrf.CookieCsrfTokenRepository and org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository classes which are implementation of CsrfTokenRepository, inside spring-security-web-X.X.X.RELEASE.jar.
If you want to have the CSRF token in cookies with response at client side (say browser) then:-
#Configuration
#EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.....
.forLogin();
}
}
Enable the csrf and use CookieCsrfTokenRepository csrf token repository.
Fetch the token from cookie with cookie name - "XSRF-TOKEN"
Use this token in another requests [except GET, HEAD, TRACE, OPTIONS] header with header key as X-XSRF-TOKEN

Related

Spring Boot + Keycloak: optional auth endpoint

I'm trying to configure a Spring Boot application with Keycloak to have an endpoint that is both accessible for authenticated and unauthenticated users. For authenticated users, I want to return some extra information. Here is a simple example of what I'm trying to achieve:
#RestController
public class HelloController {
#GetMapping("/")
public String index(Principal principal) {
KeycloakPrincipal keycloakPrincipal = (KeycloakPrincipal) principal;
if (keycloakPrincipal != null) {
return "Hello " + keycloakPrincipal.getKeycloakSecurityContext().getToken().getPreferredUsername();
} else {
return "Hello";
}
}
}
application.properties:
keycloak.securityConstraints[0].authRoles[0] = *
keycloak.securityConstraints[0].securityCollections[0].name = Hello
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /*
So far, I only got it to work for one of both cases. If I protect the endpoint using the security constraint above, the endpoint is only accessible to authenticated users. If I remove the security constraint, the endpoint is accessible for everyone, but then the principal will always be null.
Is it possible to achieve the intended behavior?
Have you tried something like Principal principal = SecurityContextHolder.getContext().getAuthentication();?
I believe the Principal as method parameter is only populated on secured endpoints but am unsure if it would exist in the SecurityContext. If not, you need to add a Filter to add it yourself.
I was able to solve the problem by calling the authenticate() method on the HttpServletRequest object. This will trigger the authentication process and will populate the user principal whenever possible. From the docs:
Triggers the same authentication process as would be triggered if the
request is for a resource that is protected by a security constraint.
To avoid triggering an authentication challenge, I pass in a dummy response object to the authenticate() call.

What is the correct way of retrieving an OAuth2 Bearer token in Spring

I am making service to service requests using Spring's WebClient that require an OAuth2 bearer token to be added as a header to the request. I Can do this relatively easily by creating an ExchangeFilterFunction that intercepts the request, retrieves an access token, adds it to the header, and continues on. Since this is not a user request, the SecurityContextHolder does not contain an Authentication that would hold an access token for me, so instead of retrieving from that, I would like to get an access token based on my Spring security configuration (currently defined in the spring.security.oauth2.client.registration and provider properties).
The way I'm doing this now is by Autowiring an OAuth2ClientContext and then getting the AccessToken from it. Reducing the code only to what I care about for this question, I have:
#Component
public class OAuth2WebClientFilter implements ExchangeFilterFunction {
#Autowired
private OAuth2ClientContext oAuth2ClientContext;
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
// simple retrieval of the token
String oAuth2Token = oAuth2ClientContext.getAccessToken().getValue();
// adding the token to the header of the request
request = ClientRequest.from(request).header(HttpHeaders.AUTHORIZATION, "Bearer " + oAuth2Token).build();
return next.exchange(request);
}
}
This does exactly what I want it to. However, I have recently upgraded spring-security-oauth2 to 2.5.0.RELEASE, and it is saying the OAuth2ClientContext is deprecated, but I haven't found a simple replacement for this process. So is there still a way to get an access token in a relatively simple fashion like above, and if so, how?
Also note: this concept is used elsewhere in the project and not just for the WebClient, so I'm looking to see how to properly replace an injected OAuth2ClientContext. Thanks!
Spring Security provides an exchange filter function called ServletOAuth2AuthorizedClientExchangeFilterFunction.
The ServletOAuth2AuthorizedClientExchangeFilterFunction provides a
simple mechanism for requesting protected resources by using an
OAuth2AuthorizedClient and including the associated OAuth2AccessToken
as a Bearer Token. It directly uses an OAuth2AuthorizedClientManager
and therefore inherits the following capabilities:
An OAuth2AccessToken will be requested if the client has not yet been
authorized.
authorization_code - triggers the Authorization Request redirect to
initiate the flow
client_credentials - the access token is obtained directly from the
Token Endpoint
password - the access token is obtained directly from the Token
Endpoint
If the OAuth2AccessToken is expired, it will be refreshed (or renewed)
if an OAuth2AuthorizedClientProvider is available to perform the
authorization
See https://docs.spring.io/spring-security/reference/servlet/oauth2/client/authorized-clients.html#oauth2Client-webclient-servlet for details.

How to check if url is protected or not in TomEE?

I'm trying to implement JWT based authentication in TomEE 8 (based on Tomcat 9).
I use org.glassfish.soteria:jakarta.security.enterprise:1.0.1 as an implementation of Jakarta Security.
Following this tutorial https://github.com/payara/Payara-Examples/blob/master/javaee/security-jwt-example/src/main/java/fish/payara/examples/security/JWTAuthenticationMechanism.java java class looks like this:
#RequestScoped
public class JWTAuthenticationMechanism implements HttpAuthenticationMechanism
private static final Logger LOGGER = Logger.getLogger(JWTAuthenticationMechanism.class.getName());
/**
* Access to the
* IdentityStore(AuthenticationIdentityStore,AuthorizationIdentityStore) is
* abstracted by the IdentityStoreHandler to allow for multiple identity
* stores to logically act as a single IdentityStore
*/
#Inject
private IdentityStoreHandler identityStoreHandler;
#Inject
private TokenProvider tokenProvider;
#Override
public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext context) {
LOGGER.log(Level.INFO, "validateRequest: {0}", request.getRequestURI());
// Get the (caller) name and password from the request
// NOTE: This is for the smallest possible example only. In practice
// putting the password in a request query parameter is highly insecure
String name = request.getParameter("name");
String password = request.getParameter("password");
String token = extractToken(context);
if (name != null && password != null) {
LOGGER.log(Level.INFO, "credentials : {0}, {1}", new String[]{name, password});
// validation of the credential using the identity store
CredentialValidationResult result = identityStoreHandler.validate(new UsernamePasswordCredential(name, password));
if (result.getStatus() == CredentialValidationResult.Status.VALID) {
// Communicate the details of the authenticated user to the container and return SUCCESS.
return createToken(result, context);
}
// if the authentication failed, we return the unauthorized status in the http response
return context.responseUnauthorized();
} else if (token != null) {
// validation of the jwt credential
return validateToken(token, context);
} else if (context.isProtected()) {
// A protected resource is a resource for which a constraint has been defined.
// if there are no credentials and the resource is protected, we response with unauthorized status
return context.responseUnauthorized();
}
// there are no credentials AND the resource is not protected,
// SO Instructs the container to "do nothing"
return context.doNothing();
}
...
User sends login request with username and password, identityStoreHandler validates it. Then we generate JWT token and send it back. Frontend attaches it to each next request.
This works.
validateRequest() is triggered for every request, protected or unprotected. As I understand it comes from the spec and is a desired behaviour.
Now, if token is expired and user sends request to not protected url - it will be rejected, because token is present and invalid.
I want first to check if url is protected or not and only if it's protected check for token presence and validity. But ((HttpMessageContext ) context.isProtected()) always returns false. In controller protected methods are annotated with #RolesAllowed and #PermitAll annotations. I tried this also with web.xml based constraints, but isProtected() is still false.
Why is it always false?
Update
I was under the wrong impression that annotation-based security and via descriptor (web.xml) are interchangeable.
If web.xml doesn't contain any security constraints - requests of unauthenticated user to resources with #RolesAllowed are rejected with 403 error, requests to resources with #PermitAll are fulfilled. This is strange behaviour, both require authenticated user and should be rejected.
If web.xml has auth-constraint tag then context.isProtected() returns true for that url-pattern.
But it still returns false for methods annotated with #RolesAllowed and #PermitAll, if path in those methods doesn't match url-pattern in web.xml.
According to this https://www.ibm.com/support/knowledgecenter/SSEQTP_8.5.5/com.ibm.websphere.base.doc/ae/twbs_jaxrs_impl_securejaxrs_annotations.html
Annotated constraints are additional to any configured security constraints. The JAX-RS runtime environment checks for annotated constraints after the web container runtime environment has checked for security constraints that are configured in the web.xml file.
Does it mean that container based security (Soteria) will not consider annotated methods as protected?

Springboot submit not finding controller

When I click submit on my HTML form it is not hitting the /greeting endpoint
#org.springframework.stereotype.Controller
#EnableAutoConfiguration
public class Controller {
#Autowired
assessment.PdfGeneratorUtil pdfGenaratorUtil;
#GetMapping ("/")
String home() {
return "static/assessment.html";
}
#PostMapping("/greeting")
public String greetingSubmit() {
Map<String,String> data = new HashMap<String,String>();
data.put("name","James");
Thymeleaf config
#Configuration
public class TheymeLeafConfiguration {
#Bean
public ClassLoaderTemplateResolver emailTemplateResolver(){
ClassLoaderTemplateResolver emailTemplateResolver=new ClassLoaderTemplateResolver();
emailTemplateResolver.setPrefix("templates/");
emailTemplateResolver.setTemplateMode("HTML5");
emailTemplateResolver.setSuffix(".html");
emailTemplateResolver.setTemplateMode("XHTML");
emailTemplateResolver.setCharacterEncoding("UTF-8");
emailTemplateResolver.setOrder(1);
return emailTemplateResolver;
}
}
And a snippet of the html:
<form action="#" th:action="#{/greeting}" method="post">
Which when I submit the form doesn't hit the breakpoint on the POST controller (the same line as the Map) and I get a 405 in the browser:
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'POST' not supported
The URL in the browser after submit is:
http://localhost:8080/assessment.html#
Project structure:
UPDATE
It is working with this code, in so far as it hits the Get controller method, and also the Post controller method on submission. I am not quite sure what changed.
Note I am not using #ModelAttribute at this point in time since I was testing that the controller methods are getting called at the correct times.
#GetMapping("/greeting")
String greetingForm() {
return "assessment";
}
#PostMapping("/greeting")
public String greetingSubmit() {
System.out.println(" HELLLOO HELLOOOO ");
Map<String,String> data = new HashMap<String,String>();
data.put("name","James");
try {
pdfGenaratorUtil.createPdf("greeting",data);
} catch (Exception e) {
e.printStackTrace();
}
return "finished";
}
With the minimal details that you have given in your code, I will try to answer your question.
The first problem that I see in your code is you are not passing any value to the method with postmapping, which I see at odd as post are used to create resource in rest.
So if you want to map the values received from input to some model data you need to do it using #ModelAttribute annotation.
#PostMapping("/greeting")
public String greetingSubmit(#ModelAttribute Greeting greeting) {
return "greeting";
}
See Spring boot form handling
Check whether your template webpages are found. I dont see any page mapping to show the result of this submission.
So you need to return some page name where your browser will forward the response of your form submission. So if you dont add that, spring find the method, executes the code, but dont know where to redirect. In this case as well it shows the 415 HttpStatus code.
So add something like below:
return "greeting";
So this above code will look for /templates/greeting.html as per your resolver configuration.
I assume you are also using spring security in your project (assumption from screenshot. [class SecurityConfig.])
If you use spring security, the CSRF filter is enabled by default in spring-boot. So either you need to disable it or add csrf param in your login form post request. (recommended). For the remaining forms, CSRF token will be automatically added to forms with hidden input.
To Disable CSRF:
In your spring securityConfig disable the csrf protection on HttpSecurity object something like below:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginProcessingUrl("/authentication")
.usernameParameter("j_username")
.passwordParameter("j_password").permitAll()
.and()
.csrf().disable()
}
Or To add csrf in your login form as below:
input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />

Getting exception when requesting XSRF-TOKEN using Spring RestTemplate

I'm trying to call a service which has CSRF enabled and all it's endpoints are configured to request authentication header from the user.
I'm using Spring RestTemplate as follows:
ResponseEntity<String> responseEntity = getRestTemplate().exchange(
"localhost:9090/",
"HEAD",
entity,
String.class);
return responseEntity.getBody();
However, I'm not able to read the Headers from the response as I'm getting HTTP 401 error.
My workaround is to read the token from the exception that RestTemplate throws HttpClientErrorException. Like this:
exception.getResponseHeaders().get("Set-Cookie");
for (String header : headers) {
if (header.startsWith("XSRF-TOKEN")) {
token = header.split("=")[1];
break;
}
}
Is there any way to get XSRF-TOKEN token with out relying on reading it from the exception?
You are not getting an exception when accessing with GET method. Hence, I would create a get endpoint for retrieving the token and then use it for next POST calls.
Hope that approach makes sense.
the csrf only blocks requests of type post, put, delete ... that is, the get is free, therefore in order to obtain the token, first you have to make a request to a get method and extract the token from there that you would use to the next requests.
in case the token is not generated, add this to the configure of your security configuration:
http.csrf (). csrfTokenRepository (CookieCrsfTokenRepository.withHttpOnlyFalse) .any () ........
XSRF-TOKEN following spring specification is marker for header by default. So you should try get it in this way:
List tokenList = responseEntity.getHeaders().get("XSRF-TOKEN");
This collection consist of single element as usual, so first element should be your token.

Categories

Resources