I'm trying to design an application that should expose two global API path:
/ user must be authenticated
/public no authentication
Moreover /public API will offer light version of some / API by displaying less informations that is not authorize if no authentication is provided.
Even if Controller does not contains core function, some of them provide data validation or other check. Thus if I want to create a /public version of a current API I have 4 solutions:
Duplicate code
#Autowired / controller and use method call
forward request (I can't redirect because security filter will be applied)
Create Controller that manage both / and /public API
Is there any good practice or pattern for my scenario?
IMHO, Best way to solve this problem is by using 4th solution.
1st solution: First rule of computer science is you do not duplicate your code.
2nd solution: calling controller from another controller is a serious design flaw.
3rd solution: could have been a solution but ruled out by you.
4th solution: IMHO best one in your case
class MyController{
#RequestMapping("/getData")
public ResponseObject getData(#RequestBody SomeDTO dto){
Validator.validate(dto);
return myService.getData(dto);
}
#RequestMapping("/public/getData")
public ResponseObject getPublicData(#RequestBody SomeDTO dto){
Validator.validate(dto);
return myService.getPublicData(dto);
}
}
Filter data in your service layer.
It's possible to be achieved with Spring Security.
First, you will need to enable this URL to be called without security, like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
/** some security code **/
http
.authorizeRequests().antMatchers("/resources/**","/public/**").permitAll().anyRequest().authenticated().and()
/** other stuffs **/
}
#RestController
#RequestMapping(value="/public")
public class PublicRestController {
#Autowired private DataRepository data;
#RequestMapping(value = "/data/",method = RequestMethod.GET)
public Model getModelData(){
/** Do what you need here **/
}
}
So, all you have to do is build a REST Controller to match your URL and you are done.
And as you suggested, use #Autowired to expose only the code that you need. So you can put all your login on service/component beans and serve them as needed.
Related
as the title suggests, I have configured security in my Spring WebFlux application by using #EnableWebFluxSecurity and #EnableReactiveMethodSecurity.
I am using RouterFunction to handle the request routing. The following code is for the router:
#Component
public class UserServiceRequestRouter {
#Autowired
private UserServiceRequestHandler requestHandler;
#Bean
public RouterFunction<ServerResponse> route() {
//#formatter:off
return RouterFunctions
.route(GET("/user/{userId}"), requestHandler::getUserDetails);
//#formatter:on
}
}
And the request handler is:
#Component
public class UserServiceRequestHandler {
#Autowired
private UserService userService;
#PreAuthorize("#userServiceRequestAuthorizer.authorizeGetUserDetails(authentication, #request)")
public Mono<ServerResponse> getUserDetails(ServerRequest request) {
//#formatter:off
return userService.getUserDetails(request.pathVariable("userId"))
.convert()
.with(toMono())
.flatMap(
(UserDetails userDetails) -> ServerResponse.ok()
.contentType(APPLICATION_NDJSON)
.body(Mono.just(userDetails), UserDetails.class)
);
//#formatter:on
}
}
Note: The #Autowired UserService is to fetch data from the database in a reactive way.
Next, I have defined a #Component as:
#Component
#SuppressWarnings("unused")
#Qualifier("userServiceRequestAuthorizer")
public class UserServiceRequestAuthorizer {
public boolean authorizeGetUserDetails(JwtAuthenticationToken authentication, ServerRequest request) {
// #formatter:off
if (authentication == null) {
return false;
}
Collection<String> roles = authentication.getAuthorities()
.stream()
.map(Objects::toString)
.collect(Collectors.toSet());
if (roles.contains("Admin")) {
return true;
}
Jwt principal = (Jwt) authentication.getPrincipal();
String subject = principal.getSubject();
String userId = request.pathVariable("userId");
return Objects.equals(subject, userId);
// #formatter:on
}
}
It is notable here that I am using Spring OAuth2 Authorization Server, which is why the parameter authentication is of type JwtAuthenticationToken.
The application is working as per the expectation. But I am wondering if I am doing it the right way, meaning is this the best practice of doing method level Authorization in a reactive way?
The followings are my stack:
JDK 17
org.springframework.boot:3.0.0-M4
org.springframework.security:6.0.0-M6
Any advice you could give would be much appreciated.
Update
As mentioned by M. Deinum in the comment why shouldn't I use hasAuthority("Admin") or principal.subject == #userId, the reason is that the authorization code I provided is merely for demonstration purposes. It can get complicated and even if that complicacy might be managed by SpEL, I would rather not for the sake of simplicity.
Also the question is not about using inline SpEL, it's more about its reactiveness. I don't know if the SpEL mentioned in the #PreAuthorize is reactive! If it is reactive by nature then I can assume any expression mentioned in the #PreAuthorize would be evaluated reactively.
As far as I know, SpEL expressions evaluation is synchronous.
Unless your UserServiceRequestAuthorizer does more than checking access-token claims against static strings or request params and payload, I don't know why this would be an issue: it should be very, very fast.
Of course, if you want to check it against data from DB or a web-service this would be an other story, but I'd say that your design is broken and that this data access should be made once when issuing access-token (and set private claims) rather than once per security evaluation (which can happen several times in a single request).
Side notes
It is notable here that I am using Spring OAuth2 Authorization Server, which is why the parameter authentication is of type JwtAuthenticationToken.
I do not agree with that. It would be the same with any authorization-server (Keycloak, Auth0, Microsoft IdentityServer, ...). You have a JwtAuthenticationToken because you configured a resource-server with a JWT decoder and kept the default JwtAuthenticationConverter. You could configure any AbstractAuthenticationToken instead, as I do in this tutorial.
It can get complicated and even if that complicacy might be managed by SpEL, I would rather not for the sake of simplicity.
I join #M.Deinum point of view, writing your security rules in a service, like you do, makes it far less readable than inlining expressions: hard to guess what is checked while reading the expression => one has to quit current source file, open security service one and read the code.
If you refer to the tutorial already linked above, it is possible to enhance security DSL and write stuff like: #PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('greet')") to stick to your sample, this would give #PreAuthorize("is(#userId) or isAdmin()).
Right now, with Spring Security's HttpSecurity, we're able to restrict wildcard paths to specific roles/authorities:
.mvcMatchers(POST, "/users").hasAuthority("create:users")
.mvcMatchers(PUT, "/users/{id}").hasAuthority("update:users")
is there an easy way to do:
.mvcMatchers(POST, "/{whateverGoesHere}").hasAuthority("create:${whateverGoesHere}")
.mvcMatchers(PUT, "/{whateverGoesHere}/{id}").hasAuthority("update:${whateverGoesHere}")
?
It doesn't have to be a solution using the configure(HttpSecurity http) API specifically, I'm just looking for an easy way to generify authorization rules for multiple REST entities at once.
This is obviously a more advanced scenario, to say the least. However, improvements in Spring Security 5.5 have introduced the new AuthorizationManager interface and the http.authorizeHttpRequests() method for configuring authorization rules that utilize it. See The AuthorizationManager in the reference docs for more info. It is extremely powerful! I believe this is probably the best option for your use case.
There are numerous implementations available in Spring Security that can be used to build composite and/or delegating implementations. Here's an example that uses your convention:
public final class ResourceAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
private final String action;
public ResourceAuthorizationManager(String action) {
this.action = action;
}
#Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
AuthorizationManager<RequestAuthorizationContext> delegate =
AuthorityAuthorizationManager.hasAuthority(createAuthority(context));
return delegate.check(authentication, context);
}
private String createAuthority(RequestAuthorizationContext context) {
String resource = context.getVariables().get("resource");
return String.format("%s:%s", this.action, resource);
}
}
The action can be create, read, update, delete or anything you like as part of your authority string. This implementation relies on URI variables provided through the RequestAuthorizationContext. As it happens, there's an existing implementation (RequestMatcherDelegatingAuthorizationManager) that handles that scenario. It is actually the one handling .mvcMatchers() authorization rules in the Spring Security DSL. Here's an example that uses it to delegate to the convention-based AuthorizationManager above:
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
.mvcMatchers(HttpMethod.POST, "/{resource}").access(new ResourceAuthorizationManager("create"))
.mvcMatchers(HttpMethod.PUT, "/{resource}/{id}").access(new ResourceAuthorizationManager("update"))
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
I think that you should not have hidden behavior in your code. If a developer wants to add a new endpoint and wants to have it require some authority, it should be done intentionally. Otherwise, it could become a debugging nightmare if the dev intends to add an open endpoint and wonders why it is secured.
But you could add a default behavior for all endpoints that you did not specify. That behavior could be to deny access. That way, every developer has to add some kind of access granting entry. That would guarantee that it is not forgotten, but it is still intentionally done.
...
.mvcMatchers(POST, "/users").hasAuthority("create:users")
.mvcMatchers(PUT, "/users/{id}").hasAuthority("update:users")
.anyRequest().denyAll()
Is there an option to specify a request header once in spring web RestController instead of doing it on every request?
e.q.
#RestController("workflowController")
public class MyClass{
public Value list(#RequestHeader(USER_ID_HEADER_PARAM) String user) {
...some code
}
public Workflow create(#RequestBody Workflow workflow, #RequestHeader(USER_ID_HEADER_PARAM) String user) {
... some code
}
}
the #RequestHeader(USER_ID_HEADER_PARAM) will be repeated in every request.
is there a way to specity it in the #RestCotroller level or the class level?
Thanks
Use some kind of filter class that can be configured to wrap around your requests in your servlets based on the URL path.
Here is info about the generic Servlet API filter API:
https://www.oracle.com/technetwork/java/filters-137243.html
If you're using Spring, there's another way to do it:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#filters
https://www.baeldung.com/intercepting-filter-pattern-in-java
I am using Spring 3 and implemented MVC using simpleUrlMapping. I am having CustomerController class. In CustomerController I am having three methods:
View customer
Add customer
Delete customer
The above actions are getting called using method name resolver.
My requirement over here depending upon the logged in user and privilege I want to protect the corresponding method calls.
Delete customer method should be called by the privilege user and not by all the user.
I am using Spring Security as well. Is there any way to protect the delete customer method with Spring security?
options:
#RequestMapping
public void deleteCustomer(HttpServletRequest request) {
if(request.isUserInRole("ROLE_ADMIN"){
// do deletion
};
}
or use #EnableGlobalMethodSecurity
#PreAuthorize("hasRole('ROLE_ADMIN')")
#RequestMapping
public void deleteCustomer(HttpServletRequest request) {
I am trying to do a simple android application that communicates with a Spring server.
I'd like to use Sessions to store data of each logged in User.
My App exchange Json objects with the server and the Request Mapping is like this:
#Controller
public class LoginController {
#Autowired
private IUserDao userDao;
#RequestMapping( value = "/loginJson",method = RequestMethod.POST)
public #ResponseBody loginResponse login(#RequestBody loginModel login) {
loginResponse response=userDao.checkCredentials(login.getUsername(),login.getPassword());
System.out.println("Result="+response.isSuccess());
System.out.println("Received:"+login.getUsername()+" "+login.getPassword());
return response;
}
}
The controller is working fine, but I can't figure out how to store a sessione variable. I found many documents explaining Spring Sessions, but each of them different from the other.
Someone can suggest me some simple way to do this or some kind of good tutorial?
Not sure what you mean by saying Spring session, but you can declare additional HttpSession parameter in your method, and then do whatever you like inside of the method. Is this what you wanted to find out?