Need help adding OAuth2 security to spring boot microservices - java

I have created a set of spring boot microservices having followed along roughly with the tutorial I found here. I've had success getting my project up and going without any attempt to secure the rest endpoint or the user interface. But eventually I need to secure everything using LDAP, and I will need to conditionally allow access to user interface sections based upon user roles. For the time being, without involving LDAP, I have tried roughly a score of tutorials on how to use in-memory authentication in conjunction with OAuth2 and I cannot understand what it is exactly that I need to do to make this go.
I have created a github repository that contains two branches. The first branch is called no-authentication which demonstrates a simplified version of my project before any attempt to add authentication is begun. The README describes the project makeup, but it is essentially a configuration server, a service registry, one rest resource with two endpoints that are not authenticated, and a user interface module that access the two endpoints.
The idea is that the first endpoint will eventually require one group membership ("USER"), and the other endpoint another group membership ("ADMIN"). But I haven't gotten so far yet.
The second branch is called oauth-server. This branch adds another spring boot module called oauth2-server, and that module contains the code that my user interface is forwarded to in order authenticate.
I am presented with a login page as I would hope, but when I enter valid credentials (user/user or admin/admin) I see an error on the page:
OAuth Error
error="invalid_grant", error_description="Invalid redirect: http://localhost:8084/authtest/login does not match one of the registered values."
I am totally new to this area, and I have been really just playing whack-a-mole with my various attempts to get this working. You can view the code directly, and you will need to be well versed in order to understand the setup. Here is roughly how the authentication is set up to work.
There ui that needs to be authenticated is called authentication-test-ui. Its configuration can be found in its resources/bootstrap.yml, and the rest of its configuration comes from the control-center/config-server module's classpath, the source located at resources/config-repo/authentication-test-ui.yml. This is the security config:
gateway-server: 'http://localhost:8901/authserver'
security:
oauth2:
client:
client-id: ui
client-secret: uisecret
scope: ui
access-token-uri: ${gateway-server}/oauth/token
user-authorization-uri: ${gateway-server}/oauth/authorize
pre-established-redirect-uri: http://localhost:8084/authtest
resource:
user-info-uri: ${gateway-server}/userInfo
The UI's application class is annotated with #EnableOAuth2Sso.
The authorization server is in the module services/oauth2-server. This is annotated with #EnableAuthorizationServer and follows a similar configuration pattern as the ui (has a bootstrap.yml in its own module and a yml on the config server). The code performs in-memory authentication like this:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("ui")
.secret(passwordEncoder.encode("uisecret"))
.authorizedGrantTypes("authorization_code", "implicit", "password", "client_credentials", "refresh_token")
.scopes("ui")
.redirectUris(
"http://localhost:8084/authtest"
);
}
And the security configuration looks like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.passwordEncoder(passwordEncoder())
.withUser("admin").password(passwordEncoder().encode("admin")).roles("USER", "ADMIN")
.and()
.withUser("user").password(passwordEncoder().encode("user")).roles("USER");
}
I most likely have not provided the right information to be able to understand the issue simply by looking at the snippets I've pasted, so looking at the project is probably the only way to fully get it.
If anyone can help me with this I would greatly appreciate it. I've been on this for nearly a week and feel like I can't be too far off the mark. I'm really looking for the simplest way to get authenticated.

You configured redirect uri as http://localhost:8084/authtest but while calling you specified redirect uri as http://localhost:8084/authtest/login so it is failing.

Related

How to secure REST API using external authorization server using minimal configuration in C# .NET

I have been working in Java for couple of years and recently switched to C# .NET. One of the common scenarios I was dealing with as backend developer was building secured REST API. Each request to the API contains Authorization header with Bearer access token. We use AWS Cognito as an external authorization server.
In Spring Boot, this was fairly easy task:
use spring-boot-starter-oauth2-resource-server
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
add security configuration
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf(CsrfConfigurer::disable)
.sessionManagement(SessionManagementConfigurer::disable)
.authorizeRequests(c -> c.anyRequest().authenticated())
.oauth2ResourceServer()
.jwt();
return http.build();
}
define issuer-uri and jwk-set-uri properties
spring.security.oauth2.resourceserver.jwt.issuer-uri: ...
spring.security.oauth2.resourceserver.jwt.jwk-set-uri: ...
Now with this fairly generic configuration, all endpoints are secured.
However, I am unable to find the way to implement the same thing in C# .NET. All the tutorials and articles I found followed different approach, all of them bounding the solution with user management. What I am looking for is a pure resource server. Any help or pointing in the right direction would be appreciated.
Eventually I managed to come up with the solution, leaving it here as it may save someone else's time as well. Picked up Asgardeo this time as the external authorization server. Solution was tested in .NET 6.0:
add authentication
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Audience = "<client_id>";
options.MetadataAddress = "https://api.asgardeo.io/t/<app_name>/oauth2/token/.well-known/openid-configuration";
});
use authentication (authentication, authorization and controller mapping must be in correct order)
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
add secured endpoint to controller
[HttpGet]
[Route("Private")]
[Authorize]
public IActionResult Private()
{
return Ok(new
{
Message = "Hello from a private endpoint."
});
}
This solution works for applications without client secret.

Spring Security anyRequest.authenticated behavior

i am trying to implement a simple Spring security project but I am facing an issue where the behavior of http.authorizeRequests().anyRequest().authenticated(); is not understandable. what i expect from this method is to prevent all incoming requests until the user is authenticated but in my case all the requests are go through and no interception happened. the normal behavior of preventing request to go through took a place when I un-comment the lines which include hasAnyAuthority.
Below is My security configs
#Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthFilter customAuthFilter = new CustomAuthFilter(authenticationManagerBean());
//override behavior of url for our api
customAuthFilter.setFilterProcessesUrl("/api/login");
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers("/api/login/**").permitAll();
http.authorizeRequests().antMatchers("/register/**").permitAll();
/////http.authorizeRequests().antMatchers(GET,"/api/users/").hasAnyAuthority("ROLE_USER");
//////http.authorizeRequests().antMatchers(POST,"/api/user/save/**").hasAnyAuthority("ROLE_ADMIN");
http.authorizeRequests().anyRequest().authenticated();
http.addFilter(customAuthFilter);
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
}
i have resolved the issue , it was just a miss-understanding of how authenticated method works.
so first Spring security checks if the user authenticated and and then checks if this endpoint need any type of authorization. if authenticated and not authorization exist the user would be redirected to the endpoint. it make sense for me now.
Thank you.

GET , POST , PUT , DELETE type based Authentication in keycloak

I have a resource in an API for which URI is /product/{id} and doing three operations VIEW, GET, DELETE basse on HttpMethod.
How can I manage one user is allowed to only VIEW and admin is allowed to VIEW, GET, DELETE i.e. all options.
I had seen Keycloak Adapter Policy Enforcer but, I don't understand how it works.
I am not getting methods option in create permission.
Can somebody help me in implementing this or suggest some way to do it.
I guess you installed Keycloak for being able to control not just authentication, but also authorization. Then you don't need spring security at all. You need to enable authorization for your client and configure resources, policies and permissions using Keycloak admin console. Here is documentation
To be able to control your resources more granular use policy enforcers and map HTTP methods to scopes like described here: How to add HTTP methods in Keycloak resources for Authorization (Without adapters).
One of the good examples worth to look at is authz-spring-boot. It has complete authorization flow, but without method restriction that can be manually added.
You also can check how does your policy work using "Evalute" tab at Keycloak. This simulates client call to the resource and shows the result
What you need is spring security. You can add it to your project using:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
You could define your security settings like this (I'm assuming that other configuration is already done):
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// ...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//HTTP Basic authentication
.httpBasic()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/product/**").hasRole("USER")
.antMatchers(HttpMethod.POST, "/product").hasRole("ADMIN")
.antMatchers(HttpMethod.PUT, "/product/**").hasRole("ADMIN")
.antMatchers(HttpMethod.PATCH, "/product/**").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/product/**").hasRole("ADMIN")
.and()
.csrf().disable()
.formLogin().disable();
}
}
First of all the best suitable option is to use annotation based policy michanisam.
so before each and every rest service you need to write its access policy, for example :
#Secured("ROLE_VIEWER")
public String getUsername() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return securityContext.getAuthentication().getName();
}
So as you can see the getUsername() method will be only allowed by the viewer.
#Secured("ROLE_ADMIN")
public boolean isValidUsername(String username) {
return userRoleRepository.isValidUsername(username);
}
So as you see the above method will only allow to be accessed by admin, the same annotation can be used for rest services as well. to use this facelity you need to integrate spring security with your spring boot application.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
For more detail you can check out this artical, but i do see this is the best way to control the data security and the service security in a spring boot application.
Reference : https://www.baeldung.com/spring-security-method-security
I also had the same problem. The following answer provides the explanation.
Thus, you can define the HTTP methods as scopes and check for permission via the following Keycloak API.
curl -X POST \
http://${host}:${port}/auth/realms/${realm}/protocol/openid-connect/token \
-H "Authorization: Bearer ${access_token}" \
--data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
--data "audience={resource_server_client_id}" \
--data "permission=Resource A#GET"

Spring - Access denied for an Anonymous Authentication

I don't have much expertise with Spring Security and I have a question that could be a bit silly. I've been trying to solve an issue for few days and maybe I misunderstood the way to configure my app. I'd appreciate if anyone can bring some light into this.
I implemented a REST API with Spring and I wanted to add security to it. From the Spring Security docs I read ...
It’s generally considered good security practice to adopt a "deny-by-default" where you explicitly specify what is allowed and disallow everything else.
...
which I agree. So I added that Authentication requirement to my configuration.
However, I'd like to make few of the calls to the API public (from my GUI), so I thought that Anonymous Authentication would work. Again, from the docs I read...
no real conceptual difference between a user who is "anonymously authenticated" and an unauthenticated user
so, all good.
However, when I execute those calls, I get a 403 response (AbstractSecurityInterceptor throws an AccessDeniedException on decide about the Authentication). So, I leave here several questions + my configuration lines in case you know what problems and misunderstandings I have.
Config...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().and()
.anonymous().and()
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST, "/users/login").permitAll()
.anyRequest().authenticated().and()
// We filter the api/login requests
.addFilterBefore(new JwtLoginFilter("/users/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
Questions...
1) AnonymousAuthenticationToken is still authenticated, right?
2) Is the anonymous() line needed if I don't customize the configuration for Anonymous Authentication? The Anonymous token is there (when I debug) when an authentication is missing
3) Is there anything missing in the configuration to accept those requests?
4) The docs mention the ExceptionTranslatorFilter that should process the AccessDeniedException to the AuthenticationEntryPoint. Do I need to define an AuthenticationEntryPoint? If so, with what purpose?
I've tried to be as much precise as possible. I hope someone can reply.
Thank you very much for your help!
You need to put this into your configuration file:
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll();
Do you have unauthenticated access to the root '/'? I ask this because looks like is what you intend to with the line:
.antMatchers("/").permitAll()
I guess in order to allow unauthenticated request (or anonymous authenticated request, note that for Spring Anonymous and unauthenticated is basically the same) you will need to add the ant match for those paths, something like:
.antMatchers("/public-page").permitAll()

Using Spring Security with zero-legged Oauth 1

I am writing an LTI application using Spring boot. LTI applications are basically a plug-in for a learning management system (in this case Canvas) which work by sending an Oauth1 signed POST to my server. The result of this request is displayed to the user inside of an iframe. There is a pre-shared key and secret that the LMS uses to sign the request. If the signature on the POST checks out, I have an authenticated user. I have this part working, partially based on this question.
During the initial request (which comes to the URL "/launch") I can call SecurityContextHolder.getContext().getAuthentication() and use this without problems. My problem is when the user makes another request, say for a picture or by clicking on a link in my content, the SecurityContext object isn't following them. I'm pretty sure I'm not setting up the Spring security filter chain correctly so the SecurityContextPersistenceFilter isn't being hit on subsequent requests. At the end of the day, SecurityContextHolder.getContext().getAuthentication() returns null.
The OAuth signature verification happens in a WebSecurityConfigurerAdapter like so: (again, based on this)
#Configuration
public static class OAuthSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
//spring auto-wiring to set up the
//zeroLeggedOauthProviderProcessingFilter (see linked question)
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/launch")
.addFilterBefore(zeroLeggedOAuthProviderProcessingFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().anyRequest().hasRole("OAUTH")
.and().csrf().disable();
}
}
So this works and creates an authenticated principal and everything. But due to that antMatcher, it only applies to the /launch path.
It seems like it should be simple to add another security configurer adapter that will ensure that all other paths in the application are protected by an authenticated session and in so doing would cause the SecurityContext associated with this user to become available but I have been unable to come up with the magic sauce. The documentation focuses more on standard login form based authentication setups. I'm also kind of new to Spring in general so I'm clearly missing something. I tried this but it causes all other requests to return a 403:
#Configuration
public static class SessionSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("OAUTH")
.and().csrf().disable();
}
}

Categories

Resources