Spring WebClient header from ReactiveSecurityContext - spring

I'm trying to set WebClient header value accordingly to the authenticated user, something like this:
webClient.post().header(HttpHeaders.AUTHORIZATION, getUserIdFromSession())...
and
public String getUserIdFromSession() {
Mono<Authentication> authentication = ReactiveSecurityContextHolder.getContext().map(SecurityContext::getAuthentication);
//do something here to get user credentials and return them
}
Should I store the required header value somewhere else? Because in reactive way, everything returns a Mono/Flux and I'm currently unable to use the authenticated user data as I have used it with Spring MVC. There I could just do SecurityContextHolder.getContext().getAuthentication()

Is there a reason you aren't just flatmapping the Authentication and then calling the webclient? You could also just return the Mono<String> from your method
ReactiveSecurityContextHolder.getContext().map(SecurityContext::getAuthentication)
.flatMap(auth -> webClient.post().header(HttpHeaders.AUTHORIZATION, auth.getUserId()).exchange();

Related

Spring Security 6.0 CsrfToken behavior change

I tested Spring Security as part of my Spring Boot Setup in version 6.0-M5, 6.0-RC1 and 6.0-RC2. I recognized a behavior change and wanted to ask whether this may be a bug. I return the CSRF token as a serialized JSON, but since RC1 the content of the token in the JSON is garbage.
My working code in Spring Boot 6 Milestone 5 still working as expected.
#RestController
public class CsrfController {
#GetMapping("/rest/user/csrf")
public CsrfToken csrf(CsrfToken token) {
return token;
}
}
In my use case I query the controller using a unit test.
#LocalServerPort
int serverPort;
#Autowired
private TestRestTemplate webclient;
#Test
public void getCsrf() {
ResponseEntity<String> entity = webclient.getForEntity("http://localhost:" + serverPort +
"/rest/user/csrf", String.class);
// ... here some code to get the token from the JSON body ...
assertTrue(result.matches("^[a-f0-9\\-]+$"));
This is the first query of the server. A session object between client and server is not established in past queries. This worked in M5 but stopped working in Spring Boot 6 RC1 and RC2
The following controller code made it work again in RC2:
#GetMapping("/rest/user/csrf")
public CsrfToken csrf(HttpServletRequest request, HttpServletResponse response) {
CsrfToken repoToken = tokenRepo.loadToken(request);
if (repoToken != null) {
return repoToken;
}
// required because it is required but ay not be initialized by the tokenRepo
request.getSession();
repoToken = tokenRepo.generateToken(request);
tokenRepo.saveToken(repoToken, request, response);
return repoToken;
}
If I tried the old code in RC2, I received on client side a malformed string. I did not receive a UUID styled token in my JSON serialized response body. I think it is related to the uninitialized session object.
Is this a bug or is an uninitialized session and a resulting not working CrsfToken specified behavior?
I think the issue is in the way I try to get and use the XSFR token.
Because I want to use an Angular frontend, I configured my token repository to provide the tokens via Cookie.
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
This produces cookies the old UUID style. However the authentication expects the new tokens as generated by https://github.com/spring-projects/spring-security/issues/11960 . Probably the cookie mechanism still needs to be migrated until final Spring Boot 3.0.

Spring-boot authentication

I have few Spring-boot controller classes to expose few rest web-services. Whenever some user tries to access any of those services, I need to invoke an web-service to check whether the user (user id will be passed as RequestHeader) is authorized or not. If not authorised, need to display an error page (freemarker template) to the user.
I don't want to write a method which will invoke the authentication webservice and call that from each controller methods and throw an exception and redirect the user to the access denied error page using #ControllerAdvice as here I have to call the method from all controller methods.
I'm not sure whether I can use WebSecurityConfigurerAdapter/AuthenticationManagerBuilder to call the webservice and do the validation.
I'm looking for some solution where I would write an interceptor and spring-boot will invoke the webservice before calling the controller classes and will be able to redirect to the error page, if validation fails.
As a recommendation, take a few minutes for reading about Spring Security (https://projects.spring.io/spring-security/), you must configure it and probably you will spend more time than expected, anyway you have so much more profits than make security by ourself.
Benefits are things like:
#PreAuthorize("hasRole('ROLE_USER')")
On every place you can get the user logged through the SecurityContext with something like:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
The way SpringSecurity authenticate users is with JWT (JsonWebToken) this is a really nice way because you can pass and retrieve all information you want:
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("customInfo", "some_stuff_here");
additionalInfo.put("authorities", user.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
And you can forget every possible problem (bad authentication, phishing, xss or csrf..) because it works with public/private key and secrets, so anyone can create a token.

How to handle login success in Spring Security

Apologies if my question seems like something a novice would ask. I am new to the Spring world. I am using Spring Security to authenticate users. Authentication is working properly, but after authentication success I want Spring to call the Controller method .`
#RequestMapping(value = "/login", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public JwtAuthenticationResponse userLogin() {
System.out.println("Login Success");
JwtUser user = (JwtUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String token = userService.generateToken(authenticationService.loadUserByUsername(user.getUsername()));
JwtAuthenticationResponse response = new JwtAuthenticationResponse(token, user.getAuthorities().toArray(),user.getUsername());
return response;
}
}
In Spring Security Configuration I added the following
http.authorizeRequests().anyRequest().permitAll().and().formLogin().loginProcessingUrl("/login").defaultSuccessUrl("/login");
http.addFilterBefore(filter,UsernamePasswordAuthenticationFilter.class);
In this case, it is returning a default Spring Login form. It is not calling my controller method.
I made the request using
http://localhost:8080/myapp/login
Can someone suggest what I have to do to invoke the Controller after login is successful, in order to send the Authentication token after login.
Appreciate any help!
Thank you
If you are providing your custom controller for login then just remove following
.formLogin().loginProcessingUrl("/login").defaultSuccessUrl("/login")
From httpSecurity configuration. And this will remove spring security's default login page.
or you can follow : http://docs.spring.io/spring-security/site/docs/current/guides/html5/form-javaconfig.html
I found the mistake I was doing. I need not declare request mapping for login. I realized that Spring will take care of it.

Using "spring-security-oauth2," can custom parameters be passed in the "Authorization Phase" of OAuth2?

I am implementing the Authorization Code flow + JWT.
I would like to know if and how it may be possible to add additional custom parameters to the Authorization Phase of the flow.
Essentially, I am looking to do the following:
When redirecting the user to the /oauth/authorize endpoint I would like to
pass in an additional parameter (customParemphasized textameter) in the
GET url
http://.../oauth/authorize?...customParameter=[VALUE] such that
VALUE is dynamic
I will need to retrieve VALUE when creating the
JWT, populating the JWT with that VALUE
Is this possible? How can I implement?
My idea is add your parameter during AuthorizationRequest in custom OAuth2RequestFactory at createAuthorizationRequest method like here:
#Override
public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {
//here
authorizationParameters.put("your", "parameter");
//
AuthorizationRequest request = super.createAuthorizationRequest(authorizationParameters);
if (securityContextAccessor.isUser()) {
request.setAuthorities(securityContextAccessor.getAuthorities());
}
return request;
}
You can populate request and inject Request or Session in your custom OAuth2RequestFactory and retrieve it
The custom parameters passed will also get stored in HttpSession.
Below is sample code to retrieve them.
HttpSession httpSession = httpServletRequest.getSession(false);
DefaultSavedRequest savedRequest = (DefaultSavedRequest)
httpSession.getAttribute("SPRING_SECURITY_SAVED_REQUEST");
Map<String, String[]> parametersMap = savedRequest.getParameterMap();

Authentication in Spring MVC via REST

I've been looking for a way to authenticate a user via REST controller (URL params).
The closest thing to do so is the following:
#Controller
#RequestMapping(value="/api/user")
public class UserController extends BaseJSONController{
static Logger sLogger = Logger.getLogger(UserController.class);
#RequestMapping(value = "/login", method = RequestMethod.POST)
public #ResponseBody String login(#RequestParam(value="username") String user, #RequestParam(value="password") String pass) throws JSONException {
Authentication userAuth = new UsernamePasswordAuthenticationToken(user, pass);
MyCellebriteAuthenticationProvider MCAP = new MyCellebriteAuthenticationProvider();
if (MCAP.authenticate(userAuth) == null){
response.put("isOk", false);
}
else{
SecurityContextHolder.getContext().setAuthentication(userAuth);
response.put("isOk", true);
response.put("token", "1234");
}
return response.toString();
}
}
However, this doesn't create a cookie.
Any idea or a better way to implement what I want to achieve?
Firstly, you should not do this manually:
SecurityContextHolder.getContext().setAuthentication(userAuth)
It is better to employ special filter responsible for authentication, setting security context and clearing it after request is handled. By default Spring Security uses thread locals to store security context so if you don't remove it after client invocation, another client can be automatically logged in as someone else. Remember that server threads are often reused for different request by different clients.
Secondly, I would recommend using basic or digest authentication for your RESTful web service. Both are supported by Spring Security. More in docs http://static.springsource.org/spring-security/site/docs/3.1.x/reference/basic.html
And finally, remember that RESTful web service should be stateless.
Also remember that Spring Security documentation is your friend. :-)

Resources