Securing Spring Boot service with keycloak - JWT token - spring-boot

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

Related

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.

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

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 ,

Spring security doest not restrict access

I have Spring MVC project and try to add security. My problem is that spring doesn't deny access to pages. I mean if I go to /product page, it will open. I have the following security config:
#Configuration
#EnableWebSecurity
public class SecureConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("a").password("1")
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated();
http.csrf().disable();
}
}
At first, I thought, these methods are not executed by spring context, but then I found that they are executed.
if I understand correctly, this configuration should deny access to all pages, but the opposite happens, I can go to any page (/, /product, /test pages)
My security dependencies:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.4.0</version>
</dependency>
Application class:
public class Application implements WebApplicationInitializer {
Logger logger = LoggerFactory.getLogger(Application.class);
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(WebConf.class, SecureConfig.class);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
dispatcher.addMapping("/*");
dispatcher.setLoadOnStartup(1);
}
}
I solved this problem by adding the following class:
public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}
SpringSecurityFilterChain did not work without this class, that's why security didn't work.

Spring Keycloak Bearer only

Iam developing a angular webapp which is connected to a java (spring framework) backend. Authentication is done via a keycloak server.
On my local machine with the embedded tomcat server the angular application and the spring application runs without errors.
For deployment i need to use the old fashioned way by using an existing tomcat server.
The angular frontend is available in the ROOT directory via http://myurl/
The spring backend is placed as war file and reachable via http://myurl/api/
Everything works on the server except the authentication part.
Angular app is able to login via redirect etc. and gets an access token.
This token is transmitted on a request to the spring backend.
But the backend return a not authorized message.
Any help is apriciated!
Message is:
Unable to authenticate using the Authorization header
I have created a SecurityConfig class:
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(
AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider
= keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(
new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(
new SessionRegistryImpl());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/*")
.authenticated()
.anyRequest()
.permitAll();
}
}
Added this line to the application properties
keycloak
keycloak.auth-server-url=https://authserver.net/auth
keycloak.realm=myRealm keycloak.bearer-only=true
keycloak.resource=myclient
keycloak.cors=true
And added this dependancies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>1.5.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>3.3.0.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Disabling the csrf token solved this issue.
Example:
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/*")
.authenticated()
.anyRequest()

OAuth2AccessToken not set in Oauth2ClientContext after login

I'm having some difficulties to get client token relay working with Spring Boot 2.0.0.M7 and Spring Cloud Finchley M5. Please find the example code on github: https://github.com/hansvanbeneden/oauth-example
I have configured the oauth2Login like this:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2Login()
.and()
.csrf().disable();
}
}
and the client registration repo like this:
#Configuration
#EnableOAuth2Client
public class OAuth2LoginConfig {
#Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(this.myWebsiteClientRegistration());
}
private ClientRegistration myWebsiteClientRegistration() {
return ClientRegistration
.withRegistrationId("myauth")
...
.build();
}
#Bean
public OAuth2RestOperations restOperations(OAuth2ClientContext oauth2ClientContext) {
return new OAuth2RestTemplate(resource(), oauth2ClientContext);
}
private OAuth2ProtectedResourceDetails resource() {
ClientRegistration myauthClient = myWebsiteClientRegistration();
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
resource.setScope(new ArrayList<>(myauthClient.getScopes()));
...
return resource;
}
}
I would expect that the oauth2Login somehow sets the OAuth2AccessToken in the Oauth2ClientContext for the OAuth2RestTemplate to use it. But apparently this is not the case, because a UserRedirectRequiredException is thrown when I use the OAuth2RestTemplate.
Is there some magic annotation that I'm missing?
Can someone please send me in the right direction?
Please feel free to correct me if i'm wrong, but basically this is how I understand the issue:
Spring Boot 2.0 dropped support for spring-security-oauth2, since it now has it's own OAuth support (documented here: https://docs.spring.io/spring-security/site/docs/5.0.0.BUILD-SNAPSHOT/reference/htmlsingle/#jc-oauth2login)
This means you can't use the new oauth2Login configuration and expect the Oauth2RestTemplate to be able to use the oauth tokens set by oauth2Login
The following project was setup to allow the use of spring-security-oauth2 features in Spring Boot 2.0: https://docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/
To solve my issue I added the spring-security-oauth2-autoconfigure dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</dependency>
I removed the oauth2Login and added the EnableOauth2Ssso from the WebSecurityConfig:
#Configuration
#EnableOAuth2Sso
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
}
Then my Oauth2RestTemplate was able to find the OAuthToken that was acquired by logging in.
#Bean
public RestOperations restOperations(OAuth2ProtectedResourceDetails resourceDetails,
OAuth2ClientContext clientContext) {
return new OAuth2RestTemplate(resourceDetails, clientContext);
}
I have committed this solution on the following branch: https://github.com/hansvanbeneden/oauth-example/tree/implementation-with-spring-security-oauth2-boot

Resources