How to send java.security.Principal with a webclient request - spring-boot

There is a rest api secured using keycloak(OAUTH) as below:
#PreAuthorize("hasRole('user')")
#GetMapping(value = "/employees", produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<List<Employee>> findAll(java.security.Principal principal,
#RequestParam(name = "filterationCriteria", required = false) String fields) {
if(Objects.nonNull(principal)){
return employeeManagementService.findAll(fields);
}
return null;
}
i want to consume this api using webclient as below:
public <T> T get(URI url, Class<T> responseType, Principal principal) {
return WebClient.builder().build().get().uri(url)
.header("Authorization", "Basic " + principal)
.retrieve().bodyToMono(responseType).block();
}
above method is throwing below exception from findAll() method, which generally happens if the principal is not found:
org.springframework.web.reactive.function.client.WebClientResponseException$Unauthorized:
401 Unauthorized from GET
Am i doing something wrong, is that the correct way to send principal with the request?
Any suggestion is appreciated.
Thanks in advance.
UPDATE
I am sending request to the secured rest-api using webclient from a different service and i need to pass the principal manually(may be including it into the header).
if i simply do below:
WebClient.builder().build().get().uri(url).retrieve().bodyToMono(responseType).block()
then the principal is coming null in the findAll method
Security config
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.anyRequest().permitAll().and()
.csrf().disable();
}
NOTE: i have put Authorize constraint at method level.

I have found a solution. the principal has to be sent like this:
public <T> T get(URI url, Class<T> responseType, Principal principal) {
RequestHeadersSpec uri = webClient.get().uri(url);
if (Objects.nonNull(principal)) {
uri = uri.header("Authorization", "Bearer "
+ ((KeycloakAuthenticationToken) principal).getAccount().getKeycloakSecurityContext().getTokenString());
}
ResponseSpec response = uri.retrieve();
Mono<T> bodyToMono = response.bodyToMono(responseType);
return bodyToMono.block();
}

Related

Spring boot authorization returns 403 for any authorization request using #RolesAllowed, #Secured or #PreAuthorize

I've been working from this article (and a few other similar ones): https://medium.com/omarelgabrys-blog/microservices-with-spring-boot-authentication-with-jwt-part-3-fafc9d7187e8
The client is an Angular 8 app which acquires a Jwt from an independent microservice. Trying to add filter(s) to a different microservice to require specific authorization via jwt roles.
Consistently receiving 403 errors.
Security Config:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true,
securedEnabled = true,
jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurityConfig() {}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
// make sure we use stateless session; session won't be used to store user's state.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// Add a filter to validate the tokens with every request
.addFilterAfter(new JwtAuthorizationFilter2(), UsernamePasswordAuthenticationFilter.class)
// authorization requests config
.authorizeRequests()
// Any other request must be authenticated
.anyRequest().authenticated();
}
}
Filter:
public class JwtAuthorizationFilter2 extends OncePerRequestFilter {
private final String HEADER = "Authorization";
private final String PREFIX = "Bearer ";
private final String SECRET = "foo";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String token = request.getHeader(SecurityConstants.HEADER_STRING);
if (token != null) {
// parse the token.
DecodedJWT decoded = JWT.require(Algorithm.HMAC512(SecurityConstants.SECRET.getBytes()))
.build()
.verify(token.replace(SecurityConstants.TOKEN_PREFIX, ""));
String user = decoded.getSubject();
List<SimpleGrantedAuthority> sgas = Arrays.stream(
decoded.getClaim("roles").asArray(String.class))
.map( s -> new SimpleGrantedAuthority(s))
.collect( Collectors.toList());
if (sgas != null) {
sgas.add(new SimpleGrantedAuthority("FOO_Admin"));
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
user,
null,
sgas);
SecurityContextHolder.getContext().setAuthentication(auth);
}
else {
SecurityContextHolder.clearContext();
}
chain.doFilter(request, response);
}
}
}
This code works fine without any authorization requirements defined, but if an authorization is defined in WebSecurityConfig, or by decorating a controller method, http 403 is received for all requests in scope.
Examples:
.authorizeRequests().antMatchers("/**").hasRole("FOO_Admin")
// or any of these
#PreAuthorize("hasRole('FOO_Admin')")
#RolesAllowed({"FOO_Admin"})
#Secured({"FOO_Admin"})
Device get(#PathVariable String id) {
// some code
}
When code is halted at SecurityContextHolder.getContext().setAuthentication(auth),
auth.authenticated = true
and
auth.authorities includes a SimpleGrantedAuthority for "FOO_Admin"
So I'm wondering whether:
The FilterChain needs an Authentication Filter (or does authentication occur in JwtAuthorizationFilter2?)?
There is not a spelling or formatting or capitalization difference to role name.
I'm stupefied. Any help would be greatly appreciated.
#PreAuthorize("hasRole('FOO_Admin')) expects the user has an authority ROLE_FOO_Admin, which will be prefixed by ROLE_. However, the user only has the authority FOO_Admin , hence it fails to access the method.
You have several options:
(1) Change the prefix by declaring a GrantedAuthorityDefaults bean:
#Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("FOO");
}
And use #PreAuthorize(hasRole('Admin')) to secure the method.
(2) Or more simpler is to use #PreAuthorize("hasAuthority('FOO_Admin')") , which will directly check if the user has the authority FOO_Admin , without adding any prefix to it.
P.S JwtAuthorizationFilter2 only verifies if an user is valid and get the related user information which prepare for the authorization user later. It is an authentication and I would rename it to JwtAuthenticationFilter2 to describe more exactly what it does actually.

SpringSecurity: How to Continue Forwarding Request to RestController After a Successful Authentication?

I am doing a pure backend project with REST APIs (not MVC) and would like to use SpringSecurity with JWT token to project these APIs. The implementation is good and all APIs are successfully protected with the token, and I can post a JSON string with username and password to "/login" path to get token
My problem is:
The SpringSecurity will return the response with token directly in successfulAuthentication() rather than keep forwarding to RestController (RestController's "/login" path gets no data)
And my question is:
What should I do, after a successful authentication, to allow SpringSecurity can keep forwarding the request to RestController's "/login" path so that I can do something else on the request and the newly built token beside the security in the path?
Appreciate any helps, Thank you!
My code:
#Component
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
// ...
#Override
protected void configure(HttpSecurity http) throws Exception {
// ...
http.authorizeRequests()
.antMatchers("/registry").permitAll() // allow path /registry
.antMatchers("/login").permitAll() // allow path /login
.antMatchers("/verify").permitAll() // allow path /verify
.anyRequest().authenticated();
// ...
}
}
#RestController
public class EntranceEndpoint {
#RequestMapping(path = "/login", method = RequestMethod.POST)
public RestResponse<String> login(LoginMetaInfo login) {
System.out.println(login); // no output here when login
// some further operations for a successful login, and return a REST response
}
}
And this is what the SpringSecurity do on a successful login
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
// ...
/**
* on login success
*/
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) throws IOException {
// here build the token and insert into response for commitment
// - the SpringSecurity soon returns the response directly, rather then keep forwarding to RestController
String token = xxxx;
response.setStatus(StatusCode.SUCCESS().getCode());
RestResponse<String> body = RestResponse.succeeded(StatusCode.SUCCESS().withMsg(LoginResponseCode.LOGIN), token);
response.setContentType(MediaType.APPLICATION_JSON);
response.setCharacterEncoding(MediaType.CHARSET);
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getWriter(), body );
}
}
What about simply using HttpServletResponse's sendRedirect instead of writing to the response?
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) throws IOException {
// do what you want here
response.sendRedirect("/login");
// response.sendRedirect("https://yoururl");
}

Spring Boot : Oauth 2.0 : Pass access token in JSON request body

For Oauth2.0, the access token should be passed either in header or in request parameter. But My requirement is to somehow extract this token from request body to support existing public apis which are passing access token as a JSON field in request body.
Using ContentCachingRequestWrapper, I am able to extract the access token field from request JSON in a custom filter. This custom filter is configured to be invoked before OAuth2AuthenticationProcessingFilter. But I am not able to set it either in the request header or add it to request parameter for the
OAuth2AuthenticationProcessingFilter to be able to extract this token and authenticate.
I referred to this link Spring Security: deserialize request body twice (oauth2 processing) which had a similar requirement. This talks about overriding HttpServletRequestWrapper.
With latest versions of Spring and Spring Boot, is there any other cleaner way of doing this rather than creating a HttpServletRequestWrapper?
Or is there any way of injecting my own implementation for BearerTokenExtractor (used by OAuth2AuthenticationProcessingFilter) which can also try to get the token from request attributes?
#Component
public class CustomOAuthTokenFilter extends GenericFilterBean {
#Autowired
private ObjectMapper objectMapper;
#Override
public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
ContentCachingRequestWrapper reqWrapper = new ContentCachingRequestWrapper(httpRequest);
String requestBody = IOUtils.toString(reqWrapper.getInputStream(), java.nio.charset.StandardCharsets.UTF_8);
JsonNode requestJson = objectMapper.readTree(requestBody);
Iterator<JsonNode> jsonIter = requestJson.elements();
while (jsonIter.hasNext()){
JsonNode parent = jsonIter != null ? jsonIter.next() : null;
JsonNode hdr = parent != null ? parent.get("hdr") : null;
JsonNode accessTokenNode = hdr != null ? hdr.get("accessToken") : null;
String accessToken = accessTokenNode != null ? accessTokenNode.asText() : null;
}
chain.doFilter(reqWrapper, response);
}
}
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired
private CustomOAuthTokenFilter customOAuthTokenFilter;
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/auth/token","/oauth/token", "/oauth/authorize**").permitAll().anyRequest()
.authenticated()
.and().addFilterBefore(customOAuthTokenFilter, AbstractPreAuthenticatedProcessingFilter.class);
}
}

how to fetch JWT tokens from keycloak API in a springboot application

I am new to Keycloak. I started using it as a part of my spring-boot application.
I have extended KeycloakWebSecurityConfigurerAdapter and overridden methods like configure, configureGloabl etc., to have a specific authentication (see below code snippet.)
I am wondering if there is a possibility to get access of JWT token object for fetching further properties. Also it is not clear to me how to invalidate the token once the user is logged-out. At present once a single user is logged in, I am unable to sign him out and the JWT token seems to be remaining all the time.
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/customers*")
.hasRole("user")
.anyRequest()
.permitAll();
}
you can get the access token in postman using this;
here ConfigKeycloak is realm name and config-app is client name.
Another way to get the access token is this.
#RequestMapping(value = "/customers", method = RequestMethod.GET)
#PreAuthorize("hasRole('ROLE_USER')")
public String getCustomers(){
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) request.getUserPrincipal();
KeycloakPrincipal principal=(KeycloakPrincipal)token.getPrincipal();
KeycloakSecurityContext session = principal.getKeycloakSecurityContext();
AccessToken accessToken = session.getToken();
String a = principal.getName();
username = accessToken.getPreferredUsername();
emailID = accessToken.getEmail();
lastname = accessToken.getFamilyName();
firstname = accessToken.getGivenName();
realmName = accessToken.getIssuer();
AccessToken.Access realmAccess = accessToken.getRealmAccess();
}
you can logout from the session using this.
#RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpServletRequest request) throws ServletException {
request.logout();
return "/";
}

How to bypass UsernamePasswordAuthentication in Spring Security

I'm implementing an API that accepts a JWT as request parameter and on authentication, returns a new JWT.
#RequestMapping(value = "/authenticate/token", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity authenticate(#RequestParam("login_token") final String token, HttpServletResponse response) {
LOG.debug("Request to login with token : {}", token);
try {
String jwt = authService.loginByToken(token);
response.addHeader(JWTConfigurer.AUTHORIZATION_HEADER, "Bearer " + jwt);
return ResponseEntity.ok(new IdentityToken(jwt));
} catch (AuthenticationException ae) {
LOG.trace("Authentication exception trace: {}", ae);
return new ResponseEntity<>(Collections.singletonMap("AuthenticationException",
ae.getLocalizedMessage()), HttpStatus.UNAUTHORIZED);
}
}
My loginByToken implementation looks as below
#Override public String loginByToken(String token) {
if (!tokenProvider.validateToken(token)) {
throw new BadCredentialsException("Token is invalid.");
}
SecureToken secureToken = tokenProvider.parseJwtToken(token);
User user = userRepository.findByEmail(secureToken.getEmail());
// TODO: Check Account Status is valid, User status is valid
Calendar c = Calendar.getInstance();
c.setTime(new Date());
c.add(Calendar.DATE, Constants.PASSWORD_EXPIRY_DAYS);
if (user.getPasswordExpiryDt() != null
&& user.getPasswordExpiryDt().after(c.getTime())) {
throw new BadCredentialsException("Password changed");
}
// TODO: Find how to create authentication object and return ID token.
// return tokenProvider.createToken(authentication, false);
return token;
}
At this point, I'm not sure how to create an authentication object that contains all user details that I could pass to createToken function that creates an identity token.
Here is my project without the changes mentioned in this post - https://github.com/santoshkt/ngx-pipes-test.
I have read about Anonymous Authentication, PreAuthenticated etc but not sure how to deal with this case. Will appreciate any pointers on how to do this.
If you want to use Spring Security, you should probably not use a Spring MVC endpoint to handle (pre-)authentication.
In your case you probably want to change your Spring security configuration so that it will have a filter that obtains your token from your request parameters and an authentication provider that retrieves the user/authentication object from your token:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/authenticate/token")
.authorizeRequests()
.anyRequest().authenticated()
.and()
// This is a filter bean you'll have to write
.addFilterBefore(filter(), RequestHeaderAuthenticationFilter.class)
// This is your token verifier/decoder
.authenticationProvider(authenticationProvider())
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
For the filter you could extend from AbstractPreAuthenticatedProcessingFilter and make it return the login_token parameter. In here you have to implement two methods being getPreAuthenticatedPrincipal() and getPreAuthenticatedCredentials().
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
// You could already decode your token here to return your username
return request.getParameter("login_token");
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return request.getParameter("login_token");
}
Your authentication provider should be of type PreAuthenticatedAuthenticationProvider and in here you can set an AuthenticationUserDetailsService:
#Bean
public AuthenticationProvider authenticationProvider() {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
// service is a bean of type AuthenticationUserDetailsService
// You could autowire this in your security configuration class
provider.setPreAuthenticatedUserDetailsService(service);
return provider;
}
Now you can create your own AuthenticationUserDetailsService to retrieve a UserDetails object based on your token:
#Service
public class TokenAuthenticationUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
#Override
public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken authentication) throws UsernameNotFoundException {
// In this case the authentication.getCredentials() will contain your token and you can return a UserDetails object
return new User(/** ... */);
}
}
Since you want to provide the HTML page for the JWT token request the best approach is that you create you own Spring Security Custom Entry Point
You may give a look here for an example
If it's another system to manage the authentication and you want just manage the authorization you can "trust" the other System and then manage your own authorizations; in this case you can use the PreAuthentication Scenario as described here; you can find a sample here
I hope it's useful

Resources