What's the recommended way to handle user management in Play! Framework?
This is my setup:
UserAwareControllerBase as a base class for controllers
The main view template includes a login/logout button
custom Security class extending Secure.Security, and controllers that only allow signed-in users are annotated with #With(Secure.class)
(I haven't implemented a real password/login system yet, just entering the correct email suffices to login. TBD)
The setup is nice because Controllers don't need to bother writing user management code, and can easily get the signed-in user by calling getUser(). However, I'm already starting to feel the limitations of this setup. I'm getting a convoluted inheritance hierarchy, and am facing a problem if I want to inherit from the CRUD class.
What's the best practice for handling user authentication/authorization in Play! without repeating code?
UserAwareControllerBase.java
public abstract class UserAwareControllerBase extends Controller {
protected final static UserRepository userRepo = new UserRepository();
#Before
static void setConnectedUser() {
if(Security.isConnected()) {
User user = userRepo.findByEmail(Security.connected());
renderArgs.put("user", user);
}
}
static User getUser() {
return renderArgs.get("user", User.class);
}
}
template.html
<div id='header'>
...
#{if user}
Log out (${user.email})
#{/if}
#{else}
Log in
#{/else}
</div>
Security.java
public class Security extends Secure.Security {
protected final static UserRepository userRepo = new UserRepository();
static boolean authenticate(String username, String password) {
User user = userRepo.findByEmail(username);
return user != null;
}
public static void onDisconnected() {
Application.index();
}
}
If you want to share code between controllers, prefer using the #With annotation rather than using inheritance.
For user management, I am used to put some rights in the session in the onAuthenticated method like this
static void onAuthenticated() {
session.put(Const.MY_RIGHT, true);
}
And then my check method is
static boolean check(String profile) {
return Boolean.parseBoolean(session.get(profile));
}
With this I can use the #check annotation to check user rights. In the onAuthenticated method you can do whatever you want to map complex rights managements into simple constants.
Related
I'm trying to implement custom auth flow in Keycloak. It should work similar to username&password flow (POST /openid-connect/token with params 'username'+'password'+'grant_type=password' -> response with access_token & refresh_token) but instead of username and password it will receive another fields (e.g. fieldA, filedB and hash)
I wrote an implementation of
org.keycloak.authentication.Authenticator
that does this auth, but I can't figure out what should I do next. How can I make keycloak validate user using this flow?
So If I understand you correctly: U have a custom implementation of the Authenticator interface, to register it in keyckoak you also need AuthenticatorFactory - implementation and add the path to it into the config file with the name: org.keycloak.authentication.AuthenticatorFactory. So you should have something like:
public class CustomAuthenticator implements Authenticator {
#Override
public void authenticate(AuthenticationFlowContext context) {
//todo make your auth validation check logic
String username = "admin";
UserModel user = context.getSession().users().getUserByUsername(username, context.getRealm());
context.setUser(user);
context.success(); // With context.success(), or failing with an error, you can manage your custom validation
}
}
public class CustomAuthenticatorFactory implements AuthenticatorFactory {
public static final String PROVIDER_ID = "custom-sso";
private static CustomAuthenticator SINGLETON = new CustomAuthenticator();
#Override
public String getDisplayType() {
return "custom sso";
}
#Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
#Override
public Authenticator create(KeycloakSession session) {
return SINGLETON;
}
#Override
public String getId() {
return PROVIDER_ID;
}
}
And also in file with path: src\main\resources\META-INF\services\org.keycloak.authentication.AuthenticatorFactory
need to add a path to the factory class.
After all these changes, you should be able to change your authentication flow from keyclaok admin page.
P.S. you cant change existed Browser flow, but you can copy it, change the copy and then bind the browser flow to your custom.
P.S.S.
I found almost the same question: Keycloak adding new authenticator
But with a better-described answer :-)
I want to fetch some resources and I expect 2 different behaviours depending on the connected user.
If the user is an admin, he retrieves all the resources, otherwise he only retrieves the resources he can access.
Are there some recommendations about such a case? What is the best way to do this and why?
Here are the different options I found :
At the controller level :
#GetMapping(value = "/myresources")
public void exportKdSchema(HttpServletResponse response) {
User user = getConnectedUser();
if(user.isAdmin()) {
resourceService.getAllResources();
}
else {
resourceService.getAllResourcesByUser(user);
}
}
At the service level :
#GetMapping(value = "/myresources")
public void exportKdSchema(HttpServletResponse response) {
User user = getConnectedUser();
resourceService.getResourcesByUser(user)
}
#Service
public class ResourceService{
public List<Resource> getResourcesByUser(User user) {
if(user.isAdmin()) {
resourceRepository.findAll()
}
else {
resourceRepository.findAllByUserId(user.getId())
}
}
}
Should I create 2 controllers :
#PreAuthorize("hasRole('RESOURCES_ALL')")
#GetMapping(value = "/myresources")
public void exportKdSchema(HttpServletResponse response) {
resourceService.getAllResources();
}
#GetMapping(value = "/myresources/{userId}")
public void exportKdSchema(#PathVariable("userId") Long userId) {
resourceService.getAllResourcesByUser(user);
}
You will probably get a lot of different opinions on how it should be done. I would personally create two different endpoints for user and admin functionality:
/resources/my and /admin/resources/{userId}
Think about the case where some admin (your client) might also use the system as a simple user. System admining and normal usage are two different usecases, so I wouldn't mix them together.
Plus you also have more predictable responses from your endpoints that are independent of user roles.
Prefixing /admin endpoints may also allow for easier configuration, depending on the Access Control granularity you might need.
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!
I want to implement a public/global variable so that I can access from any layer(controller/service/dao) of a spring project. For example
class Abc{
public User user;
public String subdomain;
}
Now I want to get user, subdomain values from any layer. But remember that, my project has user management. So I need to specific value for each user session.
Note:
The life cycle of this values is session
This is not singletone forall users
This is specific per session
Thanks
A possible solution is to have a service which has the ability to lookup the currently logged on user, and provides the context information you require. As #siledh mentioned, once you have this information, you can then pass it into layers where you do not want to have any concept of context (e.g. in your DOAs)
#Service
public class ContextService {
public User getLoggedOnUser() {
// Get user id/username from Spring Security principal
// Use id/username to lookup the User
// Return the logged on user
}
}
#Controller
public class SomeController {
#Autowired
private ContextService context;
#Autowired
private SomeReposity someReposity;
#RequestMapping("/home")
public String homePage() {
User loggedOn = contextService.getLoggedOnUser();
String userInformationNeededForHomePage =
someReposity.findSomethingForUser(loggedOn);
...
}
}
#Repository
public class SomeReposity {
public String findSomethingForUser(User user) {
// find something
}
}
I'm following the Controller -> Service -> DAO pattern. When I call a DAO implementation, I get back a DTO/Data object. Which then gets passed to the service layer, bringing together it's respective business object and it's data object. Like so:
public User getUserById(int id) {
return new User(userDAO.getUserById(id));
}
class User {
private UserDTO userDTO;
public User(UserDTO userDTO) {
this.userDTO = userDTO;
}
}
What I'd like to do is wrap ALL my business logic inside the business class but I require additional dependencies.
For example, I'd like to be able to do something like this:
//... some code
User user = userService.getByUserId(1);
user.delete(); // this should delete the user from the database
In order for me to delete the user this way, I would need to Autowire the UserService into the business class but this will not work since I am manually instantiating the User class in the User Service.
class User {
#Autowired
private UserService userService; // this won't work since I call instantiate User myself, ie. new User()
private UserDTO userDTO;
public User(UserDTO userDTO) {
this.userDTO = userDTO;
}
public boolean delete() {
userService.deleteByUserId(userDTO.getId());
}
}
Is there a pattern I can follow to allow me to do what I want?
I don't think it's a good design to have business class as a member of your DTO objects.
A more proper approach would be to have your delete method in the business class. That would help loose coupling.
I think you can do this with the #Configurable annotation, though this really isn't how Spring is supposed to work. It will add lots of overhead to your application and make debugging harder.
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-atconfigurable