I was exploring spring security and tried to build a small application wherein I have a an entity name User and a userRepository having a one declared method findByUserName(String userName)
#Entity
#Table(name="user")
class User {
#id
private Long id;
private String userName;
private String password;
}
I have heard that spring security depends on principles and not users.
So we have to have a class which implements UserDetails (provided by spring security).
What's the reason behind this?
Secondly, once we have written all this code we need to configure it into a class which I have done as shown below:
public class AppSecurityConfid extends WebSecurityCongigurerAdapter {
// here we have to autowire the service class which we have made to call the
userRepository and find the user based on userName
#Bean
public DAOAuthenicationProvider authenicationProvider() {
// wherein we create an instance and pass the autowired instance and set the
password encoder and return the instance
}
protected void configurer(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider());
}
}
Things up to here make sense, but why we need Authentication Build Manager in this scheme of things?
I am not an expert but I'd like to say something, maybe it can help:
Spring uses "in the background" a way to retrieve user data for authentication when you activate Spring Security. Of course, this method can be overriden so the developer can change how Spring obtains this data in order to support situations where the data is sparced in different tables, from a file, an API REST query, etc.
The authentication data is structured as a list, where each element corresponds to the data used to authenticate each user. This data is structured as a tuple of 3 elements: String username, String hashedPassword and boolean isAccountActive.
You need to provide a way to obtain this data for each user. You do not need to provide the data explicitly, just the way (method) to obtain it. One way to do it (as you said) is creating a class that implements UserDetailsService, which, for example, forces you to implement UserDetails loadUserByUsername(String email);. In this method you need to provide an instance of a class that implements UserDetails, which corresponds to the UserDetails of the User with the username passed as a parameter. This methods (and similar) are used by Spring Security "in the background" to retrieve the UserDetails of a certain user when is trying to access your web server.
If the Userdetails match with the credentials provided in the request, Spring will allow the request to hit your controllers; else it will throw a HTTP 401. Of course there are other authentication methods, but for the sake of simplicity we understand credentials as user / password with HTTP basic authentication.
So, why do we need a class that implements UserDetails? Because is the contract that a class needs to fulfill if it has to be used for internal authentication in Spring Security. Also to separate from a User class the logic of the business with the logic of the security. Probably creating your own class that extends UserDetails is the best idea, but is not actually necessary. For example if you have your own class to contain the data of a user, you just need to understand how to transform your User instance to UserDetails, so Spring Security can use it transparently and the opposite: how the UserDetails instance can be transformed into one of your users.
For example this is a method to obtain the User instance using the UserDetails instance that is currently authenticated in Spring Boot.
#Service
public class SecurityServiceClass{
#Override
public User getLoggedUser() {
String username = ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
Optional<User> user = this.userService.get().stream().filter(r -> r.getEmail().equals(username)).findFirst();
UserDetails userDetails = ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal());
// TODO: make error in case of null
return user.orElse(new User());
}
}
Here I retrieve the User by retrieving the username from the UserDetails and querying it to the DB to recover the User. I am accessing the DB using a repository class.
Here I do the opposite, transforming a User to a UserDetails by creating a Userdetails instance based on the relevant data of the User. (Note that I use the email as username)
#Service
public class UserServiceClass extends GenericServiceClass<User, UUID> {
#Autowired
public UserServiceClass(UserRepository userRepository) {
super(userRepository);
}
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Optional<User> selected = ((UserRepository) this.genericRepository).getUserByEmail(s);
if (selected.isPresent())
{
// Obtain user by email (username)
User user = selected.get();
// Obtain the roles of this user to construct the instance of UserDetails for SpringBoot Security.
Set<Role> roles = user.getRoles();
return org.springframework.security.core.userdetails.User
.withUsername(s)
.roles(roles.stream().toArray(
(n) -> {
return new String[n];
}
))
.password(user.getHashedPassword())
.build();
}
else
{
throw new UsernameNotFoundException("The user with email " + s + " is not registered in the database");
}
}
Finally, regarding AuthenticationManagerBuilder: This is a method that is used to configure authentication. As far as I know, you can define how your application should obtain the UserDetails. I am not sure if you can provide a method or a lambda to retrieve the triplet for authentication String username, String hashedPassword and boolean isAccountActive. What I do know and did in my application is provide the SQL query used to retrieve the triplet from my DB since I have there all that information. Code:
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationBuilder) throws Exception
{
Session session = this.sessionFactory.getCurrentSession();
session.beginTransaction();
authenticationBuilder.userDetailsService(this.userDetailsService()).passwordEncoder(this.passwordEncoder()).and()
.jdbcAuthentication().dataSource(this.dataSource)
.usersByUsernameQuery("select email, hashed_password as passw, true from user where email = ?")
.authoritiesByUsernameQuery("SELECT user.email, CONCAT(elementpermission.journal_id, '_', elementpermission.authority)\n" +
"FROM user, elementpermission\n" +
"WHERE elementpermission.user = user.uuid \n" +
"AND user.email = ?");
session.getTransaction().commit();
session.close();
}
TL;DR
Spring Security needs instances that fulfill the contract of the interface UserDetails because is the interface that Spring Security uses to obtain the relevant data for authentication.
The authentication manager builder is used to config howto obtain the data used for authentication.
You can check this links if you want better information:
https://www.baeldung.com/spring-security-jdbc-authentication
https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/jdbc.html
jdbcAuthentication() instead of inMemoryAuthentication() doesn't give access - Spring Security and Spring Data JPA
Related
Based on my understanding, there are a number of different ways to retrieve the authenticated username in Spring Security.
I'm currently grabbing the username by included the Principal as a controller method argument:
#RequestMapping(value = "/dashboard", method = RequestMethod.GET)
public ModelAndView displayHomePage(ModelAndView modelAndView, Principal principal) {
modelAndView.addObject("email", principal.getName());
// Render template located at src/main/resources/templates/dashboard.html
modelAndView.setViewName("dashboard");
return modelAndView;
}
Does Spring Security offer an easy way for me to store the User object into the session so it can be easily retrieved by any controller method?
I want to avoid performing a DB lookup each time:
// Lookup user in database by e-mail
User user = userService.findUserByEmail(principal.getName());
I'm using Spring Security 4.2.
Spring Security provides you with a static method for quickly and easy access:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName();
Or
User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String name = user.getUsername();
Maybe you would like do this in a base abstract class
public abstract class BaseController {
protected User getCurrentUser() {
return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
...
public YourController extends BaseController {
...
}
Update
If you want to store the current authenticated user in session, then you need store only first time in a object as suggested by #gkatzioura.
#Component
#Scope("session")
public class MySessionInfo {
private User user;
protected User getCurrentUser() {
if (user == null) {
user = userService.findUserByEmail(SecurityContextHolder.getContext().getAuthentication().getPrincipal().getName());
}
return user;
}
}
You can inject this bean in yours controllers like
#Autowired
private MySessionInfo mySessionInfo;
You must take care about cases when user is not logged, but this is another problem.
You can always use the methods that spring security provides to get basic information such as name, authorities and everything provided by the Authentication.class.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
authentication.getAuthorities();
authentication.getName();
But if you want more information, using a session bean to store the information is also a good idea.
#Component
#Scope("session")
public class UserInfo { .. }
I have a simple #RepositoryRestResource that exposes a REST interface for a User object. My repo also includes a custom method for finding a User by email address. I'm using #PostAuthorize to restrict access to only the logged in user.
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserRepository extends JpaRepository<User, Long> {
#PostAuthorize("hasAuthority('ROLE_ADMIN') OR principal.id == returnObject.id")
#Query("SELECT u FROM User u WHERE u.email = :email")
User findUserByEmail(#Param("email") String email);
I also want to use UserRepository.findUserByEmail inside my UserDetailsService for auth.
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findUserByEmail(email);
Since findUserByEmail is annotated with PostAuthorize my UserDetailsService is throwing an auth exception due to there not being a principal available.
Is there any way to use a secured and exported #RepositoryRestResource's from Java code?
I'm trying to get authentication done from a Spring Boot app with an external provider I will need to code for a 3rd party software equipment . The app issues commands on that external software and thus a user credential is needed to connect and operate.
The authentication needs to be performed using username and password provided in a form against an Active Directory database (Checks if the user exists in the company), and then an internal database which tells the app if the user is allowed to use the app and whether he's an admin or not (For customizing the menu bar later on).
Afterwards, the user is authenticated with this external software by means of a binary executable present on the server (Using ProcessBuilder). It's a bit complex but that's the way it has to be because of external contraints.
Furthermore, once the user is authenticated in this 3rd party software, he must pick a role out of a list which contains all roles available to that user. Only after this, the connection is finally set up and we have to redirect the user to the main page from where he can use the app.
The login page shows a form with username and password fields, and a button which will trigger the auth process and present the user with the list of roles, and after picking one and clicking another button the role will be selected and the user will be redirected to the home page.
The problem is that I don't have any clues to implement this in Spring Boot.
My LoginController contains:
#Inject
public LoginController(final LoginService loginService) {
this.loginService = loginService;
}
#RequestMapping("/login.html")
public ModelAndView getLoginView() {
LOGGER.debug("Received request to get login view");
ModelMap model = new ModelMap();
model.addAttribute("authenticationTypes",loginService.getAuthenticationTypes());
model.addAttribute(loginService);
return new ModelAndView("login", model);
}
I had working code in a LoginServiceImpl module I was using in a older JSF application which would like to reuse but don't know how.
Like a similar answer here, you need to create your own CustomAuthenticationProvider, which must implements AuthenticationProvider.
For example:
#Component
public class CustomAuthenticationProvider
implements AuthenticationProvider {
#Autowired
private ThirdPartyClient thirdPartyClient;
public void setAtpClient(ThirdPartyClient atpClient) {
this.thirdPartyClient = atpClient;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
Request3rd requestTO = new AtpAuthenticateRequestDTO();
requestTO.setPassword(password);
requestTO.setUsername(username);
Response3rd authenticate = this.thirdPartyClient.authenticate(requestTO);
if (authenticate != null) {
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(authenticate.getUsername(), password, grantedAuths);
return auth;
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Then in the SecurityConfig class, which extends WebSecurityConfigurerAdapter override in this configure method:
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(this.authenticationProvider);
}
Where you can autowire the customAuthenticationProvider created before:
#Autowired
private CustomAuthenticationProvider authenticationProvider;
I'm building an app with Spring Boot that has integration with LDAP. I was able to connect successfully to LDAP server and authenticate user. Now I have a requirement to add remember-me functionality. I tried to look through different posts (this) but was not able to find an answer to my problem. Official Spring Security document states that
If you are using an authentication provider which doesn't use a
UserDetailsService (for example, the LDAP provider) then it won't work
unless you also have a UserDetailsService bean in your application
context
Here the my working code with some initial thoughts to add remember-me functionality:
WebSecurityConfig
import com.ui.security.CustomUserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.event.LoggerListener;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
String DOMAIN = "ldap-server.com";
String URL = "ldap://ds.ldap-server.com:389";
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/ui/**").authenticated()
.antMatchers("/", "/home", "/UIDL/**", "/ui/**").permitAll()
.anyRequest().authenticated()
;
http
.formLogin()
.loginPage("/login").failureUrl("/login?error=true").permitAll()
.and().logout().permitAll()
;
// Not sure how to implement this
http.rememberMe().rememberMeServices(rememberMeServices()).key("password");
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
.userDetailsService(userDetailsService())
;
}
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(DOMAIN, URL);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper());
return provider;
}
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
UserDetailsContextMapper contextMapper = new CustomUserDetailsServiceImpl();
return contextMapper;
}
/**
* Impl of remember me service
* #return
*/
#Bean
public RememberMeServices rememberMeServices() {
// TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("password", userService);
// rememberMeServices.setCookieName("cookieName");
// rememberMeServices.setParameter("rememberMe");
return rememberMeServices;
}
#Bean
public LoggerListener loggerListener() {
return new LoggerListener();
}
}
CustomUserDetailsServiceImpl
public class CustomUserDetailsServiceImpl implements UserDetailsContextMapper {
#Autowired
SecurityHelper securityHelper;
Log ___log = LogFactory.getLog(this.getClass());
#Override
public LoggedInUserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> grantedAuthorities) {
LoggedInUserDetails userDetails = null;
try {
userDetails = securityHelper.authenticateUser(ctx, username, grantedAuthorities);
} catch (NamingException e) {
e.printStackTrace();
}
return userDetails;
}
#Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
I know that I need to implement UserService somehow, but not sure how that can be achieved.
There are two issues to configuration of the RememberMe features with LDAP:
selection of the correct RememberMe implementation (Tokens vs. PersistentTokens)
its configuration using Spring's Java Configuration
I'll take these step by step.
The Token-based remember me feature (TokenBasedRememberMeServices) works in the following way during authentication:
user gets authenticated (agaisnt AD) and we currently know user's ID and password
we construct value username + expirationTime + password + staticKey and create an MD5 hash of it
we create a cookie which contains username + expiration + the calculated hash
When user wants to come back to the service and be authenticated using the remember me functionality we:
check whether the cookie exists and isn't expired
populate the user ID from the cookie and call the provided UserDetailsService which is expected to return information related to the user's ID, including the password
we then calculate the hash from the returned data and verify that the hash in the cookie matches with the value we calculated
if it matches we return the user's Authentication object
The hash checking process is required in order to make sure that nobody can create a "fake" remember me cookie, which would let them impersonate another user. The problem is that this process relies on possibility of loading password from our repository - but this is impossible with Active Directory - we cannot load plaintext password based on username.
This makes the Token-based implementation unsuitable for usage with AD (unless we start creating some local user store which contains the password or some other secret user-based credential and I'm not suggesting this approach as I don't know other details of your application, although it might be a good way to go).
The other remember me implementation is based on persistent tokens (PersistentTokenBasedRememberMeServices) and it works like this (in a bit simplified way):
when user authenticates we generate a random token
we store the token in storage together with information about user's ID associated with it
we create a cookie which includes the token ID
When user wants to authenticate we:
check whether we have the cookie with token ID available
verify whether the token ID exists in database
load user's data based on information in the database
As you can see, the password is no longer required, although we now need a token storage (typically database, we can use in-memory for testing) which is used instead of the password verification.
And that gets us to the configuration part. The basic configuration for persistent-token-based remember me looks like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
....
String internalSecretKey = "internalSecretKey";
http.rememberMe().rememberMeServices(rememberMeServices(internalSecretKey)).key(internalSecretKey);
}
#Bean
public RememberMeServices rememberMeServices(String internalSecretKey) {
BasicRememberMeUserDetailsService rememberMeUserDetailsService = new BasicRememberMeUserDetailsService();
InMemoryTokenRepositoryImpl rememberMeTokenRepository = new InMemoryTokenRepositoryImpl();
PersistentTokenBasedRememberMeServices services = new PersistentTokenBasedRememberMeServices(staticKey, rememberMeUserDetailsService, rememberMeTokenRepository);
services.setAlwaysRemember(true);
return services;
}
This implementation will use in-memory token storage which should be replaced with JdbcTokenRepositoryImpl for production. The provided UserDetailsService is responsible for loading of additional data for the user identified by the user ID loaded from the remember me cookie. The simpliest implementation can look like this:
public class BasicRememberMeUserDetailsService implements UserDetailsService {
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username, "", Collections.<GrantedAuthority>emptyList());
}
}
You could also supply another UserDetailsService implementation which loads additional attributes or group memberships from your AD or internal database, depending on your needs. It could look like this:
#Bean
public RememberMeServices rememberMeServices(String internalSecretKey) {
LdapContextSource ldapContext = getLdapContext();
String searchBase = "OU=Users,DC=test,DC=company,DC=com";
String searchFilter = "(&(objectClass=user)(sAMAccountName={0}))";
FilterBasedLdapUserSearch search = new FilterBasedLdapUserSearch(searchBase, searchFilter, ldapContext);
search.setSearchSubtree(true);
LdapUserDetailsService rememberMeUserDetailsService = new LdapUserDetailsService(search);
rememberMeUserDetailsService.setUserDetailsMapper(new CustomUserDetailsServiceImpl());
InMemoryTokenRepositoryImpl rememberMeTokenRepository = new InMemoryTokenRepositoryImpl();
PersistentTokenBasedRememberMeServices services = new PersistentTokenBasedRememberMeServices(internalSecretKey, rememberMeUserDetailsService, rememberMeTokenRepository);
services.setAlwaysRemember(true);
return services;
}
#Bean
public LdapContextSource getLdapContext() {
LdapContextSource source = new LdapContextSource();
source.setUserDn("user#"+DOMAIN);
source.setPassword("password");
source.setUrl(URL);
return source;
}
This will get you remember me functionality which works with LDAP and provides the loaded data inside RememberMeAuthenticationToken which will be available in the SecurityContextHolder.getContext().getAuthentication(). It will also be able to re-use your existing logic for parsing of LDAP data into an User object (CustomUserDetailsServiceImpl).
As a separate subject, there's also one problem with the code posted in the question, you should replace the:
authManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
.userDetailsService(userDetailsService())
;
with:
authManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
;
The call to userDetailsService should only be made in order to add DAO-based authentication (e.g. against database) and should be called with a real implementation of the user details service. Your current configuration can lead to infinite loops.
It sounds like you are missing an instance of UserService that your RememberMeService needs a reference to. Since you are using LDAP, you'd need an LDAP version of UserService. I'm only familiar with JDBC/JPA implementations, but looks like org.springframework.security.ldap.userdetails.LdapUserDetailsManager is what you are looking for. Then your config would look something like this:
#Bean
public UserDetailsService getUserDetailsService() {
return new LdapUserDetailsManager(); // TODO give it whatever constructor params it needs
}
#Bean
public RememberMeServices rememberMeServices() {
TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("password", getUserDetailsService());
rememberMeServices.setCookieName("cookieName");
rememberMeServices.setParameter("rememberMe");
return rememberMeServices;
}
I've been having some trouble figuring this one out.
I've got an multitenant system where users are organized into organizations. Within that organization the usernames must be unique. Otherwise, two organizations can have the same user name.
I've got spring security hooked up with the jdbc-user-service and all that works fine. My problems start when I'm trying to get the current user.
I took a look at a link that leverages spring 3 and the Principal object as a method parameter. This works great, except a Principal doesn't have enough information! First, usernames aren't unique in my usecase, also having easy access to the organization that the user belongs to would be great.
A little more searching dug up this awesome answer. The problem with this is the same problem as before. It relies on the Principal object, which just doesn't have enough info.
(here's the magic)
#Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
if (this.supportsParameter(methodParameter)) {
Principal principal = webRequest.getUserPrincipal();
return (User) ((Authentication) principal).getPrincipal();
} else {
return WebArgumentResolver.UNRESOLVED;
}
}
Is there a good way to override the User object to use my own? Am i stuck writing a custom UserDetailService? Is there a better approach than continuing to walk down this path?
Thanks
Make your User object inherit UserDetails(or use a wrapper) and use it as principal.
e.g.
public class MyCustomUser implements UserDetails {
// ..
}
Make a custom UserDetailsService that returns your User object:
#Service
public class MyCustomUserDetailsServiceImpl implements UserDetailsService {
#Autowired
private MyCustomUserDAO userDAO;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userDAO.getByUsername(username);
}
}
Now you can extract your user:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Object principal = auth.getPrincipal();
MyCustomUser user = (MyCustomUser)principal;
user.myCustomMethod();