I have a successfully working Spring 4 MVC project with lots of RESTful web-services on the back-end. We use Hibernate 5 and Spring Security 4 as well.
1) We authenticate users from a third-party source, OpenAM 10.x, and this creates a token in the browser.
2) From the front-end, we pass in the Ajax call a request header which contains that OpenAM token.
3) In the back-end, using the SiteMinder example, have a service: CustomUserDetailsService which does the following:
a) this uses our code to openAM and pass in the token
b) we get back JSON data we parse to get the username
c) from there we use the Hibernate Spring Security code to get further details for this user and get the roles
System.out.println("loadUserByUsername: username : " + username);
UserAccountEntity userAccountEntity = userAccountDao.getByUsername(username);
System.out.println("loadUserByUsername: userAccountEntity : " + userAccountEntity);
if (userAccountEntity == null)
{
System.out.println("CustomUserDetailsService: userAccountEntity not found");
throw new UsernameNotFoundException("Username not found");
}
System.out.println("CustomUserDetailsService: START: springframework.user");
// this "User" object is: org.springframework.security.core.userdetails.User
User user = new User(userAccountEntity.getUsername(), userAccountEntity.getPassword(), true, true, true, true,
getGrantedAuthorities(userAccountEntity));
From here, we have working security on the URL endpoints ... this works great!
Now, here is the problem ... my boss wants to use our custom proprietary ACL system to make secure query calls. Making a call to get records, we want to make sure we get only the records the user has access to. We have our own ACL tables that allow us security for a user or role to certain objects. Suffice it to say, this was written before Spring Security ACL ever existed. I'd prefer to use Spring Security 4 ACL, but that is out of the question.
Consider that we have 4 levels, Entity, Dao, Services, Web-Services. A Single controller looks like this:
#RequestMapping(value = "", method = RequestMethod.GET, headers = "Accept=application/json")
public #ResponseBody ArrayList<SponsorEntity> getSponsorList1() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User user = null;
if (principal instanceof User)
{
user = ((User) principal);
}
ArrayList<SponsorEntity> sponsorEntityList = (ArrayList) service.getAllList();
return sponsorEntityList;
}
Like I said, this goes through the CustomUserDetailsService and sets the User as defined above. So, the first questions is, in the DAO layer, what is the code I would use to pull this org.springframework.security.core.userdetails.User?
If I can get this User object from the DAO layer, then I can "pipe" that username into our old existing legacy code.
The second question ... in a DAO Unit Test, how can I create/setup this Security? This way I can test to see if my back-end code is really getting the username from an authenticated user.
Please let me know if you need any more information. Thanks!
Related
I'm making a simple spring boot application and I want to incorporate filtering data by logged-in user or user session. But I don't know what is the best way to set up the application in a way that every user can access data specific to him. I have made the authentification and authorization part of the app.
I'm also working on that kind of application and I've done this by adding as argument#AuthenticationPrincipal CustomUserDetails userDetails to method where you want to get userId. Then you can call: Integer userId = userDetails.getUserId();
So for example something like this:
#GetMapping("/demo")
public String showTemplate(#AuthenticationPrincipal CustomUserDetails userDetails) {
Integer userId = userDetails.getUserId();
return "index";
}
And then you can do with this userId whatever you want. For example, create method in repository findByUserId
I have implemented OIDC authentication in my Spring Boot web application by adding the spring-boot-starter-oauth2-client dependency and configuring OAuth2 client settings in application.properties.
In the Spring Boot and OAuth2 guide there is a section "How to Add a Local User Database":
How to Add a Local User Database
Many applications need to hold data
about their users locally, even if authentication is delegated to an
external provider. We don’t show the code here, but it is easy to do
in two steps.
Choose a backend for your database, and set up some repositories
(using Spring Data, say) for a custom User object that suits your
needs and can be populated, fully or partially, from external
authentication.
Implement and expose OAuth2UserService to call the Authorization
Server as well as your database. Your implementation can delegate to
the default implementation, which will do the heavy lifting of calling
the Authorization Server. Your implementation should return something
that extends your custom User object and implements OAuth2User.
Hint: add a field in the User object to link to a unique identifier in
the external provider (not the user’s name, but something that’s
unique to the account in the external provider).
I have searched a bit but I have not found a code example for the scenario described in the excerpt.
What is the best way to implement the scenario above?
I guess the main parts would be:
On OIDC login, automatically create a user in the database if it does not exist
The web application controller methods have access to the database object that represents the logged-in user
Update:
The guide has a github issue comment that suggests to look at the custom-error sample from the guide's source code. I guess the first part (on OIDC login, automatically create a user if one does not exist) can be done after the call to DefaultOAuth2UserService().loadUser(request). But what about the second part - how can my custom db-backed-user-object be made available to my web application's controller methods?
#Bean
public OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService(WebClient rest) {
DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
return request -> {
OAuth2User user = delegate.loadUser(request);
if (!"github".equals(request.getClientRegistration().getRegistrationId())) {
return user;
}
OAuth2AuthorizedClient client = new OAuth2AuthorizedClient
(request.getClientRegistration(), user.getName(), request.getAccessToken());
String url = user.getAttribute("organizations_url");
List<Map<String, Object>> orgs = rest
.get().uri(url)
.attributes(oauth2AuthorizedClient(client))
.retrieve()
.bodyToMono(List.class)
.block();
if (orgs.stream().anyMatch(org -> "spring-projects".equals(org.get("login")))) {
return user;
}
throw new OAuth2AuthenticationException(new OAuth2Error("invalid_token", "Not in Spring Team", ""));
};
}
Github uses OAuth2UserService<OAuth2UserRequest, OAuth2User> what you need is OAuth2UserService<OidcUserRequest, OidcUser>. So
did you try creating another #Bean which plugs into the correct place spring expects?
If not, create one like this
#Bean
public OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
final OidcUserService delegate = new OidcUserService();
return (userRequest) -> {
// Delegate to the default implementation for loading a user
OidcUser user = delegate.loadUser(userRequest);
log.info("User from oauth server: " + user);
//OAuth2AccessToken accessToken = userRequest.getAccessToken();
//Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
//Fetch the authority information from the protected resource using accessToken
//Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
//Create a copy of user using mappedAuthorities
//Insert/update local DB
//user = new DefaultOidcUser(mappedAuthorities, user.getIdToken(), user.getUserInfo());
return user;
};
}
I'm trying to get logged in users' details who are connected via websocket on Spring Boot 2. To do this, I'm currently using SimpUserRegistry to find a list of connected SimpUsers.
Since Spring Boot 2.4, I noticed the SimpUser class has a getPrincipal() method that returns a generic Principal object. As each user is supposed to login via Spring Security's mechanisms, I thought I was able to cast it to Spring Security's UserDetails to get the logged in user , but I realize it wasn't the case.
Does anyone know how I can make use of getPrincipal or other ways to get logged in userDetails?
First of all, let's make it clear that, to use getPrincipal() with websocket, you have to implement websocket authentication and authorization through Interceptor (as far as I know SpringSecurity doesn't do this automatically).
After doing the above correctly, you can now use the getPrincipal () method. It will return The identity of the principal being authenticated (maybe Username, email,...)
You can use code that looks like this:
#MessageMapping("/test")
public void doSomething(#Payload AppMessage appMessage, Principal principal) {
String username = principal.getName();
// find userDetail with username here
}
I am developing my very first application with Spring (4.0.2), Spring #MVC (4.0.2) and Spring Security (3.2.5). I am now able to successfully log in by using Spring Security but now a "Good practices" question has come to my mind:
Currently I hace implemented my own version of UserDetails and UserDetailsService in order to save the details fetched from the DB.
Which is the best way (cleaner) to get the UserDetails?
So far I am using these two alternatives:
Using the next code line in the method
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Adding the parameter Authentication auth and the next line inside the method
User user = ((CurrentUserDetails) auth.getPrincipal()).getCurrentUser();
My impression is that I am dirtying the code. Don't you think so?
On every project I have worked on, we have always implemented SecurityUtils class with at least the following methods:
/**
* Get the active authentication object.
* #param strict Whether to throw an exception if no authentication object is found.
* #return Authentication object. Can be null only in non-strict mode.
*/
public static Authentication getAuthentication(boolean strict) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (strict && authentication == null) {
throw new AuthenticationCredentialsNotFoundException("Missing authentication object.");
}
return authentication;
}
/**
* Get active person principal.
* #return Active person principal user. Never null.
* #throws AccessDeniedException in case there is no active person principal.
*/
public static PersonPrincipal getActivePersonPrincipal() {
Object principal = getAuthentication(true).getPrincipal();
if (!(principal instanceof PersonPrincipal)) {
throw new AccessDeniedException("Invalid principal '" + principal + "'.");
}
return (PersonPrincipal) principal;
}
This class usually sits in {projectPackage}.core.security package. Whenever any code needs access to the current user, it calls this class.
Also we never let controller layer tell service layer anything about authentication. Service layer (or even Hibernate event listeners) are always asking SecurityUtils about the current authentication.
Spring Security is able to put the Authentification in the request where it can be retrieved with HttpServletRequest.getUserPrincipal(). And in Spring MVC, the controller can have access to the principal simply by passing it as a parameter.
So, IMHO one of the simpler way to access the login user from a controller is :
#RequestMapping(...)
public String method(Principal principal, ...) {
Authentication auth = (Authentication) principal;
...
}
Edit (per comment) :
If you do not want to repeat the code on each controller method, you can :
use a #ModelAttribute method to populate the model with the login user in a single place (mainly if you use it in views)
put it in a request scoped bean and directly inject it in controllers through an AOP scoped proxy.
I have a Spring MVC web app secured with Spring Security and I'm in the process of writing tests. I'm struggling with getting one of my (custom) user retrieved by Spring Security in its SecurityContextHolder.
Once my user is "inserted" (java-configured) with :
auth.inMemoryAuthentication().getUserDetailsService().createUser(myCustomUser);
I can then create the related token (a UsernamePasswordAuthenticationToken) and ask for Spring to authenticate my user with this token. The problem is Spring doesn't retrieve a custom user instance but an instance of its User class. When Spring looks for such a user in the following method (from Spring's InMemoryUserDetailsManager) :
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = users.get(username.toLowerCase());
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(),
user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
}
It instantiates a new User with the details provided by my configuration.
I don't see the problem with having the InMemoryUserDetailsManager directly returning what was sent to him via the "getUserDetailsService().createUser" call but there must be one probably...
Anyway, I'm probably doing something wrong here, any idea ?
Like suggested, I ended up writing a custom InMemoryUserDetailsManager which I feed my Spring Security configuration with.
To anyone wondering, it seems that it's the only way.