Facebook Login with Spring Social using Existing User Access Token - ios

Here's what I currently have:
Spring REST service where many of the APIs require the user to be authenticated
A 'registration' API (/api/v1/register)
A 'login' API that takes username/password (/api/v1/login)
'Facebook Login' API that relies on Spring Social and Spring Security to create a User Connection and log my user in (/auth/facebook)
My problem is that I want these APIs to be used by multiple clients, but the way Facebook Login is right now, it doesn't work well on mobile (works great on a website).
Here's the mobile scenario:
I use Facebook's iOS SDK to request permission from the user
Facebook returns a user access token
I want to send my backend service this token and have Spring Social accept it, create the User Connection, etc.
Can this be done? Or am I going to have to write my own API to persist the User Connection?
Appreciate any help!

I had the exact same issue and here's how I made it work. You probably have a SocialConfigurer somewhere with the following:
#Configuration
#EnableSocial
public class SocialConfig implements SocialConfigurer {
#Autowired
private DataSource dataSource;
#Bean
public FacebookConnectionFactory facebookConnectionFactory() {
FacebookConnectionFactory facebookConnectionFactory = new FacebookConnectionFactory("AppID", "AppSecret");
facebookConnectionFactory.setScope("email");
return facebookConnectionFactory;
}
#Override
public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
cfConfig.addConnectionFactory(facebookConnectionFactory());
}
#Override
public UserIdSource getUserIdSource() {
return new AuthenticationNameUserIdSource();
}
#Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
}
// Other #Bean maybe ...
}
From here, what you can do is, in a Controller/RestController, add a mapping with a RequestParam for your token that you will send to your server:
#Autowired
private FacebookConnectionFactory facebookConnectionFactory;
#Autowired
private UsersConnectionRepository usersConnectionRepository;
#RequestMapping(value = "/my-facebook-url", method = RequestMethod.POST)
public String fb(#RequestParam String token) {
AccessGrant accessGrant = new AccessGrant(token);
Connection<Facebook> connection = facebookConnectionFactory.createConnection(accessGrant);
UserProfile userProfile = connection.fetchUserProfile();
usersConnectionRepository.createConnectionRepository(userProfile.getEmail()).addConnection(connection);
// ...
return "Done";
}
Useful references
UsersConnectionRepository
ConnectionRepository

Related

Combining multiple spring security authentication providers into custom single one

My goal is to make two step authentication with credentials. First step is to check whether the user with principal has a role in a special database table. Second is to perform standard ldap authentication.
What I need is to perform both checks together but a common approach with authentication providers is to claim the authentication success after first success from any authentication provider. So I decided to create a custom AuthenticationProvider implementation which calls for LdapAuthenticationProvider and then performs DB check logic, but it doesn't work since there is nothing to autowire with AbstractLdapAuthenticationProvider.
Please, tell me whether
The approch to solve such problem is rational
If it is rational how can I inject AbstractLdapAuthenticationProvider?
Security configuration code is
#Autowired
private DBRoleAuthenticationProvider dbRoleAuthenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource()
.url("...")
.managerDn("...")
.managerPassword("...")
.and()
.userSearchFilter("uid={0}");
auth.authenticationProvider(dbRoleAuthenticationProvider);
}
Custom authentication provider is
#Component
public class DBRoleAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserHasRoleInDBService userHasRoleInDBService;
#Autowired
private AbstractLdapAuthenticationProvider ldapAuthenticationProvider;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
authentication = ldapAuthenticationProvider.authenticate(authentication);
if (!authentication.isAuthenticated()) {
return authentication;
}
try {
String loginToSearch = (String) authentication.getPrincipal();
if (!userHasRoleInDBService.userHasRole(loginToSearch)) {
authentication.setAuthenticated(false);
}
} catch (Exception e) {
authentication.setAuthenticated(false);
}
return authentication;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Thanks in advance!
Looks like auth.authenticationProvider(dbRoleAuthenticationProvider);
is defeating auth.ldapAuthentication()
You should customize the way you retrieve authorities for the authenticated user by looking at the doc of
userDetailsContextMapper(UserDetailsContextMapper userDetailsContextMapper)
and
ldapAuthoritiesPopulator(LdapAuthoritiesPopulator ldapAuthoritiesPopulator)
of LdapAuthenticationProviderConfigurer

Adding bearer token for custom request in Web Api

I am using a 3rd party library names telogis map in my project. For one of its functionality called Clustering, it is not possible to send request header. Only query string can be passed for clustering and entire logic of API call is done within the JS library.
My project use Bearer token based authenticate and built with Web API 2. To resolve this issue I have passed access token in query string and want validate the request. I created below CustomAuthorize attribute for this:
public class ClusterRequestAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
base.OnAuthorization(actionContext);
}
public override Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
string accessToken = actionContext.Request.GetQueryNameValuePairs().Where(w => w.Key == "access_token").Select(w => w.Value).DefaultIfEmpty().FirstOrDefault();
actionContext.Request.Headers.Remove("Authorization");
actionContext.Request.Headers.Add("Authorization", accessToken);
actionContext.ControllerContext.Request.Headers.Remove("Authorization");
actionContext.ControllerContext.Request.Headers.Add("Authorization", accessToken);
HttpContext.Current.Request.Headers.Remove("Authorization");
HttpContext.Current.Request.Headers.Add("Authorization", accessToken);
return base.OnAuthorizationAsync(actionContext, cancellationToken);
}
protected override bool IsAuthorized(HttpActionContext actionContext)
{
return base.IsAuthorized(actionContext);
}
}
But IsAuthorized is always returning false. I reviewed the Authorize API internal function using Git Link
According to it, I have to set actionContext.ControllerContext.RequestContext.Header which is not accessible due to protection level as it is marked as internal.
Is there any other work around for this issue or can it be done in better way?

Use SignalR to notify connected clients of new login to Web.API

I have an exiting set of APIs developed with ASP.NET Web API 2. Currently users need to authenticate before using most of the APIs (a few are public). Here is my scaled down OAuthProvider.
public class MyOAuthProvider : OAuthAuthorizationServerProvider
{
// Validation of user name and password takes place here
// Code not show for brevity
// If user is validated issue then issue them a JWT token
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, "JWT");
var ticket = new AuthenticationTicket(oAuthIdentity, null);
context.Validated(ticket);
}
In separate application I have experimented with SignalR and have a Hub class that takes the connectionId and userName of a user and stores it to an in-memory Dictionary named connectionDictionary.
public class ChatHub : Hub
{
public static Dictionary<string,string> connectionDictionary = new Dictionary<string, string>();
public override Task OnConnected()
{
connectionDictionary.Add(Context.ConnectionId, Context.User.Identity.Name);
return base.OnConnected();
}
}
How do I go about joining these together so that when a user authenticates with the API, their connectionId and userName are stored in the Dictionary? The idea being that once a user has logged in I will notify all other connected clients.

How to secure reactor netServer with spring security?

I try to develop an "hybrid" server using spring boot webApplication with embedded tomcat and a netServer from reactor to scale-up my Rest Api.
There are no Spring controller, all the incoming request are handled by the netServer.
Never the less i'd like to have a login page using spring security remember me facilities to authenticate the user and use this authentication to secure incoming request on the reactor netServer.
I start to implements the netServer, according to this tutorial reactor thumbmailer
here is my netServer :
NetServer<FullHttpRequest, FullHttpResponse> server = new TcpServerSpec<FullHttpRequest, FullHttpResponse>(NettyTcpServer.class)
.env(env)
.dispatcher("sync")
.listen(8080)
.options(opts)
.consume(ch -> {
// attach an error handler
ch.when(Throwable.class, UserController.errorHandler(ch));
// filter requests by URI
Stream<FullHttpRequest> in = ch.in();
// serve image thumbnail to browser
in.filter((FullHttpRequest req) -> req.getUri().startsWith(UserController.GET_USER_PROFILE))
.consume(UserController.getUserProfile(ch));
})
.get();
So when a user try to load his profile, the incoming request is handled by the userController :
public static Consumer<FullHttpRequest> getUserProfile(NetChannel<FullHttpRequest, FullHttpResponse> channel) {
UserService userService = StaticContextAccessor.getBean(UserService.class);
return req -> {
try {
LoginDTO login = RestApiUtils.parseJson(LoginDTO.class, RestApiUtils.getJsonContent(req));
DefaultFullHttpResponse resp = new DefaultFullHttpResponse(HTTP_1_1, OK);
String result = userService.loadUserProfile(login);
resp.headers().set(CONTENT_TYPE, "application/json");
resp.headers().set(CONTENT_LENGTH, result.length());
resp.content().writeBytes(result.getBytes());
channel.send(resp);
} catch (Exception e) {
channel.send(badRequest(e.getMessage()));
}
};
}
Here is the hack : getUserProfile is a static methode, so i can't use GlobalMethodSecurity to secure it.
i then inject a userService in this controller using a StaticContextAccessor :
#Component
public class StaticContextAccessor {
private static StaticContextAccessor instance;
#Autowired
private ApplicationContext applicationContext;
#PostConstruct
public void registerInstance() {
instance = this;
}
public static <T> T getBean(Class<T> clazz) {
return instance.applicationContext.getBean(clazz);
}
}
UserService :
#Service
#PreAuthorize("true")
public class UserServiceImpl implements UserService{
public String loadUserProfile(LoginDTO login){
//TODO load profile in mongo
return new GsonBuilder().create().toJson(login);
}
}
the service is managed by spring so i guess i could use spring GlobalMethodSecurity on it (i m still developping this part, but i'm not sure this is the best way to secure my netServer)
Is there a easier way to use Spring security on reactor netServer ???
My first web site version was developped with nodeJS to handle many concurent users, and i try to refactor it using a JVM nio solution.
So is spring / reactor / netty a good solution to have a highly scalable server, or should i use something like play! or vertx.io ?
Thank you so much
Have you tried bootstrapping your NetServer from within a JavaConfig #Bean method? Something like:
#Configuration
#EnableReactor
class AppConfig {
public Function<NetChannel, UserController> users() {
return new UserControllerFactory();
}
#Bean
public NetServer netServer(Environment env, Function<NetChannel, UserController> users) {
return new TcpServerSpec(NettyTcpServer.class)
.env(env)
.dispatcher("sync")
.listen(8080)
.options(opts)
.consume(ch -> {
// attach an error handler
ch.when(Throwable.class, UserController.errorHandler(ch));
// filter requests by URI
Stream<FullHttpRequest> in = ch.in();
// serve image thumbnail to browser
in.filter((FullHttpRequest req) -> req.getUri().startsWith(UserController.GET_USER_PROFILE))
.consume(users.apply(ch));
})
.get();
}
}
This should preserve your Spring Security support and enable you to share handlers as beans rather than as return values from static methods. In general, just about everything you need to do in a Reactor TCP app can be done using beans and injection and by returing the NetServer as a bean itself.

How to propagate spring security context to JMS?

I have a web application which sets a spring security context through a spring filter. Services are protected with spring annotations based on users roles. This works.
Asynchronous tasks are executed in JMS listeners (extend javax.jms.MessageListener). The setup of this listeners is done with Spring.
Messages are sent from the web application, at this time a user is authenticated. I need the same authentication in the JMS thread (user and roles) during message processing.
Today this is done by putting the spring authentication in the JMS ObjectMessage:
SecurityContext context = SecurityContextHolder.getContext();
Authentication auth = context.getAuthentication();
... put the auth object in jms message object
Then inside the JMS listener the authentication object is extracted and set in the context:
SecurityContext context = new SecurityContextImpl();
context.setAuthentication(auth);
SecurityContextHolder.setContext(context);
This works most of the time. But when there is a delay before the processing of a message, message will never be processed. I couldn't determine yet the cause of these messages loss, but I'm not sure the way we propagate authentication is good, even if it works in custer when the message is processed in another server.
Is this the right way to propagate a spring authentication ?
Regards,
Mickaƫl
I did not find better solution, but this one works for me just fine.
By sending of JMS Message I'am storing Authentication as Header and respectively by receiving recreating Security Context. In order to store Authentication as Header you have to serialise it as Base64:
class AuthenticationSerializer {
static String serialize(Authentication authentication) {
byte[] bytes = SerializationUtils.serialize(authentication);
return DatatypeConverter.printBase64Binary(bytes);
}
static Authentication deserialize(String authentication) {
byte[] decoded = DatatypeConverter.parseBase64Binary(authentication);
Authentication auth = (Authentication) SerializationUtils.deserialize(decoded);
return auth;
}
}
By sending just set Message header - you can create Decorator for Message Template, so that it will happen automatically. In you decorator just call such method:
private void attachAuthenticationContext(Message message){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String serialized = AuthenticationSerializer.serialize(auth);
message.setStringProperty("authcontext", serialized);
}
Receiving gets more complicated, but it can be also done automatically. Instead of applying #EnableJMS use following Configuration:
#Configuration
class JmsBootstrapConfiguration {
#Bean(name = JmsListenerConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public JmsListenerAnnotationBeanPostProcessor jmsListenerAnnotationProcessor() {
return new JmsListenerPostProcessor();
}
#Bean(name = JmsListenerConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME)
public JmsListenerEndpointRegistry defaultJmsListenerEndpointRegistry() {
return new JmsListenerEndpointRegistry();
}
}
class JmsListenerPostProcessor extends JmsListenerAnnotationBeanPostProcessor {
#Override
protected MethodJmsListenerEndpoint createMethodJmsListenerEndpoint() {
return new ListenerEndpoint();
}
}
class ListenerEndpoint extends MethodJmsListenerEndpoint {
#Override
protected MessagingMessageListenerAdapter createMessageListenerInstance() {
return new ListenerAdapter();
}
}
class ListenerAdapter extends MessagingMessageListenerAdapter {
#Override
public void onMessage(Message jmsMessage, Session session) throws JMSException {
propagateSecurityContext(jmsMessage);
super.onMessage(jmsMessage, session);
}
private void propagateSecurityContext(Message jmsMessage) throws JMSException {
String authStr = jmsMessage.getStringProperty("authcontext");
Authentication auth = AuthenticationSerializer.deserialize(authStr);
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
I have implemented for myself a different solution, which seems easier for me.
Already I have a message converter, the standard JSON Jackson message converter, which I need to configure on the JMSTemplate and the listeners.
So I created a MessageConverter implementation which wraps around another message converter, and propagates the security context via the JMS message properties.
(In my case, the propagated context is a JWT token which I can extract from the current context and apply to the security context of the listening thread).
This way the entire responsibility for propagation of security context is elegantly implemented in a single class, and requires only a little bit of configuration.
Thanks great but I am handling this in easy way . put one util file and solved .
public class AuthenticationSerializerUtil {
public static final String AUTH_CONTEXT = "authContext";
public static String serialize(Authentication authentication) {
byte[] bytes = SerializationUtils.serialize(authentication);
return DatatypeConverter.printBase64Binary(bytes);
}
public static Authentication deserialize(String authentication) {
byte[] decoded = DatatypeConverter.parseBase64Binary(authentication);
Authentication auth = (Authentication) SerializationUtils.deserialize(decoded);
return auth;
}
/**
* taking message and return string json from message & set current context
* #param message
* #return
*/
public static String jsonAndSetContext(Message message){
LongString authContext = (LongString)message.getMessageProperties().getHeaders().get(AUTH_CONTEXT);
Authentication auth = deserialize(authContext.toString());
SecurityContextHolder.getContext().setAuthentication(auth);
byte json[] = message.getBody();
return new String(json);
}
}

Categories

Resources