Is there a way to allow a field as input but exclude it from output in JAX-B? I added #XMLTransient, but that prevents the field from being used as an input field. I'm using Jersey 2.25.1 and Moxy.
The scenario is a password field on a user record. I want to allow it to be passed in when creating a new user record, but I don't want to include it in any responses as part of the user POJO.
EDIT:
I tried the #XmlReadOnly attribute and it seems to have done the trick. It is part of the org.eclipse.persistence.oxm.annotations package.
One way would be to use an XmlAdapter. You could return the result on the unmarshal() and return null on the marhsall()
public class OnlyInputAdapter extends XmlAdapter<String, String> {
#Override
public String unmarshal(String s) throws Exception {
return s;
}
#Override
public String marshal(String v) throws Exception {
return null;
}
}
Then just annotate the password property on the model
#XmlJavaTypeAdapter(OnlyInputAdapter.class)
public String getPassword() {
return this.password;
}
Here's a test
public class PasswordSerializationTest extends JerseyTest {
private User serverUser;
#Path("test")
#Produces("application/json")
#Consumes("application/json")
public class TestResource {
#POST
public User postUser(User user) {
serverUser = user;
return user;
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig().register(new TestResource());
}
#Test
public void doIt() {
User clientUser = target("test")
.request()
// post string; can't post User as the
// password field wouldn't serialize
.post(Entity.json("{\"password\":\"secret\"}"), User.class);
assertThat(serverUser.getPassword()).isEqualTo("secret");
assertThat(clientUser.getPassword()).isEqualTo(null);
}
}
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
#Path("/users/A")
public class UserResource1 {
#GET
#Produces("text/xml")
public String getUser(#PathParam("username") String userName) {
...
}
}
#Path("/users/B")
public class UserResource2 {
#GET
#Produces("text/xml")
public String getUser(#PathParam("username") String userName) {
...
}
}
you can have same method name and request mapping with different path url
/*
To handle A type users logic
http://localhost:8080/users/A
*/
#Path("/users/A")
public class UserResource1 {
#GET
#Produces("text/xml")
public String getUser(#PathParam("username") String userName) {
}
}
/*
To handle B type users logic
http://localhost:8080/users/B
*/
#Path("/users/B")
public class UserResource2 {
#GET
#Produces("text/xml")
public String getUser(#PathParam("username") String userName) {
}
}
finally you have two end points
http://localhost:8080/users/A?username=bob
http://localhost:8080/users/B?username=testUser
I want to pass the user object I use for authentication in a filter to the resource. Is it possible?
I'm using wildfly 10 (resteasy 3)
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
#Inject
private UserDao userDao;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
logger.warn("Filter");
String uid = requestContext.getHeaderString("Authorization");
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
}
private User validateUser(String uid) {
return userDao.getById(uid);
}
}
There are two ways I could see to do this. The first is, perhaps, the more standard way but is also more code. Ultimately you'll inject the user as part of the request. However, the first thing you need for this solution is a Principal. A very simple one might be:
import java.security.Principal;
...
public class UserPrinicipal implements Prinicipal {
// most of your existing User class but needs to override getName()
}
Then, in your filter:
...
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return user;
}
#Override
public boolean isUserInRole(String role) {
// whatever works here for your environment
}
#Override
public boolean isSecure() {
return containerRequestContext.getUriInfo().getAbsolutePath().toString().startsWith("https");
}
#Override
public String getAuthenticationScheme() {
// again, whatever works
}
});
In the class where you want the User, you could do something like:
#Path("/myservice")
public class MyService {
#Context
private SecurityContext securityContext;
#Path("/something")
#GET
public Response getSomething() {
User user = (User)securityContext.getUserPrincipal();
}
}
I've implemented it this way and it works pretty well. However, an arguably simpler way is to just store the user in the session:
#Context
private HttpServletRequest request;
...
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
request.getSession().setAttribute("user", user);
Then, in your service:
#Path("/myservice")
public class MyService {
#Context
private SecurityContext securityContext;
#Path("/something")
#GET
public Response getSomething(#Context HttpServletRequest request) {
User user = (User)request.getSession().getAttribute("user");
}
}
The downside of the second method is that you are really no longer a stateless service as you're storing state somewhere. But the HttpSession is there even if you don't use it.
I'm trying to add a request parameter with a default value - however I'd like that default value to be the logged in user's name.
I have a method getUsername() which returns the current user's name but I can't set the value of an annotation to a method call (or a class attribute). Here's my method:
#RequestMapping(method = RequestMethod.GET)
public #ResponseBody ResponseEntity<WebUser> getUser(
#RequestParam(value = "username", defaultValue = getUsername()) String username) {
return ResponseEntity.ok(service.getUser(getUsername(), username.toLowerCase()));
}
I can make the RequestParam not required and populate it if null - but this doesn't feel very elegant (or spring-ish). Is there another way to accomplish this?
As suggested by fateddy, the easiest way to do this is by implementing a HandlerMethodArgumentResolver.
public class UsernameHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterType().equals(Username.class);
}
#Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
String username = nativeWebRequest.getParameter("username");
if (username == null && nativeWebRequest.getUserPrincipal() != null) {
username = nativeWebRequest.getUserPrincipal().getName();
}
return new Username(username);
}
}
This requires a simple username class:
public class Username {
private String username;
public Username(String username) {
this.username = username;
}
public String getValue() {
return this.username;
}
}
as well as an annotation
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface UserRequest {}
In order to get this configured properly this requires a very minor change to the WebMvcConfigurerAdapter:
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new UsernameHandlerMethodArgumentResolver());
}
And that's it. Good to go. Now we can be simply drop the argument into a controller endpoint:
#RequestMapping(method = RequestMethod.GET)
public #ResponseBody ResponseEntity<WebUser> getUser(#UserRequest Username username) {
return ResponseEntity.ok(service.getUser(username, username.toLowerCase()));
}
I am not sure whether you can call method in annotation or not,but you can get user name value just before the #RequestMapping call execution by something like below.
I think this can be achieved using #ModelAttribute.
#ModelAttribute("username")
public String getUserName() {
String name = "XYZ";
return name;
}
And in your #RequestMapping do like below.
#RequestMapping(method = RequestMethod.GET)
public #ResponseBody ResponseEntity<WebUser> getUser(
#ModelAttribute("username") String username) {
return ResponseEntity.ok(service.getUser(username, username.toLowerCase()));
}
Further reading
I have Spring rest controller that provides operations on Project entity. All methods use same entity accessing code. I don't want to copy&paste #PathVariable parameters in all methods, so I've made something like this.
#RestController
#RequestMapping("/projects/{userName}/{projectName}")
public class ProjectController {
#Autowired
ProjectService projectService;
#Autowired
protected HttpServletRequest context;
protected Project project() {
// get {userName} and {projectName} path variables from request string
String[] split = context.getPathInfo().split("/");
return projectService.getProject(split[2], split[3]);
}
#RequestMapping(method = GET)
public Project get() {
return project();
}
#RequestMapping(method = GET, value = "/doSomething")
public void doSomething() {
Project project = project();
// do something with project
}
// more #RequestMapping methods using project()
}
Is it possible to autowire path variables into controller by annotation so I don't have to split request path and get parts of it from request string for project() method?
In order to do custom binding from request you've got to implement your own HandlerMethodArgumentResolver (it's a trivial example without checking if path variables actually exist and it's also global, so every time you will try to bind to Project class this argument resolver will be used):
class ProjectArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterType().equals(Project.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Map<String, String> uriTemplateVars = (Map<String, String>) webRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
return getProject(uriTemplateVars.get("userName"), uriTemplateVars.get("projectName"));
}
private Project getProject(String userName, String projectName) {
// replace with your custom Project loading logic
Project project = new Project(userName, projectName);
return project;
}
}
and register it using WebMvcConfigurerAdapter:
#Component
public class CustomWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new ProjectArgumentResolver());
}
}
In your controller you have to put Project as a method argument, but do not annotate it with #PathVariable:
#Controller
#RequestMapping("/projects/{userName}/{projectName}")
public class HomeController {
#RequestMapping(method = RequestMethod.GET)
public void index(Project project){
// do something
}
}