Getting subject from id token in Spring boot - java

I have configured my Spring Boot application as a resource server. And it is working perfectly by blocking all the request that does not contain the authentication bearer token. However, once I am in service I would like to get the subject(sub in JWT token) out of the token an use it. How can I fetch the value of subject from Spring Authentication framework?
Since I am using Spring Boot's auto-configuration, I do not validate the token myself, it is been done automaticlly so I do not know the subject.

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
...
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
...

I added Principle to the argument.
#Controller
public class SecurityController {
#RequestMapping(value = "/username", method = RequestMethod.GET)
#ResponseBody
public String currentUserName(Principal principal) {
return principal.getName();
}
}
This link helped me

Related

Spring CSRF unrestricted RequestMapping

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

Content negotiation defined security config for multiple oauth2 providers

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?

Micronaut all routes forbidden

I created an app in Micronaut using JWT tokens for security
mn create-app --features=security-jwt,data-jdbc,reactor,graalvm example.micronaut.micronautguide --build=gradle --lang=java
And now all my routes are forbidden. How to exclude certain routes (ie. login) from JWT token checking. I tried both without annotation and with annotation IS_ANONYMOUS
package logfetcher;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;
import io.micronaut.http.annotation.Produces;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.rules.SecurityRule;
#Controller("/log")
public class LogFethcerContoller
{
#Get
#Secured(
SecurityRule.IS_ANONYMOUS )
#Produces(MediaType.TEXT_PLAIN)
public String index() {
return "Hello World";
}
#Get("log")
public String index1() {
return "Hello World";
}
}
I get 401 on both routes.
How can I have routes that do not need the JWT token.
I get 401 on both routes.
From micronaut-projects.github.io/micronaut-security/3.2.0/guide/#gettingStarted...
By default Micronaut returns HTTP Status Unauthorized (401) for any
endpoint invocation. Routes must be explicitly allowed through the
provided mechanisms.
Question:
How can I have routes that do not need the JWT token.
There are a number of ways.
From micronaut-projects.github.io/micronaut-security/3.2.0/guide/#securityRule...
The decision to allow access to a particular endpoint to anonymous or
authenticated users is determined by a collection of Security Rules.
#PermitAll is one way to do it. Others are documented at the links above.

Read JWT token in Spring Boot RouterFunction

In Spring Boot Controller implementation we can get the JwtAuthenticationToken as a parameter in our method. Same token can be read, manipulated and validated for authorization like below
#PostMapping("/hello")
#PreAuthorize("hasAuthority('SCOPE_Internal') or hasAuthority('ROLE_User')")
public Mono<String> testHello(JwtAuthenticationToken token) {
log.info("token is " + token.getTokenAttributes().toString());
return Mono.just("OK");
}
We are using reactive Spring Boot and we have replaced our controllers with RouterFunction. We are wondering how above feature - Authorization and get the token in our router method calls.
public RouterFunction<ServerResponse> route() {
return RouterFunctions.route(GET("/hello"), helloHandler::testHello);
}
When we tried passing the JwtAuthenticationToken in the router method call, it threw
Could not autowire. No beans of 'JwtAuthenticationToken' type found.
public RouterFunction<ServerResponse> route(JwtAuthenticationToken jwtAuthenticationToken) {
return RouterFunctions.route(GET("/hello"), helloHandler::testHello);
}
We came up this solution if it makes any sense, or valid. We ran into same issue lately as we began a journey of converting our legacy and synchronous spring boot server app to an asynchronous one. The JwtAuthenticationToken which we use to get some added attribute to the token used by the system works out of the box when we used the RestController and pass it as an argument in the protected endpoint method. But with Router Function we could not get it to work. After 1 day of painstaking research on google, stackoverflow, spring's reactive application resource server docs, we could not get any head way. However, this post got us thinking even more. so we came up with this solution:
#Slf4j
#Component
#RequiredArgsConstructor
public class FitnessAccountWebHandler {
private final FitnessAccountService accountService;
public Mono<ServerResponse> getAccountByUserId(ServerRequest request) {
String userId = request.pathVariable(AccountResourceConfig.USER_ID);
// This will give you the JwtAuthenticationToken if a Principal is
// returned or empty if no authentication is found
Mono<JwtAuthenticationToken> authentication = request
.principal()
.cast(JwtAuthenticationToken.class);
return authentication.flatMap(auth -> {
log.info("Subject: {}", auth.getName());
return accountService.getSomething(userId)
.flatMap(ServerResponse.ok()::bodyValue)
.switchIfEmpty(ServerResponse.notFound().build());
});
}
}
Hope this helps someone and save some time.

How to use RESTful with Basic Authentication in Spring Boot

Helllo, I'm using RESTful with basic authentication and this code is a part from the RestController:
#GetMapping("/jpa/users/{username}/goals")
public List<Goal> getAllGoals(#PathVariable String username) {
userId = getUserIdFromUsername(username);
return goalJpaRepository.findByUserId(userId);
}
public Long getUserIdFromUsername(String username) {
User user = userJpaRepository.findByUsername(username);
userId = user.getId();
return userId;
}
And I have a problem, for example I'm using Postman to retrieve the goals for a speciffic user like this:
http://localhost:8080/jpa/users/john/goals with GET request
Then I use the basic authentication for the username john and the password for this username and I receive the goals for john.
After that if I do a GET request for this link http://localhost:8080/jpa/users/tom/goals I receive the goals for tom, but I'm logged in with john at this moment of time, so john can see his goals and also he can see tom's goals.
The question is how can I access the login username in the RestController, because I want to do something like this:
if (loginUsername == username) {
return goalJpaRepository.findByUserId(userId);
}
return "Access denied!";
So I want to know if it is possible to access the login username from HTTP Header?
Thank you!
UPDATE - Yes the framework is Spring Boot, also I'm using Spring Security with Dao Authentication because I want to get the user from a MySQL database. Anyway I'm not an expert at Spring Security.
Now I understand how to use Principal in my controller methods, but I don't know how to use Spring Security for this specific case. How should I implement it? For example the user john should see and modify only his goals.
Spring Security Configuration:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.dgs.restful.webservices.goaltrackerservice.user.MyUserDetailsService;
#Configuration
#EnableWebSecurity
public class SpringSecurityConfigurationBasicAuth extends WebSecurityConfigurerAdapter {
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Autowired
private MyUserDetailsService userDetailsService;
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(bCryptPasswordEncoder());
return authProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/allusers").permitAll()
.anyRequest().authenticated()
.and()
// .formLogin().and()
.httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
}
Assuming you are using Spring as your Java framework, you should use Spring security to configure the basic authentication. Many tutorials available online (https://www.baeldung.com/spring-security-basic-authentication,
Spring Security will then provide a security context available throughout the app (SecurityContextHolder.getContext()) from which you can retrieve the connected user information (username, ...).
For instance to retrieve the username of the connected user, you should do :
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String loginUsername = authentication.getName();
Alternatively, as mentioned by #gervais.b Spring can inject the Principal (or Authentication) in your controller methods.
As said by #Glains An even better alternative is to use the #PreAuthorize and #PostAuthorize annotations, which alows you to define simple rules based on Spring Expression Language.
Please note that you are not doing any security at this time.
As said by #Matt "It depends which framework you are using". But I guess you are using spring. You should then have a look at the spring-securuty module documentation.
Basically you can inject the authenticated user into your method parameter :
#GetMapping("/jpa/users/{username}/goals")
public List<Goal> getAllGoals(#PathVariable String username, Principal principal) {
if ( username.equals(principal.getName()) ) {
userId = getUserIdFromUsername(username);
return goalJpaRepository.findByUserId(userId);
} else {
throw new SomeExceptionThatWillBeMapped();
}
}
But spring-security and many frameworks provide better patterns to manage the security.
You can also solve this problem with #PreAuthorize, an annotation offered by the Spring Security Framework, which uses the Spring Expression Language.
#PreAuthorize("principal.name == #username")
#GetMapping("/jpa/users/{username}/goals")
public List<Goal> getAllGoals(#PathVariable String username) {
return goalJpaRepository.findByUserId(userId);
}
Behind the scenes Spring will use the already mentioned SecurityContextHolder to fetch the currently authenticated principal. If the expression resolves to false, the response code 403 will be returned.
Please note that you have to enable global method security:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
To answer your new question on "Dao Authentication" the answer is to provide a custom UserDetailsService.
From the configuration that you have attached to your question it looks that you already have a MyUserDetailsService.
There are plenty of articles that explain how to use a custom DetailsService. This one seems to match your requirements : https://www.baeldung.com/spring-security-authentication-with-a-database
Edit: On how to be sure that only John can see John's items.
Basically, the only ting that you can do to esnure that only John can see his goals it to restrict the goals to only those owned by John. But there is plenty way of doing this.
As you suggest in your initial question, you can just select the goals for a specific user. The power with spring-security is that it can inject a Principal but also kind of orher authentication object.
You can also make that more implicit an filter a the DAO/Repository side by using the SecurityContextHolder. This approach is fine and looks better when your system is more user centric or like a multi-tenant system.
Using some specific #Annotations or Aspects would also be a solution but maybe less obvious in this case.

Categories