I want to create an API that allows multiple oauth2 providers, and depending on the provider it calls a particular method via ACCEPT header(content negotiation) versioning.
I try to write controller that gets called via versioning in content negotiation.
I have written the below which works fine based off the Accept header.
#Controller
pubic class LeagueController{
#Autowired
private LeagueService leagueService;
#Autowired// context is loaded via #EnableWebSecurity
private SecurityContext context;
// RESTful method
#RequestMapping(value="/leagues", produces = "application/vnd.com.gim.v1+json")
#ResponseStatus(HttpStatus.OK)
public #ResponseBody List<League> doSomethingOauth2FirstPreference(Principal principal) {
return leagueService.getLeagues(principal);
}
// RESTful method
#RequestMapping(value="/league", produces = "application/vnd.com.me.model.v2+json")
#ResponseStatus(HttpStatus.OK)
public #ResponseBody List<List<League> > doSomethingOauth2SecondPrefence(Principal principal) {
List<Authorities> authorities = context.getAuthoritiesForUser(principal) );
return leagueService.getLeagues(authorities);
}
I have followed this to create multiple oauth2 security configurations using a filter:
https://spring.io/guides/tutorials/spring-boot-oauth2/
But what I want is if the version of the Accept header is "application/vnd.com.gim.v2+json" it goes through gmail login, otherwise to doSomethingOauth2FirstPreference as default which uses a different oauth2 provider.
Is this possible in spring boot? Multiple oauth2 providers that the security filter invokes on versions? Or is there another way of doing this other than content negotiation?
Related
Small question regarding a SonarQube scan on a SpringBoot project please.
I have a very simple handler, super simple, as follow:
#ResponseBody
#RequestMapping(method = { RequestMethod.GET, RequestMethod.POST}, path = "/my/api")
public Mono<String> question() {
return myService.dosomething();
}
I have Spring Security in my class path, this project is a Spring WebFlux Application using 2.6.7 and Java 11.
Upon static analysis scan, I am being flagged with:
Spring CSRF unrestricted RequestMapping
Description
<p>Methods annotated with <code>RequestMapping</code> are by default mapped to all the HTTP request methods.
However, Spring Security's CSRF protection is not enabled by default
for the HTTP request methods <code>GET</code>, <code>HEAD</code>, <code>TRACE</code>, and <code>OPTIONS</code>(as this could cause the tokens to be leaked).
Therefore, state-changing methods annotated with <code>RequestMapping</code> and not narrowing the mapping
to the HTTP request methods <code>POST</code>, <code>PUT</code>, <code>DELETE</code>, or <code>PATCH</code>are vulnerable to CSRF attacks.</p><p> <b>Vulnerable Code:</b><br/><pre><code>#Controller
public class UnsafeController {
...
<b>References</b><br/>Spring Security Official Documentation: Use proper HTTP verbs (CSRF protection)<br/>OWASP: Cross-Site Request Forgery<br/>OWASP: CSRF Prevention Cheat Sheet<br/>CWE-352: Cross-Site Request Forgery (CSRF)</p>
I do not understand this issue.
What I tried:
I tried splitting the Controller onto two different controllers, one for each verb, and for some reason, this fix the issue:
#ResponseBody
#GetMapping(path = "/my/api")
public Mono<String> questionGet() {
return myService.dosomething();
}
#ResponseBody
#PostMapping(path = "/my/api")
public Mono<String> questionPost() {
return myService.dosomething();
}
But I am now carrying a duplicate, therefore, I would like to stay with my:
#ResponseBody
#RequestMapping(method = { RequestMethod.GET, RequestMethod.POST}
I also added Spring Security to protect myself against CSRF, but no luck, the issue still persist.
May I ask what is the proper way to fix this please?
Thank you
I have a Spring Boot + Keycloak project and I found out that the Spring Boot does not validate the JWT with the keycloak. For example if I get a token from Keycloak and turn off the Keycloak, I still can use this JWT token to access my end points. I have this security configurer class:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
#RequiredArgsConstructor
public class KeycloakSecurityConfigurer extends WebSecurityConfigurerAdapter {
private final RoleConverter converter;
#Value("${spring.security.oauth2.keycloak.jwt.issuer-uri}")
private String issuerUri;
#Override
public void configure(final HttpSecurity http) throws Exception {
http.headers().frameOptions().disable()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.oauth2ResourceServer(
oauth2ResourceServer -> oauth2ResourceServer.jwt(
jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));
http.authorizeRequests().antMatchers("/**").authenticated();
}
private Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter() {
JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
return jwtConverter;
}
#Bean
public JwtDecoder jwtDecoder() {
return JwtDecoders.fromOidcIssuerLocation(issuerUri);
}
}
The "converter" is nothing special, just extracts the roles out of JWT token and returns a list of them.
How to force the Spring Security to validate the JWT token?
application.yml:
spring:
security:
oauth2:
keycloak:
jwt:
issuer-uri: http://localhost:8180/auth/realms/test-realm
You can look at the implementation of JwtDecoders.fromOidcIssuerLocation(issuerUri).
What is happening is that the keys are being fetched at the startup of your application and the application caches them in order to perform the validation after. With this in mind, even if you turn off Keycloak the JWT will still be validated because the keys are still cached.
The JWT tokens are been cached in your springboot application, this is the default cache store. In order to delete this token from your springboot app use should use some custom caches like redis cache to be configured in your app instead of default. There is no possible way to delete the tokens stored in default caches. The token will automatically get invalidate only after the timeout that's been set inside token
JWTs are meant to be validated offline, and it is what usually happens. The receiving application (consumer of JWT), does not need constant access to the Authorization Server in order to be able to validate a JWT. Even though Spring can't talk to your Keycloak, it does not mean that tokens are not validated. As others pointed out, Spring caches the keys used to validate JWTs' signature and will use the cache if it can.
If, for some reason, you want your service / API to validate the JWT online (maybe because you want to implement a mechanism to revoke tokens), you could switch to using opaque tokens with Token Introspection. On every request your service will have to call Keycloak to exchange the opaque token for a JWT. Mind that this solution will use much more resources, and you should use it only if you have strong reasons for it.
I am trying to build an OAuth2 Client using spring boot to access a custom API.
Here is my code so far:
#SpringBootApplication
public class MyClient {
public static void main(String[] args){
SpringApplication.run(MyClient.class, args);
}
}
#Configuration
#EnableOAuth2Sso
public class ApplicationSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception{
http.antMatcher("/**")
.authorizeRequests().antMatchers("/","/callback","/login**","/error**")
.permitAll().anyRequest().authenticated();
}
}
#RestController
public class HomeController {
#RequestMapping("/user")
public String login() {
String userInfoJson = "get data returned from API somehow";
return userInfoJson;
}
#RequestMapping("/callback")
public void callback(HttpServletResponse response) {
response.sendRedirect("/user");
}
}
I have created an application.yml file with all the properties needed and the login process works perfectly, returning the flow to /callback after a successful login.
At that point I should have received a token that I can use to fetch the user's data from the server.
How can I access this token?
Also, does spring boot have any classes to do the token validation process automatically, or do I have to create the request manually?
Thanks
from the documentation
An OAuth2 Client can be used to fetch user details from the provider (if such features are available) and then convert them into an Authentication token for Spring Security.
The Resource Server above support this via the user-info-uri property This is the basis for a Single Sign On (SSO) protocol based on OAuth2, and Spring Boot makes it easy to participate by providing an annotation #EnableOAuth2Sso.
The Github client above can protect all its resources and authenticate using the Github /user/ endpoint, by adding that annotation and declaring where to find the endpoint (in addition to the security.oauth2.client.* configuration already listed above):
application.yml.
security:
oauth2:
...
resource:
userInfoUri: https://api.github.com/user
preferTokenInfo: false
So as you can read this should be done automatically if you set where the user info shall be fetched from, and then it will be populated.
When an Authentication token is populated you can fetch this from the security context in multiple different ways.
#RequestMapping("/callback")
public void callback(Authentication authentication) {
final String name = authentication.getName();
response.sendRedirect("/user");
}
If you wish to access the raw json, you'll probably have to make the rest call yourself. If you want to add custom values to the authentication object you have to implement your own UserDetails class.
I'm using Spring OAuth 2.0, and I want to protect my application against URL redirect attack. Is there a way to validate the redirect URL at the authorization server ?
The authorization server should only perform a redirect if the redirect_uri matches the one registered by the client. So there shouldn't be any need for you to perform a separate check.
If in doubt, try sending an authorization request with a completely different redirect_uri and see what happens.
Should be implemented ClientDetailsService class and configure registered redirect uri's.
#Service
#Transactional
public class CustomClientDetailsService implements ClientDetailsService {
#Override
public ClientDetails loadClientByClientId(String clientId) {
String registeredRedirectUris; // Get registeredRedirectUris
BaseClientDetails clientDetails = new BaseClientDetails();
clientDetails.setRegisteredRedirectUri(registeredRedirectUris);
return clientDetails;
}
}
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.