Redis caches the principal object issue - java

While using the redis, I'm not able to change the user's property since Principal object is cached.
I have a service which is for changing the user's property. To do that, I've created a CustomUserDetails class and implemented the UserDetails interface. CustomUserDetails class have 2 fields; propertyid, currentPropertyid(transient). See the implementation:
public class CustomUserDetails implements UserDetails {
private Long propertyid;
private Long currentPropertyid;
public Long getPropertyid() {
if(getCurrentPropertyid() != null){
return getCurrentPropertyid();
}
return propertyid;
}
public void setPropertyid(Long propertyid) {
this.propertyid = propertyid;
}
#Transient
public Long getCurrentPropertyid() {
return currentPropertyid;
}
public void setCurrentPropertyid(Long propertyid) {
this.currentPropertyid = propertyid;
}
}
Service implementation:
#PutMapping(value="change/property")
public void changeProperty(Long propertyid) {
CustomUserDetails user = ((CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal())
user.setCurrentPropertyid(propertyid);
}
So, basically service's aim is to change propertyid to see the other property's data.
This is working fine, but when I enable the redis, it is not working. Some how redis caches the Principal object. I didn't add here my redis implementation, because, in my redis implementation, I don't have any implementation that caches the UserDetails object. In any case, I've comment out the RedisTemplates that I implemented to project, but redis is not disabled, again same problem.
Any idea to handle this problem?

I figure out that redis caches principal object by OAuth2AccessToken. So that, I needed to override the OAuth2AccessToken. Here is my updated service method;
#Autowired
#Qualifier("customRedisTokenStore")
CustomRedisTokenStore mCustomRedisTokenStore;
#PutMapping(value="change/property")
public void changeProperty(Long propertyid) {
CustomUserDetails user = ((CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal())
user.setCurrentPropertyid(propertyid);
OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) SecurityContextHolder.getContext().getAuthentication();
OAuth2AccessToken accessToken = mCustomRedisTokenStore.getAccessToken(oAuth2Authentication);
mCustomRedisTokenStore.storeAccessToken(accessToken, oAuth2Authentication);
}

Related

Best practice for custom Principal in java (jersey)

I have a java jersey application where I use SecurityContext. I have created my own implementation of Principal which contains additional user information (id, ..). My problem is that I always have to cast either Principal or SecurityContext to my own custom implementation. Am I doing it wrong, or is this just how you go around this?
Update:
My code looks like:
class User implements Principal {
...
public String getId() {
return id;
}
public String getEmplyeeId() {
return employeeId;
}
}
class SecurityContextImpl implements SecurityContext {
Principal getPrincipal() {
return new User();
}
}

Conditional property serialization with Jackson and Jersey

I'm unsuccessfully trying to conditionally and dynamically pick which property to serialize to respond to each request with Jersey (using Jackson). The idea behind this is to securely access to properties of objects within a REST API.
I have several objects that I return in API calls that should show/hide fields depending in the user who is authenticated.
For example, lets say I have an object Car
public class Car implements Serializable {
private Long id;
private String VIN;
private String color;
...
}
Lets say that if an user with the ROLE_ADMIN is authenticated, all properties should be returned, but if there isn't a logged user only the first two need to be shown.
I was thinking on building something that's annotation based. Something like:
public class Car implements Serializable {
private Long id;
private String VIN;
#Secured({AccessRole.ROLE_ADMIN})
private String color;
...
}
In this case, the color property should only be returned if the access role of the requesting user matches the ones passed via the annotation.
But I'm unable to get a hook on where should I implement this logic.
What I'm trying to implement is a sort of #JsonIgnore but that's conditional and dynamic. All solutions I found so far are static.
Is this even possible?
Jersey has support for Entity Filtering. Aside from general filtering, it also supports Role-based Entity Filtering using (javax.annotation.security) annotations.
So you can use the #RolesAllowed, #PermitAll, and #DenyAll annotations on the domain model properties
public static class Model {
private String secured;
#RolesAllowed({"ADMIN"})
public String getSecured() { return this.secured; }
}
To make this work though, you need to have set the SecurityContext inside of a request filter. Jersey will look up the SecurityContext to validate the roles. You can read more about it in this post (Note: the entity filtering is separate from any real authorization that is mentioned in that post. But the post does explain about the SecurityContext).
Basically you will have something like (notice the last line where you set the SecurityContext).
#PreMatching
public static class SimpleAuthFilter implements ContainerRequestFilter {
private static final Map<String, User> userStore = new ConcurrentHashMap<>();
static {
userStore.put("peeskillet", new User("peeskillet", Arrays.asList("ADMIN", "USER")));
userStore.put("paulski", new User("paulski", Arrays.asList("USER")));
}
#Override
public void filter(ContainerRequestContext request) throws IOException {
final String authHeader = request.getHeaderString("Authorization");
final String username = authHeader.split("=")[1];
final User user = userStore.get(username);
if (user == null) {
throw new NotAuthorizedException("No good.");
}
request.setSecurityContext(new SimpleSecurityContext(user));
}
}
Where the SimpleSecurityContext is just a class of your own, where you need to override the isUserInRole method and check if the user has the role
private static class SimpleSecurityContext implements SecurityContext {
private final User user;
SimpleSecurityContext(User user) {
this.user = user;
}
#Override
public Principal getUserPrincipal() {
return new Principal() {
#Override
public String getName() {
return user.getUsername();
}
};
}
#Override
public boolean isUserInRole(String role) {
return user.getRoles().contains(role);
}
#Override
public boolean isSecure() {
return false;
}
#Override
public String getAuthenticationScheme() {
return "simple";
}
}
That's pretty much it. You will also need to register the SecurityEntityFilteringFeature with the application to make it all work.
See a complete test case in this Gist
You can register a custom MessageBodyWriter https://jersey.java.net/documentation/latest/user-guide.html#d0e6951
The MessageBodyWriter will use your custom logic to decide what to write.
It can be done with #JsonView as #dnault suggested.
http://www.baeldung.com/jackson-json-view-annotation
Your MessageBodyWriter will hold a jackson mapper and you will apply the writerWithView with the matching view class as described in the above link.
EDIT: see this one - Jackson Json serialization: exclude property respect to the role of the logged user

Jax RS Authorization

I have an existing code at a class which is extended from javax.ws.rs.core.Application
...
Context childContext = component.getContext().createChildContext();
JaxRsApplication application = new JaxRsApplication(childContext);
application.add(this);
application.setStatusService(new ErrorStatusService());
childContext.getAttributes().put("My Server", this);
...
ChallengeAuthenticator challengeGuard = new ChallengeAuthenticator(null, ChallengeScheme.HTTP_BASIC, "REST API Realm");
//Create in-memory users with roles
MemoryRealm realm = new MemoryRealm();
User user = new User("user", "user");
realm.getUsers().add(user);
realm.map(user, Role.get(null, "user"));
User owner = new User("admin", "admin");
realm.getUsers().add(owner);
realm.map(owner, Role.get(null, "admin"));
//Attach verifier to check authentication and enroler to determine roles
challengeGuard.setVerifier(realm.getVerifier());
challengeGuard.setEnroler(realm.getEnroler());
challengeGuard.setNext(application);
// Attach the application with HTTP basic authentication security
component.getDefaultHost().attach(challengeGuard);
I don't have a web.xml at my code. I would like to add authorization to my code. This: https://restlet.com/technical-resources/restlet-framework/guide/2.3/core/security/authorization does not apply to me since I don't have restlet resources.
How can I implement jax rs authorization into my code?
EDIT 1: Existing code uses restlet JAX-RS extension: https://restlet.com/technical-resources/restlet-framework/guide/2.2/extensions/jaxrs
I've tried that at my jax-rs resource class:
#GET
#Path("/")
public String getStatus() {
if (!securityContext.isUserInRole("admin")) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
...
}
However, it throws 403 even I log in with admin user.
EDIT 2:
When I check here: https://restlet.com/technical-resources/restlet-framework/guide/2.2/extensions/jaxrs There is a piece of code:
this.setRoleChecker(...); // if needed
This may solve my issue but I don't know how to set a role checker.
PS: I use jersey 1.9 and restlet 2.2.3.
It's not really clear (at least to me :-) ) what you are trying to achieve.
If you have a class which is a subclass of javax.ws.rs.core.Application, you should be able to simply add #RolesAllowed("user") as an annotation to your resource classes, as shown in https://jersey.java.net/documentation/latest/security.html
#Path("/")
#PermitAll
public class Resource {
#RolesAllowed("user")
#GET
public String get() { return "GET"; }
#RolesAllowed("admin")
#POST
public String post(String content) { return content; }
#Path("sub")
public SubResource getSubResource() {
return new SubResource();
}
}
Accessing that resource should prompt you for your credentials. If that doesn't work, then you need to provide a small code sample, which compiles and doesn't do what you want it to do. Then it's easier to see where the problem is and what needs to be done to make it work
I could make it work like that:
Application class:
...
application.setRoles(getRoles(application));
...
public static List<Role> getRoles(JaxRsApplication application) {
List<Role> roles = new ArrayList<>();
for (AuthorizationRoleEnum authorizationRole : AuthorizationRoleEnum.values()) {
roles.add(new Role(application, authorizationRole.toString()));
}
return roles;
}
...
Authorization enum:
public enum AuthorizationRoleEnum {
USER("user"),
ADMIN("admin");
private final String value;
AuthorizationRoleEnum(String value) {
this.value = value;
}
#Override
public String toString() {
return value;
}
}
At my resource classes:
...
#Context
SecurityContext securityContext;
...
allowOnlyAdmin(securityContext);
...
public void allowOnlyAdmin(SecurityContext securityContext) {
if (securityContext.getAuthenticationScheme() != null
&& !securityContext.isUserInRole(AuthorizationRoleEnum.ADMIN.toString())) {
throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
.entity("User does not have required " + AuthorizationRoleEnum.ADMIN + " role!").build());
}
}
...
You need to implement your RoleChecker using this interface.
As the doc says:
Because the Restlet API does not support its own mechanism for role checks (as e.g. the Servlet API), you must use this inteface if you need role checks in a JAX-RS application.
This interface is used to check, if a user is in a role. Implementations must be thread save.
so as an example of implementation you can do smth like this:
public class MyRoleChecker implements RoleChecker {
public boolean isInRole(Principal principal, String role) {
return principal.getRole().equals(role);
}
}
Edited:
On the other hand as you use the new API, you need to implement SecurityContext and inject it using #Context in your resource methods.
Then you fetch roles list from the storage by username. The storage implementation is up to you. Please refer to this example
#Priority(Priorities.AUTHENTICATION)
public class AuthFilterWithCustomSecurityContext implements ContainerRequestFilter {
#Context
UriInfo uriInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String authHeaderVal = requestContext.getHeaderString("Auth-Token");
String subject = validateToken(authHeaderVal); //execute custom authentication
if (subject!=null) {
final SecurityContext securityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return new Principal() {
#Override
public String getName() {
return subject;
}
};
}
#Override
public boolean isUserInRole(String role) {
List<Role> roles = findUserRoles(subject);
return roles.contains(role);
}
#Override
public boolean isSecure() {
return uriInfo.getAbsolutePath().toString().startsWith("https");
}
#Override
public String getAuthenticationScheme() {
return "Token-Based-Auth-Scheme";
}
});
}
}
}

Custom authorities with LDAP authentication

I’ve found few Spring XML Configuration examples for logging in with LDAP and configuring the authorities of the logged in user with the help of a custom method and not through LDAP.
Unfortunately, I could not find any Spring Boot example with annotations.
In our case, there is a central LDAP repository in which the usernames and passwords of the users are stored, but the groups of the users are not stored there.
I appreciate any example or reference.
Thank you in advance.
Below you can find our final implementation at the project.
Basic flow is:
a)Check the user id and roles during the authentication. If user is not defined or does not have the roles for the application, do not authenticate the user.
b)if user pass the database check, continue with ldap authentication.
c)Merge the roles coming from database with ldap to be used during the application.
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter implements InitializingBean {
...
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(this.ldapAndDatabaseAuthenticationProvider());
}
#Bean(name="ldapAuthenticationProvider")
public AuthenticationProvider ldapAndDatabaseAuthenticationProvider(){
LdapUserDetailsMapper userDetailsMapper = new LdapUserDetailsMapper();
userDetailsMapper.setRoleAttributes(new String[]{"groupMembership"});
LdapAndDatabaseAuthenticationProvider provider =
new LdapAndDatabaseAuthenticationProvider(this.ldapAuthenticator(), this.ldapAuthoritiesPopulator());
provider.setUserDetailsContextMapper(userDetailsMapper);
return provider;
}
#Bean( name = "ldapAuthoritiesPopulator" )
public LdapAndDatabaseAuthoritiesPopulator ldapAuthoritiesPopulator(){
return new LdapAndDatabaseAuthoritiesPopulator(this.contextSource(), "");
}
#Bean( name = "ldapAuthenticator" )
public LdapAuthenticator ldapAuthenticator() {
BindAuthenticator authenticator = new BindAuthenticator( this.contextSource() );
authenticator.setUserDnPatterns(new String[]{"cn={0},ou=prod,o=COMP"});
return authenticator;
}
#Bean( name = "contextSource" )
public DefaultSpringSecurityContextSource contextSource() {
DefaultSpringSecurityContextSource contextSource =
new DefaultSpringSecurityContextSource( ldapUrl );
return contextSource;
}
Here is how additional roles populator (LdapAndDatabaseAuthoritiesPopulator ) implemented.
public class LdapAndDatabaseAuthoritiesPopulator extends DefaultLdapAuthoritiesPopulator{
public LdapAndDatabaseAuthoritiesPopulator(ContextSource contextSource, String groupSearchBase) {
super(contextSource, groupSearchBase);
}
protected Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user,
String username) {
Set<GrantedAuthority> mappedAuthorities = new HashSet<GrantedAuthority>();
/* Add additional roles from other sources for this user*/
/* below add is just an example of how to add a role */
mappedAuthorities.add(
new GrantedAuthority() {
private static final long serialVersionUID = 3618700057662135367L;
#Override
public String getAuthority() {
return "ROLE_MYAPP_USER"; //this is just a temporary role we are adding as example. get the roles from database.
}
#Override
public String toString(){
return this.getAuthority();
}
});
for (GrantedAuthority granted : mappedAuthorities) {
log.debug("Authority : {}", granted.getAuthority().toString());
}
return mappedAuthorities;
}
}
Below is how the Custom Ldap authentication provider (LdapAndDatabaseAuthenticationProvider) implemented to check if user has required roles defined in the database to access the application. If user is not in the database or roles are missing, authentication will throw account DisabledException.
franDays also suggested to use "Custom Authentication Provider". I would like to give him a credit.
public class LdapAndDatabaseAuthenticationProvider extends LdapAuthenticationProvider{
public LdapAndDatabaseAuthenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authoritiesPopulator) {
super(authenticator, authoritiesPopulator);
}
#Override
protected DirContextOperations doAuthentication(
UsernamePasswordAuthenticationToken authentication) {
log.debug("Checking if user <{}> is defined at database to use this application.", authentication.getName());
// Here is the part we need to check in the database if user has required role to log into the application.
// After check if user has the role, do nothing, otherwise throw exception like below example.
boolean canUserAuthenticate = isActiveUserExist(authentication.getName());
log.debug("canUserAuthenticate: {}", canUserAuthenticate);
if (!canUserAuthenticate)
throw new DisabledException("User does not have access to Application!");
return super.doAuthentication(authentication);
}
private boolean isActiveUserExist(String userId) {
// Do your logic here are return boolean value...
}
You can implement your own AuthenticationProvider. The authenticate method would query an LdapTemplate and upon successful attempt then query the groups from wherever they are stored. It could look like below:
public class CustomAuthenticationProvider implements AuthenticationProvider {
private LdapTemplate ldapTemplate;
private UserRepository userRepository;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = (String) authentication.getPrincipal();
boolean success = ldapTemplate.authenticate(...);
if (!success) {
throw new BadCredentialsException("Wrong username or password");
}
User user = userRepository.findByUsername(username);
if (user == null) {
throw new BadCredentialsException("Username not known by the application");
}
return new CustomAuthentication(username, user.getRoles());
}
}
I omitted the initialization of the LdapTemplate because it depends on the specifics of your case. Same thing for the Authentication object you return for which you would need to implement a class and allow a way to build an instance by passing the username and password.
If you need guidance on how to register your auth provider using java config, this post might help: Custom Authentication provider with Spring Security and Java Config

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.

Categories

Resources