Cookie based authorization before calling controllers method in Spring - java

Recently I came accross this issue. Let's say that I want to give different authorizations to different methods within my controller. I would like to do this using the cookie that was send with the request and to do that I would like to use annotations. I tried to figure out how force #PreAuthorize annotation to work but I am missing something here.
Does anyone know if it's possible scenario (pseudo-code)
#Controller
class myController
{
#SomeAuthorizationAnnotation
#RequestMapping("/")
void doSth()
{
//something is done after authorization
}
}
And now i'd like to write my custom method which grants me access to http request sent to this controller where i could let's say write something like
HttpServletRequest ht = ....
Optional<Cookie> cookie = getCookie(ht.getCookies(), AUTH_TOKEN);
if (cookie.isPresent()) {
//grant authorization for the method call
}
I hope that this is somewhat understandable.
P.S I don't want to bind filters or sth to url's, i want to bind it to methods.

In way to secure your methods you can use #Secured or #PreAuthorize annotations but also you should declare it <global-method-security secured-annotations="enabled" />
See also method security

Well, my solution to this problem was by simply using Authentication Providers within Spring Security and implement interface of UserDetailsService to match my demands according to this project. So then my sessions are automatically handled without necessity for handling Cookies myself. And of course, annotations work.

Related

Testing a Spring controller secured with Keycloak

I am trying to write a few tests for my Spring controller. The endpoints are secured with Keycloak (open id connect).
I tried mocking an authenticated user using the #WithMockUser annotation but I need to retrieve claims from the token (preferred_username) and I end up getting a null pointer exception from here:
return Long.parseLong(((KeycloakPrincipal) authentication.getPrincipal()).getKeycloakSecurityContext().getToken().getPreferredUsername());
Is there any way to mock the Keycloak token? I came across this similar question but I do not want to use the suggested external library.
Thank you guys in advance, any help would be greatly appreciated as I have been stuck on this problem for a while.
I came across this similar question but I do not want to use the suggested external library.
Well, you'd better reconsider that.
Are you using the deprecated Keycloak adapters?
If yes, and if you still don't want to use spring-addons-keycloak, you'll have to manualy populate test security context with a KeycloakAuthenticationToken instance or mock:
#Test
public void test() {
final var principal = mock(Principal.class);
when(principal.getName()).thenReturn("user");
final var account = mock(OidcKeycloakAccount.class);
when(account.getRoles()).thenReturn(Set.of("offline_access", "uma_authorization"));
when(account.getPrincipal()).thenReturn(principal);
final var authentication = mock(KeycloakAuthenticationToken.class);
when(authentication.getAccount()).thenReturn(account);
// post(...).with(authentication(authentication))
// limits to testing secured #Controller with MockMvc
// I prefer to set security context directly instead:
SecurityContextHolder.getContext().setAuthentication(authentication);
//TODO: invoque mockmvc to test #Controller or test any other type of #Component as usual
}
You'll soon understand why this #WithMockKeycloakAuth was created.
If you already migrated to something else than Keycloak adapters, solution with manualy setting test-security context still applies, just adapt the Authentication instance. If your authentication type is JwtAuthenticationToken, you can use either:
jwt() request post processor for MockMvc I wrote (it is available from spring-security-test)
#Test
void testWithPostProcessor() throws Exception {
mockMvc.perform(get("/greet").with(jwt().jwt(jwt -> {
jwt.claim("preferred_username", "Tonton Pirate");
}).authorities(List.of(new SimpleGrantedAuthority("NICE_GUY"), new SimpleGrantedAuthority("AUTHOR")))))
.andExpect(status().isOk())
.andExpect(content().string("Hi Tonton Pirate! You are granted with: [NICE_GUY, AUTHOR]."));
}
#WithMockJwtAuth, same author, different lib
#Test
#WithMockJwtAuth(authorities = { "NICE_GUY", "AUTHOR" }, claims = #OpenIdClaims(preferredUsername = "Tonton Pirate"))
void testWithPostProcessor() throws Exception {
mockMvc.perform(get("/greet"))
.andExpect(status().isOk())
.andExpect(content().string("Hi Tonton Pirate! You are granted with: [NICE_GUY, AUTHOR]."));
}
Note that only second option will work if you want to unit-test a secured #Component that is not a #Controller (a #Service or #Repository for instance).
My two cent advices:
drop Keycloak adapters now: it will disapear soon, is not adapted to boot 2.7+ (web-security config should not extend WebSecurityConfigurerAdapter any more) and is way too adherent to Keycloak. Just have a look at this tutorial to see how easy it can be to configure and unit-test a JWT resource-server (with identities issued by Keycloak or any other OIDC authorization-server)
if your team does not let you abandon Keycloak adapters yet, use #WithMockKeycloakAuth, you'll save tones of time and your test code will be way more readable.

CAS redirect to URL on succesfull login

Here a solution is described to handle redirects to a custom URL based on a condition via use of AccessStrategy.
This however is part of the unauthorized login logical flow therefore results into a still not-logged in user arriving at the end url we redirect to. (via getUnauthorizedUrl)
If we want to redirect the user based on a condition, say via injecting an action to the webflow, how can we manipulate the return URL to be changed into a custom one?
WebUtils.getService(requestContext) include getters of the source/originalUrl but no obvious way to set/manipulate said value through an action bean.
p.s. Currently using CAS version 5.3.x
Responses for normal web applications from CAS are built using WebApplicationServiceResponseBuilder.
If you examine this block you will find that the final response is built using WebApplicationServiceResponseBuilder bean. It is only created conditionally, if an existing bean is not already found in the context by the same name. So to provide your own, you just need to register a bean with the same name using your own #Configuration class.
#Bean
public ResponseBuilder<WebApplicationService> webApplicationServiceResponseBuilder() {
return new MyOwnWebApplicationServiceResponseBuilder(...);
}
...and then proceed to design your own MyOwnWebApplicationServiceResponseBuilder, perhaps even by extending WebApplicationServiceResponseBuilder and overriding what you need where necessary to build the final redirect logic conditionally.
To learn about how #Configuration classes work in general, you can:
Review this post
or this post
or consult the documentation for Spring and/or Spring Boot.

how can i convince spring 4.2 to pass OPTIONS request through to the controller

we have are using spring mvc with #RestController annotations on our controllers, and we're handling authorization in the controller. we use the same code to set the allowed methods in responses to CORS pre-flight request. to achieve this, we have:
<init-param>
<param-name>dispatchOptionsRequest</param-name>
<param-value>true</param-value>
</init-param>
in the configuration of the dispatcher servlet, and then we have:
#RequestMapping(value="/some/collections", method=RequestMethod.OPTIONS)
public void collectionOptions(
HttpServletRequest req,
HttpServletResponse res) {
List<RequestMethod> methods = new ArrayList<>();
// check actual permissions, add the appropriate methods
CORS.setAllowedMethodHeaders(res,methods);
}
we also have an interceptor that do basic checks on CORS pre-flight to see if the origin can possibly have any permissions at all.
we do it like this mostly because permissions for some requests actually depend on #RequestParams, i.e.:
OPTIONS /api/collections?userId=122
might be allowed if you have administrative privileges OR you actually the user with the ID 122. also, we have API keys, so
OPTIONS /api/collections?userId=122&apiKey=ABC
might be OK for one origin, but not for another one.
this works fine, but spring 4.2 now decides wether or not it handles an OPTIONS request, via a call to:
CorsUtils.isCorsRequest(request);
in AbstractHandlerMapping and then returns
HandlerInterceptor[] interceptors = chain.getInterceptors();
chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
instead of the HandlerMethod ...
what we would need is some way to tell spring to let the controller handle OPTIONS requests, no matter what preflight request handlers are present.
We don't seem to be able to find a point where we can EITHER tell the built-in CORS handling to be quiet or to configure some subclass somewhere that would allow us to bypass the newly added code in:
AbstractHandlerMapping.getHandler(HSR request)
Is this in any way possible? Wouldn't it be nice for a feature like this to be quiet until I actively enable it (through WebMvcConfigurerAdapter or via those #CrossOrigin annotations)?
-------- EDIT -------------
the HTTP standard says the following about the OPTIONS method:
The OPTIONS method represents a request for information about the communication options available on the request/response chain identified by the Request-URI. This method allows the client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval.
thinking beyong just CORS, i think that intercepting a CORS options call although a corresponding method is mapped on the controller is not the right way to go. yes, CORS is one thing you can do with an OPTIONS call. but it is by no means the only one.
if there is nothing mapped and if a handler method is mapped with a different request method and the #CrossOrigin annotation, the assumptions that i want the built in CORS support to be triggered would be fine, but i do not think that any request that has the origin header set should automatically ONLY go to the CORS handlers.
I just convinced Spring 4.3 to pass CORS preflights to my controller by adding a custom handler mapping:
public class CorsNoopHandlerMapping extends RequestMappingHandlerMapping {
public CorsNoopHandlerMapping() {
setOrder(0); // Make it override the default handler mapping.
}
#Override
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, CorsConfiguration config) {
return chain; // Return the same chain it uses for everything else.
}
}
Note: you'll still need to tell Spring to dispatch OPTIONS requests to your controller to begin with - that is, set dispatchOptionsRequest to true in dispatcherServlet like it says in this question.
WHY IT WORKS
Sébastien's above answer suggests using your own CorsProcessor. As far as I can tell, this will still not use your controller as the handler; it will just pass a different CorsProcessor to its own CORS handler.
By default, it looks like the method AbstractHandlerMapping#getCorsHandlerExecutionChain will throw out your controller when it detects a preflight. Instead of using your controller as the handler, it instantiates a new PreFlightHandler and uses that instead. See Spring source code. This is the problematic line:
chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
What it's doing here is rebuilding the execution chain with a PreFlightHandler instead of your controller. This is not what we want, so we can override it to return the input chain.
As suggested by zeroflagl, I also think that this is not a good idea to mix access control and CORS. And you should keep in mind that only preflight CORS requests are OPTIONS ones.
If you need to customize Spring CORS handling, you can use AbstractHandlerMapping#setCorsProcessor() to provide your own implementation that could eventually extend DefaultCorsProcessor.
Please notice that by default, CORS processing is enabled but no remote origin is allowed so customizing CorsProcessor is rarely needed. More info on this blog post.
Your access control check could be done as a regular HandlerInterceptor since a successful CORS preflight request will be followed by an actual request.

How to ensure the user is logged into the system at the controller level?

I'm using spring MVC, and I have a custom authentication/security system that I had to build.
NOTE: I know of spring security, but my requirements were to do this in a custom way so please not looking for suggestions about using spring's security modules.
When the user logs into the system, it creates a session cookie. When the user visits a page, a interceptor looks for the existance of that cookie, and looks up the session guid in mysql and if it is present that it loads some data and stores it in the request's attributes.
Now for pages where the user has to be logged in, how can I restrict access at the controller level?
I could do this in an interceptor:
if url.contains("projects/") ...
If I want to restrict access to only logged in users in the ProjectController, but this isn't really something I want to do.
But I am looking for maybe a annotation I could add at the controller level, or maybe somehow create a BaseController that all controllers that require a loggedin user will inherit from.
What are my options for something like this?
In ASP.NET, I created a baseController, and the controller has an event cycle, and in the before-action fired event I checked to see if the user was logged in.
So looking for suggestions for spring mvc?
Update
For example, in ASP.NET you have 2 methods, 1 that fires just before the controller's action method and one that fires after:
Controller.OnActionExecuting
Controller.OnActionExecuted
http://msdn.microsoft.com/en-us/library/system.web.mvc.controller.onactionexecuting.aspx
So in the OnActionExecuting, I can actually see exactly which controller I am in, and which action is about to get called in a programatic way, not by looking at the request URL and then doing string compares to see if it is a particular controller etc.
So in this event, I can simply check for things in cookies or in my request attributes etc.
This is a much more stable way to do it, does spring have anything similiar?
If you need this at the controller level, you could:
1) declare a java.security.Principal parameter in the controller method signature, which Spring will fill in with a Principal object, or
2) implement a PermissionEvaluator, which can be called on a controller method using the #PreAuthorize annotation, and which would have access to a Authentication object.
Similar to what you did in ASP.NET, you can take advantage of OncePerRequestFilter and chain it to the chain of filters you have in web.xml or Spring application context. The good point about this filter is that it's independent of the MVC approach that you take and no need for a "base controller".
On the other hand, if you're also using Spring security module, you can use a custom filter configuration and place it in the correct place that it should be.
If the check fails, then you'd probably want to raise exceptions or redirect user to the correct navigation.
Based on the last comment, you can also use mapped interceptors:
<mvc:interceptors>
<mvc:interceptor>
<mapping path="/myFirstPath/*"/>
<mapping path="/mySecondPath/*"/>
<bean class="org.example.SomeInteceptor" />
</mvc:interceptor>
<mvc:interceptor>another one</mvc:interceptor>
</mvc:interceptors>

Securing Methods with Spring Security

For our current project, we are integrating JSF and the Spring Framework. I'd like to use Spring Security to handle authentication and authorization. So far, I have implemented a custom PasswordEncoder and AccessDecisionVoter which are working fine. Now I'm trying to secure methods using the #Secured annotation (among others) but I can't get that to work as I would expect it to do.
It seems that the #Secured annotation works for bean methods called directly from the JSF layer, only. Here's a simplified example:
#Named("foobarBean")
#Scope("access")
public class FoobarBean
{
#Secured("PERMISSION_TWO")
public void dummy()
{
}
#Secured("PERMISSION_ONE")
public String save()
{
dummy();
}
}
The method save() is called from the JSF layer like this:
<h:commandButton id="save" action="#{foobarBean.save}" />
Our AccessDecisionVoter is then asked to vote on PERMISSION_ONE but not on PERMISSION_TWO. Is this working as designed (I hope not) or am I doing something wrong (what could that be?).
I'd post more code or config but I'm not sure which part is relevant, and I don't want to clutter this post.
It is a simple problem of Proxy AOP! If you use Proxy AOP for Security, then the Proxy can only intercept calles that go through the proxy. If one method invoke an other method of the same bean directly, then there is no proxy that can intercept this call. -- And this is the reason why only the the Security Annotation of save() is taken in account.
One solution would be using AspectJ AOP instead of Proxy AOP. (It is supported by Spring (Security) too.)
Yes, That is how the AccessDecisionVoter works. It takes all roles allowed on a resource(method in your case) and vote for those roles form the current authenticated user's role. If the Role is matched, then only the permission is granted.
In your case also, the only Role defined for the save method is PERMISSION_ONE so the security system will check against this role only. If logged in user has that role, this method will be executed.

Categories