I am trying to configure Spring Boot OAuth2 with keycloak. Code is working fine when I am using properties from application.properties file. Current config is as follow:
rest.security.issuer-uri=http://172.30.30.172:8080/auth/realms/<REALM_NAME>
security.oauth2.resource.id=test
security.oauth2.resource.token-info-uri=${rest.security.issuer-uri}/protocol/openid-connect/token/introspect
security.oauth2.resource.user-info-uri=${rest.security.issuer-uri}/protocol/openid-connect/userinfo
security.oauth2.resource.jwt.key-value=<PUBLIC KEY>
security.oauth2.client.client-id=<CLIENT ID>
security.oauth2.client.client-secret=<CLIENT SECRET>
security.oauth2.client.user-authorization-uri=${rest.security.issuer-uri}/protocol/openid-connect/auth
security.oauth2.client.access-token-uri=${rest.security.issuer-uri}/protocol/openid-connect/token
security.oauth2.client.scope=openid
security.oauth2.client.grant-type=client_credentials
I want to configure the client config settings in a dynammic manner by fetching the client config from database for each client (its own realm) request. What should be the Java Spring Boot Security configuration for setting the client properties in dynamic manner.
Current Security configuration is as follow:
#Configuration
#EnableWebSecurity
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
#ConditionalOnProperty(prefix = "rest.security", value = "enabled", havingValue = "true")
#Import({SecurityProperties.class})
public class SecurityConfigurer extends ResourceServerConfigurerAdapter{
private ResourceServerProperties resourceServerProperties;
private SecurityProperties securityProperties;
/* Using spring constructor injection, #Autowired is implicit */
public SecurityConfigurer(ResourceServerProperties resourceServerProperties, SecurityProperties securityProperties) {
this.resourceServerProperties = resourceServerProperties;
this.securityProperties = securityProperties;
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(resourceServerProperties.getResourceId());
}
#Override
public void configure(final HttpSecurity http) throws Exception {
http.cors()
.configurationSource(corsConfigurationSource())
.and()
.headers()
.frameOptions()
.disable()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers(securityProperties.getApiMatcher())
.authenticated();
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
if (null != securityProperties.getCorsConfiguration()) {
source.registerCorsConfiguration("/**", securityProperties.getCorsConfiguration());
}
return source;
}
#Bean
public JwtAccessTokenCustomizer jwtAccessTokenCustomizer(ObjectMapper mapper) {
return new JwtAccessTokenCustomizer(mapper);
}
}
I tried to override bean ResourceServerProperties, using following code. However its throwing NoUniqueBeanDefinitionException exception.
#Configuration
#Import({ResourceServerProperties.class})
public class ResourceSecurityProperties {
#Primary
#Bean
ResourceServerProperties resourceServerProperties(){
ResourceServerProperties resourceServerProperties= new ResourceServerProperties("<CLIENT-ID>", "<CLIENT-SECRET>");
ResourceServerProperties.Jwt jwt= resourceServerProperties.new Jwt();
resourceServerProperties.setId("test001001");
resourceServerProperties.setTokenInfoUri("http://172.30.30.172:8080/auth/realms/conf/protocol/openid-connect/token/introspect");
resourceServerProperties.setUserInfoUri("http://172.30.30.172:8080/auth/realms/conf/protocol/openid-connect/userinfo");
jwt.setKeyValue("<PUBLIC KEY>");
resourceServerProperties.setJwt(jwt);
return resourceServerProperties;
}
}
Related
I have a problem with CORS, but only at some versions of Firefox and Safari: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ... (Reason: CORS request did not succeed). At Chrome it's fine for all testing machines. Here's my configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
#EnableAutoConfiguration
public class ApplicationConfig extends WebSecurityConfigurerAdapter {
private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/some_public_urls")
);
private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
TokenAuthenticationProvider provider;
public ApplicationConfig(final TokenAuthenticationProvider provider) {
super();
this.provider = requireNonNull(provider);
}
#Autowired
private Environment env;
#Override
protected void configure(final AuthenticationManagerBuilder auth) {
auth.authenticationProvider(provider);
}
#Override
public void configure(final WebSecurity web) {
web.ignoring().requestMatchers(PUBLIC_URLS);
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.headers()
.and()
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.exceptionHandling()
.defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
.and()
.authenticationProvider(provider)
.addFilterBefore(restAuthenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(PROTECTED_URLS)
.authenticated()
.and()
.cors()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Bean
TokenAuthenticationFilter restAuthenticationFilter() throws Exception {
final TokenAuthenticationFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#Bean
SimpleUrlAuthenticationSuccessHandler successHandler() {
final SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
successHandler.setRedirectStrategy((httpServletRequest, httpServletResponse, s) -> {
// No redirect is required
});
return successHandler;
}
/**
* Disable Spring boot automatic filter registration.
*/
#Bean
FilterRegistrationBean disableAutoRegistration(final TokenAuthenticationFilter filter) {
final FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
#Bean
AuthenticationEntryPoint forbiddenEntryPoint() {
return new HttpStatusEntryPoint(FORBIDDEN);
}
}
Each RestController is annotated with:
#RestController
#CrossOrigin
I could guess that you are performing testing on some old browsers and it doesn't work.
Here is the landscape of CORS support in browsers. Please check it out.
As of mid-2014, approximately 83% of the browsers out there have full
support for CORS, and another 6% have partial support.
If it's the case, you could try some other techniques like
JSON-P or using Proxy Server to make cross-origin requests in older browser.
Ok,
It turned out, that it was certificate issue. We have a certificate bundle (wildcard certificate) and it was placed in wrong order. Some browsers could handle this, some versions of Firefox were blocking it.
I want to create a simple REST API, I am using Angular 6 and Spring Boot. I wanted to implement logging to my app but whenever I try to sign up I get 404 Not Found /login. I have looked up to existing similar issues I found but nothing seems to help me.
Here's my spring security configuration ( I am sure that my sign up form and controller works properly, so I guess the issue lies somewhere down there)
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${allowedOriginAddress}")
private String allowedOriginAddress;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public UserDataDetailsService userDataDetailsService() {
return new UserDataDetailsService();
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(
Arrays.asList(
allowedOriginAddress,
allowedOriginAddress + "/*")
);
configuration.setAllowedMethods(Arrays.asList("GET","POST","DELETE","OPTIONS","PUT"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("*"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
.authorizeRequests()
.anyRequest().permitAll()
.and().csrf().disable()
.formLogin()
.loginPage("/")
.usernameParameter("email")
.loginProcessingUrl("/login")
.failureForwardUrl("/prev")
.successForwardUrl("/next")
.and().authorizeRequests().anyRequest().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDataDetailsService()).passwordEncoder(passwordEncoder());
}
}
#SpringBootApplication
#Configuration
public class WhispererApplication implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(WhispererApplication.class, args);
}
}
I cannot find the authentication configuration in your project. Please try adding the next code:
.anyRequest().authenticated()
I think your code will be:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.cors().and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/public/**").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated().
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
The JWTAuthenticationFilter and JWTAuthorizationFilter are to manage the JWT if you are using that to authenticate the user.
Despite lot of subject, i cant figure out how to authenticate with my angular project to my back with spring boot so i try to post with my setup.
So far, all my authentification is handle by spring boot and work
#Configuration
#EnableOAuth2Sso
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout().clearAuthentication(true)
.logoutSuccessUrl("/")
.permitAll();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://localhost:4200"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
configuration.setAllowedHeaders(Arrays.asList("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
i started a new angular project and try to bind it with angular-oauth2-oidc.
in auth.config.js
import { AuthConfig } from 'angular-oauth2-oidc';
export const authConfig: AuthConfig = {
clientId: 'xxxxxx',
issuer: 'https://accounts.google.com/',
// loginUrl: 'http://localhost:8080',
redirectUri: window.location.origin + '/user.html',
scope: 'openid profile email',
tokenEndpoint: 'https://www.googleapis.com/oauth2/v3/token',
// strictDiscoveryDocumentValidation: false,
userinfoEndpoint: 'http://localhost:8080/user',
// disableAtHashCheck: true,
// nonceStateSeparator: ',',
// clearHashAfterLogin: false,
};
in login.component.ts
import { Component, OnInit } from '#angular/core';
import { OAuthService, JwksValidationHandler } from 'angular-oauth2-oidc';
import { authConfig } from '../auth.config';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
constructor(private oauthService: OAuthService) {
this.oauthService.configure(authConfig);
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
this.oauthService.loadDiscoveryDocumentAndTryLogin();
}
ngOnInit() {
this.oauthService.initImplicitFlow(encodeURIComponent('http://localhost8080/'));
}
}
I dont understand how the authentication must be handle in this config.
The annotation #EnableOAuth2Sso transforms your spring application in an OAuth2 client
In your scenario, instead, you want that your application is a ResourceServer
So you should use the #EnableResourceServer annotation.
Spring security should be configured like this:
#Configuration
#EnableWebSecurity
#EnableResourceServer
#PropertySource(value = { "classpath:application.properties" }, encoding = "UTF-8", ignoreResourceNotFound = false)
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired
private Environment env;
#Override
public void configure(final HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/swagger-ui.html","/webjars/**","/swagger-resources/**", "/v2/**","/csrf")
.permitAll()
.antMatchers("/**")
.authenticated()
.and()
.cors()
.configurationSource(corsConfigurationSource())
.and()
.exceptionHandling()
.accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
#Override
public void configure(final ResourceServerSecurityConfigurer config) {
config
.tokenServices(tokenServices())
.resourceId("RES_ID");
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
final DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
return tokenServices;
}
#Bean
public TokenStore tokenStore()
{
JwkTokenStore result = new JwkTokenStore("JWTKS_URL", accessTokenConverter());
return result;
}
#Bean
public JwtAccessTokenConverter accessTokenConverter()
{
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setAccessTokenConverter(new DefaultAccessTokenConverter() {
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
final OAuth2Authentication auth = super.extractAuthentication(map);
auth.setDetails(map);
return auth;
}
});
return converter;
}
#Bean
public JwtClaimsSetVerifier jwtClaimsSetVerifier() {
return new DelegatingJwtClaimsSetVerifier(Arrays.asList(issuerClaimVerifier(), customJwtClaimVerifier()));
}
#Bean
public JwtClaimsSetVerifier issuerClaimVerifier() {
try {
return new IssuerClaimVerifier(new URL("ISSUER CLAIMS URL"));
} catch (final MalformedURLException e) {
throw new RuntimeException(e);
}
}
#Bean
public JwtClaimsSetVerifier customJwtClaimVerifier() {
return new CustomClaimVerifier();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
boolean abilitaCors = new Boolean(env.getProperty("profile.manager.web.cors.enbaled"));
if( abilitaCors )
{
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.addAllowedOrigin("*");
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
configuration.setExposedHeaders(Arrays.asList("X-Auth-Token","x-auth-token", "x-requested-with", "x-xsrf-token","Access-Control-Allow-Origin", "content-type"));
source.registerCorsConfiguration("/**", configuration);
}
return source;
}
}
On angular side I suggest to you to use angulat-oauth2-oidc plugin https://github.com/manfredsteyer/angular-oauth2-oidc
I'm trying to configure Spring for CORS in order to use Angular web UI:
I tried this:
#Configuration
#ComponentScan("org.datalis.admin.config")
public class AppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
PropertySourcesPlaceholderConfigurer conf = new PropertySourcesPlaceholderConfigurer();
conf.setLocation(new ClassPathResource("application.properties"));
return conf;
}
#Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("127.0.0.1");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<CorsFilter>(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}
Apache server with Angular FE is running with Wildly server on the same server so I configured 127.0.0.1 for source.
But still I get:
Access to XMLHttpRequest at 'http://123.123.123.123:8080/api/oauth/token' from origin 'http://123.123.123.123' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
auth:1 Failed to load resource: the server responded with a status of 404 (Not Found)
Do you know how I can fix this issue?
Second way that I tried:
#Configuration
#EnableResourceServer
public class ResourceSecurityConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("resource_id").stateless(true);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/users/**").permitAll()
.anyRequest().authenticated()
.and()
.cors().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.anyRequest()
.fullyAuthenticated()
.and()
.httpBasic()
.and()
.csrf().disable();
}
#Bean
public CorsConfigurationSource corsConfigurationSources() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token"));
configuration.setExposedHeaders(Arrays.asList("x-auth-token"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
With the second configuration I get has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
auth:1 Failed to load resource: the server responded with a status of 404 (Not Found)
What is the best way to achieve this result?
Your allowed origin is 127.0.0.1 but your client side has the ip 123.123.123.123. Try to change this:
config.addAllowedOrigin("127.0.0.1");
To this:
config.addAllowedOrigin("123.123.123.123");
You need to tell Spring Security to use the CORS Configuration you created.
In my project I configured Spring Security in this way:
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.antMatchers("/rest/protected/**")
.authenticated()
//Other spring sec configruation and then:
.and()
.cors()
.configurationSource(corsConfigurationSource())
}
Where corsConfigurationSource() is:
#Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
boolean abilitaCors = new Boolean(env.getProperty("templating.oauth.enable.cors"));
if( abilitaCors )
{
if( logger.isWarnEnabled() )
{
logger.warn("CORS ABILITATI! Si assume ambiente di sviluppo");
}
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200","http://localhost:8080", "http://localhost:8180"));
configuration.setAllowedMethods(Arrays.asList( RequestMethod.GET.name(),
RequestMethod.POST.name(),
RequestMethod.OPTIONS.name(),
RequestMethod.DELETE.name(),
RequestMethod.PUT.name()));
configuration.setExposedHeaders(Arrays.asList("x-auth-token", "x-requested-with", "x-xsrf-token"));
configuration.setAllowedHeaders(Arrays.asList("X-Auth-Token","x-auth-token", "x-requested-with", "x-xsrf-token"));
source.registerCorsConfiguration("/**", configuration);
}
return source;
}
I hope it's useful
Angelo
This is my working #Configuration class to handle CORS requests used only in dev environment.
#Configuration
//#Profile(PROFILE_DEV)
public class CorsConfiguration {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("*")
.allowedMethods("*");
}
};
}
}
You have also to configure Spring Security to ignore HttpMethod.OPTIONS used by preflight request (as the exception you mentioned)
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
//...
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
//others if you need
.antMatchers(HttpMethod.OPTIONS, "/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.exceptionHandling()
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/**").authenticated();
}
}
Because when you use cors you have Simple Request and Preflighted Request that triggers an HttpMethod.OPTIONS
I recommend you to use a WebMvcConfigurer, and in the addCorsMappings method set the CORS configuration.
Somethingo like this
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:9798")
.allowedMethods("POST", "GET")
//.allowedHeaders("header1", "header2", "header3")
//.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600);
}
}
Here there is a link with a fully functional Spring with CORS project, just download and run it.
https://github.com/reos79/spring-cors
It has a html page (person.html) this page does nothing but call the service on the port (9797). So you need to load this project twice, once on port 9797 to load the service and the other on port (9798). Then on you browser you call the page person on the server localhost:9798 and it will call the service on localhost:9797, in the file application.properties I configured the port.
You need to add #CrossOrigin class level in your controller class like below
#CrossOrigin
public class SampleController {
// Your code goes here
}
annotation to your rest controller class
Try changing your bean name to corsConfigurationSource removing the "s"
Documentation https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#cors
// by default uses a Bean by the name of corsConfigurationSource
I am trying to protect my microservices on Spring Boot using Oath2 with Client Credentials flow.
By the way, those microservices will only talk each other over the middleware layer, I mean no user credentials are needed to allow the authorization (user login process as Facebook).
I have looked for samples on the Internet showing how to create an authorization and resource server to manage this communication. However I just found examples explaining how to do it using user credentials (three legs).
Does anyone have any sample how to do it in Spring Boot and Oauth2? If it is possible give further details about the scopes used, token exchanging would be grateful.
We have REST services protected with Oauth2 Client credentials scheme. The Resource and authorization service are running in the same app, but can be split into different apps.
#Configuration
public class SecurityConfig {
#Configuration
#EnableResourceServer
protected static class ResourceServer extends ResourceServerConfigurerAdapter {
// Identifies this resource server. Usefull if the AuthorisationServer authorises multiple Resource servers
private static final String RESOURCE_ID = "*****";
#Resource(name = "OAuth")
#Autowired
DataSource dataSource;
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests().anyRequest().authenticated();
// #formatter:on
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);
resources.tokenStore(tokenStore());
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Resource(name = "OAuth")
#Autowired
DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore());
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
}
}
Datasource config for the Oauth2 tables:
#Bean(name = "OAuth")
#ConfigurationProperties(prefix="datasource.oauth")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
Communicating with authentication & resource server goes as followed
curl -H "Accept: application/json" user:password#localhost:8080/oauth/token -d grant_type=client_credentials
curl -H "Authorization: Bearer token" localhost:8080/...
The following record is present in the Oauth2 Database:
client_id resource_ids client_secret scope authorized_grant_types web_server_redirect_uri authorities access_token_validity refresh_token_validity additional_information autoapprove
user **** password NULL client_credentials NULL X NULL NULL NULL NULL
Resttemplate configuration in client application
#Configuration
#EnableOAuth2Client
public class OAuthConfig {
#Value("${OAuth2ClientId}")
private String oAuth2ClientId;
#Value("${OAuth2ClientSecret}")
private String oAuth2ClientSecret;
#Value("${Oauth2AccesTokenUri}")
private String accessTokenUri;
#Bean
public RestTemplate oAuthRestTemplate() {
ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setId("1");
resourceDetails.setClientId(oAuth2ClientId);
resourceDetails.setClientSecret(oAuth2ClientSecret);
resourceDetails.setAccessTokenUri(accessTokenUri);
/*
When using #EnableOAuth2Client spring creates a OAuth2ClientContext for us:
"The OAuth2ClientContext is placed (for you) in session scope to keep the state for different users separate.
Without that you would have to manage the equivalent data structure yourself on the server,
mapping incoming requests to users, and associating each user with a separate instance of the OAuth2ClientContext."
(http://projects.spring.io/spring-security-oauth/docs/oauth2.html#client-configuration)
Internally the SessionScope works with a threadlocal to store variables, hence a new thread cannot access those.
Therefore we can not use #Async
Solution: create a new OAuth2ClientContext that has no scope.
*Note: this is only safe when using client_credentials as OAuth grant type!
*/
// OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, oauth2ClientContext);
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, new DefaultOAuth2ClientContext());
return restTemplate;
}
}
You can inject the restTemplate to talk (Asynchronously) to the Oauth2 secured service.
We do not use scope at the moment.
Update with Spring-boot-2.7 and Java 17.
https://chuangtc.com/Java/spring-boot-27-security-social-login.php
public class SecurityConfig {
#Value("${spring.social.facebook.appSecret}")
String appSecret;
#Value("${spring.social.facebook.appId}")
String appId;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private FacebookConnectionSignup facebookConnectionSignup;
#Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService)
.and()
.build();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/login*", "/signin/**", "/signup/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout();
return http.build();
}
#Bean
// #Primary
public ProviderSignInController providerSignInController() {
ConnectionFactoryLocator connectionFactoryLocator = connectionFactoryLocator();
UsersConnectionRepository usersConnectionRepository = getUsersConnectionRepository(connectionFactoryLocator);
((InMemoryUsersConnectionRepository) usersConnectionRepository).setConnectionSignUp(facebookConnectionSignup);
return new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, new FacebookSignInAdapter());
}
private ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(appId, appSecret));
return registry;
}
private UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new InMemoryUsersConnectionRepository(connectionFactoryLocator);
}
}
FacebookSignInAdapter
#Service
public class FacebookSignInAdapter implements SignInAdapter {
#Override
public String signIn(String localUserId, Connection<?> connection, NativeWebRequest request) {
System.out.println(" ====== Sign In adapter");
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(connection.getDisplayName(), null, Arrays.asList(new SimpleGrantedAuthority("FACEBOOK_USER"))));
return null;
}
}