Spring security clarifications - spring

I am new to Spring security and learnt the concepts of AuthenticationInterceptor, AuthenticationManager, UserLoginService. I know how to implement the userloginservice for checking a valid user in the system. The problem is we have two types of users 1)Specialists 2) Users and their credentials are stored in two different tables. The implementation of UserDetailsService should check both tables to authenticate and get the details of the user. Is there possibility to use multiple authentication providers and use the appropriate one based upon the type of user? or Do I have to use the same authentication provider to query both tables to find out the user validity? What is the best way to handle this scenarios when the user has to be searched in two tables?

I think you can do sth like that:
#Autowired
private UsersDAO usersDAO;
#Autowired
private OtherDAO otherDAO;
#Override
#Transactional
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
Users user = usersDAO.find(username);
Other oth = otherDAO.find(username);
// your queries
if(check if your desire is correct based on user){
List<GrantedAuthority> authorities = buildUserAuthority(user.getUserRole());
return buildUserForAuthentication(user, authorities);
} else {
//...
}
}

Related

Get Logged-in User

Please bare with me as I am learning Spring Data REST as I go. Definitely feel free to suggest a safer approach if what I am proposing here is not the safest approach or even possible.
Problem
A user logs into my Spring Data REST API via Google OAuth2. After logging in, I need to get the user's ID, which is just the value of their primary key, from the User table. The reason I need the ID is to restrict access to endpoints such as /users/{id}. If a user's ID is 1, then he is only allowed to view /users/1, unless he is an Administrator.
Current Login Architecture
This portion of the application works as expected:
OAuth2AuthenticationSuccessHandler.java:
#Component("oauth2authSuccessHandler")
public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Autowired
private UserDataRestRepository userRepository;
#Autowired
private RandomStringGenerator randomStringGenerator;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
OAuth2AuthenticationToken authenticationToken = (OAuth2AuthenticationToken) authentication;
String email = authenticationToken.getPrincipal().getAttributes().get("email").toString();
if (!userRepository.existsByEmail(email)) {
// If email not found, create local user account.
String firstName = CaseUtils.toCamelCase(
authenticationToken.getPrincipal().getAttributes().get("given_name").toString().toLowerCase(),
true);
String lastName = CaseUtils.toCamelCase(
authenticationToken.getPrincipal().getAttributes().get("family_name").toString().toLowerCase(),
true);
// Generate temporary username.
BigInteger usernameSuffix = userRepository.getNextValSeqUserUsername();
String username = "User" + usernameSuffix;
// Generate temporary password.
String password = randomStringGenerator.generate(20).toUpperCase();
// Encode Password.
String encodedPassword = passwordEncoder.encode(password);
User newUser = new User();
newUser.setFirstName(firstName);
newUser.setLastName(lastName);
newUser.setUsername(username);
newUser.setUserSetUsername(false);
newUser.setEmail(email);
newUser.setPassword(encodedPassword);
newUser.setUserSetPassword(false);
newUser.setCreated(new Timestamp(System.currentTimeMillis()));
newUser.setDisabled(false);
newUser.setLocked(false);
userRepository.save(newUser);
}
// Redirect to root page.
redirectStrategy.sendRedirect(request, response, "/");
}
}
UserDataRestRepository.java:
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserDataRestRepository extends PagingAndSortingRepository<User, Integer>, CrudRepository<User, Integer> {
public boolean existsByEmail(String email);
#Query(value="SELECT NEXT VALUE FOR Seq_User_Username", nativeQuery=true)
public BigInteger getNextValSeqUserUsername();
}
OAuth2LoginSecurityConfig.java:
#Configuration
#EnableWebSecurity
public class OAuth2LoginSecurityConfig {
#Autowired
private AuthenticationSuccessHandler oauth2authSuccessHandler;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.anyRequest()
.authenticated()
)
.oauth2Login()
.successHandler(oauth2authSuccessHandler);
return http.build();
}
}
Database Tables
User:
Role:
UserRole:
Current Post-authentication Problems
Currently, after a user logs in via OAuth2, we do not have access to the user's ID (which is the value of their primary key in the User table) unless we access the Authentication object, get the email address, then get the user's ID from the User table based on the email address. Basically: Select Id from User where Email = user#example.com
Proposed Login Architecture
I am wondering if the UserDetailsService can solve the problem, illustrated in green:
My train of thought is: by getting the user's info from the User table, then loading it into the UserDetailsService, the entire application now has access to the user's ID (primary key) and all of the other info in that row via the UserDetailsService.
Thanks for your help.
I would not include a round-trip to the DB for each incoming request on resource-server(s), this is a waste of resources.
Have you considered using an authorization-server in front of Google and either directly bound to your user database, or capable of calling a web-service to fetch additional data? Many propose it, along with "login with Google".
You would have complete control of access and ID tokens claims: you can add about anything as private claim, but the request to the DB (or Web-Service) happens only once per token issuance (and not once per request to a resource-server). And if you define an Authentication of your own (easy, just extend AbstractAuthenticationToken), you can put accessors to cast those private claims from Object to something more relevant to your security / domain model.
I demo something similar (add a private claim to access-token with a value returned by a web-service and then use it for access-control with custom Authentication and security DSL) in this project.

Spring boot auth server client/ resource server: OAuth 2.1 Authorization Code Grant programatic simulation? Password grant no longer exists

Spring Authorization Server OAuth 2.1.
How can i programatically simulate the authorization_code grant?
Since all grants except for authorization_code and client_credentials have been dropped this has become quite a headache.
The scenario calls for a #Scheduled job to login as a specific user where the client credentials are encoded properties within the server performing the login.
The user roles are important when executing downstream resources and is considered a regular user of the registered Client.
Using the Password grant was perfect for this scenario in OAuth 2.0.
Before i start hacking our Spring Auth server and implement a Password grant for registered resources or maybe overloading the client_credentials for user_credentialed payloads.
Quite a pain if you ask me, so please enlighten me? Are there any patterns for implementing this that i have not yet discovered?
While I'm curious what specific use case you have that needs to perform tasks as a particular user (as opposed to a single confidential client), it should still be possible with customization.
maybe overloading the client_credentials for user_credentialed payloads
This approach makes the most sense to me as a way to adapt supported flows in OAuth 2.1 to emulate a deprecated flow like the resource owner password grant. You can use a variation of this github gist, extending it with your user's authorities if needed. One possible solution might look like the following:
#Component
public final class DaoRegisteredClientRepository implements RegisteredClientRepository {
private final RegisteredClient registeredClient;
private final UserDetailsService userDetailsService;
public DaoRegisteredClientRepository(RegisteredClient registeredClient, UserDetailsService userDetailsService) {
this.registeredClient = registeredClient;
this.userDetailsService = userDetailsService;
}
#Override
public void save(RegisteredClient registeredClient) {
throw new UnsupportedOperationException();
}
#Override
public RegisteredClient findById(String id) {
return this.registeredClient.getId().equals(id) ? this.registeredClient : null;
}
#Override
public RegisteredClient findByClientId(String clientId) {
UserDetails userDetails;
try {
userDetails = this.userDetailsService.loadUserByUsername(clientId);
} catch (UsernameNotFoundException ignored) {
return null;
}
return RegisteredClient.from(this.registeredClient)
.clientId(userDetails.getUsername())
.clientSecret(userDetails.getPassword())
.clientSettings(ClientSettings.builder().setting("user.authorities", userDetails.getAuthorities()).build())
.build();
}
}
This uses a single client registration, but makes use of a UserDetailsService to resolve a subject representing your user's username and a secret which is actually the user's password. You would then need to provide an #Bean of type OAuth2TokenCustomizer<JwtEncodingContext> to access the user.authorities setting and add those authorities to the resulting access token (JWT) using whatever claim your resource server expects them in.
Alternatively, you could just override the scopes parameter of the returned RegisteredClient if desired.
I had the similar problem and ended up creating a password grant emulation servlet filter. Please refer to my example:
https://github.com/andreygrigoriev/spring_authorization_server_password_grant

Springboot Security hasRole not working

I’m unable to use hasRole method in #PreAuthorize annotation. Also request.isUserInRole(“ADMIN”) gives false. What am I missing?
Although .hasAuthority(“ADMIN”) works fine.
I am assigning authorities to the users from a database.
You have to name your authority with prefix ROLE_ to use isUserInRole, see Spring Security Reference:
The HttpServletRequest.isUserInRole(String) will determine if SecurityContextHolder.getContext().getAuthentication().getAuthorities() contains a GrantedAuthority with the role passed into isUserInRole(String). Typically users should not pass in the "ROLE_" prefix into this method since it is added automatically. For example, if you want to determine if the current user has the authority "ROLE_ADMIN", you could use the following:
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
Same for hasRole (also hasAnyRole), see Spring Security Reference:
Returns true if the current principal has the specified role. By default if the supplied role does not start with 'ROLE_' it will be added. This can be customized by modifying the defaultRolePrefix on DefaultWebSecurityExpressionHandler.
See also Spring Security Reference:
46.3.3 What does "ROLE_" mean and why do I need it on my role names?
Spring Security has a voter-based architecture which means that an access decision is made by a series of AccessDecisionVoters. The voters act on the "configuration attributes" which are specified for a secured resource (such as a method invocation). With this approach, not all attributes may be relevant to all voters and a voter needs to know when it should ignore an attribute (abstain) and when it should vote to grant or deny access based on the attribute value. The most common voter is the RoleVoter which by default votes whenever it finds an attribute with the "ROLE_" prefix. It makes a simple comparison of the attribute (such as "ROLE_USER") with the names of the authorities which the current user has been assigned. If it finds a match (they have an authority called "ROLE_USER"), it votes to grant access, otherwise it votes to deny access.
I had to improvise a little, maybe there is other ways simpler then mine, but at the time I worked on this I had no other choice but to improvise a bit, after a thorough research came up with this solution.
Spring Security has an interface called AccessDecisionManager, you will need to implement it.
#Component
public class RolesAccessDecisionManager implements AccessDecisionManager {
private final static String AUTHENTICATED = "authenticated";
private final static String PERMIT_ALL = "permitAll";
#Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
collection.forEach(configAttribute -> {
if (!this.supports(configAttribute))
throw new AccessDeniedException("ACCESS DENIED");
});
}
#Override
public boolean supports(ConfigAttribute configAttribute) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
String rolesAsString = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(","));
if (configAttribute.toString().contains(rolesAsString))
return true;
else
return (configAttribute.toString().contains(PERMIT_ALL) || configAttribute.toString().contains(AUTHENTICATED));
}
return true;
}
#Override
public boolean supports(Class<?> aClass) {
return true;
}
}
Now to support this custom access-decision-manager with your security config do this in the security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
// other configs
.accessDecisionManager(this.accessDecisionManager)
accessDecisionManager is the autowired bean of the AccessDecisionManager implementation you've created.
You can use either hasRole() or hasAuthority(). The difference is that, you have to user ROLE_ for hasAusthority() method.
So for the ROLE_ADMIN,
#PreAuthorize("hasRole('ADMIN')") == #PreAuthorize("hasAuthority('ROLE_ADMIN')")

Spring social oauth how to get sign in provider in SocialUserDetailsService?

In my Spring Social app, I'm trying to integrate certain Social Login functionalities. After being redirected from, for example Twitter, Spring calls the following to look up the user.
public class SimpleSocialUserDetailsService implements SocialUserDetailsService {
#Override
public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException, DataAccessException {
/*
Commented
*/
}
However, since I will have multiple social login providers, the userId alone is not enough for me to look up the user in my database. I need at least the sign in provider or access token.
Is there anyway to get the sign in provider, or more information, in SocialUserDetailsService? Any other way to solve my problem would be great!
Spring Social is rather Agnostic to the Sign in Providers when properly implemented. I believe you are confused on the flow of Spring Social. At the point you describe spring social has already looked up the connections table and presumably found a record, so it looks up your user table for the user matching with userId (as referenced in the connections table) This is usually associated with the username.
This connection <-> User matching is done in the SocialAuthenticationProvider before calling the SocialUserDetails loadUserByUserId method.
So the SocialAuthenticationProvider already does what you ask for by querying the usersConnectionRepository and comparing the provider connection to find the appropriate user.
Now for your case you would can go ahead and override the user service that you have setup. As long as the userId used on the doPostSignUp call matches the one you look up in the loadUserByUserId, the proper user will be retrieved.
This is a sample:
Wherever your signup logic is executed, you call the doPostSignup and pass the desired user id (Username or another uniquely identifiable String)
ProviderSignInUtils.doPostSignUp(variableForNewUserObject.getId().toString(), request);
Now you Override the loadUserByUserId in SimpleSocialUserDetailsService
#Autowired
private UserDetailsService userDetailsService;
#Override
public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException, DataAccessException {
UserDetails userDetails = userDetailsService.loadUserByUsername(userId);
return (SocialUserDetails) userDetails;
}

defining userroles with inheriting rights

I'm currently looking into the spring-security framework - great stuff so far, pretty impressed.
However, I haven't found out where or how to define a inheritance of permissions.
e.g. I want the ROLE_ADMIN to have at least the same rights as the ROLE_USER. I defined three intercep-urls for spring:
<intercept-url pattern="/auth/login.do" access="permitAll"/>
<intercept-url pattern="/voting/*" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/admin/*" access="hasRole('ROLE_ADMIN')"/>
When trying to access any site nesting from /voting/, while being logged in as a ROLE_ADMIN user, I am being denied. Am I missing something here? I know, I could define several roles for the /voting/* branch, but if I imagine that I might have 10 different user roles in one of my real-life usecases, I can imagine the .xml file to get really messy, really fast.
Can I configure the inheritance of roles somewhere?
cheers
EDIT:
Thanks to the great community and their input, I came up with a working solution - it may be good style or not - it works :D
I defined an enum which reflects the inheriting spring-sec roles:
public enum UserRoles {
ROLE_USER(new String[]{"ROLE_USER"}),
ROLE_ADMIN(new String[]{"ROLE_USER", "ROLE_ADMIN"});
private final String[] roles;
private UserRoles(String[] roles) {
this.roles = roles;
}
public String[] getRoles() {
return roles;
}
}
I then implemented my own UserDetailsService:
Within the methode
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { ... }
where it comes to adding granted authorities to a UserDetail, I get the corresponding enum value and append all the roles defined by this enum value:
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>(2);
for (String role : UserRoles.ROLE_ADMIN.getRoles()) {
authList.add(new GrantedAuthorityImpl(role));
}
UserDetails user = null;
try {
//user = new User(username, md5.hashPassword(username), true, true, true, true, authList);
} catch (NoSuchAlgorithmException ex) {
logger.error(ex.getMessage(), ex);
}
My domain object which is persisted, contains a #Enumerated field with a UserRole - in a real environment, this field is loaded from the DB and the corresponding Roles are picked from that enum.
thanks again for the input - love this community ^^
Check out RoleHierarchy and RoleHierarchyImpl and this question.
As far as I know, Spring Security does not support the concept of Roles and Privileges. In Spring security are only Roles sometimes called Authority -- Moreover: In Spring Security are Roles/Authorities that what in a Roles and Privileges System is called Privileges.
So if you want to build a System of Roles and Privileges, then you need to do it by your one by building your own Spring Security AuthenticationManager, and tread the Spring Security Roles/Authorities like Privileges.
#See This Blog: Spring Security customization (Part 1 – Customizing UserDetails or extending GrantedAuthority) -- It is written for Spring Security 2.0 and shows how to implement what I am talking about. It also stayes that RoleHierarchy has some drawbacks, but this article is about 2.0, may the drawbacks are gone in 3.0

Resources