Vaadin, Spring Boot, Microsoft Graph - How can I extend the acces token with the refresh token - spring-boot

I am developing an application that accesses Microsoft Graph and everything works fine so far. But after an hour the access token has expired and I do not understand how I can refresh the token. I researched a lot and nothing seems to fit to the approach with azure spring boot starter. It would be really nice if someone has a tip because I was not able to find something helpful in the Microsoft documentation or anywhere else.
As far as I am understanding this, I have to send a new Post request with the refresh_token to get a new access token. But how can I do this in this scenario?
I am using Vaadin 14.7, here are the other libraries:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
<version>2.5.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>azure-spring-boot-starter-active-directory</artifactId>
<version>3.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-core</artifactId>
<version>5.5.2</version>
</dependency>
<dependency>
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph</artifactId>
<version>5.4.0</version>
</dependency>
This is my code to get the GraphServiceClient to make requests against the Graph API
public GraphServiceClient<Request> getGraphService() throws OAuthTokenException {
OAuth2AuthenticationToken token;
try {
token = (OAuth2AuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
} catch (ClassCastException e) {
throw new OAuthTokenException("Token cannot be cast");
}
if (token == null)
throw new OAuthTokenException("No valid OAuth Token");
OAuth2AuthorizedClient authorizedClient = this.oAuth2AuthorizedClientRepository.loadAuthorizedClient(
token.getAuthorizedClientRegistrationId(),
SecurityContextHolder.getContext().getAuthentication(),
(HttpServletRequest) VaadinService.getCurrentRequest());
return GraphServiceClient.builder().authenticationProvider(new GraphAuthenticationProvider(authorizedClient))
.buildClient();
}
The Authentication Provider:
public class GraphAuthenticationProvider extends BaseAuthenticationProvider {
private final OAuth2AuthorizedClient graphAuthorizedClient;
/**
* Set up the GraphAuthenticationProvider. Allows accessToken to be
* used by GraphServiceClient through the interface IAuthenticationProvider
*
* #param graphAuthorizedClient OAuth2AuthorizedClient created by AAD Boot starter. Used to surface the access token.
*/
public GraphAuthenticationProvider(OAuth2AuthorizedClient graphAuthorizedClient) throws OAuthTokenException {
if (graphAuthorizedClient == null)
throw new OAuthTokenException("No valid client!");
this.graphAuthorizedClient = graphAuthorizedClient;
}
/**
* This implementation of the IAuthenticationProvider helps injects the Graph access
* token into the headers of the request that GraphServiceClient makes.
*
* #param requestUrl the outgoing request URL
* #return a future with the token
*/
#Override
public CompletableFuture<String> getAuthorizationTokenAsync(final URL requestUrl){
return CompletableFuture.completedFuture(graphAuthorizedClient.getAccessToken().getTokenValue());
}
}
My Configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Lazy
#Autowired
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.headers().frameOptions().disable().and()
.authorizeRequests()
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
.antMatchers("/login**", "/error**", "/api/**").permitAll()
.anyRequest().authenticated()
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/login")
.and()
.oauth2Login()
.userInfoEndpoint()
.oidcUserService(oidcUserService);
}
...
#Bean
public OAuth2AuthorizedClientRepository authorizedClientRepository() {
return new HttpSessionOAuth2AuthorizedClientRepository();
}
}

you can use refresh_token to refresh old token , below steps can be help to get the refresh token, please refer the Microsoft documentation .

As we can see in your code the graph version is old , so try to you update the version to 5.17.0 firs,please follow the docs - https://github.com/microsoftgraph/msgraph-sdk-java
Then we recommend you to use MSAL for authentication , docs https://github.com/AzureAD/microsoft-authentication-library-for-java ,
and then try to acquire the token - refer docs ,

Related

Spring Security: CSRF protection for webflux project. Not able to see cookies that spring sets [duplicate]

I have a Webflux application secured by Spring Security in which the CSRF protection is enabled by default. However, I can't get the the CSRF token to be saved in the session.
After some investigations, I noticed that it may come from WebSessionServerCsrfTokenRepository.class. In this class, there is the generateToken method that should create a Mono from a generated CSRF token:
public Mono<CsrfToken> generateToken(ServerWebExchange exchange) {
return Mono.fromCallable(() -> {
return this.createCsrfToken();
});
}
private CsrfToken createCsrfToken() {
return new DefaultCsrfToken(this.headerName, this.parameterName, this.createNewToken());
}
private String createNewToken() {
return UUID.randomUUID().toString();
}
However, even if the generateToken method is called by the CsrfWebFilter, the createCsrfToken method is never called, and I never get the CSRF token to be saved in the session. My breakpoint never goes into the createCsrfToken method, that could mean that it is never subscribed.
I'm running on Netty with Spring Boot 2.1.0.RELEASE and Spring Security 5.1.1.RELEASE.
I reproduced the problem on an empty sample application simply containing the following dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
Am I missing something or is there a problem with Spring Security?
UPDATE
From further investigations, I think that the problem comes from this method in the Spring Security CsrfWebFilter.class:
private Mono<Void> continueFilterChain(ServerWebExchange exchange, WebFilterChain chain) {
return Mono.defer(() -> {
Mono<CsrfToken> csrfToken = this.csrfToken(exchange);
exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken);
return chain.filter(exchange);
});
}
Here, the csrfToken Mono is never subscribed. When I rewrite the filter this way, I manage to get the token added in the session:
private Mono<Void> continueFilterChain(ServerWebExchange exchange, WebFilterChain chain) {
return Mono.defer(() -> {
return this.csrfToken(exchange)
.map(csrfToken -> exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken))
.then(chain.filter(exchange));
});
}
However, the _csrf parameter is never added in my Thymeleaf model, so the following test doesn't work:
<form name="test-csrf" action="/test" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<button type="submit">Escape!</button>
</form>
In case somebody runs into this issue, I had a discussion with someone from Spring Team.
It is actually intended not to subscribe to the csrfToken Mono directly to do it only when needed. It is the responsability of the developper to trigger the subscription in the application, and there are two ways of doing it.
Method 1 : explicit subscription.
Provide a subscription via #ModelAttribute in some #ControllerAdvice or abstract controller class:
#ModelAttribute(CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME)
public Mono<CsrfToken> getCsrfToken(final ServerWebExchange exchange) {
return exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty());
}
Method 2: use Thymeleaf to handle CSRF automatically.
Make sure you have the following dependencies in your POM to use Thymeleaf along with Spring Security:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
This will add the CSRF token to your model automatically, and pass it through the forms hidden inputs (still need to add it in the headers for POST Ajax request).
For more information, here's the issue I opened at Spring: https://github.com/spring-projects/spring-security/issues/6046
When I inspect the attributes property of exchange, I don't see the CsrfToken there, so I'm not able to subscribe to it. Upon inspecting the cookies in the browser session, I did see XSRF-TOKEN.
I ended up using this as a filter in our project:
#Component
#Slf4j
class CsrfHeaderFilter implements WebFilter {
#Override
Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
def xsrfToken = exchange.getRequest().getCookies().getFirst("XSRF-TOKEN").value
exchange = exchange.mutate().request({
it.header("X-XSRF-TOKEN", xsrfToken)
}).build()
log.debug(xsrfToken)
chain.filter(exchange)
}
}
Then modified my security configuration so that it placed this filter before the CsrfWebFilter:
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
class WebSecurityConfiguration {
#Autowired
CsrfHeaderFilter csrfHeaderFilter
#Bean
SecurityWebFilterChain SecurityWebFilterChain(ServerHttpSecurity http) {
http
.addFilterBefore(csrfHeaderFilter, SecurityWebFiltersOrder.CSRF)
.httpBasic().disable()
.formLogin().disable()
.oauth2Login().and()
.csrf({
it.csrfTokenRepository(new CookieServerCsrfTokenRepository())
})
.authorizeExchange()
.pathMatchers("/actuator/health").permitAll()
.pathMatchers("/**").authenticated()
.and().build()
}
}
This worked for us because one of the places CsrfWebFilter expects to find the token is in the request header X-XSRF-TOKEN.
create the webfilter bean as below in the Springboot application class.
#Bean
public WebFilter addCsrfTokenFilter() {
return (exchange, next) -> Mono.just(exchange)
.flatMap(ex -> ex.
<Mono<CsrfToken>>getAttribute(CsrfToken.class.getName()))
.doOnNext(ex -> {
})
.then(next.filter(exchange));
}

how microservice use jwt to communicate in springboot

I am using microservice in spring boot and i want to use jwt and oauth2 to access the server.But i just wonder that how microservice other than api gateway get the data in the jwt (id or name) .It seems that it is so tedious to set a decoder in every microservice.
I am thinking that is it possible to decode and add the data at the httprequest and route it the other microservice in apigateway.But it seems that i cant find a setheader method in webflux filter security.
Jwt filter:
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String authorizationheader= exchange.getRequest().getHeaders().get("Authorization").toString();
String token;
String Username = null;
String iss=null;
//check have tokem
if(authorizationheader !=null&& authorizationheader.startsWith("Bearer ")){
token=authorizationheader.substring(7);
Username=jwtDecoder.decode(token).getSubject();
iss= String.valueOf(jwtDecoder.decode(token).getIssuer());
} //verify by check username and iss
if(Username!=null && iss!=null&& SecurityContextHolder.getContext().getAuthentication()==null){
if(iss.equals("http://localhost:8080")){
UserDetails userDetails=new User(Username,null,null);
UsernamePasswordAuthenticationToken AuthenticationToken=new UsernamePasswordAuthenticationToken(
userDetails,null,userDetails.getAuthorities());
//set username and id to the request
SecurityContextHolder.getContext().setAuthentication(AuthenticationToken);
}
}
return chain.filter(exchange);
}
Securityfilter bean:
#Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity httpSecurity) throws Exception {
return httpSecurity
/*.csrf(csrf -> csrf.ignoringRequestMatchers("/Job/getRegionjobs/**",
"/Job/getalljobs","/login/oauth2/code/google"))*/
.csrf(csrf -> csrf.disable())
.authorizeExchange(auth->auth.anyExchange().authenticated())
.addFilterBefore(jwtFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt)
//.sessionManagement(session-> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.httpBasic(withDefaults())
.build();
}
Please help
It seems that it is so tedious to set a decoder in every microservice.
No, it is not. Configuring a resource-server (OAuth2 REST API) can be as simple as:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<!-- replace "webmvc" with "weblux" if your micro-service is reactive -->
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<version>6.0.12</version>
</dependency>
#Configuration
#EnableMethodSecurity
public static class WebSecurityConfig { }
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/realm1
com.c4-soft.springaddons.security.issuers[0].authorities.claims=realm_access.roles,ressource_access.some-client.roles,ressource_access.other-client.roles
com.c4-soft.springaddons.security.cors[0].path=/some-api
If you don't want to use my starters, you can still create your own copying from it (it is open source and each is composed of 3 files only).
If you don't implement access-control in each micro-service, then you can't bypass the gateway and it's going to be a hell to implement rules involving the resources itself (like only user who created that kind of resource can modify it).

Get Keycloak AccessToken in controller

I am trying to get the access token after a successfully login, and after a lot of researched I got to this post, how to get Keycloak access token and store it in db for spring boot?, where it's said to make a Keycloak login manually, but I don't know how. The link to the document in the comments doesn't work anymore.
I also tired to get it thought the headers, but no Authorization header is sent.
String authHeader = servletRequest.getHeader("Authorization"); //returns null
if (authHeader != null
&& !authHeader.isEmpty()
&& authHeader.startsWith("Bearer")) {
String accessToken = authHeader.substring("Bearer".length()).trim();
if (accessToken != null) {
return new ResponseEntity(true, HttpStatus.OK);
} else {
return new ResponseEntity(false, HttpStatus.UNAUTHORIZED);
}
} else {
log.error("Invalid authorization header. ");
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
I tried also to get it throught the Principal, but I get an error:
java.lang.ClassCastException: class org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken cannot be cast to class org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder
.currentRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
KeycloakAuthenticationToken userPrincipal = (KeycloakAuthenticationToken) request.getUserPrincipal();
SimpleKeycloakAccount userPrincipalDetails = (SimpleKeycloakAccount) userPrincipal.getDetails();
return userPrincipalDetails
.getKeycloakSecurityContext()
.getToken();
The same error is displayed if I try:
KeycloakAuthenticationToken authenticatication = (KeycloakAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
I can login/logout, but I cannot get the accessToken...
#KeycloakConfiguration
#EnableWebSecurity
#Order(1)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
private final KeycloakLogoutHandler keycloakLogoutHandler;
public SecurityConfig(KeycloakLogoutHandler keycloakLogoutHandler) {
this.keycloakLogoutHandler = keycloakLogoutHandler;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/somepage/*").permitAll()
.anyRequest().authenticated();
http.oauth2Login()
.and()
.logout()
.addLogoutHandler(keycloakLogoutHandler)
.logoutSuccessUrl("/");
}
}
Any ideas?
First, do not use Keycloak libs for Spring: it is (very) deprecated. Instead use:
spring-boot-starter-oauth2-resource-server if your app is a REST API. Instruction in the first of this series of tutorials.
spring-boot-starter-oauth2-client if your app serves UI (with thymeleaf or whatever)
The exact type of Authentication returned by SecurityContextHolder.getContext().getAuthentication() depends on your app being a client or a resource-server and it being configured with JWT decoder or token introspection, but all expose the Bearer access-token string. Just get it from there.
In the case your app is a resource-server (REST API), you might use one of the spring-boot starters I maintain for spring-boot-starter-oauth2-resource-server auto-configuration from properties. This quite simplifies the configuration compared to the first tutorial linked before:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<!-- replace "webmvc" with "weblux" if your app is reactive -->
<!-- replace "jwt" with "introspecting" to use token introspection instead of JWT decoding -->
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<!-- this version is to be used with spring-boot 3.0.0-RC1, use 5.x for spring-boot 2.6.x or before -->
<version>6.0.4</version>
</dependency>
#EnableMethodSecurity
public static class WebSecurityConfig { }
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/master
com.c4-soft.springaddons.security.issuers[0].authorities.claims=realm_access.roles,ressource_access.some-client.roles
com.c4-soft.springaddons.security.cors[0].path=/some-api
The Authentication for authorized requests will the be JwtAuthenticationToken:
#RestController
#RequestMapping("/demo")
public class DemoController {
#GetMapping("/access-token")
#PreAuthorize("isAuthenticated()")
public String getAccessToken(JwtAuthenticationToken auth) {
return auth.getToken().getTokenValue();
}
}
Disclaimer: be carefull with what you do with access-tokens and who you expose it to. If it leaks, it might be used for identity usurpation.

Securing Spring backed, when fronted is secured with adal auth

So we have this application that has two parts
Front end ui - using Angular JS
Back end - rest api using Spring boot
Front end is secured using microsoft-adal-angular6 library to authenticate with Azure Active Directory
My question is what is the right way to secure the Back end so only active directory authenticated users can access the API?
I would suggest to use a jwt token, that is attached to every request to your backend as 'Authorization' header. The token consists of three parts, where one is holding data about the user and one a signature, so you can validate that your token was created by a trusted source. The data part can look something like this:
{
"iss": "Online JWT Builder",
"iat": 1580283510,
"exp": 1611819510,
"aud": "www.example.com",
"sub": "jrocket#example.com",
"GivenName": "Johnny",
"roles": ["PROJECT_MANAGER", "ADMIN"]
"scope": "WEBAPP"
}
On the spring side, I would suggest using Spring Security 5 with the latest configuration.
You will need those dependencies:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.x.x.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>5.x.x.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
<version>5.x.x.RELEASE</version>
Now you can enable security and configure it with a configuration class. Inside you can define which scope the request must have, how to sign the token and with route should be public or secured.
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
String jwkSetUri;
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.cors().disable()
.authorizeRequests()
.antMatchers(("/webapp/**")).hasAuthority("SCOPE_WEBAPP")
.antMatchers(("/admin/**")).hasRole("ADMIN")
...
.and()
.oauth2ResourceServer().jwt(jwtConfigurer -> jwtConfigurer.decoder(jwtDecoder())
.jwtAuthenticationConverter(new CustomJwtAuthenticationConverter()))
...
// #formatter:on
}
#Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
}
}
I had to use a custom JwtConverter to get the roles from the jwt, but it depends on how you do it, I guess.
public class CustomJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
private final JwtGrantedAuthoritiesConverter defaultGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
public CustomJwtAuthenticationConverter() {
}
#Override
public AbstractAuthenticationToken convert(#NotNull final Jwt jwt) {
Collection<GrantedAuthority> authorities = Stream
.concat(defaultGrantedAuthoritiesConverter.convert(jwt).stream(), extractResourceRoles(jwt).stream())
.collect(Collectors.toSet());
return new JwtAuthenticationToken(jwt, authorities);
}
private static Collection<? extends GrantedAuthority> extractResourceRoles(final Jwt jwt) {
Collection<String> userRoles = jwt.getClaimAsStringList("roles");
if (userRoles != null)
return userRoles
.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toSet());
return Collections.emptySet();
}
}
This enables you to secure your application on an url basis.
The roles in the jwt, the JwtConverter and the #EnableGlobalMethodSecurity annotation enable you to secure even on a method level.
#Transactional
#PreAuthorize("hasRole('ROLE_PROJECT_MANAGER')")
public Page<Project> findAll(Pageable pageable) {
return projectRepository.findAll(pageable);
}
Azure Active Directory should support jwt, but I don't have experience with this IDP.
What I can't answer is, how you can inject custom claims like roles inside the token and where to get the jwks (Json Web Key Set), which is used to validate the token's signature.

Securing Spring Boot service with keycloak - JWT token

So, I'm using keycloak to secure my services. Client app gets access token from keycloak server and uses it to secure access to Spring boot app. I've configured my Spring Boot application with keycloak properties using bearer-only access type:
keycloak.realm = master
keycloak.realmKey = ...
keycloak.auth-server-url = http://localhost:8080/auth
keycloak.ssl-required = external
keycloak.resource = boot-app
keycloak.bearer-only = true
keycloak.cors = true
Spring boot keycloak starter:
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
And configuring KeycloakWebSecurityConfigurerAdapter:
#Configuration
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter
{
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
#Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception
{
final KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public KeycloakConfigResolver keycloakConfigResolver()
{
return new KeycloakSpringBootConfigResolver();
}
/**
* Defines the session authentication strategy.
*/
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy()
{
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected void configure(final HttpSecurity http) throws Exception
{
super.configure(http);
http
.authorizeRequests()
.antMatchers(
"/v2/api-docs",
"/configuration/ui",
"/swagger-resources",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**",
"/swagger-resources/configuration/ui",
"/swagge‌​r-ui.html",
"/swagger-resources/configuration/security").permitAll()
.antMatchers("/*").hasRole("user")
.anyRequest().authenticated();
}
}
Now, everything works fine. My question is: Bearer token is JWT token, all you need to decode it (and verify access) is public key, which is
keycloak.realmKey
Why would you need other settings, specificaly:
keycloak.auth-server-url
Isn't public key everything you need?
Thanks in advance
Indeed for a bearer-only you could wonder why the KC URL is needed but since a few KC versions the realmKey is not mandatory anymore since we are use key rotation. It means that your app will retrieve dynamically the public key from the KC server using the auth-server-url property.
If you have a spring-boot application, latest spring-security will handle it neatly. All you need is to define jwks-uri in the application properties and the required dependencies.
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:8780/auth/realms/my-realm/protocol/openid-connect/certs
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
<version>5.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>5.3.3.RELEASE</version>
</dependency>
Note that, you can also use issuer uri instead of jwks, if you want
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8780/auth/realms/my-realm

Resources