Spring Security pass URL parameters to Authentication Provider - java

Is there a way to pass URL parameters to an authentication provider in Spring Security 3?
Our login page will need to receive an email token as a parameter that the authentication system will need to be aware of when it sets the status of the user. Specifically, it will let a user with a correct token log in that would not otherwise be able to.
I have a custom class extending the DaoAuthenticationProvider class. My authentication logic is in that class's authenticate method.
I'm hoping there is some way to pass this data into the authenticate method.

You could inject the HttpServletRequest object on your authentication provider class:
private #Autowired HttpServletRequest request;
Now, you should be able to access the request parameters with APIs such as request.getParameterValues(paramName)

You need to override UsernamePasswordAuthenticationFilter.setDetails() and pass extra information to your custom authentication provider via details property of UsernamePasswordAuthenticationToken.

Related

Different response based on logged user role

In my RestController I have POST method which returns different object based on user role:
#PostMapping("getlocal")
#PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
public ResponseEntity<LocalDto> getLocal(#RequestBody LocalRequest localRequest){
return status(OK).body(localService.findLocalBylocalId(localRequest));
}
Service method:
public LocalDto findLocalBylocalId(LocalRequest localRequest) {
Role role = userRoleRepository.findByUsername(localRequest.getUsername());
if(role == Role.ROLE_ADMIN) //return localDto infromation object for ADMIN
else if(role == Role.ROLE_USER) //return localDto information for USER
}
LocalRequest contains username of current logged in user.
The problem is when user will authenticate and hit this endpoint he can pass to RequestBody admin's username. In this case he will get access to admin resources even if he is logged as USER.
How to avoid this situation and modify the code?
Should I create two different endpoints - one secured only for USER, and one secured only for ADMIN?
Should I pass Principal object instead passing username in POST method as parameter? Can I pass Princpal object if I am using jwt mechanism?
You can access the currently authenticated username by specifying Principal as an argument. For example:
#PostMapping("getlocal")
#PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
public ResponseEntity<LocalDto> getLocal(Principal principal){
return status(OK).body(localService.findLocalBylocalId(principal.getName()));
}
This works because Spring MVC will resolve the Principal argument from the HttpServletRequest.getUserPrincipal() method and Spring Security overrides the method to align with the currently logged in user. You would then need to update your LocalDto to accept a username instead of a Localrequest.
Alternatively, you can also resolve the entire Spring Security Authentication in the same way since the HttpServletRequest.getUserPrincipal() will be an Authentication.
#PostMapping("getlocal")
#PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
public ResponseEntity<LocalDto> getLocal(Authentication authentication){
return status(OK).body(localService.findLocalBylocalId(principal.getName()));
}
This gives you access to the roles without needing to look them up again. The disadvantage of this approach is that you are now relying on Spring Security's API directly.
You can also consider using the #AuthenticationPrincipal annotation to decouple yourself from Spring Security. This approach is the best if you need access to more than just the username because you can still be decoupled from Spring Security, but it also involves more work (i.e for username/password authentication you need a custom UserDetailsService, custom UserDetails). Because the amount of work/variables here, it is difficult to provide more guidance than a link to the documentation without further details.

How does #Secured know what role a user is from database?

I understand what #Secured DOES but not really sure HOW it does it.
Can someone explain how #Secured grabs the roles? Is it getting it from the authorities-by-username-query? Can I put any String in the params as long as it's in the database?
User's roles are stored in SecurityContext, or to be more specific in Authentication object that is stored in the SecurityContext. When you authenticate, the authentication information is loaded and stored in the security context. The roles can originate from database, depending on your configuration. In your case they are loaded using authorities-by-username-query query.
When the security interceptor processes the authorization (for instance method-level authorization using #Secured annotation) it determines whether the user should be able to access it based on the Authentication stored in the context.
To better understand what happens under the hood, you should look at the Spring Security Filter chain and Architecture section of the reference guide.
SpringSecurity provides and awesome AOP way of securing methods in
Java Application by using #Secured. Spring logically ORs the roles
listed in #Secured annotation. The collection of GrantedAuthorities is obtained from SecurityContextHolder.getContext().getAuthentication().getAuthorities()
AbstractSecurityInterceptor is the abstract class that implements
security interception for secure objects.
Obtain the Authentication object from the SecurityContextHolder.
Determine if the request relates to a secured or public invocation
by looking up the secure object request against the SecurityMetadataSource.
For an invocation that is secured (there is a list of
ConfigAttributes for the secure object invocation):
If either the Authentication.isAuthenticated() returns false, or the alwaysReauthenticate is true, authenticate the request against the configured AuthenticationManager. When authenticated, replace the Authentication object on the SecurityContextHolder with the returned value.
Authorize the request against the configured AccessDecisionManager.
Perform any run-as replacement via the configured RunAsManager.
Pass control back to the concrete subclass, which will actually proceed with executing the object. A InterceptorStatusToken is returned so that after the subclass has finished proceeding with execution of the object, its finally clause can ensure the AbstractSecurityInterceptor is re-called and tidies up correctly using finallyInvocation(InterceptorStatusToken).
The concrete subclass will re-call the AbstractSecurityInterceptor via the afterInvocation (InterceptorStatusToken, Object) method.
If the RunAsManager replaced the Authentication object, return the SecurityContextHolder to the object that existed after the call to AuthenticationManager.
If an AfterInvocationManager is defined, invoke the invocation manager and allow it to replace the object due to be returned to the caller.
Look at the source code for more understanding.
AccessDecisionManager is the interface which is implemented as AffirmativeBased, ConsensusBased or UnanimousBased orchestrates the voters and asks each in turn whether the requesting user should be let through the #Secured annotation or denied.

Passing variable from Authenticator to endpoint JAX-RS and RESTlet

I have a custom authenticator (ie. a class that extends Authenticator and does some custom authentication in authenticate(Response res, Request req)) that I then want to pass a value to the function that handles the actual API call. I thought adding an argument like #Context Request request could work but I don't think so.
Anyone know how I can pass an object from Authenticator to resource?
Thanks,
Daniel
jax-rs has a mechanism for that. You can create a custom context that implements SecurityContext. This security context returns your your user object as Principal.
Your authenticator which probably works as a filter can then set the security context of your request. From there on you can access your User object via the SecurityContext.
While not perfect, I have a working piece of code on github that can get you started.

Java SpringSecurity automatic login with HTML GET parameters

I want to make an automatic login into web application, which will be accessible on company's intranet.
The login will function in a way that when user is accessing the application, he will automatically send its credentials (username and password), example (company.com/myapplication/login?user=jhon&pass=123).
How can I implement that instead standard Spring Security login using HTML forms? Maybe using hidden form which will be then filled with GET parameters? I can't find any examples for that scenario.
My part is only after user has sent the link with parameters.
You can do this is two ways (or more). (after our comment, it seams that you need to go with way two, because you do not have a username and password)
First way:
Use the standard spring form login and then modify you application so that is send login request like the normal web form login would do.
Assume you have configured the login-processing-url="/login/j_spring_security_check"
<security:form-login
login-processing-url="/login/j_spring_security_check"
login-page="/login"
authentication-failure-url="/login?login_error=t" />
then send a
HTTP POST to https://yourApplication/login/j_spring_security_check with the two POST parameters
j_username=<login>
j_password=<password>
Second way:
Write your own Authentication Processing filter. That is a class that extends AbstractAuthenticationProcessingFilter and it is responsible for
taking stuff that the user used to authenticate (normally username and password) from the request,
forming some the users authentication token object from them (for example an UsernamePasswordAuthenticationToken)
and invoking AuthenticationManager.authenticate(Authentication authentication) with this authentication token
To register your filter, you only need to add them to the spring security filter chain:
<security:custom-filter ref="yourFilter" after="FORM_LOGIN_FILTER"/>
I recommend to have a view on the UsernamePasswordAuthenticationFitler (and keep in mind that is extends AbstractAuthenticationProcessingFilter) - this will explain it best.
If you do not have an username and password, then you need to create your own token (extends Authentication) and then you have to implement your own AuthenticationProvider (and register with the AuthenticationManager). A AuthenticationProvider is reponsible to
- consume an (special type of) Authentication Token and
- validating that the stuff in the Authentication Token is valid
- creating an OTHER Authentication object with UserDetails and Privileges
- or, if the Authentication Token is not valid, throwing an AuthenticationException
Have a look at AbstractUserDetailsAuthenticationProvider and its subclass, for an example.

Spring - Call custom-authentication-provider from a controller

I have a custom-authentication-provider defined in my Spring Security configuration. This class implements AuthenticationProvider, and I can successfully log in using the form defined on my page. The issue is I want to call this class not just on the login page, but from the registration page as well.
The registration page uses a different command class and collects more information than the login form. Right now, when the user registers, I call the appropriate controller, add the record to the database and they can then log in but they aren't logged in automatically. As they've just given me their user name/password on the registration page, can I then pass this to the custom AuthenticationProvider class so they are also logged in?
I've tried creating an org.springframework.security.Authentication class in the registration controller and calling the authenticate method on my customer AuthenticationProvider class, and this doesn't error out, but the user isn't logged in. Do I have to call a method higher in the Spring Security filter chain to accomplish this? Should I redirect the controller to the j_spring_security_check URL? If so, how would I pass the username/password?
You need to put the result of AuthenticationProvider.authenticate() into SecurityContext (obtained from SecurityContextHolder).
Also be aware of AuthenticationSuccessEvent - if your application rely on this event (some Spring Security features may use it, too), you should publish it (you can obtain the default AuthenticationEventPublisher via autowiring). It may be useful to wrap your authentication provider with ProviderManager, it publishes the event automatically using the given publisher.
The problem you are having is that although you have successfully authenticated the user you have not stored the result of this authentication in the user's SecurityContext. In a web application this is a ThreadLocal object which the SecurityContextPersistenceFilter will use to store the user's credentials in the HTTPSession
You should also avoid authenticating directly with your custom authentication provider if you can. Your xml configuration should contain an AuthenticationManager which your custom authentication provider has been wired into. For example,
<bean id="customAuthenticationProvider"
class="com.foo.CustomAuthenticationProvider">
<property name="accountService" ref="accountService"/>
</bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="customAuthenticationProvider"/>
</security:authentication-manager>
If you wire the authenticationManager into your registration service and authenticate using that it will additionally,
allow you to swap in/out additional authentication providers at later points
publish the authentication result to other parts of the Spring Security framework (eg success/failure Exception handling code)
Our registration service does this as follows
final UsernamePasswordAuthenticationToken authRequest = new
UsernamePasswordAuthenticationToken(username, password);
final Authentication authentication =
authenticationManager.authenticate(authRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
We also optionally store the authentication result at this point in a remember-me cookie using the onLoginSuccess() method of TokenBasedRememberMeServices.

Categories