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);
}
Related
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.
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.
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);
In my Spring Boot application I need to programmatically create a new user and obtain OAuth2 access/refresh tokens for him from internal(part of this application) OAuth2 Authorization Server.
Then, I plan to send these access/refresh tokens to some external (client) application that will interact with my first application on behalf of this user.
Is it possible to programmatically obtain OAuth2 access/refresh tokens for this user without providing password(during the programmatic creation of this user I don't want to deal with password, only username).
Yes you can, take a look at the code below
#Autowired
private TokenEndpoint tokenEndpoint;
public ResponseEntity<?> createToken(User user) {
Principal principal = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword(), user.getAuthorities());
HashMap<String, String> parameters = new HashMap<String, String>();
parameters.put("client_id", "XXX");
parameters.put("client_secret", "XXX");
parameters.put("grant_type", "password");
parameters.put("password", user.getPassword());
parameters.put("scope", "XXX");
parameters.put("username", user.getUserName());
return tokenEndpoint.getAccessToken(principal, parameters);
}
but you are violating the OAuth2 spec. Authorization should be performed by Resource Owner.
I've got a little webapp secured by spring-security using a username/password combo on a sql-db as credentials.
I now want to add facebook/twitter authentication with spring-social. Using the examples I am able to store the users credentials in my db. I'm now working on authenticating the user against his current session on my app using the following piece of code:
public String signIn(String userId, Connection<?> connection, NativeWebRequest request) {
User user = userService.getUserById(Long.parseLong(userId));
user.setPassword(this.passwordEncoder.encodePassword(user.getAccessToken(), this.salt));
this.userService.store(user);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getDisplayName(), user.getAccessToken());
HttpServletRequest req = request.getNativeRequest(HttpServletRequest.class); // generate session if one doesn't exist
token.setDetails(new WebAuthenticationDetails(req));
Authentication authenticatedUser = this.authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
return "/user/dashboard";
}
The authentication works, I am not getting any BadCredential-exceptions. But after being redirected to /user/dashboard I am thrown back to the login.
I am out of ideas, a similar piece of code for authenticating the session is working after a classical signup.
Does anyone have any ideas why this happens or how to debug this?
Thanks very much in advance!
Hendrik
I have similar code that works for me, and also adds "remember me" support:
// lookup by id, which in my case is the login
User user = userService.findByLogin(userId);
// code to populate the user's roles
List<GrantedAuthority> authorities = ...;
// create new instance of my UserDetails implementation
UserDetailsImpl springSecurityUser = new UserDetailsImpl(user, authorities);
// create new Authentication using UserDetails instance, password, and roles
Authentication authentication = new UsernamePasswordAuthenticationToken(springSecurityUser, user.getPassword(), authorities);
// set the Authentication in the SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
// optional: remember-me support (must #Autowire in TokenBasedRememberMeServices)
tokenBasedRememberMeServices.onLoginSuccess(
(HttpServletRequest) request.getNativeRequest(),
(HttpServletResponse) request.getNativeResponse(),
authentication);