I have spring boot application which exposes REST endpoints protected by spring security.
I need to restrict access to some paths depending on service call. Let's say I have a service like this:
#Service
public class AccessService {
boolean hasAccess(String requestedPath) {
// some business logic here
}
}
The service will check user roles, some business conditions and return true or false.
Now I need to integrate this service call into my security configuration.
So far I have configuration like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.and().authorizeRequests()
.anyRequest().hasRole("USER");
}
I see no way of adding the service call here (as it is completely static).
What I'm trying:
Currently I'm thinking of overriding my AuthenticationProvider and extending it with the additional functionality.
The other option would be to extend my REST controllers from a class which would do some sort of authorization, but I'm not sure if it is possible.
Question: How can I protect REST endpoints based on service method call? What is the proper way of doing that?
This is explained in the reference guide. Basically you need to use the access expression instead of the hasRole. You can then write powerful security expressions.
Something like the following should do the trick:
anyRequest()
.access("#accessService.hasAccess(request.requestURI) && hasRole('USER')");
This restricts access to user with the role ROLE_USER and which have access according to your own custom logic.
I think a good way to to this is to use #PreAuthorize
Some documentation can be found here: Expression-Based Access Control.
You are also able to add your own evaluator class/methods to customize to your specific needs:
#PreAuthorize("#customPermissionEvaluator.accessMethod(variable)")
Example class:
#Service(value = "customPermissionEvaluator")
public class CustomPermissionEvaluatorImpl implements CustomPermissionEvaluator {
#Override
public boolean accessMethod(int variable) {
if (variable == 1) {
return true;
}
return false;
}
}
Related
I would like to access the http request, specifically auth header in AuthenticationManager.authenticate() context.
Requirement is to authenticate a custom token. There is an external library which does that and so I don't have the luxury to read out principal from the token. Hence, in the custom filter, I am returning the full token in the getPreAuthenticatedPrincipal() method. This seems borderline incorrect and I would like to not pass the token pretending it to be principal.
Is there any way I can get it without violating any framework constraints?
Or is there a better way to handle the scenario which I'm trying to achieve?
Here's the config class:
#Configuration
#EnableWebSecurity(debug = true)
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity httpSecurity) throws Exception{
CustomTokenFilter customTokenFilter = new CustomTokenFilter();
customTokenFilter.setAuthenticationManager(new CustomAuthenticationMgr());
httpSecurity
// csrf etc etc
.addFilter(customTokenFilter)
.authorizeRequests()
.mvcMatchers("/users/**")
.authenticated()
.and()
.authorizeRequests()
.mvcMatchers("/other-api/**")
.permitAll()
.and()
.httpBasic();
}
Here's the custom token filter class:
public class CustomTokenFilter extends AbstractPreAuthenticatedProcessingFilter {
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
String authorization = request.getHeader("authorization");
if(authorization.indexOf("Custom") == 0){
return Map.of("Custom",authorization.split(" ")[1]);
}
return null;
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return "";
}
}
And finally, the custom authentication manager class:
public class CustomAuthenticationMgr implements AuthenticationManager {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Map<String,String> map = (Map) authentication.getPrincipal();
String token = map.get("Custom");
// Custom validation - checking length here just to simplify
if(token.length() > 0)
authentication.setAuthenticated(true);
return authentication;
}
}
Version: Spring Boot 2.6.7 (transitive: spring-core 5.3.19)
Constraints: Cannot upgrade to other versions at the moment
Thanks in advance!
You're right, this isn't a good way to do it. (It's great you noticed -- too few people care whether their code is idiomatic.)
A better way would be to start by writing your own filter that actually just... does the authentication. You can extend OncePerRequestFilter rather than something more specific. That's what Spring Security itself does, both for basic authentication (BasicAuthenticationFilter) and for OAuth bearer tokens (BearerTokenAuthenticationFilter). You may want to take a careful look at the code for BearerTokenAuthenticationFilter since the problem it solves is very similar to yours. (I wouldn't extend it, though, since it's very clearly intended to do OAuth specifically. I wouldn't straight up copy the code either -- it's fairly simple as Spring Security filters go but probably still does more than you need. Try to understand the code instead; that will help a lot with your understanding of Spring Security in general.)
Okay, so you have a filter which looks a lot like BearerTokenAuthenticationFilter. That is, it contains an AuthenticationManager and its doFilter method consists of extracting the token from the request, passing that into the AuthenticationManager and then doing some SecurityContext-related stuff. Except, problem: AuthenticationManager.authenticate() expects an Authentication, not a String, and the token is a String.
The solution is to write a wrapper object for your token which implements Authentication. You can do this a couple of ways. Personally, what I'd do is use two classes: one which you pass into AuthenticationManager.authenticate(), and one which you get back. So we have, say, CustomTokenAuthenticationRequest implements Authentication and CustomTokenAuthentication implements Authentication. Both are immutable.
CustomTokenAuthenticationRequest basically just contains the token; its isAuthenticated() is return false, its getPrincipal() returns the token and its getCredentials() also returns the token. This is essentially what Spring Security itself does with BearerTokenAuthenticationToken.
CustomTokenAuthentication, on the other hand, probably contains a UserDetails of some sort; its isAuthenticated() is return true, its getName() is a username or user id or something, etc.
Now you need to teach the AuthenticationManager to authenticate CustomTokenAuthenticationRequests. The way to do this isn't to implement AuthenticationManager, it's to implement AuthenticationProvider. So you write a class that looks roughly like
public class CustomTokenAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication a) {
String token = ((CustomTokenAuthenticationRequest) a).getToken();
if (/* the token is valid */) {
CustomTokenAuthentication returnValue = // whatever you want it to be
return returnValue;
}
throw new BadCredentialsException("Invalid token");
}
#Override
public boolean supports(Class<?> authClass) {
return authClass == CustomTokenAuthenticationRequest.class;
}
}
Finally, wire it all up. Add the authentication provider to your HttpSecurity using its authenticationProvider() method. (If you do this, and you don't change the default authentication manager configuration, authenticationProvider() results in your authentication provider getting added to an AuthenticationManager which Spring Security configures for you -- an instance of ProviderManager.) Add the filter using addFilterAt(BasicAuthenticationFilter.class). Also, don't call httpBasic() because this adds a BasicAuthenticationFilter which I am guessing you don't want. Or maybe you want basic authentication and also your custom token authentication? But you didn't say that. If you do want both, you'll want to add your filter with addFilterBefore or addFilterAfter, and you need to think about ordering. Generally filter ordering is important in Spring Security.
I glossed over a lot of stuff here, barely gave you any code, and still wrote something of blog post length. Spring Security is very complex, and the thing you're trying to do isn't easily done in an idiomatic manner if you don't have much experience. I highly recommend just reading the Spring Security reference documentation from start to finish before you try implementing any of my suggestions. You'll also need to read quite a lot of Javadoc and tutorials and/or framework code. If there's something specific you want to follow up on I might respond to a comment, but I don't promise it; I had to do some research for this answer and have already spent more time on it than I planned to.
you should look spring-security-lambda-dsl,add filter,add auth provider
I have a REST API written in Spring Boot with Spring Security and OAuth2. The resources are secured this way:
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/v1/security/**").hasRole("ADMIN");
}
I'd like to introduce a new part of the API where the permissions are fine grained, based on projects. Let's consider a simple endpoint that prints the project configuration.
GET /api/v1/project/{projectId}/config
How would I configure the resource server to only allow access for users who have the role ROLE_PROJECT_{projectId}_ADMIN without having to manually specify all projects?
Also if this mechanism has a specific name, please let me know in comments to I can change the question title.
You can use path values in authorization expressions.
According to Path Variables in Web Security Expressions you should write your custom authorization logic.
public class WebSecurity {
public boolean checkUserHasAccessToProjectId(Authentication authentication, int projectId) {
// here you can check if the user has the correct role
// or implement more complex and custom authorization logic if necessary
}
}
Then in your Java security configuration you can refer to this method and pass it the value of the relevant path fragment.
http.authorizeRequests()
.antMatchers("/api/v1/project/{projectId}/config")
.access("#webSecurity.checkUserHasAccessToProjectId(authentication,#projectId)")
...
I have a micro service architecture with spring boot. I decided to add Spring security for each micro service which will authenticate, authorise the user.
So i develop a separate project with has Spring Security authentication.
I have use a Filter which extends AbstractAuthenticationProcessingFilter.
The paths which needs authentication and authorisation are mentioned in my filter class as below,
private AntPathRequestMatcher[] authenticationMatcher = {
new AntPathRequestMatcher("//api/myservice1/**"),
new AntPathRequestMatcher("/api/myservice")
};
private AntPathRequestMatcher[] authorizationMatcher = {
new AntPathRequestMatcher("/api/myservice")
};
So in the filter class doFilter method i check request path and do relevant logics.
My SecurityConfig class configure method just look like below,
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(getMyAuthenticationFilter(), BasicAuthenticationFilter.class);
}
So my questions are,
What approach i should do for introduce this module (project) to each micro service?
What i had in my mind is expose this as a jar file and use it in any micro service. In that case how can i over ride those authenticationMatcher and authorizationMatcher url's which will be specific to each micro services?
Am i declare those url's in correct place and if so what Object Oriented principles i should apply?
Is there a possibility i can by pass authentication filter if required and enable it when required? Like switching it?
I believe this approach can work like you want and can be done using Spring Boot. In regards to your questions:
In your filter class you can declare something like this which can be populated by bean initialization.
#Value("${security.checkValues}")
private String[] checkValues;
Below is an example I used with my own custom filter declared as a bean and passed in to the security configuration. You probably don't need all of it.
#Configuration
#Order(3)
public static class SubscriptionWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/subscribe**","/subscribe/**").and()
.addFilterBefore(applicationSecurityTokenFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.csrf().disable();
}
#Bean
public ApplicationSecurityTokenFilter applicationSecurityTokenFilter() {
return new ApplicationSecurityTokenFilter();
}
}
Then each application.properties instance in your micro services (assuming they are separate projects) will have a line like below to specify the URLs to use.
security.checkValues=/api/myservice,/api/myservice2
If you include an empty string property like this:
security.checkValues=
Then the String array will be set to a 0 length array and you can know that it should not be active. I'm not entirely sure this is what your question was referencing so please review.
Let me know if this is what you are looking for. We can flush it out a little further if necessary.
I am using spring & jersey2 to serve some rest-requests like:
#GET
#Path("/someservice")
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
public String getSomeStuff(...) {
login(...);
// ...
}
During a rest-request, I get an authorized user of the rest-request.
Now I need this user while updating or creating entities like:
#MappedSuperclass
public abstract class PersistentObject {
#PrePersist
#PreUpdate
public void onSaveOrUpdate() {
setCreationUser(...); // How to get the user of this session?
}
// ...
}
How can I get the current user of the rest-request there?
You can try to perform your login operation (for appropriate resource methods) in a ContainerRequestFilter and set SecurityContext:
#Provider
public class SecurityFilter implements ContainerRequestFilter {
#Override
public void filter(final ContainerRequestContext context) throws IOException {
final Principal user = login(...);
context.setSecurityContext(new SecurityContext() {
public Principal getUserPrincipal() {
return user;
}
// Other methods omitted.
});
}
}
Make sure you have jersey-spring3 module on your class-path and the Jersey-Spring integration allows you to inject SecurityContext into a Spring service:
#Service
public MySpringService implements MyService {
#Context
private SecurityContext context;
public String doStuff() {
final Principal user = context.getUserPrincipal();
// ...
}
}
You can't do this if the service, you want to use user principal in, is neither managed by Jersey nor Spring.
Spring Security might be useful to you in two ways:
It can manage authentication, (you would not need to do that login(...) call yourself, it would be done automatically by Spring Security filter chain. But you can still do it manually if you want.
Once a request has been authenticated, as long as the request is alive you can access the authenticated user from anywhere just by doing:
Authentication auth = SecurityContextHolder.getSecurityContext().getAuthentication();
// auth is an object that holds the authenticated user's data
I think you need some sort of authentication by the fact that you make a login(...) and you want to audit the user afterwards. You might not nedd an authentication form, but you do need authentication. Spring Security is not only for interactive applications, you can set up an authentication filter that does authentication based on cookies, request parameters, client certificates or whatever, all of that without user interaction.
Furthermore, Spring Security is very extensible, if you have your authentication method already implemented, integrating with Spring Security is easy. And it is also flexible: you don't need to use the security filter chain if it is too heavyweight for your use case. You can do most things manually and use just a little bit of Spring Security if you want.
I really suggest you take a deeper look at Spring docs about:
Spring Security core components overview and Spring Security authentication overview
I think with just that you will be able to get something working.
I'd like to use some (Java) methods in my controllers that are similiar to the built-in expressions provided by Spring Security e.g. hasRole([role]) or isFullyAuthenticated().
Do you know where I can find these methods and how to call them within a Java method of a controller (I don't want to use Spring EL, I want to use plain Java)? E.g something like
SomeStaticSpringSecutityClass.isFullyAuthenticated();
EDIT:
SecurityContextHolder.getContext().getAuthentication().isAuthenticated()
Actually doesn't really work. This method also returns true if the user is authenticated as "Anonymous". See the link to the spring security docs from above:
isAuthenticated() Returns true if the user is not anonymous
Instead you have to use something like that:
public boolean isAuthenticated() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return !(authentication == null || authentication instanceof AnonymousAuthenticationToken);
}
But anyway: I don't really want to implement logic again, that is already implemented somewhere in Spring Security. Additionally
SecurityContextHolder.getContext().getAuthentication()
does not provided methods like isFullyAuthenticated() or hasRole().
Take a look at org.springframework.security.core.contex.SecurityContextHolder. For instance to check if the current user is authenticated:
SecurityContextHolder.getContext().getAuthentication().isAuthenticated()
I found the SecurityContextHolderAwareRequestFilter that use the interface:
AuthenticationTrustResolver.isAnonymous(Authentication authentication);
If you want to use the Spring source code, you can check the class SecurityContextHolderAwareRequestWrapper that instantiates this interfaces as below:
private final AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
public Authentication getAuthentication() {
if (!authenticationTrustResolver.isAnonymous(auth)) {
return auth;
}
return null;
}
I looked into documentation and I didn't find a utility class to do it.