Implementation for Implicit social signup in spring-boot project - spring

By adding the spring-social signin / signup to my current spring-boot project, through the implementation of the classes ConnectionSignUp and SignInAdapter, the application, after the authorization with the social network website, it's requiring the implementation of a mapping for "/signup" in my controller. In this method, I do basically the same what I already have implemented on the ConectionSignUp method. Anyone knows what I can do to avoid this duplicity, and direct the application to my ConnectionSignUp class instead of one extra method on my controller?
my implementation so far includes the following classes:
ConnectionSIgnUp
#Component
public class CustomConnectionSignUp implements ConnectionSignUp {
#Autowired
private UsuarioDao account;
#Autowired
private JavaMailSender mailSender;
public String execute(Connection<?> connection) {
UserProfile profile = connection.fetchUserProfile();
String user;
try {
Usuario novo = new Usuario(profile.getUsername(),UUID.randomUUID().toString().replaceAll("-", ""),null,null,false,true);
account.insert(novo);
return novo.getLogin();
} catch (Exception e) {
return null;
}
}
}
SignInAdapter
#Component
public class CustomSignInAdapter implements SignInAdapter {
#Autowired
private SocialUserDetailsService socialUserDetailsService;
public String signIn(String userId, Connection<?> connection, NativeWebRequest request) {
SocialUserDetails user = socialUserDetailsService.loadUserByUserId(userId);
if(user != null)
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user.getUserId(), null, null));
return null;
}
}
SocialUserDetailsService
#Service
public class CustomSocialUserDetailsService implements SocialUserDetailsService {
#Autowired
private UsuarioDao account;
public SocialUserDetails loadUserByUserId(String userId) {
for(Usuario usuario : account.select())
if(usuario.getLogin().equals(userId))
return new SocialUser(usuario.getLogin(), usuario.getSenha(), usuario.isEnabled(), usuario.isAccountNonExpired(), usuario.isCredentialsNonExpired(), usuario.isAccountNonLocked(), usuario.getAuthorities());
return null;
}
}
application.properties
# SPRING SOCIAL (SocialWebAutoConfiguration)
spring.social.auto-connection-views=false
# SPRING SOCIAL FACEBOOK (FacebookAutoConfiguration)
spring.social.facebook.app-id=...
spring.social.facebook.app-secret=...
# SPRING SOCIAL TWITTER (TwitterAutoConfiguration)
spring.social.twitter.app-id=...
spring.social.twitter.app-secret=...

It seems to me that you didn't Inject ConnectionSignUp into UsersConnectionRepository. Therefore Spring tries redirect user to page with sign up form. To perform implicit sign up try to pass your ConnectionSignUp bean to setConnectionSignUp().

Related

Custom Principal with OAuth2 in existing form login application

I'm trying to add OAuth2 login to an existing form login application. So far I've added the required configuration to get Google auth working and the goal is to enable existing username/password users (or new users) to login both ways.
All my controllers rely on my UserDetails implementation:
public class User implements UserDetails {
private Long id;
private String email;
private String password;
private String googleAccessToken;
// ...
And I can get the logged user in controllers like this:
#GetMapping
public String index(#AuthenticationPrincipal User user, Model model) {
So what I've done is to implement my custom OAuth2UserService to fetch the existing user from the database but I can't find the way to set the User as the principal.
In previous versions of the OAuth2 integration it seemed to exist a simpler solution based on PrincipalExtractor but it is no longer available.
#Service
#RequiredArgsConstructor
public class OAuth2UserDetailsService implements OAuth2UserService<OidcUserRequest, OidcUser> {
private final UsersRepository usersRepository;
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
final OidcUserService delegate = new OidcUserService();
User user;
// Delegate to the default implementation for loading a user
OidcUser oidcUser = delegate.loadUser(userRequest);
switch (userRequest.getClientRegistration().getClientName()) {
case "google":
user = usersRepository.findOneByGoogleAccessToken(userRequest.getAccessToken());
break;
default:
throw new OAuth2AuthenticationException(new OAuth2Error("invalid_token"));
}
// here I should return my user principal
return new DefaultOidcUser(null, null);
}
Any ideas?
Finally solved this returning an instance of OidcUser:
public class UserPrincipal implements UserDetails, OidcUser {
// ...
}
And in the OAuth2UserService:
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
final OidcUserService delegate = new OidcUserService();
User user;
// Delegate to the default implementation for loading a user
OidcUser oidcUser = delegate.loadUser(userRequest);
// ...
return new UserPrincipal(user, oidcUser);
}

SecurityContextHolder.getContext().getAuthentication() always return 'anonymousUser'

I created Spring boot application with the following configuration:
Spring boot 2.1.0.RELEASE
OpenJdk 11
I have an AuditConfiguration class in my project that looks like:
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class AuditConfiguration {
#Bean
public AuditorAware<String> auditorProvider() {
return new AuditorAwareImpl();
}
class AuditorAwareImpl implements AuditorAware<String> {
#Override
public Optional<String> getCurrentAuditor() {
Principal principal =
SecurityContextHolder.getContext().getAuthentication();
return Optional.of(principal.getName());
}
}
}
and SecurityContextHolder.getContext().getAuthentication() always returns anonymousUser.
However, the following code returns the correct user name.
#RestController
#RequestMapping("/history")
public class HistoryEndpoint {
#RequestMapping(value = "/username", method = RequestMethod.GET)
#ResponseBody
public String currentUserName(Principal principal) {
return principal.getName();
}
}
I need your help for resolving this issue.
I got authenticared user using following class. i had problem with JPA Auditing.
#CreatedBy always saved null. then i tried to get authenticated user SecurityContextHolder.getContext().getAuthentication() using this method. that method returned annonymousUser. however my issue is fixed.
#ManagedBean
#EnableJpaAuditing
public class SpringSecurityAuditorAware implements AuditorAware<String> {
private final HttpServletRequest httpServletRequest;
public SpringSecurityAuditorAware(HttpServletRequest httpServletRequest) {
this.httpServletRequest = httpServletRequest;
}
#Override
public Optional<String> getCurrentAuditor() {
return Optional.ofNullable(httpServletRequest.getUserPrincipal())
.map(Principal::getName);
}
}

Spring boot: FactoryBean<RestTemplate> jwt token headers initialized

I'd like to create a FactoryBean<RestTemplate> in order to avoid to create a RestTemplate each time a component, bean, service... requires it.
I mean, I need to inject a ResTemplate which it's already configured with Authorization header.
Up to now, I've been able to to create it, but I don't quite figure out what I need to write inside afterPropertiesSet:
#Component
public class RestTemplateFactory
implements FactoryBean<RestTemplate>, InitializingBean {
private RestTemplate restTemplate;
public RestTemplate getObject() {
return restTemplate;
}
public Class<RestTemplate> getObjectType() {
return RestTemplate.class;
}
public boolean isSingleton() {
return true;
}
public void afterPropertiesSet() {
//???
}
}
Also, I've implemented a service engaged to update current jwt token. Basiclly:
#Service
public class JWTService {
private String jwt;
public String getJwt() {
return jwt;
}
//JWT handling related code
}
Any ideas?

Spring Social Facebook Template always return same user

I am using Spring Social 2.0.2.RELEASE to provide social login with Facebook. My problem is that Spring Social always return the same first user when I use FacebookTemplate. Here the example:
```
#Autowired
private Facebook facebook;
#RequestMapping(value = "/facebook/login", method = RequestMethod.GET)
public ModelAndView handleFacebookLogin(HttpServletResponse response) {
//always the same user
User profile = facebook.fetchObject("me", User.class, "id", "name", "link", "email");
return new ModelAndView("redirect:/dashboard");
}
```
I also have a Custom ConnectController:
```
#Controller
#RequestMapping("/connect")
public class CustomConnectController extends ConnectController {
#Autowired
public CustomConnectController(ConnectionFactoryLocator connectionFactoryLocator,
ConnectionRepository connectionRepository) {
super(connectionFactoryLocator, connectionRepository);
}
#Override
protected RedirectView connectionStatusRedirect(String providerId, NativeWebRequest request) {
return new RedirectView("/facebook/login");
}
}
```
If a open two browsers and try to login with different users, it always return the first one. My current solution is just copy the entire ConnectController to my app and change the behaviour. It is terrible and I hope that I am making a big mistake.
I had the same issue and solved the problem by creating this class:
#Configuration
public class UniqueSessionUserID extends SocialConfigurerAdapter {
#Override
public UserIdSource getUserIdSource() {
return new UserIdSource() {
#Override
public String getUserId() {
RequestAttributes request = RequestContextHolder.getRequestAttributes();
String uuid = (String) request.getAttribute("_socialUserUUID", RequestAttributes.SCOPE_SESSION);
if (uuid == null) {
uuid = UUID.randomUUID().toString();
}
request.setAttribute("_socialUserUUID", uuid, RequestAttributes.SCOPE_SESSION);
return uuid;
}
};
}
}
Here is a link where it is explained in more detail why this is necessary:
Spring Social Facebook more than one user

How to implement AuditorAware with Spring Data JPA and Spring Security?

We use Hibernate/JPA, Spring, Spring Data and Spring Security in our application. I have a standard User entity which is mapped using JPA. Further, I have a UserRepository
public interface UserRepository extends CrudRepository<User, Long> {
List<User> findByUsername(String username);
}
which follows the Spring Data convention for naming query methods. I have an entity
#Entity
public class Foo extends AbstractAuditable<User, Long> {
private String name;
}
I want to use Spring Data auditing support. (As descripe here.) Hence I created a AuditorService as follows:
#Service
public class AuditorService implements AuditorAware<User> {
private UserRepository userRepository;
#Override
public User getCurrentAuditor() {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
List<User> users = userRepository.findByUsername(username);
if (users.size() > 0) {
return users.get(0);
} else {
throw new IllegalArgumentException();
}
}
#Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
When I create a method
#Transactional
public void createFoo() {
Foo bar = new Foo();
fooRepository.save(foo);
}
Where everything is correctly wired and FooRepository is a Spring Data CrudRepository. Then a StackOverflowError is thrown since the the call to findByUsername seems to trigger hibernate to flush the data to the database which triggers AuditingEntityListener who calls AuditorService#getCurrentAuditor which again triggers a flush and so on.
How to avoid this recursion? Is there a "canonical way" to load the User entity? Or is there a way to prevent Hibernate/JPA from flushing?
I got the same issue and what I did was just change the propagation on the findByUsername(username) method to Propagation.REQUIRES_NEW, I suspected that was a problem with the transactions, so I changed to use a new transaction and that worked well for me. I hope this can help.
#Repository
public interface UserRepository extends JpaRepository<User, String> {
#Transactional(propagation = Propagation.REQUIRES_NEW)
List<User> findByUsername(String username);
}
The solution is not to fetch the User record in the AuditorAware implementation. This triggers the described loop, since a select query triggers a flush (this is the case since Hibernate/JPA wants to write the data to the database to commit the transaction before executing the select), which triggers a call to AuditorAware#getCurrentAuditor.
The solution is to store the User record in the UserDetails provided to Spring Security. Hence I created my own implementation:
public class UserAwareUserDetails implements UserDetails {
private final User user;
private final Collection<? extends GrantedAuthority> grantedAuthorities;
public UserAwareUserDetails(User user) {
this(user, new ArrayList<GrantedAuthority>());
}
public UserAwareUserDetails(User user, Collection<? extends GrantedAuthority> grantedAuthorities) {
this.user = user;
this.grantedAuthorities = grantedAuthorities;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grantedAuthorities;
}
#Override
public String getPassword() {
return user.getSaltedPassword();
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
public User getUser() {
return user;
}
}
Further, I changed my UserDetailsService to load the User and create UserAwareUserDetails. Now it is possible to access the User instance through the SercurityContextHolder:
#Override
public User getCurrentAuditor() {
return ((UserAwareUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser();
}
It looks like you use a User entity for two different things:
authentication
audit
I think it will be better to prepare a special AuditableUser for audit purpose (it will have identical username field as original User).
Consider following case: you want to delete some User from database. If all your audit objects are linked to User then they will a) loose author b) may be deleted by cascade too (depends on how the link is implemented). Not sure that you want it.
So by using special AuditableUser you will have:
no recursion
ability to delete some User from the system and preserve all audit info about it
To be honest, You do not actually require one another entity.
For example, I had similar problem and I resolved it in following way:
public class SpringSecurityAuditorAware implements AuditorAware<SUser>, ApplicationListener<ContextRefreshedEvent> {
private static final Logger LOGGER = getLogger(SpringSecurityAuditorAware.class);
#Autowired
SUserRepository repository;
private SUser systemUser;
#Override
public SUser getCurrentAuditor() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
SUser principal;
if (authentication == null || !authentication.isAuthenticated()) {
principal = systemUser;
} else {
principal = (SUser) authentication.getPrincipal();
}
LOGGER.info(String.format("Current auditor is >>> %s", principal));
return principal;
}
#Override
public void onApplicationEvent(final ContextRefreshedEvent event) {
if (this.systemUser == null) {
LOGGER.info("%s >>> loading system user");
systemUser = this.repository.findOne(QSUser.sUser.credentials.login.eq("SYSTEM"));
}
}
}
Where SUser is both the class which I use for auditing as well as for the security.
I had maybe different use case than Yours and my approach will be deleted after, but it can be resolved like this.

Resources