I made a Rest API project with Spring Boot. There are every standard methods like get, post, put and delete in the Controller.
My aim is to make it possible for me to only be able to access the api calls (except get calls) via my angular app. Other methods (post, put and delete) can not be accessible from outside.
I tried to solve this problem with WebSecurityConfigurerAdapter and configure function but I couldn't get it.
When I first imported the security dependency (spring-boot-starter-security) on pom.xml, then all methods were blocked. I tried to permit the get calls in configure method but then I could not make a post call with basic auth over postman. Everytime I got 403 Forbidden error.
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.GET)
.antMatchers("/h2-console/**")
.antMatchers("/login/**");
}
}
By the way, I wanted to make my own username and passwort for spring security in the application.properties file. But I think that doesn't work if I use a SecurityConfig configuration file.
spring.security.user.name=myUsername
spring.security.user.password=myPassword
Regardless of my attempts, how can I actually get this from shortest and easiest way?
Then how can I call the blocked methods (post, put, delete) from my angular application?
Thanks.
If I'm not mistaken, you want your project to have no access restrictions for GET methods and everyone should have access to this method type.
All remaining requests (post, put, delete, etc.) can be accessed with an authentication.
You can achieve this as follows. Assuming you have a controller like below:
#RestController
#RequestMapping("security")
public class SecurityController {
#GetMapping("get")
public ResponseEntity<String> get() {
return ResponseEntity.ok("Get Method");
}
#PostMapping("post")
public ResponseEntity<String> post() {
return ResponseEntity.ok("Post Method");
}
#PutMapping("put")
public ResponseEntity<String> put() {
return ResponseEntity.ok("Put Method");
}
#DeleteMapping("delete")
public ResponseEntity<String> delete() {
return ResponseEntity.ok("delete");
}
}
In this case your WebSecurityConfigurer should be like below:
#EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.GET).permitAll()
.anyRequest().authenticated().and().httpBasic();
}
}
The first thing to do here is to determine that GET, which is an http method, can access without any authorization. It then authorizes the accesses of the remaining HttpMethod's. Finally, we specify that we are using Basic Auth with httpBasic(). This information consists of username and password information defined in your application.properties file.
You can see the difference between HttpSecurity and WebSecurity by examining the question here.
I hope this answer helps you.
Related
It is Spring Security's recommendation to deny all requests with missing authorization rules by default:
http
.authorizeRequests((authorize) -> authorize
.filterSecurityInterceptorOncePerRequest(true)
.mvcMatchers("/app/**").hasRole("APP")
// ...
.anyRequest().denyAll()
)
// ...
Now let's apply this to a simple real-world example.
The goal is to define 3 endpoints with different access permissions:
"/": homepage, everyone can access
"/login": only anonymous users can access
"/private": only logged in users with the authority "ROLE_USER" can access
All other pages should automatically be considered as not allowed as long as no explicit permission has been set via annotation at the endpoint method.
The controller therefore looks as follows:
#Controller
#RequestMapping
public class MainController {
#GetMapping
public String index() { return "index"; }
#GetMapping("/login")
#PreAuthorize("isAnonymous()")
public String login() { return "login"; }
#GetMapping("/private")
#PreAuthorize("hasAuthority('ROLE_USER')")
public String secrets() { return "private"; }
}
According to Spring Security's suggestion, the WebSecurityConfig looks like this:
#EnableWebSecurity
#EnableMethodSecurity(securedEnabled = true)
public class WebSecurityConfig {
#Bean
public SecurityFilterChain mainFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequestsauthorize -> authorize
.anyRequest().denyAll())
// ...
}
This configuration should declare all endpoints as restricted by default and since method declarations have a higher precedence, permissions on the methods should override accordingly.
But that is not what happens. With this approach, all endpoints are forbidden and the method annotations are not considered.
The following customization of the authorization config object respects the method annotations but ignores denyall():
#Bean
public SecurityFilterChain mainFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequestsauthorize -> authorize
.mvcMatchers("/**").permitAll()
.anyRequest().denyAll())
// ...
}
What sense does it make if you have to give permissions both in the filterchain and on the method itself, without the possibility to declare new endpoints as forbidden by default? With this approach, any new endpoint that is not explicitly restricted on method level is completely open to any user.
So my question is:
How do I configure the SecurityFilterChain in a way that all endpoints, and thus also all new endpoints, are automatically considered as forbidden, and access is exclusively set via method annotations?
Many thanks to all of you who can help to clarify the situation
I would like to know if in spring oauth2 is possible get a new pair tokens (access token and refresh token) just using another refresh token, without the basic authentication (without clientId and clientSecret, is there any way?
For exemple:
WITH BASIC AUTH
curl -u clientId:clientSecret -X POST 'http://myapplication.oauth2/accounts/oauth/token?grant_type=refresh_token&client_id=<CLIENT_ID>&refresh_token=' -v
WITHOUT BASIC AUTH
curl -u -X POST 'http://myapplication.oauth2/accounts/oauth/token?grant_type=refresh_token&client_id=<CLIENT_ID>&refresh_token=' -v
I note that sprint BasicAuthenticationFilter in spring uses validation bellow, maybe override this filter and make the authentication just with refresh token.
String header = request.getHeader("Authorization");
if (header == null || !header.toLowerCase().startsWith("basic ")) {
chain.doFilter(request, response);
return;
}
The short answer is no. The class used to manage the Spring Oauth 2 endpoints is the following one:
#FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint
Both requests, I mean, get access token and refresh one use the same endpoint with different parameters. And the method to manage those ones is:
#RequestMapping(
value = {"/oauth/token"},
method = {RequestMethod.POST}
)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");
} else {
String clientId = this.getClientId(principal);
...
As you can see, a Principal object is required (in this case provided by the Basic Authentication).
Even, if you configure the security of your project to permit that url without checking authentication, you will achieve to "enter" in above method but you will receive an InsufficientAuthenticationException because no Authentication instance has been provided.
Why custom authentication will not work
1. Create a custom AuthenticationProvider will not work because the method postAccessToken is invoked before. So you will receive an InsufficientAuthenticationException.
2. Create a OncePerRequestFilter and configure it to execute before process the current request:
#Override
protected void configure(HttpSecurity http) throws Exception {
http...
.anyRequest().authenticated()
.and()
.addFilterBefore(myCustomFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(POST, "/accounts/oauth/**");
}
with a code "similar to":
#Component
public class CustomAuthenticationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
...
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("existingUser",
"passwordOfExistingUser",
Collections.emptyList()));
...
filterChain.doFilter(request, response);
}
The problem with this approach is the principal in TokenEndpoint comes from the HttpServletRequest not from Spring context, as you can see debugging BasicAuthenticationFilter class.
In your custom filter you can try, using reflection, set a value in userPrincipal property but, as you can verify, request has several "internal request properties" and that could be a "too tricky option".
In summary, Oauth standard needs user/pass to access to the resources, if you want to workaround in almost of provided endpoints maybe that project is not what you are looking for.
Workaround to include your own object in Spring Principal
I do not recommend that but if you still want to go ahead with this approach, there is a way to include your own value inside the principal parameter received by TokenEndpoint class.
It is important to take into account BasicAuthorizationFilter will be still executed, however you will be able to override the Spring principal object by your own one.
For this, we can reuse the previous CustomAuthenticationFilter but now your have to include the filters you need, I mean, allowed urls, parameters, etc You are going to "open the doors", so be careful about what you allow and not.
The difference in this case is, instead of add the configuration in our class that extends WebSecurityConfigurerAdapter we are going to do it in:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private CustomAuthenticationFilter customAuthenticationFilter;
...
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.checkTokenAccess("isAuthenticated()");
security.addTokenEndpointAuthenticationFilter(customAuthenticationFilter);
}
...
I have a requirement to use two kinds of authentication,
for web we #EnableRedisHttpSession and for other consumers like mobile we use #EnableAuthorizationServer with #EnableResourceServer.
suppose we try to protect a controller common to both the authentication mechanisms for e.g /api/v1/test
i have hit a roadblock.
i am only able to use one kind of authentication scheme
if i set #WebSecurityConfigurerAdapter #order(2) and #ResourceServerConfigurerAdapter #order(3) then i can only access the resource via web
and if i set #ResourceServerConfigurerAdapter #order(2) and #WebSecurityConfigurerAdapter #order(3) then only OAuth works.
i am unable to use both the mechanism at the same time.how can we make the two work together, for e.g if the request comes from web use the filter responsible for that and if the request comes from mobile use the appropriate filter. web uses cookies and API Consumers use Authorization : Bearer header.
please help
It's sounds very strange. I suggest you to review how REST API is used and why it should be used by browser users. Better to separate web views and REST API, don't mix it.
However, answering your question "can I use two kinds of authentication for some URI at once" - yes, you can.
You need custom RequestMatcher that will decide how to route incoming request.
So:
for "API consumers" - check existence of Authorization header
contains "Bearer"
for "browser users" - just inverse first rule
Code example:
public abstract class AntPathRequestMatcherWrapper implements RequestMatcher {
private AntPathRequestMatcher delegate;
public AntPathRequestMatcherWrapper(String pattern) {
this.delegate = new AntPathRequestMatcher(pattern);
}
#Override
public boolean matches(HttpServletRequest request) {
if (precondition(request)) {
return delegate.matches(request);
}
return false;
}
protected abstract boolean precondition(HttpServletRequest request);
}
OAuth2 authentication
#EnableResourceServer
#Configuration
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new AntPathRequestMatcherWrapper("/api/v1/test") {
#Override
protected boolean precondition(HttpServletRequest request) {
return String.valueOf(request.getHeader("Authorization")).contains("Bearer");
}
}).authorizeRequests().anyRequest().authenticated();
}
}
Web authentication
#Configuration
#EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new AntPathRequestMatcherWrapper("/api/v1/test") {
#Override
protected boolean precondition(HttpServletRequest request) {
return !String.valueOf(request.getHeader("Authorization")).contains("Bearer");
}
}).authorizeRequests().anyRequest().authenticated();
}
}
Using this configuration it's possible to use two different authentication types for one URI /api/v1/test.
In addition, I highly recommended to read the article about Spring Security architecture by Dave Syer, to understand how does it work:
https://spring.io/guides/topicals/spring-security-architecture/#_web_security
I have a base REST controller that provides general methods (GET, PUT, POST, DELETE).
Then specific controllers inherit these methods and either use them or override them. When such a subclass controller override an inherited controller method, I sometimes needs to add for example #PreAuthorize("hasAnyRole('ROLE_ADMIN')") to restrict the access to some security roles.
However, now I have another subclass controller for which I need to allow access only to the GET inherited methods. All other inherited methods (PUT, POST, DELETE) should be forbidden to absolutely everyone.
I tried to Override the DELETE with an empty #PreAuthorize:
#PreAuthorize
#Override
#RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
#ResponseStatus(HttpStatus.NO_CONTENT)
#ResponseBody
public void removeResource(#PathVariable("id") final Long id, final Principal principal) throws UnknownResourceException {
// Deny access to everyone
}
However, with no value, it's not a valid annotation.
Is there a usual pattern to solve this issue ?
Spring recommends to secure your Mapping Handler with HttpSecurity:
In practice we recommend that you use method security at your service
layer, to control access to your application, and do not rely entirely
on the use of security constraints defined at the web-application
level. URLs change and it is difficult to take account of all the
possible URLs that an application might support and how requests might
be manipulated. You should try and restrict yourself to using a few
simple ant paths which are simple to understand. Always try to use
a"deny-by-default" approach where you have a catch-all wildcard ( /**
or **) defined last and denying access.
So for example it is secured using URL security and can be differentiate by Http method:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// ...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/myurl").hasRole("ADMIN")
.antMatchers(HttpMethod.GET, "/myurl/**").hasRole("USER")
.antMatchers(HttpMethod.DELETE, "/myurl/**").hasRole("ADMIN")
.antMatchers(HttpMethod.PUT, "/myurl/**").hasRole("USER");
}
// ...
}
I'm trying to setup a resource server to work with separate authorization server using spring security oauth. I'm using RemoteTokenServices which requires /check_token endpoint.
I could see that /oauth/check_token endpoint is enabled by default when #EnableAuthorizationServer is used. However the endpoint is not accessible by default.
Should the following entry be added manually to whitelist this endpoint?
http.authorizeRequests().antMatchers("/oauth/check_token").permitAll();
This will make this endpoint accessible to all, is this the desired behavior? Or am I missing something.
Thanks in advance,
You have to
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception
{
oauthServer.checkTokenAccess("permitAll()");
}
For more information on this ::
How to use RemoteTokenService?
Just to clarify a couple of points, and to add some more information to the answer provided by Pratik Shah (and by Alex in the related thread):
1- The configure method mentioned is overridden by creating a class that extends AuthorizationServerConfigurerAdapter:
#EnableAuthorizationServer
#Configuration
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("ger-client-id")
.secret("ger-secret")
.authorizedGrantTypes("password")
.scopes("read", "write");
}
}
2- I suggest reading this Spring guide explaining the automatic configuration carried out by Spring Boot when we include the #EnableAuthorizationServer annotation, including an AuthorizationServerConfigurer bean. If you create a configuration bean extending the AuthorizationServerConfigurerAdapter as I did above, then that whole automatic configuration is disabled.
3- If the automatic configuration suits you just well, and you JUST want to manipulate the access to the /oauth/check_token endpoint, you can still do so without creating an AuthorizationServerConfigurer bean (and therefore without having to configure everything programmatically).
You'll have to add the security.oauth2.authorization.check-token-access property to the application.properties file, for example:
security.oauth2.client.client-id=ger-client-id
security.oauth2.client.client-secret=ger-secret
security.oauth2.client.scope=read,write
security.oauth2.authorization.check-token-access=permitAll()
Of course, you can give it an isAuthenticated() value if you prefer.
You can set the log level to DEBUG to check that everything is being configured as expected:
16:16:42.763 [main] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression 'permitAll()', for Ant [pattern='/oauth/check_token']
There is no much documentation about these properties, but you can figure them out from this autoconfiguration class.
One last thing worth mentioning, even though it seems to be fixed in latest Spring versions, I just submitted an issue in the spring-security-oauth project; it seems that the token_check functionality is enabled by default if you add a trailing slash to the request:
$ curl localhost:8080/oauth/check_token/?token=fc9e4ad4-d6e8-4f57-b67e-c0285dcdeb58
{"scope":["read","write"],"active":true,"exp":1544940147,"authorities":["ROLE_USER"],"client_id":"ger-client-id"}
There are three POST parameters, namely client_id (user name), client_secret (password corresponding to the user name), token (token applied for), client_id, client_secret are different from the parameters in the /oauth/token interface
enter image description here
First, config token access expression:
#Override
public void configure(AuthorizationServerSecurityConfigurer securityConfigurer) throws Exception {
securityConfigurer
.allowFormAuthenticationForClients()
.checkTokenAccess("isAuthenticated()")
.addTokenEndpointAuthenticationFilter(checkTokenEndpointFilter());
}
Then, we need define a filter to process client authentication:
#Bean
public ClientCredentialsTokenEndpointFilter checkTokenEndpointFilter() {
ClientCredentialsTokenEndpointFilter filter = new ClientCredentialsTokenEndpointFilter("/oauth/check_token");
filter.setAuthenticationManager(authenticationManager);
filter.setAllowOnlyPost(true);
return filter;
}