Spring Security #PreAuthorize - Restrict certain roles by using Spring EL - spring

Using Spring Security 3.1.3.RELEASE
So if there are a list of roles (over 10) and there is a need to block just ONE from accessing a Spring Controller method. Can this be done using Spring Expression Language, and avoid listing each and very accepted role?
For example, by including the Not sign.
#PreAuthorize("!hasRole('ROLE_FREE_USER')")
over listing all the roles like this
#PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_PAID_USER','ROLE_PREM_USER',...)
I've looked at the documentation over here: http://static.springsource.org/spring-security/site/docs/3.0.x/reference/el-access.html
But there seems to be nothing on the NOT EQUAL to cases. Anyone face similar issue?

I'm pretty sure that NOT-sign (!) is supported in Spring Expression Language (SPEL). Naturally, it returns a boolean result.
An Example from the official documentation:
// evaluates to false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

Spring Expression Language didn't work for me in this case. Initially I tried with the following,
#RequestMapping("/post/edit")
#PreAuthorize("hasRole('ROLE_OWNER') AND !hasRole('ROLE_ADMIN')")
public String editPost(Model model, Principal principal, HttpServletRequest request, #RequestParam("postId") String postId) {
}
However, I had to recheck the Role from inside the method and redirect the page in case user has Admin Privileges.
#RequestMapping("/post/edit")
#PreAuthorize("hasRole('ROLE_OWNER')")
public String editPost(Model model, Principal principal, HttpServletRequest request{
//Admin Users does not have access to post edit page
if (request.isUserInRole("ROLE_ADMIN")) {
return TemplateNamesConstants.POST_WALL;
}
}
Do update this thread in case you found a better/alternate solution.

Related

Spring security match Principal info with request param

So I'm trying to validate my endpoint without IFs and such things, only with PreFilter and I basically have the following
#GetMapping(value = "/books/{userId}")
#PreFilter("hasRole('ADMIN') or principal.userId == pathVar.userId")
public List<Books> getBooks(#PathVariable("userId" String userId) {
//do sth
}
My problem is getting the PreFilter to work, or actually to match the userId from the authenticated principal with the path variable so that each user can request only his own books.
Yes I know that I can remove the path variable and just do principal.getUserId() inside the method, but that means that I will need to have an if user is admin.
Okay after quite a few tries I have finally found out how to do it. In case anyone is interested:
#PreAuthorize("hasRole('ADMIN') or #userId == principal.userId")
This way if you are the admin you can get the books of any user, and if you are a user you can get only your own.

How to get Swagger UI to display similar Spring Boot REST endpoints?

I have a controller class with two endpoints
#RestController
#RequestMapping
public class TestController {
#RequestMapping(
value= "/test",
method = RequestMethod.GET)
#ResponseBody
public String getTest() {
return "test without params";
}
#RequestMapping(
value= "/test",
params = {"param"},
method = RequestMethod.GET)
#ResponseBody
public String getTest(#PathParam("param") int param) {
return "test with param";
}
}
One has a parameter, one doesn't, and the both work.
If I use curl or a web browser to hit the endpoints
http://localhost:8081/test
returns
test without params
and
http://localhost:8081/test?param=1
returns
test with param
but the swagger ui only shows the one without a parameter.
If I change the value in the request mapping for the request with a parameter to
#RequestMapping(
value= "/testbyparam",
params = {"param"},
method = RequestMethod.GET)
Swagger UI displays both endpoints correctly, but I'd rather not define my endpoints based on what swagger will or won't display.
Is there any way for me to get swagger ui to properly display endpoints with matching values, but different parameters?
Edit for Clarification:
The endpoints work perfectly fine; /test and /test?param=1 both work perfectly, the issue is that swagger-ui won't display them.
I would like for swagger ui to display the endpoints I have defined, but if it can't, then I'll just have to live with swagger-ui missing some of my endpoints.
Edit with reference:
The people answering here: Proper REST formatted URL with date ranges
explicitly say not to seperate the query string with a slash
They also said "There shouldn't be a slash before the query string."
The issue is in your Request Mapping, The second method declaration is overriding the first method. As Resource Mapping value is same.
Try changing the second method to below. As you want to give input in QueryParam rather than path variable, you should use #RequestParam not #PathParam.
Note that you have to give /test/, In order to tell Spring that your mapping is not ambiguous. Hope it helps.
#RequestMapping(
value= "/test/",
method = RequestMethod.GET)
#ResponseBody
public String getTest (#RequestParam("param") int param) {
return "test with param"+param;
}
Upon reading clarifications, the issue here is that swagger-ui is doing the correct thing.
You have two controller endpoints, but they are for the same RESOURCE /test that takes a set of optional query parameters.
Effectively, all mapped controller endpoints that have the same method (GET) and request mapping (/test) represent a single logical resource. GET operation on the test resource, and a set of optional parameters which may affect the results of invoking that operation.
The fact that you've implemented this as two separate controller endpoints is an implementation detail and does not change the fact that there is a single /test resource that can be operated upon.
What would be the benefit to consumers of your API by listing this as two separate endpoints in swagger UI vs a single endpoint with optional parameters? Perhaps it could constrain the set of allowed valid query parameters (if you set ?foo you MUST set &bar) but this can also be done in descriptive text, and is a much more standard approach. Personally, I am unfamiliar with any publicly documented api that distinguishes multiple operations for the same resource differentiated by query params.
As per Open API Specification 3
OpenAPI defines a unique operation as a combination of a path and an
HTTP method. This means that two GET or two POST methods for the same
path are not allowed – even if they have different parameters
(parameters have no effect on uniqueness).
Reference - https://swagger.io/docs/specification/paths-and-operations/
This was also raised as an issue but it was closed because OAS3 doesn't allow that -
https://github.com/springdoc/springdoc-openapi/issues/859
Try including the param in the path as below.
#GetMapping("/test/{param}")
public String getTest(#PathVariable final int param) {
return "test with param";
}
I'm unclear exactly what you're attempting to do, but I'll give two solutions:
If you want to have PATH parameters e.g. GET /test & GET /test/123 you can do:
#GetMapping("/test")
public String getTest() {
return "test without params";
}
#GetMapping("test/{param}")
public String getTest(#PathVariable("param") int param) {
return "test with param";
}
If you want query parameters (GET /test and GET /test?param=123) then you need a single endpoint that takes an optional parameter:
#GetMapping("test")
public String getTest(#RequestParam("param") Integer param) {
if(param == null) {
return "test without params";
} else {
return "test with param";
}
}

How can a session variable be read in a Spring annotation expression, specifically #PreAuthorize

I'm using the Spring Security #PreAuthorize annotation and it works when I use "known good" expressions that I've seen in the documentation or in other online examples. The example code isn't a real use case it's contrived.
The following expressions are all variations of ones that worked on a method or methods similar in format to the example method below them. These expressions aren't being used at the same time or even the same method they're just displayed here together for simplicity.
#PreAuthorize("hasRole('ROLE_ADMIN')")
#PreAuthorize("hasAuthority('PERM_XYZ') or authentication.principal.id == #ownerId")
#PreAuthorize("hasRole('ROLE_USER') and #someValue == 'testValue'")
public List<Item> getSomeItems(Integer ownerId, String someValue ) {
// code goes here
}
What I would like to be able to do is test a method argument against a session variable in the expression like this:
#PreAuthorize("hasAuthority('PERM_XYZ') or #someValue == session.someOtherValue")
public List<Item> getSomeItems(Integer someValue) {
// code goes here
}
I would have thought that accessing the session in an expression would be a basic task but I haven't found a single example online or even anyone asking how to do it.
I've tried all of the following and many more but they all generate exceptions:
#PreAuthorize("#someValue == #session.someValue")
#PreAuthorize("#someValue == session.someValue")
#PreAuthorize("#someValue == session.getAttribute('someValue')")
#PreAuthorize("#someValue == request.session.someValue")
#PreAuthorize("#someValue == request.session.getAttribute('someValue')")
Everything above is related to Spring Security and the #PreAuthorize annotation but those things really aren't central to question.
I'm aware of many alternatives to accessing the session and have already solved my use case but I'm still wondering if it's possible to access the session via expression in any annotation.
So... can the user session be accessed in a Spring annotation expression and if so how? Thanks.
The spring EL expression for the current is #session.
So you could use
#PreAuthorize("#userId == session.userId")
But this session is the current HttpSession and it has no property userId. According to spring el access you can use authentication or principal
Try
#PreAuthorize("#userId == principal.username")
presuming that the username is the userId...

How to store PreRequestFilter information in AuthUserSession

I am building a web service using ServiceStack which has to support multiple vendors. The web service provides largely the same functionality to all vendors with some exceptions here and there.
In order to re-use as much functionality as possible I have come up with the following URL scheme:
http://localhost/brand1/templates
http://localhost/brand2/templates
"brand1" and "brand2" are not services but "templates" is. The Templates service's request DTO's will have a property called "Brand" like so:
[Route("/{Brand}/templates", "GET")]
public class GetTemplates
{
public Brand Brand { get; set; }
}
So in the Templates service I know which brand I am dealing with. This scheme works well.
What I cannot figure out though is this. The user of the service has to be authenticated and I cannot figure out how to handle the redirection of the service after the user has been authenticated since I have to pass along the brand information. I have created my own CustomAuthProvider class that inherits CredentialsAuthProvider. In the TryAuthenticate method I can set the authService.GetSession().ReferrerUrl property to the correct brand if I know what it was.
The only way I have found so far to get this information is to register a PreRequestFilter. My thinking here was that since the URL (e.g. http://localhost/brand1/templates) contains the brand I can store it in my own AuthUserSession class. I can't figure out how to do this. I have a "SessionFactory" method that I pass to the AuthFeature constructor. But what should I do in there? How do I get to the brand that I've obtained in the PreRequestFilter? Is it safe to store it in a field of the AppHost? I think not because of concurrency issues. How do I tie the PreRequestFilter to the SessionFactory method?
I hope I am explaining my problem clearly enough?
I was overthinking the solution because I didn't realize that I had all the information I needed in the IServiceBase parameter of the TryAuthenticate method of the CredentialsAuthProvider class.
In the end I came to the following solution:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
var session = authService.GetSession();
var origQuery = authService.Request.UrlReferrer.Query;
session.ReferrerUrl = "/error";
var queryString = origQuery.Substring(10); // strip "redirect="
var decodedUrl = HttpUtility.UrlDecode(queryString);
if (!string.IsNullOrWhiteSpace(decodedUrl))
{
var query = new Uri(decodedUrl);
session.ReferrerUrl = query.AbsolutePath;
}
return DoAuthentication(userName, password);
}
}
The different places where you can set the Url to redirect to during ServiceStack Authentication, in order of precedence are:
The Session.ReferrerUrl Url if it's populated
The Continue QueryString, FormData param when making the request to /auth (i.e Authenticate.Continue property)
The HTTP Referer HTTP Header
The CallbackUrl of the current AuthProvider used

Handle url pattern that has user name as part of the url

Suppose I have 3 url patterns that needs to be handled by Spring MVC as follows:
1) www.example.com/login (to login page)
2) www.example.com/home (to my home page)
3) www.example.com/john (to user's home page)
I would like to know what is the best practice way of handling the url pattern that has username as part of the url (real world example is facebook fanpage www.faceboo.com/{fanpage-name})
I have come up with my own solution but not sure if this is the clean way or possible to do it.
In my approach, I need to intercept the request before it being passed to Spring MVC's dispatchservlet, then query the database to convert username to userid and change the request URI to the pattern that Spring MVC can recognize like www.example/user/userId=45.
But I am not sure if this is doable since the ServletAPI does not have the setter method
for requestURI(it does have the getter method for requestURI)
Or if you have a better solution please share with me. Thank in advance :-)
Spring MVC should be able to handle this just fine with PathVariables.
One handler for /login, one handler for /home, and one handler for /{userName}. Within the username handler you can do the lookup to get the user. Something like this:
#RequestMapping(value="/login", method=RequestMethod.GET)
public String getLoginPage() {
// Assuming your view resolver will resolve this to your jsp or whatever view
return "login";
}
#RequestMapping(value="/home", method=RequestMethod.GET)
public String getHomePage() {
return "home";
}
#RequestMapping(value="/{userName}", method=RequestMethod.GET)
public ModelAndView getUserPage( #PathVariable() String userName ) {
// do stuff here to look up the user and populate the model
// return the Model and View with the view pointing to your user page
}

Resources