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
I'm trying to figure how to explain Jersey and Jackson how to deserialize a Future that I pass as byte[].
I create my own ContextResolver
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private static ObjectMapper mapper = null;
public ObjectMapperContextResolver() {
mapper = ObjectMapperFactory.getObjectMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
And the implementation of the ObjectMapper
public static ObjectMapper getObjectMapper() {
ObjectMapper defaultObjectMapper = new ObjectMapper();
SimpleModule futureModule = new SimpleModule("FutureModule");
futureModule.<Future>addDeserializer(Future.class, new FutureDeserializer<String>());
defaultObjectMapper.registerModule(futureModule);
return defaultObjectMapper;
}
And then finally in the implementation of my FutureDeserializer
public class FutureDeserializer<T> extends StdDeserializer<Future<T>>{
public FutureDeserializer() {
super(Future.class);
}
#Override
public Future<T> deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
ObjectMapper mapper=(ObjectMapper)jp.getCodec();
//TODO: Breakpoint never stop here
return null;
}
}
Then I register in my ResourceConfig before start the JerseyTest
ResourceConfig rc = new ResourceConfig();
rc.register(SpringLifecycleListener.class);
rc.register(RequestContextFilter.class);
rc.register(new JacksonFeature());
rc.register(new ObjectMapperContextResolver());
But when I run the test the ObjectMapperContextResolver is invoked and the mapper returned to Jersey, but he never use the FutureDeserializer.
Any idea what I´m doing wrong?
I'm using a customised ContextResolver with my JAX-RS application.
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#Provider
public class ObjectMapperContextResolver
implements ContextResolver<ObjectMapper> {
public ObjectMapperContextResolver() {
super();
// I want to control this
objectMapper = new ObjectMapper()
.configure(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME, true);
objectMapper.registerModule(new JaxbAnnotationModule());
}
#Override
public ObjectMapper getContext(final Class<?> type) {
return objectMapper;
}
private final ObjectMapper objectMapper;
}
As you can see, there is an option(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME) customised.
How can I turn off that option on demand in JAX-RS resources?
#Path("/items")
public class ItemsResource {
#GET
#Produces({APPLICATION_JSON, APPLICATION_XML})
public Response read(#QueryParam("wrap") final boolean wrap) {
if (wrap) {
// turn off MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME
// and then return response of [Items]
} else {
// turn on MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME
// and then return response of [List<Item>]
}
}
}
Is there any way to be injected with the provider?
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;
}
Can one use the Jackson #JsonView and #JsonFilter annotations to modify the JSON returned by a Spring MVC controller, whilst using MappingJacksonHttpMessageConverterand Spring's #ResponseBody and #RequestBody annotations?
public class Product
{
private Integer id;
private Set<ProductDescription> descriptions;
private BigDecimal price;
...
}
public class ProductDescription
{
private Integer id;
private Language language;
private String name;
private String summary;
private String lifeStory;
...
}
When the client requests a collection of Products, I'd like to return a minimal version of each ProductDescription, perhaps just its ID. Then in a subsequent call the client can use this ID to ask for a full instance of ProductDescription with all properties present.
It would be ideal to be able to specify this on the Spring MVC controller methods, as the method invoked defines the context in which client was requesting the data.
This issue is solved!
Follow this
Add support for Jackson serialization views
Spring MVC now supports Jackon's serialization views for rendering
different subsets of the same POJO from different controller
methods (e.g. detailed page vs summary view).
Issue: SPR-7156
This is the SPR-7156.
Status: Resolved
Description
Jackson's JSONView annotation allows the developer to control which aspects of a method are serialiazed. With the current implementation, the Jackson view writer must be used but then the content type is not available. It would be better if as part of the RequestBody annotation, a JSONView could be specified.
Available on Spring ver >= 4.1
UPDATE
Follow this link. Explains with an example the #JsonView annotation.
Ultimately, we want to use notation similar to what StaxMan showed for JAX-RS. Unfortunately, Spring doesn't support this out of the box, so we have to do it ourselves.
This is my solution, it's not very pretty, but it does the job.
#JsonView(ViewId.class)
#RequestMapping(value="get", method=RequestMethod.GET) // Spring controller annotation
public Pojo getPojo(#RequestValue Long id)
{
return new Pojo(id);
}
public class JsonViewAwareJsonView extends MappingJacksonJsonView {
private ObjectMapper objectMapper = new ObjectMapper();
private boolean prefixJson = false;
private JsonEncoding encoding = JsonEncoding.UTF8;
#Override
public void setPrefixJson(boolean prefixJson) {
super.setPrefixJson(prefixJson);
this.prefixJson = prefixJson;
}
#Override
public void setEncoding(JsonEncoding encoding) {
super.setEncoding(encoding);
this.encoding = encoding;
}
#Override
public void setObjectMapper(ObjectMapper objectMapper) {
super.setObjectMapper(objectMapper);
this.objectMapper = objectMapper;
}
#Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
Class<?> jsonView = null;
if(model.containsKey("json.JsonView")){
Class<?>[] allJsonViews = (Class<?>[]) model.remove("json.JsonView");
if(allJsonViews.length == 1)
jsonView = allJsonViews[0];
}
Object value = filterModel(model);
JsonGenerator generator =
this.objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(), this.encoding);
if (this.prefixJson) {
generator.writeRaw("{} && ");
}
if(jsonView != null){
SerializationConfig config = this.objectMapper.getSerializationConfig();
config = config.withView(jsonView);
this.objectMapper.writeValue(generator, value, config);
}
else
this.objectMapper.writeValue(generator, value);
}
}
public class JsonViewInterceptor extends HandlerInterceptorAdapter
{
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
JsonView jsonViewAnnotation = handlerMethod.getMethodAnnotation(JsonView.class);
if(jsonViewAnnotation != null)
modelAndView.addObject("json.JsonView", jsonViewAnnotation.value());
}
}
In spring-servlet.xml
<bean name="ViewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
</map>
</property>
<property name="defaultContentType" value="application/json" />
<property name="defaultViews">
<list>
<bean class="com.mycompany.myproject.JsonViewAwareJsonView">
</bean>
</list>
</property>
</bean>
and
<mvc:interceptors>
<bean class="com.mycompany.myproject.JsonViewInterceptor" />
</mvc:interceptors>
I don't know how things work with Spring (sorry!), but Jackson 1.9 can use #JsonView annotation from JAX-RS methods, so you can do:
#JsonView(ViewId.class)
#GET // and other JAX-RS annotations
public Pojo resourceMethod()
{
return new Pojo();
}
and Jackson will use View identified by ViewId.class as the active view. Perhaps Spring has (or will have) similar capability? With JAX-RS this is handled by standard JacksonJaxrsProvider, for what that's worth.
Looking for the same answer I came up with an idea to wrap ResponseBody object with a view.
Piece of controller class:
#RequestMapping(value="/{id}", headers="Accept=application/json", method= RequestMethod.GET)
public #ResponseBody ResponseBodyWrapper getCompany(HttpServletResponse response, #PathVariable Long id){
ResponseBodyWrapper responseBody = new ResponseBodyWrapper(companyService.get(id),Views.Owner.class);
return responseBody;
}
public class ResponseBodyWrapper {
private Object object;
private Class<?> view;
public ResponseBodyWrapper(Object object, Class<?> view) {
this.object = object;
this.view = view;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
#JsonIgnore
public Class<?> getView() {
return view;
}
#JsonIgnore
public void setView(Class<?> view) {
this.view = view;
}
}
Then I override writeInternal method form MappingJackson2HttpMessageConverter to check if object to serialize is instanceof wrapper, if so I serialize object with required view.
public class CustomMappingJackson2 extends MappingJackson2HttpMessageConverter {
private ObjectMapper objectMapper = new ObjectMapper();
private boolean prefixJson;
#Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
JsonGenerator jsonGenerator =
this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
try {
if (this.prefixJson) {
jsonGenerator.writeRaw("{} && ");
}
if(object instanceof ResponseBodyWrapper){
ResponseBodyWrapper responseBody = (ResponseBodyWrapper) object;
this.objectMapper.writerWithView(responseBody.getView()).writeValue(jsonGenerator, responseBody.getObject());
}else{
this.objectMapper.writeValue(jsonGenerator, object);
}
}
catch (IOException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
}
}
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
super.setObjectMapper(objectMapper);
}
public ObjectMapper getObjectMapper() {
return this.objectMapper;
}
public void setPrefixJson(boolean prefixJson) {
this.prefixJson = prefixJson;
super.setPrefixJson(prefixJson);
}
}
The answer to this after a many head banging dead ends and nerd rage tantrums is....
so simple. In this use case we have a Customer bean with a complex object Address embedded within it and we want to prevent the serialization of a property name surburb and street in address, when the json serialization take place.
We do this by applying an annotation #JsonIgnoreProperties({"suburb"}) on the field address in the Customer class, the number of fields to be ignored is limitless. e.g i want to ingnore both suburb and street. I would annotate the address field with #JsonIgnoreProperties({"suburb", "street"})
By doing all this we can create HATEOAS type architecture.
Below is the full code
Customer.java
public class Customer {
private int id;
private String email;
private String name;
#JsonIgnoreProperties({"suburb", "street"})
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Address.java
public class Address {
private String street;
private String suburb;
private String Link link;
public Link getLink() {
return link;
}
public void setLink(Link link) {
this.link = link;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getSuburb() {
return suburb;
}
public void setSuburb(String suburb) {
this.suburb = suburb;
}
}
In addition to #user356083 I've made some modifications to make this example work when a #ResponseBody is returned. It's a bit of a hack using ThreadLocal but Spring doesn't seem to provide the necessary context to do this the nice way.
public class ViewThread {
private static final ThreadLocal<Class<?>[]> viewThread = new ThreadLocal<Class<?>[]>();
private static final Log log = LogFactory.getLog(SocialRequestUtils.class);
public static void setKey(Class<?>[] key){
viewThread.set(key);
}
public static Class<?>[] getKey(){
if(viewThread.get() == null)
log.error("Missing threadLocale variable");
return viewThread.get();
}
}
public class JsonViewInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
JsonView jsonViewAnnotation = handlerMethod
.getMethodAnnotation(JsonView.class);
if (jsonViewAnnotation != null)
ViewThread.setKey(jsonViewAnnotation.value());
return true;
}
}
public class MappingJackson2HttpMessageConverter extends
AbstractHttpMessageConverter<Object> {
#Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
JsonGenerator jsonGenerator =
this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
// This is a workaround for the fact JsonGenerators created by ObjectMapper#getJsonFactory
// do not have ObjectMapper serialization features applied.
// See https://github.com/FasterXML/jackson-databind/issues/12
if (objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
jsonGenerator.useDefaultPrettyPrinter();
}
//A bit of a hack.
Class<?>[] jsonViews = ViewThread.getKey();
ObjectWriter writer = null;
if(jsonViews != null){
writer = this.objectMapper.writerWithView(jsonViews[0]);
}else{
writer = this.objectMapper.writer();
}
try {
if (this.prefixJson) {
jsonGenerator.writeRaw("{} && ");
}
writer.writeValue(jsonGenerator, object);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
}
}
Even better, since 4.2.0.Release, you can do this simply as follows:
#RequestMapping(method = RequestMethod.POST, value = "/springjsonfilter")
public #ResponseBody MappingJacksonValue byJsonFilter(...) {
MappingJacksonValue jacksonValue = new MappingJacksonValue(responseObj);
jacksonValue.setFilters(customFilterObj);
return jacksonValue;
}
References:
1. https://jira.spring.io/browse/SPR-12586
2. http://wiki.fasterxml.com/JacksonFeatureJsonFilter