I'm following this Tutorial and everything works fine.
Except I wan to add some extra search functionalities.
I have the following document is my Elasticsearch 6.1 index:
{
"author": "georges",
"price": 99.1,
"id": "06e68109-504a-44d6-bf2e-0debb12c984d",
"title": "Java Always"
}
My Spring Boot app runs on port 8080. I know how to insert data by using following API with postman : 127.0.0.1:8080/books and also how to get the book with its ID 127.0.0.1:8080/books/06e68109-504a-44d6-bf2e-0debb12c984d thanks to the GET request provided by E-S Java High Level Rest API:
//This works perfectly thank you
#Repository
public class BookDao {
private final String INDEX = "bookdata";
private final String TYPE = "books";
private RestHighLevelClient restHighLevelClient;
...
public Map<String, Object> getBookById(String id) {
GetRequest getRequest = new GetRequest(INDEX, TYPE, id);
GetResponse getResponse = null;
try {
getResponse = restHighLevelClient.get(getRequest);
} catch (java.io.IOException e) {
e.getLocalizedMessage();
}
Map<String, Object> sourceAsMap = getResponse.getSourceAsMap();
return sourceAsMap;
}
Question is: How can I search this book by it's author ?
I've tried exactly the same implementation but it doesnt work because GetRequest only gets documents by Document id
public Map<String, Object> getBookByAuthor(String author) throws IOException {
GetRequest getRequest = new GetRequest(INDEX, TYPE, author);
GetResponse getResponse = null;
try {
getResponse = restHighLevelClient.get(getRequest);
} catch (java.io.IOException e) {
e.getLocalizedMessage();
}
Map<String, Object> sourceAsMap = getResponse.getSourceAsMap();
return sourceAsMap;
}
My controller:
import java.util.Map;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.gvh.es.rest.es6rest.dao.BookDao;
import com.gvh.es.rest.es6rest.model.Book;
#RestController
#RequestMapping("/books")
public class BookController {
private BookDao bookDao;
public BookController(BookDao bookDao) {
this.bookDao = bookDao;
}
#GetMapping("/{id}")
public Map<String, Object> getBookById(#PathVariable String id){
return bookDao.getBookById(id);
}
#PostMapping
public Book insertBook(#RequestBody Book book) throws Exception {
return bookDao.insertBook(book);
}
#PutMapping("/{id}")
public Map<String, Object> updateBookById(#RequestBody Book book, #PathVariable String id) {
return bookDao.updateBookById(id, book);
}
#DeleteMapping("/{id}")
public void deleteBookById(#PathVariable String id) {
bookDao.deleteBookById(id);
}
}
I encountered the problem too,and this is my solution to the problem. It works with my code.
Change the username and password, and I think it may work.
private RestHighLevelClient buildClient() {
try {
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,new UsernamePasswordCredentials("username", "password"));
RestClientBuilder builder = RestClient.builder(
new HttpHost("localhost", 9200, "http")).setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
#Override
public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
}
});
restHighLevelClient = new RestHighLevelClient(builder);
} catch (Exception e) {
logger.error(e.getMessage());
}
return restHighLevelClient;
}
Related
I'm working with spring cloud gateway filters and need to get response body to log it.
I understand that it's problematic, as spring gateway is built on spring reactor, but nevertheless I'm looking for any way to do this.
Have global filter, code:
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
#Component
public class BodyRewrite implements RewriteFunction<byte[], byte[]> {
#Override
public Publisher<byte[]> apply(ServerWebExchange exchange, byte[] body) {
System.out.println("-------------------------");
System.out.println(" APPLY METHOD");
System.out.println("-------------------------");
String originalBody = body==null?"":new String(body);
if (!ServerWebExchangeUtils.isAlreadyRouted(exchange)) {
return Mono.just(originalBody.getBytes());
} else {
System.out.println("RESPONSE: " + originalBody);
}
return new Publisher<byte[]>() {
#Override
public void subscribe(Subscriber<? super byte[]> subscriber) {
}
};
}
}
#Component
class ModifyResponseBodyFilter implements GlobalFilter, Ordered {
#Autowired
private ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory;
#Autowired
private BodyRewrite bodyRewrite;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("---------------------------");
System.out.println(" GLOBAL FILTER");
System.out.println("---------------------------");
GatewayFilter delegate=modifyResponseBodyGatewayFilterFactory.apply(new ModifyResponseBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, bodyRewrite));
return delegate.filter(exchange, chain);
}
#Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER-1;
}
In the console I only get this output about 30 times in a row and no output with phrase "APPLY METHOD".
---------------------------
GLOBAL FILTER
---------------------------
im a rookie,both in English and Programing.
here is a way but may not elegant:
create a modifyResponseBodyFilter with the ModifyResponseBodyGatewayFilterFactory, and implement the RewriteFunction.
public class BodyRewrite implements RewriteFunction<byte[], byte[]> {
#Override
public Publisher<byte[]> apply(ServerWebExchange exchange, byte[] body) {
String originalBody = body==null?"":new String(body);
if (!ServerWebExchangeUtils.isAlreadyRouted(exchange)) {
return Mono.just(originalBody.getBytes());
} else {
// its the reponse body when already routed
}
}
}
public class ModifyResponseBodyFilter implements GlobalFilter, Ordered {
#Autowired
private ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory;
#Autowired
private BodyRewrite bodyRewrite;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
GatewayFilter delegate=modifyResponseBodyGatewayFilterFactory.apply(new ModifyResponseBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, bodyRewrite));
return delegate.filter(exchange, chain);
}
#Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER-1;
}
}
I have connected a Spring applications to a React front-end where I need to display my custom exceptions. The custom exceptions work perfectly in Spring, but the front-end (React) only receives the error code (417) and nothing else.
I have determined that the problem is that the exception is not being returned in JSON format because the error message is displayed in its entirety when I use Postman, but not in JSON format.
My research has shown that since I am using a #RestController (for my main controller) and #ControllerAdvice (for my custom exception handler) that it should be returning in JSON format. I also tried adding a ResponseBody bean to the specific function but that did not help either.
Controller.java
package com.chess.controller;
import com.chess.board.*;
import com.chess.gameflow.Game;
import com.chess.gameflow.Move;
import com.chess.gameflow.Player;
import com.chess.gameflow.Status;
import com.chess.models.requests.BoardRequest;
import com.chess.models.requests.PlayerRequest;
import com.chess.models.requests.StatusRequest;
import com.chess.models.responses.MovesResponse;
import com.chess.models.responses.Response;
import com.chess.models.responses.StatusResponse;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#CrossOrigin(origins= "http://localhost:3000", maxAge=7200)
#RestController
#RequestMapping("/game")
public class Controller {
Game game;
Board board = Board.boardConstructor();
#PostMapping("/players")
public List<Response> createPlayer(#RequestBody PlayerRequest request){
game = new Game(request.getName1(), request.getName2());
List<Response> returnValue = board.returnBoard();
Player player1= Game.players[0];
StatusResponse status = new StatusResponse(Status.isActive(), Status.isCheck(), player1);
returnValue.add(status);
return returnValue;
}
#PostMapping
public List<Response> makeMove(#RequestBody BoardRequest boardRequest){
StatusResponse status = Game.run(boardRequest);
List<Response> returnValue = board.returnBoard();
returnValue.add(status);
return returnValue;
}
#PostMapping("/end")
public StatusResponse endGame(#RequestBody StatusRequest statusRequest){
Status.setActive(false);
Board board = Board.boardConstructor();
board.generateBoard();
if (statusRequest.isForfeit()){
StatusResponse statusResponse = new StatusResponse(statusRequest.getPlayerName() + " declares defeat! Game Over!");
return statusResponse;
}
StatusResponse statusResponse = new StatusResponse("We have a draw! Good Game!");
return statusResponse;
}
#GetMapping("/moves")
public MovesResponse displayMoves(){
MovesResponse movesResponse = new MovesResponse(Move.returnMoveMessages());
return movesResponse;
}
}
CustomExceptionsHandler.java
package com.chess.exceptions;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
#ControllerAdvice
public class CustomExceptionsHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = InvalidMoveException.class)
#ResponseBody //this line shouldn't be necessary as I am using a RestController but I added it anyways in one of my futile attempts and I don't think it should hurt
protected ResponseEntity<Object> resolveInvalidMove(InvalidMoveException e, WebRequest req) throws Exception {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.EXPECTATION_FAILED.value(),
HttpStatus.EXPECTATION_FAILED.getReasonPhrase(),
e.getMessage(),
req.getDescription(true));
return handleExceptionInternal(e, errorResponse.toString(), new HttpHeaders(), HttpStatus.EXPECTATION_FAILED, req);
#ExceptionHandler(value = MustDefeatCheckException.class)
protected ResponseEntity<Object> resolveCheckStatus(MustDefeatCheckException e, WebRequest req){
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.EXPECTATION_FAILED.value(),
HttpStatus.EXPECTATION_FAILED.getReasonPhrase(),
e.getMessage(),
req.getDescription(true));
return handleExceptionInternal(e, errorResponse, new HttpHeaders(), HttpStatus.EXPECTATION_FAILED, req);
}
}
ErrorResponse.java
package com.chess.exceptions;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "error")
public class ErrorResponse {
private int status;
private String errReason;
private String errMessage;
private String path;
public ErrorResponse(int status, String errReason, String errMessage, String path) {
this.status = status;
this.errReason = errReason;
this.errMessage = errMessage;
this.path = path;
}
#Override
public String toString(){
return "Status Code: " + status + " " + errReason + " Message: " + errMessage + " at " + path;
}
}
InvalidMoveException.java
package com.chess.exceptions;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
#ResponseStatus(value = HttpStatus.EXPECTATION_FAILED, reason="Invalid Move")
public class InvalidMoveException extends RuntimeException {
public InvalidMoveException(String msg){ super(msg); }
}
I solved half the problem. Instead of returning a ResponseEntity with handleExceptionInternal, I was able to return the ErrorResponse itself by making it a child of Response which is the father to all my regular responses.
So now my CustomExceptionsHandler.java looks like this-
#RestControllerAdvice
public class CustomExceptionsHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = InvalidMoveException.class)
#ResponseStatus(HttpStatus.EXPECTATION_FAILED)
public ErrorResponse resolveInvalidMove(InvalidMoveException e, WebRequest req) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.EXPECTATION_FAILED.value(),
HttpStatus.EXPECTATION_FAILED.getReasonPhrase(),
e.getMessage(),
req.getDescription(true));
return errorResponse;
}
}
And I am getting the exception in JSON format when I use Postman. However, my error in
React is unchanged. I am still only getting the status code and no other information.
Board.js
DataService.makeMove(move)
.then(res => {
//console.log(res.data);
setIsWhite((prev) => !prev);
props.setTheBoard(res.data);
setStatus(res.data[64]);
updateMovesList();
})
.catch(err => {
console.log(err)
console.log(err.errMessage)
console.log(err.message)
console.log(err.status)
console.log(err.errReason)
})
DataService.js
makeMove(move){
return axios.post(`${url}`, move);
}
I always thought catching errors was very simple but apparently I am missing something
Share Test Context in Cucumber,while creating object in java to share same state for all scenarios i am getting exception
here i am creating object of endpoints class from TestContext class
Failure Trace
java.lang.IllegalStateException: Cannot stop. Current container state was: CONSTRUCTED
at org.picocontainer.lifecycle.DefaultLifecycleState.stopping(DefaultLifecycleState.java:72)
at org.picocontainer.DefaultPicoContainer.stop(DefaultPicoContainer.java:794)
at io.cucumber.picocontainer.PicoFactory.stop(PicoFactory.java:35)
at io.cucumber.core.runner.Runner.disposeBackendWorlds(Runner.java:173)
at io.cucumber.core.runner.Runner.runPickle(Runner.java:69)
at io.cucumber.junit.PickleRunners$NoStepDescriptions.run(PickleRunners.java:149)
at io.cucumber.junit.FeatureRunner.runChild(FeatureRunner.java:83)
at io.cucumber.junit.FeatureRunner.runChild(FeatureRunner.java:24)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at io.cucumber.junit.Cucumber.runChild(Cucumber.java:185)
at io.cucumber.junit.Cucumber.runChild(Cucumber.java:83)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at io.cucumber.junit.Cucumber$RunCucumber.evaluate(Cucumber.java:219)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:542)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:770)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
LoginSteps.java
package stepDefinition;
import apiEngine.Endpoints;
import apiEngine.model.requests.AuthorizationRequest;
import apiEngine.model.responses.Token;
import cucumber.TestContext;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
public class LoginSteps extends BaseStep {
private static Token tokenResponse;
#SuppressWarnings("unused")
private static String token;
public LoginSteps(TestContext testContext){
super(testContext);
}
#Given("^User is on login page$")
public void user_is_on_login_Page() throws Throwable {
System.out.println("User is in Login Page");
}
#When("^User enters \"(.*)\" and \"(.*)\"$")
public void user_enters_UserName_and_Password(String username, String password) throws Throwable {
AuthorizationRequest credentials = new AuthorizationRequest(username, password);
tokenResponse = Endpoints.authenticateUser(credentials).getBody();
}
#Then("^User enters Home Page$")
public void message_displayed_Login_Successfully() throws Throwable {
token = tokenResponse.id;
System.out.println("Done");
}
}
BaseStep.java
package stepDefinition;
import apiEngine.EndPoints;
import cucumber.TestContext;
public class BaseStep {
public EndPoints endPoints;
public BaseStep(TestContext testContext) {
testContext = new TestContext();
System.out.println("I am in BaseStep!!!!!!!!!!\n");
endPoints = testContext.getEndPoints();
}
}
TestContext.java
package cucumber;
import apiEngine.EndPoints;
public class TestContext {
private String BASE_URL = "http://localhost:3000/api/";
public EndPoints endPoints;
public TestContext() {
System.out.println("I am in TextContext!!!!!!!!!!\n");
if(endPoints == null) {
endPoints = new EndPoints(BASE_URL);
}
}
public EndPoints getEndPoints() {
return endPoints;
}
}
Endpoints.java
package apiEngine;
import apiEngine.model.requests.AddPhoneRequest;
import apiEngine.model.requests.AuthorizationRequest;
import apiEngine.model.responses.Phones;
import apiEngine.model.responses.Remove;
import apiEngine.model.responses.Token;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
public class Endpoints {
private static String BASE_URL = "http://localhost:3000/api/" ;
private static RequestSpecification request = null;
Token tokenResponse;
public Endpoints(String baseUrl) {
System.out.println("I am in endpoints");
BASE_URL = baseUrl;
System.out.println(baseUrl);
RestAssured.baseURI = baseUrl;
request = RestAssured.given();
request.header("Content-Type", "application/json");
}
public static IRestResponse<Token> authenticateUser(AuthorizationRequest credentials) {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json");
Response response = request.body(credentials).post(Route.generateToken());
return new RestResponse<Token>(Token.class, response);
}
public static IRestResponse<Phones> addPhone(AddPhoneRequest addPhoneRequest,String token) {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json").header("x-access-token", token);
System.out.println("Hello");
Response response = request.body(addPhoneRequest).post(Route.curd());
return new RestResponse<Phones>(Phones.class,response);
}
public static Response getPhonesList(String token) {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json").header("x-access-token", token);
Response response = request.get(Route.curd());
return response;
}
public static IRestResponse<Phones> getPhone(int Id,String token) {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json").header("x-access-token", token);
Response response = request.get(Route.curd(Id));
return new RestResponse<Phones>(Phones.class,response);
}
public static IRestResponse<Phones> updatePhone(AddPhoneRequest updtaephonerequest, String token, int Id) {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json").header("x-access-token", token);
Response response = request.body(updtaephonerequest).put(Route.curd(Id));
return new RestResponse<Phones>(Phones.class,response);
}
public static int getDeviceId(Phones[] phoneResponselist, String deviceName) {
int Id = 0;
for (int i = 0; i < phoneResponselist.length; i++) {
String productname1 = phoneResponselist[i].name;
if (productname1.equalsIgnoreCase(deviceName)) {
Id = phoneResponselist[i].id;
}
}
return Id;
}
public static IRestResponse<Remove> removePhone(int Id, String token) {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json").header("x-access-token", token);
Response response = request.delete(Route.curd(Id));
return new RestResponse<Remove>(Remove.class,response);
}
}
Note:
Execution begins from LoginSteps.java
This looks strange:
package stepDefinition;
import apiEngine.EndPoints;
import cucumber.TestContext;
public class BaseStep {
public EndPoints endPoints;
public BaseStep(TestContext testContext) {
testContext = new TestContext();
System.out.println("I am in BaseStep!!!!!!!!!!\n");
endPoints = testContext.getEndPoints();
}
}
First of all line testContext = new TestContext(); assigns an object to non-existing field so this code should not have been compiled..
UPD START: The line above has compiled because this reference is coming from your constructor argument :UPD END
Another point is that since you are using PicoContainer you should not create object by yourself. Container will do that for you. So your code should look like this:
package stepDefinition;
import apiEngine.EndPoints;
import cucumber.TestContext;
public class BaseStep {
public EndPoints endPoints;
TestContext testContext;
public BaseStep(TestContext testContext) {
this.testContext = testContext;
System.out.println("I am in BaseStep!!!!!!!!!!\n");
endPoints = testContext.getEndPoints();
}
}
I have adapted the code from here to call a MitreID OIDC server.
My controller:
public final String home(Principal p) {
final String username = SecurityContextHolder.getContext().getAuthentication().getName();
...
returns null and is null for all userdetails.
I have also tried:
public final String home(#AuthenticationPrincipal OpenIdConnectUserDetails user) {
final String username = user.getUsername();
and
#RequestMapping(value = "/username", method = RequestMethod.GET)
#ResponseBody
public String currentUserNameSimple(HttpServletRequest request) {
Principal principal = request.getUserPrincipal();
return "username: " + principal.getName();
}
Everything is null but the authentication is returning an access and user token.
My security config is:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2RestTemplate restTemplate;
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
#Bean
public OpenIdConnectFilter myFilter() {
final OpenIdConnectFilter filter = new OpenIdConnectFilter("/openid_connect_login");
filter.setRestTemplate(restTemplate);
return filter;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.addFilterAfter(new OAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(myFilter(), OAuth2ClientContextFilter.class)
.httpBasic().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/openid_connect_login"))
.and()
.authorizeRequests()
.antMatchers("/","/index*").permitAll()
.anyRequest().authenticated()
;
// #formatter:on
}
}
So why can my controller not access the userdetails?
EDIT: as requested, OpenIdConnectFilter:
package org.baeldung.security;
import java.io.IOException;
import java.net.URL;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import com.auth0.jwk.Jwk;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.UrlJwkProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
public class OpenIdConnectFilter extends AbstractAuthenticationProcessingFilter {
#Value("${oidc.clientId}")
private String clientId;
#Value("${oidc.issuer}")
private String issuer;
#Value("${oidc.jwkUrl}")
private String jwkUrl;
public OAuth2RestOperations restTemplate;
public OpenIdConnectFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
setAuthenticationManager(new NoopAuthenticationManager());
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
OAuth2AccessToken accessToken;
logger.info("ewd here: b " );
try {
accessToken = restTemplate.getAccessToken();
} catch (final OAuth2Exception e) {
throw new BadCredentialsException("Could not obtain access token", e);
}
try {
logger.info("ewd access token: " + accessToken);
final String idToken = accessToken.getAdditionalInformation().get("id_token").toString();
String kid = JwtHelper.headers(idToken)
.get("kid");
final Jwt tokenDecoded = JwtHelper.decodeAndVerify(idToken, verifier(kid));
final Map<String, String> authInfo = new ObjectMapper().readValue(tokenDecoded.getClaims(), Map.class);
verifyClaims(authInfo);
final OpenIdConnectUserDetails user = new OpenIdConnectUserDetails(authInfo, accessToken);
logger.info("ewd user token: " + tokenDecoded);
return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
} catch (final Exception e) {
throw new BadCredentialsException("Could not obtain user details from token", e);
}
}
public void verifyClaims(Map claims) {
int exp = (int) claims.get("exp");
Date expireDate = new Date(exp * 1000L);
Date now = new Date();
if (expireDate.before(now) || !claims.get("iss").equals(issuer) || !claims.get("aud").equals(clientId)) {
throw new RuntimeException("Invalid claims");
}
}
private RsaVerifier verifier(String kid) throws Exception {
JwkProvider provider = new UrlJwkProvider(new URL(jwkUrl));
Jwk jwk = provider.get(kid);
return new RsaVerifier((RSAPublicKey) jwk.getPublicKey());
}
public void setRestTemplate(OAuth2RestTemplate restTemplate2) {
restTemplate = restTemplate2;
}
private static class NoopAuthenticationManager implements AuthenticationManager {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
throw new UnsupportedOperationException("No authentication should be done with this AuthenticationManager");
}
}
}
In the tutorial you refer to, Google OpenId Connect is used. This service also returns the extra scope email variable that is read in:
OpenIdConnectUserDetails.class
public OpenIdConnectUserDetails(Map<String, String> userInfo, OAuth2AccessToken token) {
this.userId = userInfo.get("sub");
this.username = userInfo.get("email");
this.token = token;
}
Without knowing the specific configuration of your MitreID OIDC server maybe the openId server is not returning the email variable
I am a new auth0 user and had the same issue, trying to get some User information in the Spring Controller. I am able to access the Claims from the token using this code.
#GetMapping
public List<Task> getTasks(AuthenticationJsonWebToken authentication) {
logger.debug("getTasks called.");
DecodedJWT jwt = JWT.decode(authentication.getToken());
Map<String, Claim> claims = jwt.getClaims();
for (Object key: claims.keySet()) {
logger.debug("key: {}, value: {}", key.toString(), claims.get(key).asString());
}
return taskRepository.findAll();
}
Hope this helps.
If you need username, you can get it from JwtAuthenticationToken object as below:
#GetMapping("/home")
public String home(JwtAuthenticationToken user) {
String name = user.getName();
If you need some other information from user's profile, you can call your auth server's /userinfo endpoint with the access token as below:
This will fetch info only if you had included profile scope in your authorize call.
#GetMapping("/home")
public String home(JwtAuthenticationToken user) {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.AUTHORIZATION, "Bearer "+user.getToken().getTokenValue());
HttpEntity entity = new HttpEntity(headers);
ResponseEntity<Map> userinfo = template.exchange("https://your-auth-server/default/v1/userinfo", HttpMethod.GET, entity, Map.class);
String name = (String) userinfo.getBody().get("given_name");
You can retrieve all profile attributes from this response.
For Auth0, I was able to get user information in two ways:
First one is using JwtAuthenticationToken directly on the controller as shown below.
#GetMapping("/info")
public void users(JwtAuthenticationToken token) {
System.out.println(token.getName());
System.out.println(token.getTokenAttributes().get("name"));
}
Here, token.getName() prints the user id from auth0 and token.getTokenAttributes() returns a map from which we can retrieve any information that we need. For example to print user name, we can use token.getTokenAttributes().get("name").
Casting Authentication object to JwtAuthenticationToken:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
JwtAuthenticationToken token = (JwtAuthenticationToken) authentication;
String username = token.getTokenAttributes().get("name");
you can retrieve the user or profile related properties that were defined when creating the okta oidc application through the OidcUser class, which can be used with the AuthenticationPrincipal annotation.Follow below steps
**My Controller:**
#GetMapping("/user")
public User user(#AuthenticationPrincipal OidcUser oidcUser) {
System.out.println("oidcUser :: " + oidcUser + "\n\n");
User user = new User();
System.out.println("Attributes :: " + oidcUser.getAttributes() + "\n\n");
user.setFirstName(oidcUser.getAttribute("given_name"));
user.setLastName(oidcUser.getAttribute("family_name"));
user.setName(oidcUser.getAttribute("name"));
user.setPreferred_username(oidcUser.getAttribute("preferred_username"));
user.setEmail(oidcUser.getAttribute("email"));
user.setGroups(getGroupsFromCurrentUser());
System.out.println(user.toString() + "\n\n");
return user;
}
private List<String> getGroupsFromCurrentUser() {
List<String> groups = new ArrayList<>();
Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
System.out.println("\n\n"+authorities+"\n\n");
for (GrantedAuthority auth : authorities) {
groups.add(auth.getAuthority());
}
System.out.println("\n\n"+"groups :: "+groups+"\n\n");
return groups;
}
I am new to Spring MVC. I need to know how to consume RESTful api in UI.
And also I need to know can we use the api for the same app or we shall create new app to consume these api produced from REST. I have built a REST api in my project and I used the api in the same project with following code. But it didnt work.
RestClient.java
package com.spring.template;
import org.springframework.web.client.RestTemplate;
import com.spring.model.Employee;
public class RestClient {
public static void main(String[] args) {
try {
RestTemplate restTemplate = new RestTemplate();
final String base_url = "http://localhost:8080/SpringWebSevices/";
Employee employee = restTemplate.getForObject(base_url, Employee.class, 200);
System.out.println("Id:"+employee.getEmpid());
}
catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
}
}
EmployeeController.java
package com.spring.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.spring.model.Employee;
import com.spring.service.EmployeeService;
#RestController
public class EmployeeController {
#Autowired
EmployeeService employeeService;
#RequestMapping(value ="/", method = RequestMethod.GET, produces ="application/json")
public ResponseEntity<List<Employee>> employees() {
HttpHeaders headers = new HttpHeaders();
List<Employee> employee = employeeService.getEmployees();
if(employee == null) {
return new ResponseEntity<List<Employee>>(HttpStatus.NOT_FOUND);
}
headers.add("Number of records found:", String.valueOf(employee.size()));
return new ResponseEntity<List<Employee>>(employee, HttpStatus.OK);
}
#RequestMapping(value="/employee/add", method = RequestMethod.POST , produces ="application/json")
public ResponseEntity<Employee> addEmployee(#RequestBody Employee employee) {
HttpHeaders headers = new HttpHeaders();
if(employee == null) {
return new ResponseEntity<Employee>(HttpStatus.BAD_REQUEST);
}
employeeService.createEmployee(employee);
headers.add("Added employee id:", String.valueOf(employee.getEmpid()));
return new ResponseEntity<Employee>(employee, headers, HttpStatus.CREATED);
}
#RequestMapping(value = "/employee/edit/{id}",method=RequestMethod.PUT)
public ResponseEntity<Employee> editEmployee(#PathVariable("id") int empid,#RequestBody Employee employee) {
HttpHeaders headers = new HttpHeaders();
Employee isExist = employeeService.getEmployee(empid);
if(isExist == null) {
return new ResponseEntity<Employee>(HttpStatus.NOT_FOUND);
} else if(employee == null) {
return new ResponseEntity<Employee>(HttpStatus.BAD_GATEWAY);
}
employeeService.updateEmployee(employee);
headers.add("Employee updated:", String.valueOf(employee.getEmpid()));
return new ResponseEntity<Employee>(employee,headers,HttpStatus.OK);
}
#RequestMapping(value = "/employee/delete/{id}", method =RequestMethod.DELETE)
public ResponseEntity<Employee> deleteEmployee(#PathVariable("id") int empid) {
HttpHeaders headers = new HttpHeaders();
Employee employee = employeeService.getEmployee(empid);
if(employee == null) {
return new ResponseEntity<Employee>(HttpStatus.NOT_FOUND);
}
employeeService.deleteEmployee(empid);
headers.add("Employee deleted:", String.valueOf(empid));
return new ResponseEntity<Employee>(employee, headers, HttpStatus.NO_CONTENT);
}
}
This is the error i got:
nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of com.spring.model.Employee out of START_ARRAY token
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of com.spring.model.Employee out of START_ARRAY token
This looks like your sending a list of Employee to some method that accepts a single Employee as a parameter. Possibilities:
addEmployee
editEmployee
Your controller is returning List<Employee>, while you are trying to assign the result to a single Employee object in the line
Employee employee = restTemplate.getForObject(base_url, Employee.class, 200);
You are facing type incompatibilities, you could try
ResponseEntity<? extends ArrayList<Employee>> responseEntity = restTemplate.getForEntity(base_url, (Class<? extends ArrayList<Employee>)ArrayList.class, 200);
I haven't tested it yet but it should work.