Dropwizard JWT workflow - java

Design pattern help:
I have a dropwizard application that implements JWT for authentication. I have this working in the most basic form.
I have a User class which has all sorts of details about a user (email, name, settings etc)
However I've been advised that when I authenticate the user I should be returning a Principal rather than the actual user. Here is my method that does this:
#Override
public Optional<Principal> authenticate(JsonWebToken token) throws AuthenticationException {
final User user = userCollection.findOneById(token.claim().subject());
Principal principal = new Principal() {
#Override
public String getName() {
return user.getUsername();
}
};
return Optional.of(principal);
}
I have the users stored in a mongoDB collection, so using the ID from the token I can retrieve that specific user and assign that users name to a new principal object and return that principal.
This means in my endpoints that require authentication, I can use properties from that Principal for example:
#POST
#Path("project-create")
public Response setProject(#Auth Principal principal, #Valid Project project) {
[...]
project.setAuthorName(principal.getName());
[...]
}
This is all working, but what if I wanted to access other properties of the user which don't exist on the Principal?
Should I be using the name value of the principal to lookup the user in the database and retrieve that user each time I want to use one of the users properties?
Is there a massive flaw in what I've done? I don't have much experience on this front and find it hard to find real world examples around this.
Would appreciate some guidance around what sort of pattern/workflow I should be following.

The Authenticator generic type is Athenticator<C, P extends Principal>, so you can make your User implement Principal and make your Authenticator something like
public class JWTAuthenicator extends Authenticator<JsonWebToken, User> {
#Override
public Optional<User> authenticate(JsonWebToken token) {}
}
Then build the JWTAuthFilter with the User generic type argument
new JWTAuthFilter.Builder<User>()
.setAuthenticator(new JWTAuthenticator())
.setEverythingElse(...)
.buildAuthFilter();

Related

How can I add some additional fields into a JWT token generated by this Spring Boot service?

I am working on a Spring Boot microservice that generate a JWT token and I have the following doubt about the possibility to add additional information into the generated token.
Basically in my code I have done something like this (it works fine):
#Override
public UserDetails loadUserByUsername(String UserId) throws UsernameNotFoundException {
String ErrMsg = "";
if (UserId == null || UserId.length() < 2) {
ErrMsg = "Nome utente assente o non valido";
logger.warn(ErrMsg);
throw new UsernameNotFoundException(ErrMsg);
}
User user = this.GetHttpValue(UserId);
if (user == null) {
ErrMsg = String.format("User %s not found!!", UserId);
logger.warn(ErrMsg);
throw new UsernameNotFoundException(ErrMsg);
}
UserBuilder builder = null;
builder = org.springframework.security.core.userdetails.User.withUsername(user.getEMail());
builder.password(user.getPswd());
String[] operations = user.getUserTypes().stream()
.map(UserType::getOperations)
.flatMap(Set::stream)
.map(Operation::getName)
.distinct()
.toArray(String[]::new);
builder.authorities(operations);
return builder.build();
}
As you can see in the previous code I have this method returning a UserDetails object (belonging to Spring Security, it is this class: org.springframework.security.core.userdetails.UserDetails). This UserDetails instance was created starting from this retrieved model object (it was retrieved performing a call to a REST endpoint):
User user = this.GetHttpValue(UserId);
Basically this UserDetails object is used to generate the JWT token by this second method that uses the previous UserDetails object:
private String doGenerateToken(Map<String, Object> claims, UserDetails userDetails)
{
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.claim("authorities", userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret().getBytes())
.compact();
}
NOTE: the Jwts is io.jsonwebtoken.Jwts.
This generate a token containing the following payload information:
{
"sub": "xxx#gmail.com",
"exp": 1637412048,
"iat": 1637325648,
"authorities": [
"ADMIN"
]
}
It is correct but...is it possible to add some further information to this payload? In particular I need to add some information that are into my User DTO object but not into the previous Spring Security UserDetails instance.
I implemented this behavior following an Udemy tutorial and now I have the following doubt.
Why the previous doGenerateToken() method generate the token starting from the Spring Security UserDetails instance and not directly from the User DTO object (it contains more usefull information).
If there are some specific reason to use this UserDetails instance instead my simple User DTO object, exist a way to add these information to the UserDetails instance and then put these additional fields into my JWT token?
You are passing a map of claims to the doGenerateToken method. These are then added to the token payload as claims. You can add whatever you like to this map to create the token.
As for the question why the doGenerateToken method doesn't use the User DTO? Well, if that method is not part of an interface, then you can have it accept any class, right? If you prefer to work with the User object, then I think you can. If it's an implementation of an interface, then you should ask the authors what was the reason for that, but people rather have thought through those interfaces in Spring and there are good reasons why they are built this way or another. Even if at first they look weird.

Getting the current logged in user name when using Spring Security

Its a coupon system app using Spring security, spring MVC,
now.
when the app starts, I need to somehow initialize the current logged in user into the controller.
Issue is:
If I try to get the current user via SecurityContextHolder it is impossible because it seems like spring is initializing the controllers before the security so I cannot get it in the controller.
Is there anything I'm missing? a different approach of getting the current logged in user after he logs in?
What you need is called #AuthenticationPrincipal.
You can inject it in controller method like this:
#GetMapping("/")
public void get(#AuthinticationPrincipal User user){ ... }
Here is documentation
Alternatively, you can create your own annotation and custom argument resolver, and inject whatever you want.
Solution 1: Principal principal
#RequestMapping(value = {"/", ""})
public String start(Principal principal, Model model) {
String currentUser = principal.getName();
return currentUser;
}
Solution 2: Authentication authentication
#RequestMapping(value = {"/", ""})
public String currentUserName(Authentication authentication) {
return authentication.getName();
}
Solution 3: SecurityContextHolder
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
More Here

Spring security role-based authorisation best practices

So I basically have a controller method with a PreAuthorize annotation. By default the method will return all projects. The method signature also includes an optional query string (blank query means retrieve all records).
The issue is that if the logged in user is only supposed to view/manage his/her own records, the query string needs to include a filter in it such as "clientId:2".
Now to do that, I was thinking of using the Principal object to retrieve the logged in user and check if he/she is a client as well, then I update the query by adding the required filter to it.
I am just not sure if this is the best approach for this type of issues.
#PreAuthorize("hasAuthority('MANAGE_ALL') OR hasAuthority('VIEW_ALL') OR hasAuthority('MANAGE_OWN')")
#RequestMapping(value = "/projects", method = RequestMethod.GET)
public ResponseEntity<RestResponse> list(Principal principal, #RequestParam(value = "query", required = false, defaultValue = "") String query) {
//If a client is logged in, he/she will have the MANAGE_OWN authority so will need to update the query string to include clientId:<logged-in-client-id>
I would rather move the #PreAuthorize to an application service.
class SomeApplicationService {
UserService userService;
SecurityService securityService;
#PreAuthorize("hasAuthority('MANAGE_ALL') OR hasAuthority('VIEW_ALL') OR hasAuthority('MANAGE_OWN')")
public List<Project> getProjects(String clientId) {
User currentUser = userService.getLoggedInUser();
if(securityService.canManageAllProjects(currentUser))
//get all projects or projects of clientId
else if(securityService.canManageOwnProjects(currentUser))
//get own projects, ignore clientId
}
}

Spring security - Limiting access to my update profile page

I am using spring security in my application and ran into a problem. My application has an Update Profile page. I have added preAuthorized() with request mapping as
#PreAuthorize("isAuthenticated()")
#RequestMapping (value="/user/{uid}/profile/update", method = GET)
public String updateProfileView(#ModelAttribute("form") UserProfileForm form, #PathVariable ("uid") Integer userId, Model model){
It works fine, and unauthenticated user can not access this page.
But the issue is that every Authenticated User can access this page.
For example : User A logged in into application, he/she will be able to update every one's profile.
My CustomUserDetailService class is
#Service
#Transactional
public class CustomUserDetailsService implements UserDetailsService {
#Resource
UserService userService;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
com.analyst.future.domain.User user = userService.getUser(email);
SimpleGrantedAuthority auth = new SimpleGrantedAuthority("ROLE_USER");
Collection<SimpleGrantedAuthority> authorities = new HashSet<SimpleGrantedAuthority>();
authorities.add(auth);
User userDeatails = new User(user.getEmail(), user.getPassword(), authorities);
return userDeatails;
}
}
I don't think i can restrict it with roles as every authenticated user will have same roles.
Is there any way i can restrict Authenticated user to access only self update profile page.
I am no Spring Security expert, but try reading up on using Expression-Based Access - Link here
There is one tiny little line that matches what you want -
For example, if you wanted a particular method to only allow access to a user whose username matched that of the contact, you could write
#PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);
I think in your case it would be something like
#PreAuthorize("email == authentication.email")
This is method level though, so maybe not what you are looking for? Good news is that there is a way to use the logged in user and match it against the request user. :)
Since all previous answers talk about matching username (which is included in the principal object) but you need to match the userID, this needs a little more work. We first need to return a custom User object that extends UserDetails. Here is how you can do that:
Step 1: Make your 'User' model implement UserDetails
#Entity
public class UserAccount implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonProperty("id")
private int userId;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String email;
private String password;
// other fields and methods
}
You will also have to override some methods from UserDetails, which is easy to figure out.
Step 2: Make you User Service implement UserDetailsService
#Component
public class UserAccountService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) {
// todo
}
}
Step 3: Return YOUR user model from the loadUserByUsername method
#Override
public UserDetails loadUserByUsername(String username) {
Optional<UserAccount> userAccount = userAccountDao.findByEmail(username);
if (!userAccount.isPresent()) {
throw new MyException("User account not found for the given Username.", HttpStatus.NOT_FOUND);
}
return userAccount.get();
}
Step 4: Add the #PreAuthorize expression to your resource
#PutMapping(value = "/{userId}")
#PreAuthorize("#userId == authentication.principal.userId or hasAuthority('ADMIN')")
public UserAccount updateUserAccount(#PathVariable("userId") int userId,
#RequestBody UserAccount userAccount) {
return userAccountService.updateUserAccount(userId, userAccount);
}
Important things to notice above are:
We can check if the userId is equal above, only because our custom UserAccount model has a userId. If we had returned a raw UserDetail (like org.springframework.security.core.userdetails.User) object instead, it will not have any 'id' property to match with, so above will fail.
Above #PreAuthorize annotation checks for 2 conditions. It allows the request if the user is the owner of the Account or if the user has ADMIN authority.

Spring custom AuthenticationProvider for Principal with additional data

I'm fairly new to Spring.
My goal is to have some AuthenticationProvider with a custom authenticate Method that takes an Authenticate object as argument with more than just a user name and password. Let's say a realm name (and there can be 2 users with the same user name when they are in different realms). I already looked for answers to my question, but the simple answers to questions about authentication only explain how to extend AbstractUserDetailsAuthenticationProvider, which doesn't cater my needs because the retrieve method only takes a user name as argument while I need the realm name as well to retrieve the user. The complex ones are about extending or implementing all kinds of different Spring classes and interfaces without the context being explained.
So the simple question is:
How do I have to implement / extend an AuthenticationProvider to be able to read custom data out of an Authentication object?
My call would look like this (yes, I want to get an OAuth2 token):
curl -vX POST http://localhost:9001/oauth/token \
-d "client_id=myId&client_secret=secret&grant_type=password&username=myUser&password=1234&realm=realm3"
Notice the realm=realm3 at the end.
The call without the extra data and with my own sub class of AbstractUserDetailsAuthenticationProvider already works when there's just one realm.
Thanks in advance!
How do I have to implement / extend an AuthenticationProvider to be
able to read custom data out of an Authentication object?
RealmAuthenticationProvider
public class RealmAuthenticationProvider implements AuthenticationProvider {
private RUPAuthenticator rupAuthenticator;
public RealmAuthenticationProvider(RUPAuthenticator rupAuthenticator) {
this.rupAuthenticator = rupAuthenticator;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Object principal = authentication.getPrincipal();
Object credentials = authentication.getCredentials();
Object realm = authentication.getDetails();
if (rupAuthenticator.authenticate(principal, credentials, realm)) {
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER")); //use any GrantedAuthorities you need
return new RealmAuthenticationToken(principal, credentials, realm, grantedAuths);
};
return null;
}
#Override
public boolean supports(Class<?> authentication) {
return RealmAuthenticationToken.class.isAssignableFrom(authentication);
}
}
RealmAuthenticationToken
public class RealmAuthenticationToken extends UsernamePasswordAuthenticationToken {
private Object realm;
public RealmAuthenticationToken(Object principal, Object credentials, Object realm, Collection<? extends GrantedAuthority> authorities) {
super(principal,credentials, authorities);
this.realm = realm;
}
}
RUPAuthenticator
public interface RUPAuthenticator {
boolean authenticate(Object username, Object password, Object realm);
}
You would just have to provide an implementation for RUPAuthenticator that states whether the username, password, realm combinations are correct.
And then register the custom AuthenticationProvider (RealmAuthenticationProvider) as a bean.
Below is an example of an authentication provider that accepts requests from a specific user:
#Bean
public AuthenticationManager authenticationManager() {
List<AuthenticationProvider> providers = new ArrayList<AuthenticationProvider>();
providers.add(new RealmAuthenticationProvider(new RUPAuthenticator() {
#Override
public boolean authenticate(Object username, Object password, Object realm) {
return (username.equals("sa") && password.equals("sa") && realm.equals("realm2"));
}
}));
return new ProviderManager(providers);
}
I hope this is what you were looking for.

Categories

Resources