Keycloak in Spring Boot 3 - spring-boot

It seems that configuring Keycloak is impossible to do in new version of Spring Boot 3 and Spring Security.
I tried writing SecurityFilterChain but first it skipped the whole Chain and allowed everyone who has valid bearer token to view protected resource.
I found out that Spring Boot isn't picking up roles and putting them in Granted Authorities, just scope from JWT. Is it good idea to write my own token converter?
WebSecurityConfig.java
#RequiredArgsConstructor
#EnableWebSecurity
#Configuration
public class WebSecurityConfig {
private final JwtAuthConverter jwtAuthConverter;
#Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(jwtAuthConverter);
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/**","api/**","/api").hasAuthority(ADMINISTRATION)
.anyRequest().authenticated()
);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.cors().and().csrf().disable();
return http.build();
}
#Bean
public JwtDecoder jwtDecoder(OAuth2ResourceServerProperties oAuth2ResourceServerProperties) {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(oAuth2ResourceServerProperties.getJwt().getJwkSetUri()).build();
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(oAuth2ResourceServerProperties.getJwt().getIssuerUri()));
return jwtDecoder;
}
private static final String ADMINISTRATION = "ROLE_administration";
}
When I added #Configuration annotation to the Class all requests were rejected.
Here are the classes if you need them to answer the question.
JwtAuthConverter.java
#Component
public class JwtAuthConverter implements Converter<Jwt, AbstractAuthenticationToken> {
private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
private final JwtAuthConverterProperties properties;
public JwtAuthConverter(JwtAuthConverterProperties properties) {
this.properties = properties;
}
#Override
public AbstractAuthenticationToken convert(Jwt jwt) {
Collection<GrantedAuthority> authorities = Stream.concat(
jwtGrantedAuthoritiesConverter.convert(jwt).stream(),
extractResourceRoles(jwt).stream()).collect(Collectors.toSet());
return new JwtAuthenticationToken(jwt, authorities, getPrincipalClaimName(jwt));
}
private String getPrincipalClaimName(Jwt jwt) {
String claimName = JwtClaimNames.SUB;
if (properties.getPrincipalAttribute() != null) {
claimName = properties.getPrincipalAttribute();
}
return jwt.getClaim(claimName);
}
private Collection<? extends GrantedAuthority> extractResourceRoles(Jwt jwt) {
Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
Map<String, Object> resource;
Collection<String> resourceRoles;
if (resourceAccess == null
|| (resource = (Map<String, Object>) resourceAccess.get(properties.getResourceId())) == null
|| (resourceRoles = (Collection<String>) resource.get("roles")) == null) {
return Collections.emptySet();
}
return resourceRoles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toSet());
}
}
JwtAuthConverter.java
#Data
#Validated
#Configuration
#ConfigurationProperties(prefix = "jwt.auth.converter")
public class JwtAuthConverterProperties {
#NotBlank
private String resourceId;
private String principalAttribute;
}

First you need to make custom JWT converter to extract Authorities from Keycloaks nested structure:
"realm_access": {
"roles": [
"offline_access",
"uma_authorization"
]
}
This is an example of that class (you can use it to get other claims like email, username, ...):
#AllArgsConstructor
#Component
public class CustomJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
private JwtAuthenticationConverter jwtAuthenticationConverter;
private JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter;
public CustomJwtAuthenticationConverter() {
this.jwtAuthenticationConverter = new JwtAuthenticationConverter();
this.jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
}
#Override
public AbstractAuthenticationToken convert(Jwt jwt) {
Collection<GrantedAuthority> authorities = extractAuthorities(jwt);
return new JwtAuthenticationToken(jwt, authorities);
}
private Collection<GrantedAuthority> extractAuthorities(Jwt jwt) {
if(jwt.getClaim("realm_access") != null) {
Map<String, Object> realmAccess = jwt.getClaim("realm_access");
ObjectMapper mapper = new ObjectMapper();
List<String> roles = mapper.convertValue(realmAccess.get("roles"), new TypeReference<List<String>>(){});
List<GrantedAuthority> authorities = new ArrayList<>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
return new ArrayList<>();
}
}
After that you include it in SecurityFilterChain and define which paths you want to secure:
#Configuration
#RequiredArgsConstructor
public class WebSecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authz -> authz
.requestMatchers(HttpMethod.GET, "/api/**").hasAuthority(SOMEROLE)
.requestMatchers("/api/**").authenticated()
.anyRequest().permitAll()
);
http.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(new CustomJwtAuthenticationConverter());
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.cors().and().csrf().disable();
return http.build();
}
private static final String SOMEROLE = "SOMEROLE";
}

Related

Authentication filter, doesn't work after migration to SecurityFilterChain

I've decided to migrate from extending WebSecurityConfigurerAdapter to SecurityFilterChain, and I've met problems with AuthenticationFilter. (With an old method configuration was working).
Everytime, when I'm trying to login by hitting api with postman (/api/users/login), I'm getting 401 HttpStatus in opposite to my expectations (before migration I was getting jwt tokens.
(Credentials are correct :d)
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
class SecurityConfiguration {
private final UserDetailsService userDetailsService;
private final SuffixConfiguration suffixConfiguration;
private final AuthorizationService authorizationService;
private final AuthenticationService authenticationService;
private String loginURL = "/api/users/login";
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
AuthenticationManager authenticationManager = authenticationManager(http.getSharedObject(AuthenticationConfiguration.class));
AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager, authenticationService);
authenticationFilter.setFilterProcessesUrl(loginURL);
http.headers().cacheControl();
http.csrf().disable();
http.cors();
http
.authorizeRequests()
.antMatchers(HttpMethod.POST, loginURL).permitAll()
.antMatchers("/api/users/register").permitAll()
.antMatchers("/api/users/refreshToken").permitAll();
http
.addFilter(authenticationFilter)
.addFilterBefore(new AuthorizationFilter(authorizationService), UsernamePasswordAuthenticationFilter.class);
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.authorizeRequests()
.antMatchers("/api/department/add-moderator")
.hasAnyAuthority("[ROLE_ADMIN]");
return http.build();
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
Here is the code of AuthorizationService
#Slf4j
#Service
public class AuthorizationServiceImpl implements AuthorizationService {
#Override
public void tryAuthorize(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader(AUTHORIZATION).substring(TOKEN_PREFIX.length());
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET.getBytes());
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(token);
String username = decodedJWT.getSubject();
String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(role -> authorities.add(new SimpleGrantedAuthority(role)));
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
} catch (Exception exception) {
log.error("Logging exception: d" + exception.getMessage());
throw exception;
}
}
}
And this is AuthenticationService
#Slf4j
#RequiredArgsConstructor
public class AuthenticationServiceImpl implements AuthenticationService {
private final TokenService tokenService;
private final UserModelMapper userModelMapper;
#Override
public UsernamePasswordAuthenticationToken createUsernameAuthenticationToken(HttpServletRequest request, HttpServletResponse response) {
try {
Map<String, String> requestMap = new ObjectMapper().readValue(request.getInputStream(), Map.class);
String username = requestMap.get("username");
String password = requestMap.get("password");
log.info(username, password);
return new UsernamePasswordAuthenticationToken(username, password);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
public Map<Object, Object> successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
UserEntity user = (UserEntity) authResult.getPrincipal();
String issuer = request.getRequestURI();
String accessToken = tokenService.generateAccessToken(user, issuer);
String refreshToken = tokenService.generateRefreshToken(user, issuer);
Map<Object, Object> responseBody = new HashMap<>();
responseBody.put("access_token", accessToken);
responseBody.put("refresh_token", refreshToken);
responseBody.put("user", userModelMapper.mapUserEntityToLoginResponseDTO(user));
return responseBody;
}
}
Also as you suggested, code where users are saved
#RequiredArgsConstructor
#Service
#Transactional
class UserManagementServiceImpl implements UserManagementService {
private final UserRepository userRepository;
private final SuffixConfiguration suffixConfiguration;
private final DepartmentFacade departmentFacade;
private final RoleFacade roleFacade;
private final UserFindingService userFindingService;
private final UserModelMapper userModelMapper;
#Override
public UserResponseDTO registerNewUser(RegisterNewUserRequestDTO requestDTO) throws IllegalArgumentException {
checkIfUserWithGivenUsernameAlreadyExists(requestDTO.username());
UserEntity newUserEntity = createEntityToSave(requestDTO);
userRepository.save(newUserEntity);
return userModelMapper.mapUserEntityToUserResponseDTO(newUserEntity);
}
#Override
public void deleteUser(DeleteUserRequestDTO requestDTO) {
UserEntity requestingUser = userFindingService.getUserEntity(requestDTO.username());
List<RoleEntity> allowedRoles = Arrays.asList(roleFacade.findByRoleType(RoleType.ROLE_ADMIN), roleFacade.findByRoleType(RoleType.ROLE_MODERATOR));
if (requestingUser.getRoles().containsAll(allowedRoles)) {
userRepository.deleteByUsername(requestDTO.username());
} else {
throw new UserDoesNotHavePermissionException(requestingUser.getUsername());
}
}
#Override
public LoginResponseDTO login(LoginRequestDTO requestDTO) {
UserEntity userEntity = userFindingService.getUserEntity(requestDTO.username());
isCredentialsCorrect(requestDTO, userEntity);
return userModelMapper.mapUserEntityToLoginResponseDTO(userEntity);
}
private void isCredentialsCorrect(LoginRequestDTO requestDTO, UserEntity userEntity) {
if (!suffixConfiguration.bCryptPasswordEncoder().matches(requestDTO.password(), userEntity.getPassword())) {
throw new BadCredentialsException("Bad credentials");
}
}
private UserEntity createEntityToSave(RegisterNewUserRequestDTO requestDTO) throws IllegalArgumentException {
UserEntity newUserEntity = new UserEntity(requestDTO.username(), encodePassword(requestDTO.password()));
RoleEntity role = roleFacade.createRoleEntity(requestDTO.role());
newUserEntity.getRoles().add(role);
newUserEntity.getDepartmentEntities().add(departmentFacade.getDepartmentEntity(requestDTO.department()));
return newUserEntity;
}
private void checkIfUserWithGivenUsernameAlreadyExists(String username) {
userRepository.findByUsername(username).ifPresent(user -> {
throw new UsernameTakenException(username);
});
}
private String encodePassword(String password) {
if (password != null) {
return suffixConfiguration.bCryptPasswordEncoder().encode(password);
} else {
throw new EmptyPasswordException();
}
}
}
Thanks for help

OAuth2 GrantedAuthorities not present in JWT when using custom UserDetailsService

Using the new spring-authorization-server 0.2.3 and following https://github.com/spring-projects/spring-authorization-server/tree/main/samples as reference I was able to setup an authorization server, resource server and a client successfully when using an InMemoryUserDetailsManager as follows
#EnableWebSecurity
public class DefaultSecurityConfig {
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.build();
}
#Bean
UserDetailsService users() {
User.UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user1").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("USER", "ADMIN").authorities("r1","r2","r3").build());
return manager;
}
}
This works well, In the client, I can see the authorities Granted Authorities=["r1","r2","r3"] present.
Now when I attempt to implement my own UserDetailsService which retrieves users from a Mongo Database, I stop seeing the GrantedAuthorities being passed to the client and only see Granted Authorities=[ROLE_USER, SCOPE_openid]
This is what I now have in the DefaultSecurityConfig
#EnableWebSecurity
public class DefaultSecurityConfig {
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.build();
}
#Autowired
private MongoTemplate mongoTemplate;
#Bean
UserDetailsService users() {
return new CustomUserDetailsService(mongoTemplate);
}
}
And my CustomUserDetailsService looks like the following:
public class CustomUserDetailsService implements UserDetailsService {
private final MongoTemplate mongoTemplate;
public CustomUserDetailsService(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Criteria criteria = Criteria.where("email").is(username);
CustomUser user = mongoTemplate.findOne(new Query(criteria), CustomUser.class, "vOAuthUser");
if (user != null) {
log.info("Found user {}", user.email());
List<GrantedAuthority> authorities = getUserAuthority(user.groups());
return buildUserForAuthentication(user, authorities);
} else {
throw new UsernameNotFoundException("username not found");
}
}
private UserDetails buildUserForAuthentication(CustomUser user, List<GrantedAuthority> authorities) {
return new org.springframework.security.core.userdetails.User(user.email(), user.password(), authorities);
}
private List<GrantedAuthority> getUserAuthority(Set<String> groups) {
List<GrantedAuthority> authorities = new ArrayList<>();
groups.forEach(s -> {
Criteria criteria = Criteria.where("name").is(s);
CustomRole role = mongoTemplate.findOne(new Query(criteria), CustomRole.class, "vRole");
if (role != null) {
authorities.addAll(role.grantedAuthorities());
}
});
return authorities;
}
}
Any help is greatly appreciated.
Have you defined a OAuth2TokenCustomizer bean in your security configuration? You can add Granted Authorities there if you need, like in the following code:
#Bean
#SuppressWarnings("unused")
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return context -> {
JoseHeader.Builder headers = context.getHeaders();
JwtClaimsSet.Builder claims = context.getClaims();
Authentication principal = context.getPrincipal();
Set<String> authorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
claims.claim("authorities", authorities);
};
}

Customized Login Response in Spring Security Basic Auth

I have a table named "user_detail" having columns firstname,lastname,username,password.This table I am using for basic auth.
Currently when I give username, password in the /login controller then it validates with the table data. I want first name to be returned on successful login.
Below is my code.
public class LoginController {
#GetMapping(path = "/login")
public LoginResponse login() {
return new LoginResponse("You are authenticated");
}
}
#Configuration
#EnableWebSecurity
public class SpringSecurityConfigurationBasicAuth extends WebSecurityConfigurerAdapter {
#Autowired
MongoUserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
AuthenticationEntryPoint entryPoint = new CustomAuthenticationEntryPoint();
http
.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().httpBasic().authenticationEntryPoint(entryPoint).and()
.exceptionHandling().authenticationEntryPoint(entryPoint)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/user-registration/users");
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
configuration.setAllowedMethods(Arrays.asList("GET"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
public #Data class LoginResponse {
private String message;
public LoginResponse(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
#Component
public class MongoUserDetailsService implements UserDetailsService {
#Autowired
MongoOperations mongoOperations;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Query query = new Query(Criteria.where("emailId").is(username));
List<UserDetailData> user = mongoOperations.find(query, UserDetailData.class, "user_details");
if (CollectionUtils.isEmpty(user)) {
throw new UsernameNotFoundException("User not found");
}
List<SimpleGrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority("user"));
return new User(user.get(0).getEmailId(), user.get(0).getPassword(), authorities);
}
}
Currently it returns "You are authenticated" instead of this I want the first name of the user from the DB for that username.
You can try to use http.successHandler(handler). In the most primitive case handler can extend SimpleUrlAuthenticationSuccessHandler class. This handler redirects user to an arbitrary url after authentication.
If firstname is fetched from DB, you can convey it with custom user object:
public class ExtendedUser extends User {
private static final long serialVersionUID = -1;
private String name;
ExtendedUser(String email, String password, boolean enabled,
Collection<? extends GrantedAuthority> authorities,
String name) {
super(email, password, enabled, true, true, true, authorities);
this.name = name;
}
// getter and setter
}
Then use this class in your UserDetailsService instead of plain 'User'. After that, in success handler you can retrieve it like authentication.getPrincipal() in onAuthenticationSuccess method. Then you can put it as a query parameter of redirect url

How to customize the jwt decoder in spring boot oauth2

I want to dynamically set the jwk-set-uri for different tenant on my resource server which I get the tenant info from a filter. And I have the following resource server config.
#Slf4j
#Import(SecurityProblemSupport.class)
#RequiredArgsConstructor
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private final SecurityProblemSupport problemSupport;
private final RealmProperties realmProperties;
private final MultiTenantManager multiTenantManager;
#Override
public void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(problemSupport).accessDeniedHandler(problemSupport)
.and()
.authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
.and().requestMatcher(new OAuthRequestedMatcher()).authorizeRequests().anyRequest()
.fullyAuthenticated();
}
#Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
private static class OAuthRequestedMatcher implements RequestMatcher {
public boolean matches(HttpServletRequest request) {
String auth = request.getHeader("Authorization");
log.debug("auth decode from request: ", auth);
boolean haveOauth2Token = (auth != null) && auth.startsWith("Bearer");
boolean haveAccessToken = request.getParameter("access_token") != null;
return haveOauth2Token || haveAccessToken;
}
}
#Override
public void configure(final ResourceServerSecurityConfigurer config) {
config.resourceId("login-app");
}
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange().anyExchange().authenticated().and().oauth2ResourceServer().jwt()
.jwtDecoder(myCustomDecoder());
return http.build();
}
#Bean
ReactiveJwtDecoder myCustomDecoder() {
return realmProperties.getRealms().stream()
.filter(realm -> realm.getRealm().equals(multiTenantManager.getCurrentTenant()))
.map(realm -> new NimbusReactiveJwtDecoder(((Realm) realm).getJwkSetUri()))
.findFirst()
.orElseThrow(() -> new InternalServerErrorException("cannot find the jwk set url for the realm"));
}
}
But I got an exception saying
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.config.annotation.ObjectPostProcessor<?>' available
Any help on this? what can I do to dynamic set a jwk set uri to parse the token?
Thanks
-Peng
I solve customizations of token decoding in the following way:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
// Inject
private String resourceId;
// Inject
private String secret;
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(resourceId);
resources.tokenStore(createTokenStore(new ResourceAccessTokenConverter()));
}
private TokenStore createTokenStore(AccessTokenConverter converter) {
JwtAccessTokenConverter tokenConverter = new CustomJwtAccessTokenConverter();
tokenConverter.setAccessTokenConverter(converter);
tokenConverter.setVerifier(new MacSigner(secret));
TokenStore ts = new JwtTokenStore(tokenConverter);
return ts;
}
public class CustomJwtAccessTokenConverter extends JwtAccessTokenConverter {
#Override
protected Map<String, Object> decode(String token) {
return super.decode(token);
}
#Override
public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
return super.convertAccessToken(token, authentication);
}
#Override
public OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map) {
return super.extractAccessToken(value, map);
}
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
return super.extractAuthentication(map);
}
}
}

Spring social Facebook doesn't work in version 2.0.2

Hello everyone when I upgrade my application to spring version 2.0.2 I get this exception:
Description: Field connectionFactoryLocator in com.ssp.api.v1.security.SecurityConfiguration required a bean of type 'org.springframework.social.connect.ConnectionFactoryLocator' that could not be found.
here's my code:
SecurityConfiguration.java Configuration
#Configuration
#ComponentScan(basePackages = { "com.ssp.api.vi.security" })
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired private ConnectionFactoryLocator connectionFactoryLocator;
#Autowired private UsersConnectionRepository usersConnectionRepository;
#Autowired private FacebookConnectionSignup facebookConnectionSignup;
#Autowired private SspUserDetailsService sspUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/session").permitAll()
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/api/**").authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and().csrf().disable();
}
//#Autowired
#Bean
public ProviderSignInController providerSignInController() {
((InMemoryUsersConnectionRepository) usersConnectionRepository)
.setConnectionSignUp(facebookConnectionSignup);
return new ProviderSignInController(
connectionFactoryLocator,
usersConnectionRepository,
new FacebookSignInAdapter());
}
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(sspUserDetailsService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder(11);
}
#Override
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
#Override
#Bean(name = BeanIds.USER_DETAILS_SERVICE)
public UserDetailsService userDetailsServiceBean() throws Exception { return this.sspUserDetailsService; }
AuthUtil.java Class
protected static final Logger log = LoggerFactory.getLogger(AuthUtil.class);
public static void authenticate(Connection<?> connection) {
UserProfile userProfile = connection.fetchUserProfile();
String username = userProfile.getUsername();
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, null);
SecurityContextHolder.getContext().setAuthentication(authentication);
log.info("User {} {} connected.", userProfile.getFirstName(), userProfile.getLastName());
}
FacebookConnectionSignup.class Service
#Service
public class FacebookConnectionSignup implements ConnectionSignUp {
#Override
public String execute(Connection<?> connection) {
return connection.getDisplayName();
}
}
FacebookSignInAdapter.java Class
public class FacebookSignInAdapter implements SignInAdapter{
#Override
public String signIn(String userId, Connection<?> connection, NativeWebRequest request) {
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(
connection.getDisplayName(), null,
Arrays.asList(new SimpleGrantedAuthority("FACEBOOK_USER"))
)
);
return null;
}
}
SocialConfiguration.java Configuration
#Configuration
public class SocialConfiguration {
#Bean
public SignInAdapter authSignInAdapter() {
return (userId, connection, request) -> {
AuthUtil.authenticate(connection);
return null;
};
}
}
### SspUserDetailsService.class Service
#Service
public class SspUserDetailsService implements UserDetailsService {
#Autowired private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findById(username).orElse(null);
if (user == null)
throw new UsernameNotFoundException(username);
return new SspUserDetails(user);
}
}
Console Error:
Description:
Field connectionFactoryLocator in com.ssp.api.v1.security.SecurityConfiguration required a bean of type 'org.springframework.social.connect.ConnectionFactoryLocator' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.social.connect.ConnectionFactoryLocator' in your configuration.
This code works perfectly In Spring 1.5.10 version.
How can I solve this issue?
Before of all thank you!
In Boot 2.x, you need to define the ConnectionFactoryLocator and UsersConnectionRepository in your SecurityConfiguration class, instead of autowiring them:
private ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(appId, appSecret));
return registry;
}
private UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new InMemoryUsersConnectionRepository(connectionFactoryLocator);
}
Here, appId and appSecret are coming from application.properties.
You also need to change the implementation for your ProviderSignInController bean:
#Bean
public ProviderSignInController providerSignInController() {
ConnectionFactoryLocator connectionFactoryLocator = connectionFactoryLocator();
UsersConnectionRepository usersConnectionRepository = getUsersConnectionRepository(connectionFactoryLocator);
((InMemoryUsersConnectionRepository) usersConnectionRepository).setConnectionSignUp(facebookConnectionSignup);
return new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, new FacebookSignInAdapter());
}
You can find more details here.

Resources