AuthenticationProvider & Bean #PostConstruct ordering - spring

For a login page, I have an authentication method as:
#Component(value = "customSpringAuthentication")
public class CustomSpringAuthentication implements AuthenticationProvider {
#SuppressWarnings({ "serial", "deprecation" })
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
...
return authUser;
}
}
Also I have a bean, which is called after authentication:
#Component(value = "loggedinUserBean")
#Scope("session")
public class LoggedinUserBean {
private AuthUser authUser;
private boolean isAdminUser = false;
#PostConstruct
public void initModel() {
....
authUser = (AuthUser) SecurityContextHolder.getContext().getAuthentication();
....
}
}
My question is when I am trying to access "authUser" in initmodel()method, it is null.
I know that authenticate method did not return null. But somewhat I realized that initmodel() works few miliseconds before authenticate returns. So that it can't get authetication object properly. How can I ensure/define ordering that without authenticate() returns loggedinuser is not initalized?

To actually answer your question, the initModel() method will be run immediately after your LoggedinUserBean object is created. This doesn't mean that its initialised. Initialisation and creation are two separate things. When an object is created, the JVM allocates memory for that object and all its fields. All the fields are null because they don't have values but the memory is set aside for you to populate the fields. Initialisation means that authUser will not be null because you have given it a value and you will only give it a value when authenticate() is called. As long as you aren't setting authUser anywhere else, then you are fine.
TL;DR: Its already guaranteed by Spring Security that your authUser is null until the user successfully authenticates using one of your authentication providers. If authUser remains null after then entire springSecurityFilterChain has been tried then the login attempt has failed.

Related

Spring Session: Update Logged in user object in redis immediately

I have two web applications (A and B).
The first web application (A) is used as a reverse proxy using spring-cloud.
I'm using spring-session to store the sessions in a redis database for both applications.
The problem
When I modify a field (e.g name) of the current (logged in) user, the current logged in user object is not updated immediately and as a result, when I'm trying to retrieve current logged in user in a next call (via #AuthenticationPrincipal) I get a non-updated user object.
My custom user details object:
public class CustomUserDetails extends my.package.User implements org.springframework.security.core.userdetails.UserDetails, java.io.Serializable {
// ...
}
How can I update the current user object immediately?
Recently I've had the similar issue and I resolved it in the following manner:
1.Created a custom Authentication class
public class MyCustomAuthentication implements Authentication {
private UserDetails userDetails;
public MyCustomAuthentication(UserDetails userDetails) {
this.userDetails = userDetails;
}
...
#Override
public Object getDetails() { return userDetails; }
#Override
public Object getPrincipal() { return userDetails; }
#Override
public boolean isAuthenticated() { return true; }
...
}
update userDetails object with some fresh data (I guess, 'name' in your case)
Set new authentication created from userDetails in SecurityContextHolder
SecurityContextHolder.getContext().setAuthentication(new MyCustomAuthentication(userDetails));
Hope you will find that helpful.

Springboot Security hasRole not working

I’m unable to use hasRole method in #PreAuthorize annotation. Also request.isUserInRole(“ADMIN”) gives false. What am I missing?
Although .hasAuthority(“ADMIN”) works fine.
I am assigning authorities to the users from a database.
You have to name your authority with prefix ROLE_ to use isUserInRole, see Spring Security Reference:
The HttpServletRequest.isUserInRole(String) will determine if SecurityContextHolder.getContext().getAuthentication().getAuthorities() contains a GrantedAuthority with the role passed into isUserInRole(String). Typically users should not pass in the "ROLE_" prefix into this method since it is added automatically. For example, if you want to determine if the current user has the authority "ROLE_ADMIN", you could use the following:
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
Same for hasRole (also hasAnyRole), see Spring Security Reference:
Returns true if the current principal has the specified role. By default if the supplied role does not start with 'ROLE_' it will be added. This can be customized by modifying the defaultRolePrefix on DefaultWebSecurityExpressionHandler.
See also Spring Security Reference:
46.3.3 What does "ROLE_" mean and why do I need it on my role names?
Spring Security has a voter-based architecture which means that an access decision is made by a series of AccessDecisionVoters. The voters act on the "configuration attributes" which are specified for a secured resource (such as a method invocation). With this approach, not all attributes may be relevant to all voters and a voter needs to know when it should ignore an attribute (abstain) and when it should vote to grant or deny access based on the attribute value. The most common voter is the RoleVoter which by default votes whenever it finds an attribute with the "ROLE_" prefix. It makes a simple comparison of the attribute (such as "ROLE_USER") with the names of the authorities which the current user has been assigned. If it finds a match (they have an authority called "ROLE_USER"), it votes to grant access, otherwise it votes to deny access.
I had to improvise a little, maybe there is other ways simpler then mine, but at the time I worked on this I had no other choice but to improvise a bit, after a thorough research came up with this solution.
Spring Security has an interface called AccessDecisionManager, you will need to implement it.
#Component
public class RolesAccessDecisionManager implements AccessDecisionManager {
private final static String AUTHENTICATED = "authenticated";
private final static String PERMIT_ALL = "permitAll";
#Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
collection.forEach(configAttribute -> {
if (!this.supports(configAttribute))
throw new AccessDeniedException("ACCESS DENIED");
});
}
#Override
public boolean supports(ConfigAttribute configAttribute) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
String rolesAsString = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(","));
if (configAttribute.toString().contains(rolesAsString))
return true;
else
return (configAttribute.toString().contains(PERMIT_ALL) || configAttribute.toString().contains(AUTHENTICATED));
}
return true;
}
#Override
public boolean supports(Class<?> aClass) {
return true;
}
}
Now to support this custom access-decision-manager with your security config do this in the security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
// other configs
.accessDecisionManager(this.accessDecisionManager)
accessDecisionManager is the autowired bean of the AccessDecisionManager implementation you've created.
You can use either hasRole() or hasAuthority(). The difference is that, you have to user ROLE_ for hasAusthority() method.
So for the ROLE_ADMIN,
#PreAuthorize("hasRole('ADMIN')") == #PreAuthorize("hasAuthority('ROLE_ADMIN')")

Getting LazyInitializationException within in a #Transactional method

I’m getting this exception when I access this method from my controller:
{"status":"failure","exception":"LazyInitializationException","exceptionMessage":"failed
to lazily initialize a collection of role:
org.mainco.subco.lessonplan.domain.LessonPlan.classrooms, could not
initialize proxy - no Session","errorMessage":"failed to lazily
initialize a collection of role:
org.mainco.subco.lessonplan.domain.LessonPlan.classrooms, could not
initialize proxy - no Session"}
Controller:
#Autowired
private ThirdPartyService m_thirdPartySvc;
…
#RequestMapping(value = "/launch", method = RequestMethod.GET)
#Transactional
public String launchLti(final #RequestParam String assignmentId,
final Model model,
final HttpServletRequest request,
final HttpServletResponse response,
final Principal principal) throws InvalidKeyException, UnsupportedEncodingException, NoSuchAlgorithmException
{
final subcoAuthenticationUser auth = (subcoAuthenticationUser) ((Authentication) principal).getPrincipal();
String nextPage = null;
final User user = m_userSvc.findById(auth.getId());
// Provision the assignment in ThirdParty if not already done so
final Assignment assmt = m_lessonPlanDao.getAssignment(assignmentId);
if (!assmt.isSentToThirdParty())
{
m_thirdPartySvc.sendAssignment(assignmentId);
} // if
Is the #Transactional annotation unnecessary? Especially since I already have it on my #Service class…
#Service
#Transactional
public class ThirdPartyServiceImpl implements ThirdPartyService
{
#Override
public void sendAssignment(final String assignmentId)
{
final Assignment assignment = m_lessonPlanDao.getAssignment(assignmentId);
if (isThirdPartyAssignment(assignment))
{
final String ThirdPartyPromptId = assignment.getTocItem().getThirdPartyPromptId();
// Gather the teacher id
final LessonPlan lessonPlan = m_lessonPlanDao.getLessonPlan(assignment.getLessonPlan().getId());
final String teacherId = lessonPlan.getOwnerId();
// Gather the students who have been assigned this assignment
final List<Classroom> classes = lessonPlan.getClassrooms();
// Send one request for each class assignment
for (final Classroom classroom : classes)
{
The error occurs on the for (final Classroom classroom : classes) line. I have #Transactional everywhere, yet I’m getting this LazyInitializationException. Why? And how do I create a transaction so that I can run my method?
I’m using Spring 3.2.11.RELEASE, Hibernate 4.3.6.Final, and JPA 2.1 on JBoss 7.1.3.Final. If upgrading any of these would solve my problem, let me know.
The #Transactional boundary is being respected by application during runtime. You can find this out by calling: TransactionSynchronizationManager#isActualTransactionActive()
Add some code to print out the value of above method. If it's false, then maybe you need to make sure the component-scan is set up right.
Example: <context:component-scan base-package="com.application.dao" />
This would totally miss the classes in the com.application.service package.

Spring session based javax constraint validation

What I'm trying to accomplish is to access my Spring Session within a custom constraint
Sample scenario:
Custom constraint #UniqueEmail verifies that the email is not already in use in the system. This validation is performed on the edit endpoint and should be ignore if the user didn't change the email, 1st: because there's no need for it and 2nd: because querying the db for that email will actually return a result, which is the user itself, although there's no way of telling it without accessing the session
This works:
If I use model attributes with custom editor though #InitBinder I can set a property in the to-be-validated bean before the validation occurs like so
#InitBinder(value="myModelObj")
protected void initBinder(WebDataBinder binder, HttpSession session) {
User user = (User) session.getAttribute("user");
binder.registerCustomEditor(User.class, "user", new UidPropertyEditor(user));
}
#RequestMapping(...)
public String updateUser(#Valid #ModelAttribute("myModelObj") MyModelObj form){
...
}
MyModelObj has an attribute which will be replaced with the actual session user. Problems:
There must be a property in the bean to hold the user, even though it is not editable through the web form
The web form must submit this property as well, in my case using an input[type="hidden"] field (user can change it at will, we never trust what the user sends)
This does not work
The new endpoints have to use #RequestBody rather than #ModelAttribute, which means that (afaik) #InitBinder won't work anymore, hence losing access to the session object.
How (if possible) can I access the session from within the custom constraint?
public class EmailIsUniqueStringValidator implements ConstraintValidator<EmailIsUnique, String> {
#Autowired
private UserDAO userDAO;
HttpSession session; //Somehow initialized
#Override
public void boolean isValid(String email, ConstraintValidatorContext context) {
User user = (User) session.getAttribute("user");
if(user.getEmail().equals(email)){
return true; // No need to validate
}
else if(userDAO.emailInUse(email)) {
return false;
}
}
Non-ideal approach:
What I'm doing now is performing the session-dependant validations in the controller manually, which means I have 2 points where validation is performed.
There are some other interesting options in this post too, but if there was a way to access the session...
Thanks in advance
This can be achieved using RequestContextHolder like so:
public class EmailIsUniqueStringValidator implements ConstraintValidator<EmailIsUnique, String> {
#Autowired
private UserDAO userDAO;
HttpSession session;
#Override
public void boolean isValid(String email, ConstraintValidatorContext context) {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
session = attr.getRequest().getSession();
User user = (User) session.getAttribute("user");
if(user.getEmail().equals(email)){
return true; // No need to validate
}
else if(userDAO.emailInUse(email)) {
return false;
}
}

How to integrate spring security and spring social to have the same execution flow in both cases?

I am using spring security for the authentication purposes in my project wherein after successful authentication, I get the principal object inside which the various details are stored.
This principal object is passed to various methods which allow the entries to be reflected in the database against the current user. In short, principal helps me in giving principal.getName() everywhere i need it.
But now when I login through spring social then I do not have principal object of Principal in hand, instead I have implemented MyPrincipal class --->
public class MyPrincipal implements Principal {
public String name;
public boolean flag;
public boolean isflag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public void setName(String name) {
this.name = name;
}
#Override
public String getName() {
return name;
}
}
Then in the social login handler, I am adding the current username and flag value to myPrincipal object, and forwarding the user to the same home page where the spring security forwards in case of normal login.
MyPrincipal myPrincipal = new MyPrincipal();
myPrincipal.name = username;
myPrincipal.socialFlag = true;
modelMap.addAttribute("myPrincipal", myPrincipal);
return new ModelAndView("forward:/home");
Adding this object in session by annotating class with
#SessionAttributes({"myPrincipal"})
Now from here on-wards I want the flow to be handed over to the home page with all the functionality working for the user correctly. But each method is taking Principal principal as argument, just like this -->
#RequestMapping(value = {"/home"}, method = RequestMethod.POST)
#ResponseBody
public ModelAndView test(ModelMap modelMap, Principal principal) {
String name = principal.getName();
}
There are two different things going around in both cases-
Normal login is giving me principal directly but social login is giving me it in session attributes.
I do not want to pass principal as parameters even in case of normal spring security login, instead here also I want to put it in session attribute.
How can I do this and where to make the changes when I have implemented my own authentication provider.
I don't think I fully understand...However, in general it shouldn't be necessary to pass principal instances around. Use org.springframework.security.core.context.SecurityContextHolder.getContext() to get a hold of the context then call SecurityContext.getAuthentication().getPrincipal() or SecurityContext.getAuthentication().getDetails().

Resources