I'm using Spring Boot in an app that uses REST servers and REST clients. My problem occurs when I let people choose the URL mapping, because then I need to dynamically create a REST Controller with this URL mapping:
#SpringBootApplication
public static void main(String[] args){
SpringApplication.run(MyClass.class);
String urlMapping = "/url/url";
CreateNewRestController c = new CreateNewRestController(urlMapping);
}
#RestController
public class CreateNewRestController{
String url;
public CreateNewRestController(String url){
this.url = url
}
#RequestMapping(value = this.url,method = RequestMethod.GET)
public String getHello(){
return "Hello";
}
}
"Why are you using a url variable when you could use a constant string in the RequestMapping?" you may ask. But this is a simple example and I need to create the RequestMapping using a variable argument.
Github link - here
I found a awnser to my question and maybe a solution to used RequestMapping in real time. I created a RestController that take all the HTTP petitions to it, them the petition ask to a hash map with have the uri mapping like key and a class controller like value.
The generic controller:
#RestController
public class GeneralController {
HashMap<String,PersonalizedController> petitions = new HashMap<String,PersonalizedController>();
#RequestMapping("**")
public ResponseEntity<Object> index(HttpServletRequest request,HttpServletResponse response,#RequestBody Object body) {
// Inicialization zone - this zone don't must exist
petitions.put("/dir/esta1", new PersonalizedController("esta1"));
petitions.put("/dir/esta2", new PersonalizedController("esta2"));
//-------------------------------------------------
return handlePetition(request,response);
}
private ResponseEntity<Object> handlePetition(HttpServletRequest request, HttpServletResponse response) {
// TODO Auto-generated method stub
String petition = request.getRequestURI();
String method = request.getMethod();
return petitions.get(petition).makePetition(method,new String());
}
}
The controller class:
public class PersonalizedController {
private String name;
public PersonalizedController(String name) {
this.name = name;
}
public ResponseEntity<Object> makePetition(String method,Object body) {
// TODO Auto-generated method stub
switch (method) {
case "GET":
return doGet(body);
case "POST":
return doPost(body);
case "PUT":
return doPut(body);
case "DELETE":
return doDelete(body);
default:
return new ResponseEntity<Object>("",HttpStatus.METHOD_NOT_ALLOWED);
}
}
public ResponseEntity<Object> doGet(Object body) {
return new ResponseEntity<Object>("["+name+"] GET",HttpStatus.OK);
}
public ResponseEntity<Object> doPost(Object body) {
return new ResponseEntity<Object>("["+name+"] POST",HttpStatus.OK);
}
public ResponseEntity<Object> doDelete(Object body) {
return new ResponseEntity<Object>("["+name+"] DELETE",HttpStatus.OK);
}
public ResponseEntity<Object> doPut(Object body) {
return new ResponseEntity<Object>("["+name+"] PUT",HttpStatus.OK);
}
}
I solved this for my use case by using a Map to store 'sub' paths and passing all the requests to a generalized controller.
My use case needed a generic proxy app for multiple back-ends. Not too much different than what as you described a possible solution.
Source code -
https://github.com/savantly-net/mesh-gateway
Example -
#RestController
#RequestMapping(MeshGateway.PATH)
public class MeshGateway {
protected static final String PATH = "/gateway";
private static final Logger log = LoggerFactory.getLogger(MeshGateway.class);
private MeshGatewayConfig config;
public MeshGateway(MeshGatewayConfig config) {
this.config = config;
}
#GetMapping("/{child}/**")
public ResponseEntity<?> get(#PathVariable String child, ProxyExchange<byte[]> proxy) throws Exception {
log.debug("doing GET: {}", proxy.path());
return proxy.uri(getDestinationPath(child, proxy)).get();
}
#PostMapping("/{child}/**")
public ResponseEntity<?> post(#PathVariable String child, ProxyExchange<byte[]> proxy) throws Exception {
log.debug("doing GET: {}", proxy.path());
return proxy.uri(getDestinationPath(child, proxy)).post();
}
#PutMapping("/{child}/**")
public ResponseEntity<?> put(#PathVariable String child, ProxyExchange<byte[]> proxy) throws Exception {
log.debug("doing GET: {}", proxy.path());
return proxy.uri(getDestinationPath(child, proxy)).put();
}
#RequestMapping(path = "/{child}/**", method = RequestMethod.OPTIONS)
public ResponseEntity<?> options(#PathVariable String child, ProxyExchange<byte[]> proxy) throws Exception {
log.debug("doing GET: {}", proxy.path());
return proxy.uri(getDestinationPath(child, proxy)).options();
}
#RequestMapping(path = "/{child}/**", method = RequestMethod.PATCH)
public ResponseEntity<?> patch(#PathVariable String child, ProxyExchange<byte[]> proxy) throws Exception {
log.debug("doing GET: {}", proxy.path());
return proxy.uri(getDestinationPath(child, proxy)).patch();
}
#RequestMapping(path = "/{child}/**", method = RequestMethod.DELETE)
public ResponseEntity<?> delete(#PathVariable String child, ProxyExchange<byte[]> proxy) throws Exception {
log.debug("doing GET: {}", proxy.path());
return proxy.uri(getDestinationPath(child, proxy)).delete();
}
#RequestMapping(path = "/{child}/**", method = RequestMethod.HEAD)
public ResponseEntity<?> head(#PathVariable String child, ProxyExchange<byte[]> proxy) throws Exception {
log.debug("doing GET: {}", proxy.path());
return proxy.uri(getDestinationPath(child, proxy)).head();
}
private String getDestinationPath(String child, ProxyExchange<byte[]> proxy) {
String destination = this.config.getRoutes().get(child);
String path = proxy.path(String.format("%s/%s", PATH, child));
log.debug("with prefix removed: {}", path);
return String.format("%s%s", destination, path);
}
}
Related
everyone!
I making a defense against password brute force.
I successfully handle AuthenticationFailureBadCredentialsEvent when the user writes the right login and wrong password. But the problem is that I want to return JSON with two fields
{
message : '...' <- custom message
code : 'login_failed'
}
The problem is that it returns standart forbidden exception, but I need custom json.
#Log4j2
#Component
#RequiredArgsConstructor
public class AuthenticationAttemptsHandler {
protected final MessageSource messageSource;
private final AuthenticationAttemptsStore attemptsStore;
private final UserDetailsService userDetailsService;
private final UserDetailsLockService userDetailsLockService;
#EventListener
public void handleFailure(AuthenticationFailureBadCredentialsEvent event) {
val authentication = event.getAuthentication();
val userDetails = findUserDetails(authentication.getName());
userDetails.ifPresent(this::failAttempt);
}
private Optional<UserDetails> findUserDetails(String username) {
...
}
private void failAttempt(UserDetails details) {
val username = details.getUsername();
val attempt = attempt(loginAttemptsProperties.getResetFailuresInterval());
int failures = attemptsStore.incrementFailures(username, attempt);
if (failures >= 2) {
Instant lockedUntil = Instant.now().plus(loginAttemptsProperties.getLockDuration());
userDetailsLockService.lockUser(username, lockedUntil);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
String date = formatter.format(lockedUntil);
String message = String.format("Account will locked till %s", date);
throw new SecurityException(message);
//FailAttemptsExceptionResponse response = new FailAttemptsExceptionResponse(message, //
//"login_ failed"); <---- tryed return entity from this method. Does not work.
// return new ResponseEntity<>(response,HttpStatus.FORBIDDEN);
} else {
String message = String.format("You have %s attempts.", (3 - failures));
// FailAttemptsExceptionResponse response = new FailAttemptsExceptionResponse(message,
"login_ failed");
throw new SecurityException(message);
// return new ResponseEntity<>(response,HttpStatus.FORBIDDEN);
}
}
}
RuntimeException returns 500 status? but I need forbidden
public class SecurityException extends RuntimeException {
private static final long serialVersionUID = 1L;
public SecurityException(String msg) {
super(msg);
}
}
Responce model
public class FailAttemptsExceptionResponse {
String message;
String code;
public FailAttemptsExceptionResponse(String message, String code) {
super();
this.message = message;
this.code = code;
}
public String getMessage() {
return message;
}
public String getCode() {
return code;
}
}
Tried to handle SecurityException and then returns model? but it does not work
#ControllerAdvice
public class SeurityAdvice extends ResponseEntityExceptionHandler {
#ExceptionHandler(SecurityException.class)
public ResponseEntity<FailAttemptsExceptionResponse> handleNotFoundException(SecurityException ex) {
FailAttemptsExceptionResponse exceptionResponse = new FailAttemptsExceptionResponse(ex.getMessage(),
"login_ failed");
return new ResponseEntity<FailAttemptsExceptionResponse>(exceptionResponse,
HttpStatus.NOT_ACCEPTABLE);
}
}
I successfully handle AuthenticationFailureBadCredentialsEvent, but how can I return JSON response model from the handler with a custom message?
#ControllerAdvice
public class SeurityAdvice extends ResponseEntityExceptionHandler {
#ExceptionHandler(SecurityException.class)
public ResponseEntity<FailAttemptsExceptionResponse> handleNotFoundException(SecurityException ex, HttpServletResponse response) {
FailAttemptsExceptionResponse exceptionResponse = new FailAttemptsExceptionResponse(ex.getMessage(),
"login_ failed");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return new ResponseEntity<FailAttemptsExceptionResponse>(exceptionResponse,
HttpStatus.NOT_ACCEPTABLE);
}
}
maybe you need to add HttpServletResponse and set the http status.
Register the entry point
As mentioned, I do it with Java Config. I just show the relevant configuration here, there should be other configuration such as session stateless, etc.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(new CustomEntryPoint());
}
}
U can create AuthenticationEntryPoint.
Короч тут почитай xD
Handle spring security authentication exceptions with #ExceptionHandler
This is my project:
ProducerController .java:
package mypackage.application;
#Controller
#RequestMapping(value = "/producer")
public class ProducerController {
#RequestMapping(value="/", method = RequestMethod.GET, produces = "application/json")
public String info() {
return "hello";
}
#RequestMapping(value="/send", method = RequestMethod.GET, produces = "application/json")
public Email greeting() {
return new Email("send#tome.com","hello");
}
}
Email.java
public class Email {
private String to;
private String body;
public Email(String to, String body) {
this.to = to;
this.body = body;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
Application.java
#SpringBootApplication
#ComponentScan(basePackageClasses=ProducerController.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(EbmApplication.class, args);
}
}
When I do the GET Request http://localhost:8080/producer/send or http://localhost:8080/producer/
I have returned this error message when the return is called:
There was an unexpected error (type=Not Found, status=404).
What's wrong?
Thanks a lot
Use #RestController instead of #Controller
#RestController
#RequestMapping(value = "/producer")
public class ProducerController {
....
}
The HTTP status code of 404 is precisely the appropriate
response status code when a resource isn’t found.
because now the controller is annotated
with #RestController, the objects returned from those methods will go through
message conversion to produce a resource representation for the client (browser).
you are returning string "hello" from your method, which Spring thinks is a view name. I assume that in your case it is not, so try this:
#RequestMapping(value="/", method = RequestMethod.GET, produces = "application/json")
#ResponseBody
public String info() {
return "hello";
}
so that "hello" would be the string returned from your method.
#RestController mentioned in the comment also works, it does the same thing - with that, returned strings are assumed to be content, not view names.
I am using a spring controller which returns a string from a threadsafe method.
so i have made the controller also thread safe.
I want to know how many request are there in queue simultaneously which are calling to the spring controller
Here is my suggestion how you can solve your issue.
Imagine you have this #Controller:
#Controller
public class MyController {
#Autowired
private IMyService service;
#RequestMapping(value = "/myPathName", method = RequestMethod.GET)
public String home(HttpServletRequest request, Model model) {
// you synchronized call
service.callSynchronized(request, model);
return "someJsp";
}
// +edit
#ResponseBody
#RequestMapping(value = "/queueStatus", method = RequestMethod.GET)
public String home(HttpServletRequest request) {
//
return "inQueue: " + request.getAttribute("inQueue");
}
}
Now you can define an interceptor and count the requests before and after execution:
public class RequestsInWorkInterceptor extends HandlerInterceptorAdapter {
private static final Logger _logger = LoggerFactory.getLogger(RequestsInWorkInterceptor.class);
private final AtomicLong counter = new AtomicLong();
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String methodURI = request.getRequestURI();
long current;
if("myPathName".equals(methodURI){
current = counter.incrementAndGet();
_logger.debug("current {} clients in a queue", current);
} else {
current = counter.get(); // just get, no increment
}
// +edit: put the count in the request so you can get it in you controller
request.setAttribute("inQueue", current);
return super.preHandle(request, response, handler);
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
String methodURI = request.getRequestURI();
if("myPathName".equals(methodURI){
counter.decrementAndGet();
}
super.postHandle(request, response, handler, modelAndView);
}
}
You can get information about the number of active requests using Jetty's StatisticsHandler and JMX.
If you're using Jetty as an embedded container (the recommended approach), you can use an EmbeddedServletContainerCustomizer to set this up:
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
((JettyEmbeddedServletContainerFactory) container)
.addServerCustomizers(new JettyServerCustomizer() {
#Override
public void customize(Server server) {
MBeanContainer mbContainer = new MBeanContainer(
ManagementFactory.getPlatformMBeanServer());
server.addEventListener(mbContainer);
server.addBean(mbContainer);
StatisticsHandler statisticsHandler = new StatisticsHandler();
statisticsHandler.setHandler(server.getHandler());
server.setHandler(statisticsHandler);
}
});
}
};
}
You'll need to add a dependency on org.eclipse.jetty:jetty-jmx to get access to MBeanContainer.
You can try with spring boot actuator. On endpoint /metrics you should have field "httpsessions.active"
public class RequesInterceptor extends HandlerInterceptorAdapter {
private static Object lock = new Object();
private static int count = 0
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException {
sinchronize(lock){
count++;
}
}
}
I'm working with java and Spring MVC, In the first version of the app I was response with a ResponseEntity<String> and where I haved and error I returned something like return new ResponseEntity<String>(httpErrors.toString(), responseHeaders, HttpStatus.BAD_REQUEST); and when all were right something like return new ResponseEntity<String>(loginResponse.toString(), responseHeaders, HttpStatus.OK);. But now I believe theres is a better way to do it, without using the toString() method, returning the specific object according to the case like this:
#RestController
#RequestMapping("/user")
public class LoginController {
/** The login service to validate the user. */
#Autowired
LoginService loginService;
#RequestMapping(value = "/login", method = RequestMethod.POST)
public #ResponseBody ResponseEntity<?> validate(#RequestBody final UserLog login) {
WebUser webUser = loginService.getUserDetails(login.getLogin(), login.getPassword());
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
if (webUser == null) {
HttpErrors httpErrors = new HttpErrors(ApiCommonResources.ERROR_402, "error" + "." + ApiCommonResources.ERROR_402, ApiCommonResources.ERROR_402_TEXT);
return new ResponseEntity<HttpErrors>(httpErrors, responseHeaders, HttpStatus.BAD_REQUEST);
}
List<Account> userAccounts = loginService.getMerchantAccounts(webUser.getMerchantId());
// Json Web Token builder
token = "b7d22951486d713f92221bb987347777";
LoginResponse loginResponse = new LoginResponse(ApiCommonResources.SUCCESS_REQUEST_CODE, token);
return new ResponseEntity<LoginResponse>(loginResponse, responseHeaders, HttpStatus.OK);
}
}
The question is how can I create a class that can wraps the LoginResponse as well as HttpErrorsobject types and send it in ? as the returning object in ResponseEntity:
LoginResponse class:
public class LoginResponse{
public LoginResponse(Integer statusCode, String token){
this.setStatusCode(statusCode);
this.setToken(token);
}
private String token;
private Integer statusCode;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Integer getStatusCode() {
return statusCode;
}
public void setStatusCode(Integer statusCode) {
this.statusCode = statusCode;
}
#Override
public String toString() {
StringBuilder jsonResponse = new StringBuilder();
jsonResponse.append("{");
jsonResponse.append("\"statusCode\":");
jsonResponse.append("\"" + statusCode + "\",");
jsonResponse.append("\"token\":");
jsonResponse.append("\"" + token + "\"");
jsonResponse.append("}");
return jsonResponse.toString();
}
}
And HttpErrors class:
public class HttpErrors {
public HttpErrors(){
}
public HttpErrors(String errorCode, String errorKey, String errorMessage) {
super();
this.errorCode = errorCode;
this.errorKey = errorKey;
this.errorMessage = errorMessage;
}
private String errorCode;
private String errorKey;
private String errorMessage;
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorKey() {
return errorKey;
}
public void setErrorKey(String errorKey) {
this.errorKey = errorKey;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
#Override
public String toString() {
StringBuilder jsonError = new StringBuilder();
jsonError.append("{");
jsonError.append("\"errorCode\":");
jsonError.append("\"" + errorCode + "\",");
jsonError.append("\"errorKey\":");
jsonError.append("\"" + errorKey + "\",");
jsonError.append("\"errorMessage\":");
jsonError.append("\"" + errorMessage + "\"");
jsonError.append("}");
return jsonError.toString();
}
}
public class Response<T> {
private int httpStatus;
private T data;
//getter and setter consructor
eg constructors
public RestResponse(T data){
this(HTTP_OK,data)
}
public RestResponse(int httpStatus,T data){
this.httpStatus = httpStaus;
this.data = data;
}
Now just use this template for all your response objects (repsone objects can be POJOs too)
return new RestEntity<LoginResponse>(loginResponse,statusCode) //loginResponse object
where LoginResponse is
public class LoginResponse {
private String token;
//getter and setter and constructors.
}
You should take some time to establish a REST contracts (Read about it using google :)) and then just follow through using this basic logic. Java and spring are magic together.
Have fun.
maybe try something like this (in my opinion it will be more elegant)
create a method in controller which returns LoginResponse, but firstly perform validation of the input UserLog and once there are any issues, throw a custom exception, which in the end will be caught by the exceptionHandler
take a look at my example controller
#RestController
public class ProductController {
private ProductRequestValidator productRequestValidator;
#InitBinder
public void initBinder(WebDataBinder binder){
binder.addValidators(productRequestValidator);
}
#Autowired
public ProductController(ProductRequestValidator productRequestValidator, ProductService productService) {
this.productRequestValidator = productRequestValidator;
}
#RequestMapping(value = "/products", method = RequestMethod.GET)
public List<ProductResponse> retrieveProducts(#Valid #RequestBody ProductRequest requestProduct, BindingResult bindingResult)
throws ValidationException {
// validate input and throw exception if any error occured
if (bindingResult.hasErrors()){
throw new ValidationException(bindingResult);
}
// business logic
return new ProductRequest();
}
if you want you can check my bitbucket project which has it all implemented:
controller
exceptionHandler
customException
customValidator
Using Jersey 1.14 and Spring 3.1.2
I want to create a filter like this: https://gist.github.com/3031495
but in that filter I want access to a provider I created.
I'm getting an IllegalStateException. I suspect something in my lifecycle is hosed up. I can access #Context private HttpServletRequest and pull the session info I need from there, but then two classes have to know about where/how to get my "AuthUser" object.
Any help is appreciated!
My Provider:
#Component
#Provider
public class AuthUserProvider extends AbstractHttpContextInjectable<AuthUser> implements
InjectableProvider<Context, Type> {
private static final Logger LOG = LoggerFactory.getLogger(AuthUserProvider.class);
#Context
HttpServletRequest req;
public void init() {
LOG.debug("created");
}
#Override
// this may return a null AuthUser, which is what we want....remember, a
// null AuthUser means the user hasn't authenticated yet
public AuthUser getValue(HttpContext ctx) {
return (AuthUser) req.getSession().getAttribute(AuthUser.KEY);
}
// InjectableProvider implementation:
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
public Injectable<AuthUser> getInjectable(ComponentContext ic, Context ctx, Type c) {
if (AuthUser.class.equals(c)) {
return this;
}
return null;
}
}
My Filter:
#Component
public class TodoFilter implements ResourceFilter {
private static final Logger LOG = LoggerFactory.getLogger(TodoFilter.class);
#Autowired
private JdbcTemplate todoTemplate;
// this works
#Context
private HttpServletRequest servletRequest;
// this throws a java.lang.IllegalStateException
// #Context
// private AuthUser authUser;
public void init() throws Exception {
LOG.debug("created");
LOG.debug(todoTemplate.getDataSource().getConnection().getMetaData()
.getDatabaseProductName());
}
#Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
#Override
public ContainerRequest filter(ContainerRequest request) {
LOG.debug("checking if {} is authorized to use {}", "my authenticated user",
request.getPath());
// String name = request.getUserPrincipal().getName();
// String[] admins = settings.getAdminUsers();
// for (String adminName : admins) {
// if (adminName.equals(name))
// return request;
// }
// if (authUser.getUsername().equals("jberk")) {
// return request;
// }
// return HTTP 403 if name is not found in admin users
throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
.entity("You are not authorized!").build());
}
};
}
#Override
public ContainerResponseFilter getResponseFilter() {
return new ContainerResponseFilter() {
#Override
public ContainerResponse filter(ContainerRequest request,
ContainerResponse response) {
// do nothing
return response;
}
};
}
}
My Service (aka Resource):
#Component
#Path("/rs/todo")
#Produces(MediaType.APPLICATION_JSON)
#ResourceFilters(TodoFilter.class)
public class TodoService {
#GET / #POST methods
}
so I think I figured this out....
I added this to my ResourceFilter:
#Context
private HttpContext ctx;
#Autowired
private AuthUserProvider provider;
then I can do this in the filter method:
public ContainerRequest filter(ContainerRequest request) {
AuthUser authUser = provider.getValue(ctx);
// use authuser in some way
}
this might not be "correct"...but it's working and I don't have code duplication
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
It should be
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}