Migration ResponseEntity to Mono<ResponseEntity - spring

In a spring boot application, I do a call to a ms with webclient
ResponseEntity response = webClient.post()
.uri("/auth/login")
.body(Mono.just(loginRequest), LoginDto.class)
.retrieve()
.toEntity(LoginResponse.class)
.block();
After I take roles and put it in a Collection
Collection<? extends GrantedAuthority> authorities = response.getBody().getRoles().stream()
.map(item -> new SimpleGrantedAuthority(item)).collect(Collectors.toList());
After I create a return a Mono
Mono.just(new UsernamePasswordAuthenticationToken(username, password, authorities));
I modified ms to return Mono<ResponseEntity> to be really reactive
With this change, I don't know what to change
Mono<ResponseEntity<LoginResponse>> response = webClient.post()
.uri("/auth/login")
.body(Mono.just(loginRequest), LoginDto.class)
.retrieve()
.toEntity(LoginResponse.class);
Collection<? extends GrantedAuthority> authorities = response.getBody().getRoles().stream()
.map(item -> new SimpleGrantedAuthority(item)).collect(Collectors.toList());
Mono.just(new UsernamePasswordAuthenticationToken(username, password, authorities));

you use flatMap to map something to something else in an async way.
public Mono<UsernamePasswordAuthenticationToken> foobar() {
Mono<ResponseEntity<LoginResponse>> response = webClient.post()
.uri("/auth/login")
.body(Mono.just(loginRequest), LoginDto.class)
.retrieve()
.toEntity(LoginResponse.class);
return response.flatMap(response -> {
Collection<? extends GrantedAuthority> authorities = response.getBody()
.getRoles()
.stream()
.map(item -> new SimpleGrantedAuthority(item))
.collect(Collectors.toList());
return Mono.just(new UsernamePasswordAuthenticationToken(username, password, authorities));
}
}

Related

How to allow param with curly braces in Url when using WebClient?

I am getting IllegalArgumentException
"Not enough variable values available to expand 'email'" when calling an endpoint with curly braces in the url. I don't want to encode it since the endpoint is throwing 500 after the url is ecoded.
#Override
public Mono<UserInfoByEmailV2> findByEmail(String env, String email) {
webClient = getTokenAndSetupWebClient(env, webClient, log);
// Param email here is like {{test}}#test.com
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/user/?email={email}")
.replaceQueryParam("email", email)
.build())
.retrieve()
.bodyToMono(UserInfoByEmailV2.class);
}
I found the answer;
#Override
public Mono<UserInfoByEmailV2> findByEmail(String env, String email) {
webClient = getTokenAndSetupWebClient(env, webClient, log);
return webClient.get()
.uri("/user?email={test}", email)
.retrieve()
.bodyToMono(UserInfoByEmailV2.class);
}

Role Based Authorization using JWT - Spring security

Hope you all are doing great! I am implementing JWT Role Based Authorization in spring boot.
I have been able to implement it. The tutorial that I am following is this
https://github.com/only2dhir/spring-security-jwt
The user is being sucessfully registered. And then I assign that user a role like ADMIN.
Now I have this api #GetMapping("/users")
that should be accessed by ADMIN. However when I access this api, it gives me this error
java.lang.NullPointerException: Cannot invoke "Object.toString()" because the return value of
"io.jsonwebtoken.Claims.get(Object)" is null
This errors comes from these methods:
JwtTokenUtil:
public static final String AUTHORITIES_KEY = "scopes";
UsernamePasswordAuthenticationToken getAuthentication(final String token, final Authentication
exsitingAuth, final UserDetails userDetails){
final JwtParser jwtParser = Jwts.parser().setSigningKey(secret);
final Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
final Claims claims = claimsJws.getBody();
final Collection<? extends GrantedAuthority> authorities=
java.util.Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return new UsernamePasswordAuthenticationToken(userDetails, "", authorities);
}
This line gives error
java.util.Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
Creating authentication:
UserDetailsWithToken UserDetailsWithToken = new UserDetailsWithToken();
authenticate(authenticationRequest.getEmpID(),
authenticationRequest.getPswd());
final UserDetails userDetails =
userDetailsService.loadUserByUsername(authenticationRequest.getEmpID());
final Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
authenticationRequest.getEmpID(),
authenticationRequest.getPswd()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
final String token =
jwtTokenUtil.generateToken(userDetails,authentication);
in your code you are not setting your authorities in the jwt token try setting it:
return doGenerateToken(authorities, userDetails.getUsername());
...
private String doGenerateToken(String authorities, String subject) {
return
Jwts.builder()
.claim(AUTHORITIES_KEY, authorities)
.setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 120000))
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
make sure to create your authentication correctly
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken("username", "myPassword");
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication.getPrincipal(), authentication);
I guess you are missing to add the authorities.
See the code here .... I eidted my answer to create a token ...
public String generateToken(UserDetails userDetails, Authentication
authentication) {
List<String> roles = user.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
return doGenerateToken(userDetails.getUsername(), roles);
}
ANother method
public String doGenerateToken(String username, List<String> roles) {
try {
Claims claims = Jwts.claims().setSubject(username);
claims.put("username", roles);
claims.put(AUTHORITIES_KEY, username);
return Jwts.builder()
.setClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 120000))
.signWith(SignatureAlgorithm.HS512, secret).compact();
.compact();
} catch (InvalidKeyException e) {
logger.error(e.getMessage(), e);
}
return "";
}
Add the authorities string and pass it to doGenerateToken method.
Thanks,
Atul

How to receive a Map<String, Integer> from an endpoint using Spring WebClient get?

How can I receive a Map<String, Integer> from an endpoint web service using WebClient in Spring Boot? Here is my try: (it gives syntax error: Incompatible equality constraint: Map<String, Integer> and Map). How can I fix it?
public Flux<Map<String, Integer>> findAll(String param1, String param2) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/url")
.queryParam("param1", param1)
.queryParam("param2", param2)
.build())
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux(Map.class);
}
For generic types, like the Map, you should use ParameterizedTypeReference instead of a class in the call to the bodyToFlux method:
public Flux<Map<String, Integer>> findAll(String param1, String param2) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/url")
.queryParam("param1", param1)
.queryParam("param2", param2)
.build())
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux(new ParameterizedTypeReference<>() {});
}
In practice, probably you would like to define a constant for the type reference:
private static final ParameterizedTypeReference<Map<String, Integer>> MAP_TYPE_REF = new ParameterizedTypeReference<>() {};
public Flux<Map<String, Integer>> findAll(String param1, String param2) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/url")
.queryParam("param1", param1)
.queryParam("param2", param2)
.build())
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux(MAP_TYPE_REF);
}

Spring WebClient : Implement Fallback method

I want to call my fall-back API when my actual API is taking more than 1 second
#GetMapping("/{id}")
public String getDetailsById(#PathVariable Long id) {
var url = getAPIUrl(id);
var response = webClient.get()
.uri(url)
.retrieve()
.onStatus(HttpStatus::isError,this::myFallBackMethod)
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(1))
.block();
return response;
}
private Mono<? extends Throwable> myFallBackMethod(ClientResponse clientResponse) {
return Mono.just("");
}
I get two compile exceptions
Incompatible types
and
cannot resolve methoe myFallBackMethod
How to handle fall backs and return the String ?
I was able to do that my calling the function onErrorResume
#GetMapping("/{id}")
public String getDetailsById(#PathVariable Long id) {
var url = getAPIUrl(id);
var response = webClient.get()
.uri(url)
.retrieve()
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(1))
.onErrorResume(throwable -> myFallBackMethod(id,throwable))
.block();
return response;
}
private Mono<? extends String> myFallBackMethod(Long id, Throwable throwable) {
return Mono.just("test sample");
}

Spring Webflux OAuth 2 resource server

I have a Spring OAuth 2 server based on Spring Boot 1.5 (Spring Security v4) which generates customized tokens and a few resource servers who communicate with this authorization server, making use of /oauth/check_token endpoint by configuration of RemoteTokenServices.
All the logic related to storing/retrieving tokens on Authorization server side is done with JdbcTokenStore.
I am building a new Spring Boot 2 application which is build with Spring webflux module and trying to implement client_credentials flow with existing Authorization Server using Spring Security 5.1.1.
I found that support for resource servers was added in 5.1.0.RC1 (https://spring.io/blog/2018/08/21/spring-security-5-1-0-rc1-released#oauth2-resource-servers) and updated in 5.1.0.RC2 (https://spring.io/blog/2018/09/10/spring-security-5-1-0-rc2-released#oauth2-resource-server) but looks like it's only possible to configure it with JWT support.
I might be messing up with concepts here but looking for more info and a way to configure all these components together.
I'm in same situation as you.I solve that problem in next way, maybe it can help you:
spring-boot-starter-parent.version: 2.1.1
spring-cloud-dependencies.version: Greenwich.R1
Security configuration:
#EnableWebFluxSecurity
public class SecurityConfig {
#Autowired
private ReactiveAuthenticationManager manager; //custom implementation
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/role").hasRole("ADMIN")
.pathMatchers("/test").access(new HasScope("server")) //custom implementation
.anyExchange().authenticated()
.and()
.httpBasic().disable()
.oauth2ResourceServer()
.jwt()
.authenticationManager(manager)
.and().and()
.build();
}
}
ReactiveAuthorizationManager (HasScope) implementation:
Helper which allow search for scopes in authentication object
public class HasScope implements ReactiveAuthorizationManager<AuthorizationContext> {
public HasScope(String...scopes) {
this.scopes = Arrays.asList(scopes);
}
private final Collection<String> scopes;
#Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext object) {
return authentication
.flatMap(it -> {
OAuth2Authentication auth = (OAuth2Authentication) it;
Set<String> requestScopes = auth.getOAuth2Request().getScope();
boolean allow = requestScopes.containsAll(scopes);
return Mono.just(new AuthorizationDecision(allow));
});
}
}
ReactiveAuthenticationManager implementation:
That is the main component in configuration which create OAuth2Authentication. There is a problem with response for wrong access_token, it returns only status code without body response.
#Component
public class ReactiveAuthenticationManagerImpl implements ReactiveAuthenticationManager {
private final ResourceServerProperties sso;
private final WebClient.Builder webClient;
private final ObjectMapper objectMapper;
private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();
public ReactiveAuthenticationManagerImpl(ResourceServerProperties sso,
#Qualifier("loadBalancedWebClient") WebClient.Builder webClient, ObjectMapper objectMapper) {
this.sso = sso;
this.webClient = webClient;
this.objectMapper = objectMapper;
}
#Override
public Mono<Authentication> authenticate(Authentication authentication) {
return Mono.just(authentication)
.cast(BearerTokenAuthenticationToken.class)
.flatMap(it -> getMap(it.getToken()))
.flatMap(result -> Mono.just(extractAuthentication(result)));
}
private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
Object principal = getPrincipal(map);
OAuth2Request request = getRequest(map);
List<GrantedAuthority> authorities = authoritiesExtractor.extractAuthorities(map);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
token.setDetails(map);
return new OAuth2Authentication(request, token);
}
private Object getPrincipal(Map<String, Object> map) {
if (map.containsKey("principal")) {
try {
//that is the case for user authentication
return objectMapper.convertValue(map.get("principal"), UserPrincipal.class);
} catch (IllegalArgumentException ex) {
//that is the case for client authentication
return objectMapper.convertValue(map.get("principal"), String.class);
}
}
return null;
}
#SuppressWarnings({"unchecked"})
private OAuth2Request getRequest(Map<String, Object> map) {
Map<String, Object> request = (Map<String, Object>) map.get("oauth2Request");
String clientId = (String) request.get("clientId");
Set<String> scope = new LinkedHashSet<>(request.containsKey("scope") ?
(Collection<String>) request.get("scope") : Collections.emptySet());
return new OAuth2Request(null, clientId, null, true, new HashSet<>(scope),
null, null, null, null);
}
private Mono<Map<String, Object>> getMap(String accessToken) {
String uri = sso.getUserInfoUri();
return webClient.build().get()
.uri(uri)
.accept(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer " + accessToken)
.exchange()
.flatMap(it -> it.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {}))
.onErrorMap(InvalidTokenException.class, mapper -> new InvalidTokenException("Invalid token: " + accessToken));
}

Resources