RESTfull user sessions with Spring and JPA - java

How can I easily create spring user sessions based on information found in a mySQL database connected with JPA ?
As a simple example lets say I have 2 tables:
users
INT id
VARCHAR(30) username
VARCHAR(20) password
bookmark
INT id
VARCHAR(20) name
TEXT desctription
int user (FK)
Users will be able to view their bookmarks by requesting the following URL:
http://localhost:8080/bookmarks
In this manner I need separate user sessions (authentication not my main priority in this case) to be able to show bookmarks specific to the user.
Another way to go is by accessing bookmark information through:
http://localhost:8080/{userId}/bookmarks
In this case how can I prevent users from accessing another users' bookmark information ? (Such as preventing user id 1 from accessing user id 2s' bookmarks by using the url http://localhost:8080/2/bookmarks

If you would use Spring Security to secure your app, you can create custom UserDetailsService to read user data for each request from DB. Something like this:
#Component
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
protected UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email)
throws UsernameNotFoundException {
User user = userRepository.findByEmail(email);
if (user == null) {
throw new UsernameNotFoundException(String.format("User with email=%s was not found", email));
}
return user;
}
}
Of course assuming that you have JPA entity called User implementing UserDetails interface.
With this mechanism you can inject User instance into MVC controllers:
#GetMapping("/bookmarks")
public List<Bookmark> readBookmarks(Principal principal) {
User user = (User) principal;
// read bookmarks code
}
You can also read in anywhere in the app via:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User user = (User) principal;
REACTION ON COMMENT:
It is broad topic with a lot of decisions to make. e.g. you may consider using JWT, OAUTH2 or token based based authentication. If you are starting with Spring Security, I recommend to look at their Guides section. Especially Spting MVC guide is relevant for you.

Related

Updating user info with JWT on spring security app

I am working on a simple REST API using spring security with a JWT Filter. However, I am a little bit confused with how to update user info.
After loggin in I get a JWT that I use to make request on other endpoints. But how do I create a controller that create or update data?
I have my user table and my preferences table they are one to one related.
If I make a request like "/create/preferences" what do I put in the body in order for me to create a preference link to this user making the call to the api?
#PostMapping("/user/preferences")
public ResponseEntity<Preferences> getUserPreferences() {
/*
what to put here
*/
return new ResponseEntity<>(HttpStatus.OK);
}
Do I have to get the user from the token? Then I can create a preference object with this user id?
Thanks in advance
#RestController
#RequestMapping("/users")
public class UserController {
#Autowired
private UserService userService;
#PutMapping("/{id}")
public ResponseEntity<?> updateUser(#PathVariable Long id, #RequestBody User user) {
// Use Spring Security to get the authenticated user from the security context
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User authenticatedUser = (User) authentication.getPrincipal();
// Verify that the authenticated user is the same as the user being updated
if (!authenticatedUser.getId().equals(id)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
// Update the user information
user.setId(id);
userService.updateUser(user);
return ResponseEntity.ok().build();
}
}
What do you think about this? UserService would be responsible for actually updating the user in the database. The controller simply handles the request, authenticates the user with JWT, and checks that the authenticated user is authorized to make the update.
You can change the ResponseEntity type as you like.

Authenticating With Azure Directory and Fetch the Valid User Roles From Database

I have to design a application for an organization where Authentication need to be happen Using SSO with Azure Directory using spring boot.
All the employee Information is stored in the AD. My custom application will have it's own users and their roles which are saved in the Database. Not every Employee in the organization will have access to this application. Admin will add user and their roles in Database(Mysql).
I am able to figure it out to do SSO with AD by adding relevant details in properties-file and configuring WebSecurify Config class
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;
#Autowired
private AADAuthenticationFilter aadAuthFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers(HttpMethod.OPTIONS,
"/**").permitAll().anyRequest().authenticated()
.and()
.oauth2Login()
.userInfoEndpoint()
.oidcUserService(oidcUserService);
http
.addFilterBefore(aadAuthFilter, UsernamePasswordAuthenticationFilter.class);
http
.headers()
.frameOptions().sameOrigin()
.cacheControl();
http
.csrf().disable()
.authorizeRequests().antMatchers(HttpMethod.OPTIONS,
"/**").permitAll().anyRequest().authenticated()
}
}
Once the user is validated, In response i will get JWT token, Which i will use for all my further API requests. But the Problem is, Here i am allowing all the Employees to access the application since he is authenticated using AD. But, Once User is Authenticated i want to check whether user is available in the database or not, If he is not available, i need to send invalid user, if he is valid user to my application, i need to fetch roles and send it to front end as a Valid response.
I also want to do service level authorization for the user, I have used #PreAuthorize Annotation for most of my services.
#PreAuthorize("hasRole('ROLE_VIEWER') or hasRole('ROLE_EDITOR')")
Edit 1
public class UserDetailsServiceImpl extends OidcUserService implements
CustomUserDetailsService {
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws
OAuth2AuthenticationException {
OidcUser oidcUser;
String mail=userRequest.getClientRegistration().getRegistrationId();
Set<GrantedAuthority> grantedAuthorities = getAuthorities(mail);
oidcUser = new DefaultOidcUser(grantedAuthorities,
userRequest.getIdToken());
return oidcUser;
}
private Set<GrantedAuthority> getAuthorities(String email) {
List<RoleIdDetails>
roleIdDetails=usersRepository.getRoleIdDeatils(email);
final Set<GrantedAuthority> authorities =
roleIdDetails.stream().map(role -> new SimpleGrantedAuthority("ROLE_" +
role.getRoleName().toString().toUpperCase())).collect(Collectors.toSet());
return authorities;
}
}
I have updated With My Own Custom OidcUserService, But Still I'm not able to set the roles.
Per my understanding, you don't need to use Mysql database to store the user and their roles, we can implement this requirement just by Azure Active Directory.
First, we can create a group in Azure Active Directory by clicking "Groups" --> "New group" buttons on azure portal and select the members which you want to this group from the employee in you azure ad(shown as below screenshots):
Then go to your application in azure ad by clicking "App registrations" and click "Manifest" button.
You can also add more roles under the "appRoles" property in the screenshot above.
After that, in your application in azure ad, please click "Managed application in local directory".
Then click "Users and groups" --> "Add user".
Choose the group which we created just now and select a role for it.
Now you can do service level authorization for the user by the annotation you mentioned in your question(#PreAuthorize), you can also refer to this tutorial.
By the way, if we want to assign a role to a group in azure ad, we need to have Premium P1 or P2 plan for azure ad(Azure Active Directory pricing). If you don't have Premium plan, you can just assign a role to each user but not a group in the last screenshot.

Spring Security: how to map Active Directory users to application users?

I'm integrating Active Directory authentication in an existing Spring web app.
At the moment user details are stored in a DB table and I implemented a custom UserDetailsService to authenticate users.
Following several tutorials I implemented Active Directory support via ActiveDirectoryLdapAuthenticationProvider in my application, and it basically works, but I need something more specific.
Instead of mapping AD groups to application roles, I need to map AD users to existing users in my web app. In other words I want to enable AD authentication only for users in my Users table.
A user can access via AD credentials ONLY IF is already registered in application DB.
Authorization info for each user are stored in DB. This way, each user can be authenticated both via DB user+password or via AD.
Is it possible to achieve this with Spring Security? and how?
NOTE I'm using Spring v 3.2.9 and Spring Security v 3.2.3
As a workaround I implementend a custom AuthenticationProvider and a custom UserDetailsContextMapper.
Becouse ActiveDirectoryLdapAuthenticationProvider is a final class I implemented the ADCustomAuthenticationProvider via composition, this way:
public class ADCustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private ActiveDirectoryLdapAuthenticationProvider adAuthProvider;
#Autowired
private UserDao uDao;
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String principal = authentication.getPrincipal().toString();
String username = principal.split("#")[0];
User utente = uDao.findByUsername(username);
if (utente == null) {
throw new ADUnregisteredUserAuthenticationException("user ["
+ principal + "] is not registered");
}
return adAuthProvider.authenticate(authentication);
}
#Override
public boolean supports(Class<?> authentication) {
return adAuthProvider.supports(authentication);
}
}
In the mapper I extended LdapUserDetailsMapper implementing only mapUserFromContext method.
public class ADCustomUserDetailsContextMapper extends LdapUserDetailsMapper {
#Autowired
private UserDetailsService userDetailsService; // ... the service used for DB authentication
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx,
String username, Collection<? extends GrantedAuthority> authorities) {
return userDetailsService.loadUserByUsername(username);
}
}
(I'll probably need to implement mapUserToContext method beacuse I'm using a custom UserDetails implementation that not extends LdapUserDetails, so the reverse convertion process could throw an exception...)
NOTE This way I'm repeating the same query (to Users table) two times... I'd like to find a way to make a single query and share the result among AuthenticationProvider and UserDetailsContextMapper.e result among AuthenticationProvider and UserDetailsContextMapper.
If the user is not the user database, it should be assigned "no access" in the authorization process. So it doesn't really matter if he's authenticated or not, the application won't allow him to access because he's not authorized to the application.

Restrict user accessing Multiple pages in Web Application Using Java

In our Java based web application we have the concept of Roles and Users. Basically we have many links in our page, and these links can be accessed based on the Role assigned to the user.The Roles and User related information is stored in DB.
During the User Login time we query the DB, get all the related roles and display only the links that are assigned to his Roles. So the user can see only the links he has access.
This way is Working Very Weel. But every time user login the program checks his role level from database.this is burden to database. Please suggest another ways we prevent the accessing multiple links in Web Application.
2 options:
1) Put the user roles in the session when he is authenticated for the first time and access the session everytime he tries to access a functionality
2) Use a Map<String, List<Role>> as a cache and access this map instead of the Database for authenticating a user. Key of the map can be the username. When the user logs in, add the user to the map. When the user logs out or the session expires, remove from the map. Spring Security uses Ehcache for caching so you could do the same.
public void doLogin(HttpSevletRequest request) {
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = service.authenticate(username, password);
HttpSession session = request.getSession();
session.setAttribute("roles", user.getRoles());
}
public void save(HttpServletRequest request) {
List<Role> roles = request.getSession().getAttribute("roles");
for(Role role : roles) {
if(role.getName()=="save") {
service.save();
break;
}
}
}
A trivial example added. For the cache, it'll be a similar except instead of session use Map.

Spring security openId support and user deauthentication

I am trying to handle a situation when after an successful authentication with openId provider I discover that there is no account in my db associated with user openId identifier.
Can you tell me how should I handle the situation. Now, I am displaying register form and ask a user for creating an account. However, I have a problem with user authentication status, he is now being seen as authenticated by spring SecurityContext class.
How do I deauthenticate user in my controller action before redirecting to ''register new user page''? Is this approach a good one or should I do it in some other way?
Ok, so separating authentication from authorization as was mentioned in Samuel's post was really helpful. However there are still many gotchas and I found deauthentication still a must because there is no easy way in spring to add to user new roles. So the easiest way is to force user to login again and let spring handle role assignment during login.
In order to deauthenticate user in spring security you have to invoke:
SecurityContextHolder.clearContext();
as an alternative you can throw an exception in your UserDetailsService implementation (see below). It has the downside that you would deauthenticate user and lose user context data so it would be impossible to match new user accout with openid account during process of creating new local account. And you have to match those account after user login with traditional username and password. My solution was to deauthenticate user just after creating new account.
In order to grant user roles(privileges) you have to override UserDetailsService, in case someone find this useful here is my implementation:
public final class MyUserDetailsService implements UserDetailsService {
private final UsersDao usersDao;
#Autowired
public UserDetailsServiceImpl(final UsersDao usersDao) {
this.usersDao = usersDao;
}
#Override
public UserDetails loadUserByUsername(final String username) {
UserEntity user = usersDao.getUserByOpenIdIdentifier(username);
if (user == null) {
// there is no such user in our db, we could here throw
// an Exception instead then the user would also be deuthenticated
return new User(username, "", new ArrayList<GrantedAuthority>());
}
//here we are granting to users roles based on values from db
final Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(user.getUserType().toString()));
final UserDetails result = new User(username, "", authorities);
return result;
}
}
I think that you might be mixing two concepts: authentication and authorization. Authentication is knowing who the user is, authorization is the right to use access a resource of a feature.
In spring security, this two concepts are implemented by the authentication-manager and the access-decision-manager.
The fact that a user does not exist in your database is not a reason to deny him is identity: no deauthentication! But beeing authenticated can be a criterion in the access decision management. Example: the AuthenticatedVoter.
You should not touch at the authentication, but customize the access-decision-manager to apply the following rules:
A user who exists in your database has access to everything except account creation feature
A user who doesn't exist in your database has access only to the account creation feature.
This is all about access management, not authentication.
Read more at http://static.springsource.org/spring-security/site/docs/3.0.x/reference/ns-config.html#ns-access-manager
PS: The documentation is not exhaustive in spring security, but the source code is very readable. My advice is to check it out and look at the implementation of the elements you need to customize.

Categories