Spring Boot 2 + Oauth2: How to have separate logins for regular users and admins? - spring-boot

So, I am using Spring Boot and Security for a while now. So far I only had one "kind" of user which were simply given roles USER_ROLE or ADMIN_ROLE in order to secure my REST endpoints.
However, I am now at a point where I realize: I only have one login. That is the default /oauth/token endpoint which, eventually, loads a user from my database and adds the authorities accordingly:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
username = username.trim();
AppUserEntity appUserEntity = this.appUserRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found."));
List<GrantedAuthority> authorities = new ArrayList<>();
Collection<AppUserRoleEntity> roles = appUserEntity.getRoles();
for (AppUserRoleEntity appUserRoleEntity : roles) {
RoleEntity roleEntity = appUserRoleEntity.getRole();
authorities.add(new SimpleGrantedAuthority(roleEntity.getRoleType().toString()));
}
return new AppUserDetails(
appUserEntity.getId(),
appUserEntity.getEmail(),
appUserEntity.getPassword(),
authorities,
appUserEntity.getActivated()
);
}
The problem with this is, that there is no distinction between users. I do not know here which login (on my website) the user was using. A login will always work, even if a normal user uses the admin-login mask.
What I seek is a way to have different registration and login endpoints for admin and regular users. How would I do that?
I have seen this tutorial and also this one but they do not use OAuth2.
What are my options here and/or what is the Spring Boot way to do this?

Authentication and Authorization are two things. With OAuth, you are doing only the authentication part. Meaning, it checks whether the user has a valid username and a password. It is the application's responsibility to allow or deny access to certain areas of the application based on the grant/role assigned to the authenticated user.
You authenticate users with oauth/token endpoint and pass the token to the application with every request. Then in application's security configurations, you restrict admin area to only users who are in ADMIN_ROLE.
Please check the following section of a sample spring security configuration.
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated() //User must have a valid token
.antMatchers("/admin/**").hasRole("ADMIN"); //User must be of ADMIN_ROLE
}
So, any user having a valid username and a password cannot access (login is a bit vague word) admin area of your application.
In the above configuration, if a non-admin user try to access /admin/employee after authenticating through oauth/token, it will throw 403 error. In other terms, that user is not allowed to login to that area of the application.

Related

Logic to implement a RESTFUL logout API using oauth2ResourceServer JWT in a spring application

The issue I have is after the user is authenticated meaning user has signed in, I understand from the client side to logout a user, I delete the token from the local storage but the issue I have is how do I invalidate the token or logout from the serverside.
My intial approach was to make the logout API permit all in my SecurityFilterChain but when I try to grab the authenticated user from SecurityContextHolder after the user had signed in I was getting anonymousUser.
My second/current approach is I instead authorized LOGOUT API which means to access the API, a token has to passed in the header. Then I can then set SecurityContextHolder.getContext().setAuthentication(authentication == false); and also clearContext(). With this approach I am able to get the logged in user but my questions are:
Is this the right logic to implement a log out?
I understand a token cannot be invalidated because it is STATELESS. But is there a way to get around this? Because even after setting Authentication to false in SecurityContextHolder
and clearing security context SecurityContextHolder.clearContext(); when I try accessing Authenticated API i.e CRUD operations, I am still able to use the token.
Here is my login and logout methods in my RestController Class
logout
#PostMapping(path = "/logout", headers = "Authorization")
#ResponseStatus(OK)
public ResponseEntity<?> logout() {
LOGGER.info("Trying to Logout ");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
LOGGER.info("Username {} ", username);
authentication.setAuthenticated(false);
SecurityContextHolder.getContext().setAuthentication(authentication);
SecurityContextHolder.clearContext();
return ResponseEntity.ok().body("Successfully logged out");
}
login
#PostMapping(path = "/login", consumes = "application/json", produces = "application/json")
#ResponseStatus(OK)
public ResponseEntity<?> login(#Valid #RequestBody UserDTO userDTO) {
Authentication authentication;
LOGGER.info("Authenticating {}", userDTO.getUsername());
var authenticationToken = confirmUser(userDTO); // returns a UsernamePasswordAuthenticationToken
try {
authentication = authenticationManager.authenticate(authenticationToken); // Authenticate user password token
SecurityContextHolder.getContext().setAuthentication(authentication); // Set the security context to the logged user
} catch (AuthenticationException e) {
LOGGER.error("Stack trace {}", e.getMessage());
SecurityContextHolder.getContext().setAuthentication(null);
throw new InvalidPasswordException("Wrong username or password");
}
LOGGER.info("{} has signed in", userDTO.getUsername());
return ResponseEntity.ok()
.header( AUTHORIZATION, tokenService.generateToken(authentication) )
.build();
}
I might recommend a different approach, but let's start with your question.
Expiring Access Tokens
To expire a resource server token, you will need to add some kind of state.
This usually comes in the form of some kind of list of valid tokens. If the token isn't in the list, then the token is not valid.
A common way to achieve this is to rely on the authorization server. Many authorization servers ship with an endpoint that you can hit to see if a token is still valid.
Modeling Things Differently
That said, it might be worth considering if you should be thinking about the access token differently. The access token does not represent a user's authenticated session. It represents the user granting access to the client to operate on the user's behalf.
So after the user logs out, it still makes quite a bit of sense for the client to have a valid access token so that the user doesn't have to reauthorize the client every time they log in.

Extending a Jhipster JWT (Spring) monolith application to support impersonation

I have generated a jhipster angular/java application that is using JWT authentication.
I now want to extend the application to support impersonation.
I am interested in achieving the following:
Impersonation by admin: Allowing the admin user to login as any other user
Impersonation granted to user: Allowing another user that has been granted the right to impersonate a user (granted by the user itself) to login as that other user.
Audit - recording changes (audit function) - the audit trail must be able to distinguish between the actual user and an impersonated user and record this in the audit trail.
I see that Spring supports impersonation but it is unclear to me how I can implement it properly in my Jhipster application given that JWT is used. I am not sure if the Spring route is appropriate for JHipster-JWT-Monolith application - I am of the opinion that it not the right approach.
While there are some incomplete information on various other posts, after an extensive search I have been unable to find a post that can provide clear step by step guide on this. If somebody can do that for me it would be greatly appreciated. I expect others would also find such an answer very useful.
Thanks in advance.
Fergal
You just need to add below method in UserJwtController.java
#PostMapping("/authenticate-externalnodes")
public ResponseEntity<JWTToken> authenticateExternalnodes(#Valid #RequestBody LoginVM loginVM) {
// Get Roles for user via username
Set<Authority> authorities = userService.getUserWithAuthoritiesByLogin(loginVM.getUsername()).get()
.getAuthorities();
// Create Granted Authority Rules
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
for (Authority authority : authorities) {
grantedAuthorities.add(new SimpleGrantedAuthority(authority.getName()));
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
loginVM.getUsername(), "", grantedAuthorities);
Authentication authentication = authenticationToken;
SecurityContextHolder.getContext().setAuthentication(authentication);
boolean rememberMe = (loginVM.isRememberMe() == null) ? false : loginVM.isRememberMe();
String jwt = tokenProvider.createToken(authentication, rememberMe);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);
return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK);
}

Authentication of users by authenticationProvider from spring security through ReST API Call

I am now exploring that authentication of users in microservice. For that I am created my authentication service - checkUserAuthentication. Also providing Microservice also. this is already deployed in cloud.
Now I am creating new service with specific business logic. In this service , need to authenticate and check authorization of user to access this end-point by using authenticationProvider from spring security.
For this I am reading and exploring the following tutorials,
https://dzone.com/articles/spring-security-custom
http://roshanonjava.blogspot.in/2017/04/spring-security-custom-authentication.html
http://javasampleapproach.com/spring-framework/spring-security/spring-security-customize-authentication-provider
http://www.baeldung.com/spring-security-authentication-provider
In here they are implements AuthenticationProvider in class CustomAuthenticationProvider.
and in method they are receiving username and password is like following,
public Authentication authenticate(Authentication authentication) throws
AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
Optional<User> optionalUser = users.stream().filter(u -> u.index(name,
password)).findFirst();
if (!optionalUser.isPresent()) {
logger.error("Authentication failed for user = " + name);
throw new BadCredentialsException("Authentication failed for user = " + name);
}
// find out the exited users
List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
grantedAuthorities.add(new SimpleGrantedAuthority(optionalUser.get().role));
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(name, password,
grantedAuthorities);
logger.info("Succesful Authentication with user = " + name);
return auth;
}
These are codes from documentation. Instead of this method, I need to do in different way. Here I am adding my requirements:
My requirement: I need to receive username and password from API Request.And For checking this username and password, I need to call my deployed APIs checkUserAuthentication and checkUserAuthorization.
My doubts on this:
Can I directly call these API within "public Authentication authenticate(Authentication authentication)" method ?
How I receive username and password from the received request ?
Why we are using UsernamePasswordAuthenticationToken ? , If we are sending JWT token instead of username and password, then which class will use for providing reply?
Since I only started with Spring Security, I am new to security world.
Can I directly call these API within "public Authentication authenticate(Authentication authentication)" method ?
Yes.
How I receive username and password from the received request ?
Same as they are doing in authenticate method.
Why we are using UsernamePasswordAuthenticationToken ? , If we are sending JWT token instead of username and passowrd, then which class
will use for providing reply?
UsernamePasswordAuthenticationToken is used internally by spring security. This
comes into the picture when you create a session in spring. it contains the user information (eg. email etc.) and authorities (role).For example, when you receive a JWT token in your application, you will validate the JWT token (signature etc. ) and upon successfull validation of JWT, you will create an object of UsernamePasswordAuthenticationToken and spring will save it in session. For each incoming request, spring will call boolean isAuthenticated() method on this object to find if user can access the required resource.
Now when you have got all your answers, my recommendation is to go with Oauth2 for your boot microservices. there are plenty of example how to implement it and customize it for your requirement. (Basically, you have to implement your Authorization server which will authenticate the user with your service checkUserAuthentication and generate the accesstoken. Each consumer of your microservice needs to send this accesstoken which they have got from Authorization server and you need to validate it in your microservice. So your microservice will act as Resource Server).
Hope it will help.

Spring Security force logout when password change

I have 2 user role ADMIN and USER. ADMIN can change USER's password. I want to force logout when user's password changed by ADMIN. I can save changed password and use them when next login. But I want to force logout to them.
UserDetails userDetails = userDetailsService.loadUserByUsername(vendor.getUsername());
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setAuthenticated(false);
This is not working.
First you need to configure the session configuration under your security configuration as follows
#Override
protected void configure(HttpSecurity http) throws Exception {
// this enables ConcurrentSessionFilter to allow us to read all sessions by using getAllPrincipals
http
.sessionManagement().maximumSessions(10)
.sessionRegistry(sessionRegistry())
.expiredUrl("/login?expire");
// Rest of the configuration
}
This enables you to call sessionRegistry.getAllSessions to manage the list of active session and expire them. SessionRegistry is autowired FYI.
List<Object> principals = sessionRegistry.getAllPrincipals();
for (Object principal: principals) {
// Check for the principal you want to expire here
List<SessionInformation> sessionInformations = sessionRegistry.getAllSessions(principal);
for (SessionInformation sessionInformation : sessionInformations) {
sessionInformation.expireNow();;
}
}

Spring Boot - displaying the OTP page, restrict access to other pages using their URLs (after successful authentication on the login page)

I have created a Spring Boot web application, where after successful login, I have to send the user to the OTP page.
My problem is: When the user comes to the OTP page he can bypass it changing the URL, so he can access any page (i.e. big security risk) because the user has already authenticated from the login page.
How can I restict URL changes on the OTP page as it happens on the login page (using Spring boot-security), so the user can only get in if she/he is authenticated by OTP.
A common approach is that on successful authentication - i.e. credentials entered on login screen are verified - the user is given limited access to the application. This limited access only allows access to the OTP page(s). Once the OTP has been verified, the user is given the full set of authorisation roles to which they're entitled.
A blog outlining this approach is available here.
Create an AuthenticationSuccessHandler
If the user requires a one-time password, strip their authorities, and give them a new one, say ROLE_OTP. ROLE_OTP can only use the OTP URL and not anything else.
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication sourceAuthentication
) throws IOException, ServletException {
UserDetails sourceUser = (UserDetails) sourceAuthentication.getPrincipal();
List<GrantedAuthority> targetAuthorities = new ArrayList<GrantedAuthority>( Arrays.asList( new SimpleGrantedAuthority("ROLE_OTP") ) );
UserDetails targetUser = new User( sourceUser.getUsername() , "", targetAuthorities);
UsernamePasswordAuthenticationToken targetAuthentication = new UsernamePasswordAuthenticationToken(targetUser, null, targetAuthorities);
targetAuthentication.setDetails( sourceAuthentication.getDetails() );
SecurityContextHolder.getContext().setAuthentication(targetAuthentication);
response.sendRedirect("/otp-url");
}
If they pass the OTP, reload their real roles with loadUserByUsername()
Authentication sourceAuthentication = SecurityContextHolder.getContext().getAuthentication();
UserDetails sourceUser = (UserDetails) sourceAuthentication.getPrincipal();
UserDetails targetUser = userDetailsManager.loadUserByUsername(sourceUser.getUsername());
UsernamePasswordAuthenticationToken targetAuthentication = new UsernamePasswordAuthenticationToken(targetUser, null, targetUser.getAuthorities());
targetAuthentication.setDetails( sourceAuthentication.getDetails() );
SecurityContextHolder.getContext().setAuthentication(targetAuthentication);

Resources