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

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);

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.

Backend Create user with signup JWT

I'm trying to create a restful web application with Springboot and Gradle. I use JWT for authentication. When a user logged the backend creates a user token for the user. this token he uses whenever he opens another page to retrieve his data.
What I'm trying to do is making a signup page, but the problem is that I cant send information to my backend without a Bearer token. How do I send a post method to create a user without authentication on this one single POST operation?
To generate a token for a user that exists
#PostMapping(value = "${jwt.get.token.uri}")
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtTokenRequest authenticationRequest)
throws AuthenticationException {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
UserDetails userDetails = jwtInMemoryUserDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtTokenResponse(token));
}
What I want to call to create a user in my database:
(want to do this POST without authorization)
#PostMapping(value = "/signup")
public ResponseEntity<User> createUser(#RequestBody User user){
HBMUserService HBMuserService = new HBMUserService();
User createdUser = HBMuserService.saveUser(user);
return new ResponseEntity<>(createdUser, HttpStatus.OK);
}
My application properties:
jwt.signing.key.secret=SecretKey
jwt.get.token.uri=/authenticate
jwt.refresh.token.uri=/refresh
jwt.http.request.header=Authorization
jwt.token.expiration.in.seconds=604800
I Found out where my JWT Ignores my Auth path for login and added my signup path there as well.
.antMatchers(
HttpMethod.POST,
authenticationPath,
"/signup"
)

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

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.

Injecting Logged in User in Spring

Hi I want my user to be logged in via URL which is secured by spring. URL will contan username as well as password. I tried doing it by sending username and password via controller to customAuthenticationManager and then checked in CustomAuthentication Provider and returned UsernamePasswordAuthenticationToken. when I check isauthenticated flag it shows true but when I try to access a secured page it redirects me to the login page. Where am I going wrong ?
Its not the best way to do it but try this:
public void login(HttpServletRequest request, String userName, String password)
{
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, password);
// Authenticate the user
Authentication authentication = authenticationManager.authenticate(authRequest);
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
// Create a new session and add the security context.
HttpSession session = request.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
}

Howto integrate spring-social authentication with spring-security?

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);

Resources