I know you can get a username easily in a Spring controller by including Principal as a method argument like:
#GetMapping("/username")
#ResponseBody
public String currentUserName(Principal principal) {
return principal.getName();
}
But I am ultimately going to want access to members of a MyCustomUser class that I instantiate from a repository with a findBy method. I can put a helper method in the Controller to do the lookup and return the user based on principal.getName(), but can I go a step further and bind to MyCustomUser directly, like
#GetMapping("/stuff")
#ResponseBody
public String stuff(MyCustomUser user) {
return user.thing();
}
I was looking into creating a converter like (Ref):
#Component
public class PrincipalToMyCustomUserConverter implements Converter<Principal, MyCustomUser> {
private MyCustomUserRepository userRepository;
public PrincipalToApplicationUserConverter(MyCustomUserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public MyCustomUser convert(Principal source) {
return this.userRepository.findByUsername(source.getName());
}
}
But I don't know if that's an appropriate way to grab the repository, and I don't know how to pass the repository when registering the converter (Ref).
You're correct in that the converter you're proposing is not appropriate. Your converter can convert from an object of type Principal to an object of type MyCustomUser, however, there is no *Principal* by which to convert. The magic behind the principal injection is that Spring actually gets this from the SecurityContextHolder, it is not deserialized from request...though fields present in the request allow Spring to create the Principal. If you truly want to inject MyCustomUser, use a ModelAttribute. ModelAttributes are available to all of your Spring controller methods.
I generally like to keep stuff like this in it's own class, so I would define a class that held this and other #ControllerAdvice in one place, something like this:
#ControllerAdvice
public class SomeControllerAdvice {
#Autowired
private MyCustomUserRepository myCustomUserRepository;
#ModelAttribute
public MyCustomUser getUser(Principal principal) {
return myCustomUserRepository.findByUsername(principal.getName());
}
}
The above should suffice to make MyCustomUser available to all methods. I would note that you probably want a little error handling here, like skip over if principal is null and whatnot, also have your findByUsername method return an Optional so your can address empty returns.
see:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ModelAttribute.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html
Related
In my application, I have an abstract controller that handles common CRUD behavior already and can be instantiated for certain entities.
#RestController
public abstract class DefaultRestController<T, U extends Model> {
#GetMapping
#NotNull
public Page<U> findAll(#NotNull final Pageable pageable) {
return service.getAll(pageable);
}
// more CRUD methods here
}
Now, when I override this controller, I might want to keep most of the methods, but customize some, while still keeping the request mapping. For instance, say I want a UserController that extends the above abstract class and I still want a GET / mapping, however, the method signature shall change. For instance, I want this instead:
#RestController
#RequestMapping("/user")
public class UserController extends DefaultRestController<UserDTO, User> {
#GetMapping
public Page<User> findAllFiltered(#NotNull final Pageable pageable, Specification spec) {
// ...
}
}
This is invalid, because now I have two handlers for the same mapping (GET /). What I'd like to do now is override the findAll() method and annotate it with a custom annotation like #IgnoreRequestMapping to disable it.
I first tried the approach described here. However, that would only work if I want to disable the mapping altogether, not "override" it.
In addition I tried creating a custom version of the RequestMappingHandlerMapping and overriding registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) in a way that the actual registerHandlerMethod() is only called when no #IgnoreRequestMapping is present on the method, but that didn't work either.
I'm not very familiar with the internals of Spring. Could someone please help me achieve the behavior I want?
Well, you only want to extend parts of your parent class - Java does not allow that. You could override the original method and use a different path for it, but you have to implement it.
#RestController
#RequestMapping("/user")
public class UserController extends DefaultRestController<UserDTO, User> {
#GetMapping("/unfiltered")
#NotNull
public Page<U> findAll(#NotNull final Pageable pageable) {
throw new RuntimeException("not implemented");
}
#GetMapping
public Page<User> findAllFiltered(#NotNull final Pageable pageable, Specification spec) {
// ...
}
}
Of course this is a bit hacky (because there is no such thing like #NoMapping, so you should just use a different path for your filtered method - or don't extend your DefaultRestController.
I added Spring Security on a Rest API with JWT authentication. Now I need to retrieve some data from the token in every controller method - be it either the username or other information.
Since almost all of my controller methods would need a Principal variable, is there a way to avoid declaring it as an argument to each method?
I once used ObjectProvider to do a similar thing, like:
#RequestScope
#Component
public class MyObj // ...
Usage:
#Component
public class OtherObj {
#Autowired
private ObjectProvider<MyObj> provider;
// ...
#Override
public boolean meth() throws Exception {
MyObj o = provider.getIfAvailable();
// ...
But there I found that if no instance exists, it is created instead of being returned null or an exception being thrown.
You can create one utility class, which provides you the principal.
public static Principal getPrincipal() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return securityContext.getAuthentication().getPrincipal();
}
Ofcourse, here you would need to put the null checks in case the context or authentication is null.
I would like to get the username of the user in every request to add them to log file.
This is my solution:
First, I created a LoggedUser with a static property:
public class LoggedUser {
private static final ThreadLocal<String> userHolder =
new ThreadLocal<>();
public static void logIn(String user) {
userHolder.set(user);
}
public static void logOut() {
userHolder.remove();
}
public static String get() {
return userHolder.get();
}
}
Then I created a support class to get username:
public interface AuthenticationFacade {
Authentication getAuthentication();
}
#Component
public class AuthenticationFacadeImpl implements AuthenticationFacade {
#Override
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
Finally, I used them in my Controllers:
#RestController
public class ResourceController {
Logger logger = LoggerFactory.getLogger(ResourceController.class);
#Autowired
private GenericService userService;
#Autowired
private AuthenticationFacade authenticationFacade;
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
loggedUser.logIn(authenticationFacade.getAuthentication().getName());
logger.info(LoggedUser.get()); //Log username
return userService.findAllRandomCities();
}
}
The problem is I don't want to have AuthenticationFacade in every #Controller, If I have 10000 controllers, for example, it will be a lot of works.
Do you have any better solution for it?
The solution is called Fish Tagging. Every decent logging framework has this functionality. Some frameworks call it MDC(Mapped Diagnostic Context). You can read about it here and here.
The basic idea is to use ThreadLocal or InheritableThreadLocal to hold a few key-value pairs in a thread to track a request. Using logging configuration, you can configure how to print it in the log entries.
Basically, you can write a filter, where you would retrieve the username from the security context and put it into the MDC and just forget about it. In your controller you log only the business logic related stuff. The username will be printed in the log entries along with timestamp, log level etc. (as per your log configuration).
With Jhovanni's suggestion, I created an AOP annotation like this:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface LogUsername {
}
In the same package, I added new #Aop #Component class with AuthenticationFacade injection:
#Aspect
#Component
public class LogUsernameAop {
Logger logger = LoggerFactory.getLogger(LogUsernameAop.class);
#Autowired
private AuthenticationFacade authenticationFacade;
#Before("#annotation(LogUsername)")
public void logUsername() throws Throwable {
logger.info(authenticationFacade.getAuthentication().getName());
LoggedUser.logIn(authenticationFacade.getAuthentication().getName());
}
}
Then, in every #GetMapping method, If I need to log the username, I can add an annotation before the method:
#PostMapping
#LogUsername
public Course createCourse(#RequestBody Course course){
return courseService.saveCourse(course);
}
Finally, this is the result:
2018-10-21 08:29:07.206 INFO 8708 --- [nio-8080-exec-2] com.khoa.aop.LogUsername : john.doe
Well, you are already accesing authentication object directly from SecurityContextHolder, you can do it in your controller.
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(authentication != null){
//log user name
logger.info(authentication.get());
}
return userService.findAllRandomCities();
}
If you do not want to put all this in every endpoint, an utility method can be created to extract authentication and return its name if found.
public class UserUtil {
public static String userName(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication == null ? null : authentication.getName();
}
}
and call it in your endpoint like
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
//log user name
logger.info(UserUtil.username());
return userService.findAllRandomCities();
}
However, you are still adding lines of code in every endpoint, and after a few of them it starts to feel wrong being forced to do it. Something I suggest you to do is try aspect oriented programming for this kind of stuff. It will require you to invest some time in learning how it works, create annotations or executions required. But you should have it in a day or two.
With aspect oriented your endpoint could end like this
#RequestMapping(value ="/cities")
#LogUserName
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
//LogUserName annotation will inform this request should log user name if found
return userService.findAllRandomCities();
}
of course, you are able to remove #LogUserName custom annotation and configure the new aspect with being triggered by methods inside a package, or classes extending #Controller, etc.
Definitely it is worth the time, because you can use aspect for more than just logging user name.
You can obtain the username via request or parameter in your controller method. If you add Principal principal as a parameter, Spring Ioc Container will inject the information regarding the user or it will be null for anonymous users.
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(Principal principal){
if(principal == null){
// anonymous user
}
}
There are various ways in Spring Security to fetch the user details from the security context. But according to your requirement, you are only interested in username, so you can try this:
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(Authentication authentication){
logger.info(authentication.getName()); //Log username
return userService.findAllRandomCities();
}
Hope this helps!
First of all, I'm a relative noob to Spring Boot, so keep that in mind.
I've got a REST api in which I'm trying to minimize database calls for the same object and I've determined that using a Spring Bean scoped to the Request is what I want. Assuming that is correct, here is what I'm trying to do:
1) Controller takes in a validated PhotoImportCommandDto command
PhotoCommandController
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> importPhoto(#Valid #RequestBody PhotoImportCommandDto command){
...
}
2) PhotoImportCommandDto is validated. Note the custom #UserExistsConstraint which validates that the user exists in the database by calling a service method.
PhotoImportCommandDto
#Component
public class PhotoImportCommandDto extends BaseCommand {
#NotNull(message = "userId must not be null!")
#UserExistsConstraint
private Long userId;
...
}
What I would like to do is somehow set a Spring Bean of the user that is validated in the #UserExistsConstraint and reference it in various methods that might be called throughout a Http request, but I'm not really sure how to do that. Since I've never really created my own Spring Beans, I don't know how to proceed. I've read various guides like this, but am still lost in how to implement it in my code.
Any help/examples would be much appreciated.
You can use the #Bean annotation.
#Configuration
public class MyConfiguration {
#Bean({"validUser"})
public User validUser() {
User user;
//instantiate user either from DB or anywhere else
return user;
}
then you can obtain the validUser.
#Component
public class PhotoImportCommandDto extends BaseCommand {
#Autowired
#Qualifier("validUser")
private User validUser;
...
}
I don't really know how to make annotations in Java. Anyway, in Spring, checking where the User exists in the DataBase or not is one line of code:
userRepository.findOne(user) == null
That is accomplished by the Spring Data JPA project:
Create a JPA Entity User.
Set the spring.datasource.url and login/password in the
resources/application.properties.
Create this interface:
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
}
Note, Spring implements it behind the scences.
Inject this interface into your RestController (or any other Spring bean):
private UserRepository userRepository ;
**constructor**(UserRepository ur){
userRepository = ur;
}
Note, a Spring Bean is any class annotated #Component (this includes stereotype annotations like Controller, Repository - just look up the contents of an annotation, it may use #Component internally) or returned from a method which is annotated #Bean (can only be on the Component or Configuration class). A Component is injected by searching the classpath, Bean is injected more naturally.
Also note, injecting is specifying #Autowired annotation on field or constructor, on a factory method, or on a setter. The documentation recommends that you inject required dependencies into constructor and non-required into the setter.
Also note, if you're injecting into a constructor and it is clean by the arguments, you may omit #Autowired annotation, Spring will figure it out.
Call its method findOne.
So, you can do one of the following:
Inject the userRepository into the #RestController constructor (as shown above). I would do that.
Inject the userRepository into the #Service (internally #Component) class that will do this sorts of thing for you. Maybe you can play with it to create an annotation.
p.s. Use #PostMapping instead of #RequestMapping(method = RequestMethod.POST)
p.p.s. If ever in doubt, go to the official documentation page and just press CTRL-F: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/ Note the current word, that will always take you to the latest version.
p.p.p.s Each Spring project has its own .io webpage as well as quick Get Started Guides where you can quickly see the sample project with explanations expecting you to know nothing.
Hope that helps! :)
Don't forget to mark the answer as accepted if you wish
Using Jose's input, I took a bit of a different route.
Here's what I did:
I created a ValidatedUser class:
#RequestScope
#Component
public class ValidatedUser {
private UserEntity user;
public UserEntity getUser() {
return user;
}
public void setUser(UserEntity user) {
this.user = user;
}
}
and I also created a wrapper class HttpRequestScopeConfig to capture all variables to use over the course of an Http Request to the api.
#Component
public class HttpRequestScopeConfig {
#Autowired
private ValidatedUser validatedUser;
...
public UserEntity getValidatedUser() {
return validatedUser.getUser();
}
public void setValidatedUser(UserEntity validatedUser) {
this.validatedUser.setUser(validatedUser);
}
...
}
In my UserExistsConstraintValidator (which is the impl of #UserExistsConstraint, I set the validatedUser in the httpRequestScopeConfig:
public class UserExistsConstraintValidator implements ConstraintValidator<UserExistsConstraint, Long> {
//private Log log = LogFactory.getLog(EmailExistsConstraintValidator.class);
#Autowired
private UserCommandService svc;
#Autowired
private HttpRequestScopeConfig httpRequestScope;
#Override
public void initialize(UserExistsConstraint userId) {
}
#Override
public boolean isValid(Long userIdField, ConstraintValidatorContext context) {
try {
UserEntity user = svc.findUserOfAnyStatus((Long) userIdField);
if (user != null) {
httpRequestScope.setValidatedUser(user);
return true;
}
} catch (Exception e) {
//log.error(e);
}
return false;
}
}
Now, I can access these variables throughout the rest of my service layers by autowiring HttpRequestScopeConfig where necessary.
I have to implement validations for a web app that uses Spring MVC 3. The problem is that the bean class has methods like getProperty("name") and setProperty("name",valueObj). The validations have to be done on the data that is returned by passing different values to getProperty("name") , for eg: getProperty("age") should be greater than 16 and getProperty("state") should be required.
I would like to know if there is any support for validation this kind of Bean and if not, what can be the work around.
Thanks,
Atif
I don't think so. Bean validation is performed on javabeans, i.e. class fields with getters and setters. Even if you can register a custom validator, and make validation work, binding won't work. You would need to also register a custom binder that populates your object. It becomes rather complicated. So stick to the javabeans convention.
It sounds like you want to a custom validation class which implements org.springframework.validation.Validator.
#Component
public class MyValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyBean.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
MyBean myBean = (MyBean) target;
if (StringUtils.isBlank(myBean.getProperty("state"))) {
errors.rejectValue("state", "blank");
}
}
}
In your controller you would do manual validaton like follows:
#Autowired
private MyValidator myValidator;
#RequestMapping(value = "save", method = RequestMethod.POST)
public String save(#ModelAttribute("myBean") MyBean myBean, BindingResult result) {
myValidator.validate(myBean, result);
if (result.hasErrors()) {
...
}
...
}