I was trying to follow the same multitenancy setup as the official documentation, but with credentials in my keycloak.json file, but keycloak does not seem to authorize access token properly.
It appears to call the resolve function of my customized KeycloakConfigResolver multiple times. It does authorize the token at the first time it calls to resolve, but it then shows 'Failed to verify token' every time after that.
And at the end, it fails to verify my access token.
Using customized keycloakconfigresolver in realms with credentials result in 403 Forbidden error.
#Configuration
public class HeaderBasedConfigResolver implements KeycloakConfigResolver {
#Override
public KeycloakDeployment resolve(Request request) {
System.out.println("Start Header-based resolving");
String realm = request.getHeader("realm");
File file = new File("realm_json/" + realm + "-keycloak.json");
InputStream is = null;
try {
is = new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
return KeycloakDeploymentBuilder.build(is);
}
}
And the following is the code to initialize the KeycloakConfigResolver bean.
#Bean
#ConditionalOnMissingBean(HeaderBasedConfigResolver.class)
public KeycloakConfigResolver keycloakConfigResolver() {
return new HeaderBasedConfigResolver();
}
But when I initialize the KeycloakConfigResolver Bean with the default Implementation KeycloakSpringBootConfigResolver, it authenticates normally. I wonder how I could implement multi-tenancy in keycloak using credentials
Keycloak adapters for spring are deprecated. Instead, use spring-boot-starter-oauth2-resource-server (to secure #RestController and #Controller with #ResponseBody) or spring-boot-starter-oauth2-client (to secure server-side rendered UI with Thymeleaf, JSF or whatever). If you don't know how, you can refer to Spring-boot official doc or to those tutorials I wrote.
To configure multi-tenancy with spring-boot-starter-oauth2-resource-server, you'll have to provide a custom JwtIssuerAuthenticationManagerResolver bean capable of resolving the right authnetication-manager based on the iss claim in the access-token:
http.oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver))
In the same repo as the one holding the tutorials, I propose thin wrappers around spring-boot-starter-oauth2-resource-server. Those designed for JWT decoder support multi-tenancy definition from properties:
<!-- instead of keycloak adapters for Spring -->
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<version>6.0.8</version>
</dependency>
#EnableMethodSecurity
public static class SecurityConfig {
}
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,resource_access.spring-addons-public.roles,resource_access.spring-addons-confidential.roles
com.c4-soft.springaddons.security.issuers[1].location=https://localhost:8443/realms/realm2
com.c4-soft.springaddons.security.issuers[1].authorities.claims=realm_access.roles,resource_access.spring-addons-public.roles,resource_access.spring-addons-confidential.roles
com.c4-soft.springaddons.security.cors[0].path=/greet
com.c4-soft.springaddons.security.permit-all=/actuator/health/readiness,/actuator/health/liveness
Related
I am building an application where authentication is done by spring security for HTTP handlers, for HTTP I've disabled csrf protection, and now I want to disable csrf for spring web socket, but I can't figure out how to accomplish this, I've already tried many different approaches but no one seems to be working. If it is impossible to disable csrf for WebSocket how to get a csrf token? (I tried setting up the csrf endpoint to obtain a token but it is not work, and all tutorials I've found are outdated)
Thanks in advance!
web socket security config:
#Configuration
#EnableWebSocketSecurity
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Bean
AuthorizationManager<Message<?>> messageAuthorizationManager(
MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages.anyMessage().permitAll();
return messages.build();
}
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
security config:
#Configuration
#EnableWebSecurity(debug = true)
public class SecurityConfig {
#Autowired
private JwtFilter jwtFilter;
#Bean
SecurityFilterChain securityFilterChain(HttpSecurity HTTP) throws Exception {
return http.addFilterBefore(jwtFilter, BasicAuthenticationFilter.class)
.cors(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/authenticate").permitAll()
.requestMatchers("/createchatroom").authenticated()
.requestMatchers("/public/*").permitAll()
.requestMatchers("/private/*").permitAll()
.requestMatchers("/ws/**").authenticated()
.requestMatchers("/register").permitAll()
.requestMatchers("/csrf").authenticated()
.requestMatchers("/addEmployeeToFavorites").hasAnyAuthority(EMPLOYEE.name(),
ADMIN.name())
.requestMatchers("/addChatRoomToFavorites")
.hasAnyAuthority(EMPLOYEE.name(), ADMIN.name())
.requestMatchers("/home").hasAnyAuthority(EMPLOYEE.name(), ADMIN.name()))
.build();
}
}
By default, Spring Security requires the CSRF token in any CONNECT message type. This ensures that only a site that has access to the CSRF token can connect. Since only the same origin can access the CSRF token, external domains are not allowed to make a connection.
Spring Security 4.0 has introduced authorization support for WebSockets through the Spring Messaging abstraction.
In Spring Security 5.8, this support has been refreshed to use the AuthorizationManager API.
To configure authorization using Java Configuration, simply include the #EnableWebSocketSecurity annotation and publish an AuthorizationManager<Message<?>> bean or in XML use the use-authorization-manager attribute. One way to do this is by using the AuthorizationManagerMessageMatcherRegistry to specify endpoint patterns like so:
#Configuration
#EnableWebSocketSecurity
public class WebSocketSecurityConfig {
#Bean
AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages
.simpDestMatchers("/user/**").authenticated()
return messages.build();
}
}
Any inbound CONNECT message requires a valid CSRF token to enforce the Same Origin Policy.
The SecurityContextHolder is populated with the user within the simpUser header attribute for any inbound request.
Our messages require the proper authorization. Specifically, any inbound message that starts with "/user/" will require ROLE_USER. Additional details on authorization can be found in [websocket-authorization]
At this point, CSRF is not configurable when using #EnableWebSocketSecurity, though this will likely be added in a future release.
To disable CSRF, instead of using #EnableWebSocketSecurity, you can use XML support or add the Spring Security components yourself, like so:
Java
#Configuration
public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
AuthorizationManager<Message<?>> myAuthorizationRules = AuthenticatedAuthorizationManager.authenticated();
AuthorizationChannelInterceptor authz = new AuthorizationChannelInterceptor(myAuthorizationRules);
AuthorizationEventPublisher publisher = new SpringAuthorizationEventPublisher(this.context);
authz.setAuthorizationEventPublisher(publisher);
registration.interceptors(new SecurityContextChannelInterceptor(), authz);
}
}
web.xml
<websocket-message-broker use-authorization-manager="true" same-origin-disabled="true">
<intercept-message pattern="/**" access="authenticated"/>
</websocket-message-broker>
On the other hand, if you are using the legacy-websocket-configuration and you want to allow other domains to access your site, you can disable Spring Security’s protection. For example, in Java Configuration you can use the following:
#Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
...
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
References
WebSocket Security - Spring Security
I am pretty new in Spring Security and I am working on a Spring Boot project that uses Basic Authentication in order to protect some APIs. I am starting from an existing tutorial code (a Udemy course) trying to adapt it to my own use cases.
In this project I have this SecurityConfiguration used to configure the basic authentication.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
private static String REALM = "REAME";
private static final String[] USER_MATCHER = { "/api/utenti/cerca/**"};
private static final String[] ADMIN_MATCHER = { "/api/utenti/inserisci/**", "/api/utenti/elimina/**" };
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable()
.authorizeRequests()
.antMatchers(USER_MATCHER).hasAnyRole("USER")
.antMatchers(ADMIN_MATCHER).hasAnyRole("ADMIN")
.anyRequest().authenticated()
.and()
.httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint()).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public AuthEntryPoint getBasicAuthEntryPoint()
{
return new AuthEntryPoint();
}
/* To allow Pre-flight [OPTIONS] request from browser */
#Override
public void configure(WebSecurity web)
{
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
#Bean
public BCryptPasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
};
#Bean
#Override
public UserDetailsService userDetailsService()
{
UserBuilder users = User.builder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users
.username("ReadUser")
.password(new BCryptPasswordEncoder().encode("BimBumBam_2018"))
.roles("USER").build());
manager.createUser(users
.username("Admin")
.password(new BCryptPasswordEncoder().encode("MagicaBula_2018"))
.roles("USER", "ADMIN").build());
return manager;
}
}
So from what I have understand:
Here it id defined the list of API that can be accessed by a nornmal user and the list of API that can be accessed by and admin user:
private static final String[] USER_MATCHER = { "/api/utenti/cerca/**"};
private static final String[] ADMIN_MATCHER = { "/api/utenti/inserisci/**", "/api/utenti/elimina/**" };
Into the previous configure() method basically it is stating that the API URL matching with the USER_MATCHER are accessible by logged user having role USER while API having URL matching ADMIN_MATCHER are accessible by logged user having role ADMIN. Is this interpretation correct?
Finnally the UserDetailsService bean simply define two users: one belonging to the USER "group" and the other one belonging to both the USER and ADMIN "group".
So, if I well understood, the first one will be aple only to access to the API having enpoint URL /api/utenti/cerca/** while the second one will be able to access also to the APIs having endpoint URLs /api/utenti/inserisci/** and /api/utenti/elimina/**
Is it my reasoning correct?
And now my doubt: into a controller class of this project I defined this method:
#RestController
#RequestMapping("api/users")
#Log
public class UserController {
#Autowired
UserService userService;
//#Autowired
//private BCryptPasswordEncoder passwordEncoder;
//#Autowired
//private ResourceBundleMessageSource errMessage;
#GetMapping(value = "/test", produces = "application/json")
public ResponseEntity<String> getTest() throws NotFoundException {
log.info(String.format("****** getTest() START *******"));
return new ResponseEntity<String>("TEST", HttpStatus.OK);
}
..............................................................................................................
..............................................................................................................
..............................................................................................................
}
As you can see this method handling a GET request toward the localhost:8019/api/users/test endpoint.
This endpoint URL is not in any of the previous two list related the protected endpoint (it is not into the USER_MATCHER list neither into the ADMIN_MATCHER list. So I expected that simply this endpoint was not protected and accessible to everyone. But performing the previous request using PostMan, I obtain this error message:
HTTP Status 401 : Full authentication is required to access this resource
So basically it seems to me that also if this endpoint not belong to any protected endpoint list it is in some way protected anyway (it seems to me that at least the user must be authenticated (infact trying both the previous user I can obtain the expected output, so it should mean that the endpoint is not protected by the user rule but it is protected againts not authenticated access).
Why? Maybe it depende by the previous configure() method settings, in particular this line?
.anyRequest().authenticated()
In case is it possible to disable in some way to implement something like this:
If a called endpoint belong to one of the previous two lists (USER_MATCHER and ADMIN_MATCHER) --> the user must be authenticated and need to have the correct role.
If a called endpoint not belong to one of the previous lists --> everybody can access, also not authenticated user.
This approach make sense or am I loosing something?
I take this occasion to ask you also another information: do you think that it is possible to configure Spring security of this specific project in order to protect some specific endpoints using the basic authentication and some other specific endpoints using the JWT authentication.
Sone further notes to explain why this last question. This project is a microservice that at the moment is used by another microservice (used to generate JWT token) in order to obtain user information. (the other microservice call an API of this project in order to receive user information so it can generate a JWT token that will be used in my application. The comunication between these 2 microservice must use basic authentication).
Since this project contains all the entity classes used to map the tables related to the users on my DB, my idea was to use this project also for generic user management, so it could include functionality like: add a brand new user, changes information of an existing user, obtain the list of all the users, search a specific user, and so on.
These new APIs will be protected by JWT token because each API can be called from a specific user type having different privileges on the system.
So I am asking if in a situation like this I can add without problem 2 different types of authentication (basic authentication for the API that retrieve a user so the other microservice can obtain this info) and JWT authentication for all the other APIs. It make sense or is it better to create a brand new project for a new user management microservice?
So, if I well understood, the first one will be aple only to access to the API having enpoint URL /api/utenti/cerca/** while the second one will be able to access also to the APIs having endpoint URLs /api/utenti/inserisci/** and /api/utenti/elimina/**
Yes.
Why? Maybe it depende by the previous configure() method settings, in particular this line?
Yes, when using .anyRequest().authenticated(), any requests that have not been matched will have to be authenticated.
If a called endpoint not belong to one of the previous lists --> everybody can access, also not authenticated user.
You can achieve this by doing anyRequest().permitAll(). But this is not so secure because you are allowing access to every other endpoints, instead you should stay with anyRequest().authenticated() and allow access to specific endpoints manually, like so:
http
.authorizeRequests()
.antMatchers(USER_MATCHER).hasAnyRole("USER")
.antMatchers(ADMIN_MATCHER).hasAnyRole("ADMIN")
.antMatchers("/api/users/test").permitAll()
.anyRequest().authenticated()
...
I am trying to familiarize myself with Spring Security, in particular migrating from Spring Security OAuth to Soring Security (as in the following example/guide https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide).
However, I am seeming to only get 403 Forbidden errors. I am accessing from Postman and am using my company's existing OAuth server. I am able to get a token from the auth server, so I know I have those credentials correct and I have verified what roles the OAuth user has.
I am using the following dependencies:
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation 'org.springframework.boot:spring-boot-starter-web'
This is the simple endpoint I am attempting to access:
#RestController
public class AppController
{
#GetMapping("/hello")
public String hello()
{
return "hello";
}
}
This is my application.yml file:
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: <<company-website-here>>/uaa/oauth/token_keys
And this is my security configuration class:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/hello").hasRole("MY_ROLE")
.anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
}
}
I can't seem to figure out why I seem to only get 403 errors. I have also tried adding #EnableWebSecurity to the security config class, but that didn't make a difference. Adding the auth server URL explicitly to the server and/or manually creating a JwtDecoder didn't do the trick either; it appears the url is being automatically picked up from the yml file, based on its property name.
I am trying to move away from using the org.springframework.security.oauth.boot dependency and ResourceServerConfigurerAdapter.
I had to add my own converter like so:
private static class JwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken>
{
private final Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter;
public JwtAuthenticationConverter()
{
this.jwtGrantedAuthoritiesConverter = jwt -> jwt
.getClaimAsStringList("authorities")
.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
#Override
public final AbstractAuthenticationToken convert(#NonNull Jwt jwt)
{
Collection<GrantedAuthority> authorities = jwtGrantedAuthoritiesConverter.convert(jwt);
return new JwtAuthenticationToken(jwt, authorities, jwt.getClaimAsString("client_id"));
}
}
Then had to add this to the main security config:
.jwtAuthenticationConverter(new JwtAuthenticationConverter());
There may be a couple of things happening.
As you're migrating to Spring Security 5, you may need to extract your authorities manually. Check this post and it's correct answer.
You are using hasRole function and this will append "ROLE_" before your authority/role. So if the role on your JWT token is not ROLE_JWT_ROLE you should use
hasTransaction.
I try to implement OAuth2 with Spring Security and I have studied the samples provided by Spring in the following Github:
https://github.com/spring-projects/spring-security-oauth/blob/master/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/config/WebMvcConfig.java
This OAuth2 sample is divided in two projects :
tonr: OAuth2 client (OAuth2Client) and
sparklr: OAuth2 provider (ResourceServer & AuthorizationServer)
Tonr allows to show photos from Sparklr and also includes a Facebook API client to list friends of an account.
It seems that once got from one provider, the same token is sent to all OAuth2 providers, even if the token doesn't come from the called provider.
Steps:
I log in tonr2 (localhost:8080/tonr2/login.jsp)
I go to Sparklr photos and log in sparklr2 (localhost:8080/tonr2/sparklr/photos & localhost:8080/sparklr2/login.jsp)
I approve the scope Read and I see photos: OK
Then I go to Facebook friends page : localhost:8080/tonr2/facebook/info
The sparklr token is sent to Facebook (visible in debug logs), and obviously Facebook returns a 400 Bad Request Error.
If now, I log out from tonr, click directly to Facebook friends page and log in tonr again, it is working; a token is asked to Facebook and access is granted.
So the same OAuth2ClientContext and same token are kept from Sparklr to Facebook.
Question:
How to separate OAuth2ClientContext to keep the token with its respective resource server?
I tried to instanciate a different OAuth2ClientContext bean for facebookRestTemplate, but the OAuth2 flow is broken with:
#Bean(name = "facebookClientContext")
public OAuth2ClientContext facebookClientContext() {
return new DefaultOAuth2ClientContext();
}
#Bean
public OAuth2RestTemplate facebookRestTemplate(#Qualifier("facebookClientContext") OAuth2ClientContext clientContext) {
...
I had the same problem. I solved it the same as you did except that you should:
inject a AccessTokenRequest (request-scoped) in the constructor of your DefaultOAuth2ClientContext. (#Resource injection is necessary in this verion of Spring because AccessTokenRequest is a Map).
session-scope your facebookClientContext to not share the tokens among differents users.
See OAuth2ClientConfiguration as a guideline.
Modify your WebMvcConfig$ResourceConfiguration:
#Resource(name = "accessTokenRequest")
private AccessTokenRequest accessTokenRequest;
#Bean
#Qualifier("facebookClientContext")
#Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public DefaultOAuth2ClientContext facebookClientContext() {
return new DefaultOAuth2ClientContext(accessTokenRequest);
}
#Bean
public OAuth2RestTemplate facebookRestTemplate(
#Qualifier("facebookClientContext") OAuth2ClientContext clientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(facebook(), clientContext);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.valueOf("text/javascript")));
template.setMessageConverters(Arrays.<HttpMessageConverter<?>>asList(converter));
return template;
}
#Bean
public OAuth2RestTemplate sparklrRestTemplate(
#Qualifier("oauth2ClientContext") OAuth2ClientContext clientContext) {
return new OAuth2RestTemplate(sparklr(), clientContext);
}
#Bean
public OAuth2RestTemplate sparklrRedirectRestTemplate(
#Qualifier("oauth2ClientContext") OAuth2ClientContext clientContext) {
return new OAuth2RestTemplate(sparklrRedirect(), clientContext);
}
We are trying to implement a SSO scheme using Azure AD and Spring security.
We found a few leads:
https://github.com/spring-guides/tut-spring-boot-oauth2
https://github.com/Pytry/azure-b2c-oauth2
But none of these tell the full story.
In fact we can't get passed the access token parsing, Spring has a different idea of what the JWT token should be.
Ideally we wouldn't want to write an SSO filter from scratch but override the Token Services to implement custom filtering for starters.
Has anyone successfully implemented this?
Any help would be appreciated.
Update: I found an easier method. Just add a resource parameter after the userAuthorizationUri.
security:
oauth2:
client:
...
userAuthorizationUri: https://login.microsoftonline.com/<<tenantId>>/oauth2/authorize?resource=https://graph.windows.net
...
https://stackoverflow.com/a/45828135/2231168
Original answer
At my office we found a foreign blog post which lead us to the final implementation http://statemachine.hatenablog.com/entry/2016/04/19/155920
As a workaround, you had to add two classes to capture OAuth2RestTemplate and request enhancers. It works with spring boot 1.3.8 which contains spring 4.2.8, we couldn't make it work with higher version.
application.yml:
azure:
resource: https://graph.windows.net
security:
oauth2:
client:
clientId: <<your client id>>
clientSecret: <<your client secret>>
accessTokenUri: https://login.microsoftonline.com/<<tenantId>>/oauth2/token
userAuthorizationUri: https://login.microsoftonline.com/<<tenantId>>/oauth2/authorize
clientAuthenticationScheme: form
scope: openid
resource:
userInfoUri: https://graph.windows.net/me?api-version=1.6
AzureRequestEnhancer:
#Component
public class AzureRequestEnhancer implements RequestEnhancer {
#Value("${azure.resource:null}")
private String aadResource;
#Override
public void enhance(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, MultiValueMap<String, String> form, HttpHeaders headers) {
if (!StringUtils.isEmpty(resource)) {
form.set("resource", aadResource);
}
}
}
AzureRequestEnhancerCustomizer:
#Component
public class AzureRequestEnhancerCustomizer {
#Autowired
private OAuth2RestTemplate userInfoRestTemplate;
#Autowired
private AzureRequestEnhancer azureRequestEnhancer;
#PostConstruct
public void testWiring() {
AuthorizationCodeAccessTokenProvider authorizationCodeAccessTokenProvider = new AuthorizationCodeAccessTokenProvider();
authorizationCodeAccessTokenProvider.setTokenRequestEnhancer(azureRequestEnhancer);
userInfoRestTemplate.setAccessTokenProvider(authorizationCodeAccessTokenProvider);
}
}
The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.
I hope this helps you with the implementation.