De forma a facilitar o login de seus usuário, muitas aplicações permitem o uso de provedores de conteúdo como o Twitter ou o Facebook. Com essa técnica de autenticação, o usuário efetua login em sua conta em um desses serviços para poder acessar a aplicação. A aplicação então tenta combinar a conta do provedor com uma conta local. Se uma combinação for encontrada, o usuário é automaticamente autenticado na aplicação.
O Spring Social suporta esse tipo de autenticação através de proverdores de conteúdo com a classe ProviderSignInController
do módulo spring-social-web
. A classe ProviderSignInController
funciona de forma muito parecida com a classe ConnectController
considerando que segue o fluxo OAuth (seja OAuth 1 ou OAuth 2, dependendo do provedor).
Ao invés de criar uma conexão ao final do processo, porém, a classe ProviderSignInController
tenta encontrar uma conexão criada anteriormente e usa essa conexão para autenticar o usuário na aplicação. Se nenhuma conexão anterior for encontrada, o fluxo é direcionada para a página de criação de conta da aplicação para que o usuário possa se registrar na aplicação.
Para adicionar a capacidade de login a sua aplicação Spring, configure a classe ProviderSignInController
com um bean em sua aplicação Spring MVC:
@Bean public ProviderSignInController providerSignInController() { return new ProviderSignInController(connectionFactoryLocator(), usersConnectionRepository(), new SimpleSignInAdapter()); }
Ou com XML, se preferir:
<bean class="org.springframework.social.connect.signin.web.ProviderSignInController"> <!-- relies on by-type autowiring for the constructor-args --> </bean>
De forma análoga ao ConnectController
, ProviderSignInController
usa informações da requisição para determinar o protocolo, nome do host e numero da porta que serão usadas para criar a URL de callback. Mas você pode configurar a propriedade applicationUrl
para a URL base de sua aplicação para superar qualquer problema quando a requisição faz referência a um servidor interno. Por exemplo:
@Bean public ProviderSignInController providerSignInController() { ProviderSignInController controller = new ProviderSignInController(connectionFactoryLocator(), usersConnectionRepository(), new SimpleSignInAdapter()); controller.setApplicationUrl(environment.getProperty("application.url")); return controller; }
Ou quando for utilizado XML:
<bean class="org.springframework.social.connect.signin.web.ProviderSignInController"> <!-- relies on by-type autowiring for the constructor-args --> <property name="applicationUrl" value="${application.url}" /> </bean>
Aqui, é recomendado que você externalize o valor da URL já que pode varias dependendo do ambiente de execução.
Quando a autenticação é feita através de um provedir que suporte OAuth 2, ProviderSignInController
suporta o seguinte fluxo:
POST /signin/{providerId}
– Inicia o login através do redirecionamento para a página de autenticação do provedor.GET /signin/{providerId}?code={verifier}
– Recebe a resposta da autenticação do provedor, aceitando um código. Troca esse código por um token de acesso. Usando esse token, recupera o ID do usuário do provedor e usa para procurar por uma conta local e então autenticar o usuário através do serviço de login.- Se o ID do usuário não combinar com nenhum conta local, a classe
ProviderSignInController
redireciona o fluxo para a URL de criação de conta. A URL padrão é “/signup” (relativa a raiz da aplicação), mas pode ser personalizada configurando a propriedadesignUpUrl
. - Se o ID do usuário combinar com mais de uma conta local,
ProviderSignInController
redireciona o fluxo para a URL de login da aplicação para oferecer ao usuário a chance de entrar com outro provedor ou com seu nome de usuário e senha. A requisição para a URL de login terá um parametro “error” com o valor “multiple_users” para indicar o problema de forma que a página possa informar ao usuário. A URL padarão é “/signin” (relativa a raiz da aplicação), mas pode ser personalizada configurando a propriedadesignInUrl
. - Se algum error ocorra durante o processo de obtenção do token de acesso ou dos dados do usuário,
ProviderSignInController
redirecional o fluxo para a URL de login da aplicação. A requisição para essa URLterá um parametro “error” com o valor “provider” para indicar que ocorreu um erro durante a comunicação com o provedor. A URL padarão é “/signin” (relativa a raiz da aplicação), mas pode ser personalizada configurando a propriedadesignInUrl
.
- Se o ID do usuário não combinar com nenhum conta local, a classe
Parta provedores OAuth 1, o fluxo é um pouco diferente:
POST /signin/{providerId}
– Inicia o fluxo de login. Isso envolve obter um token do provedor e redirecionar o usuário para a página de autenticação do provedor.- Se algum erro ocorrer durante a obtenção do token,
ProviderSignInController
redireciona o fluxo para a URL de login. A requisição para essa URLterá um parametro “error” com o valor “provider” para indicar que ocorreu um erro durante a comunicação com o provedor. A URL padarão é “/signin” (relativa a raiz da aplicação), mas pode ser personalizada configurando a propriedadesignInUrl
.
- Se algum erro ocorrer durante a obtenção do token,
GET /signin/{providerId}?oauth_token={request token}&oauth_verifier={verifier}
– Recebe a resposta da autenticação do provedor, aceitando um código de verificação. Troca esse código por um token de acesso. Usando esse token, recupera o ID do usuário do provedor e usa para procurar por uma conta local e então autenticar o usuário através do serviço de login.- Se o ID do usuário não combinar com nenhum conta local, a classe
ProviderSignInController
redireciona o fluxo para a URL de criação de conta. A URL padrão é “/signup” (relativa a raiz da aplicação), mas pode ser personalizada configurando a propriedadesignUpUrl
. - Se o ID do usuário combinar com mais de uma conta local,
ProviderSignInController
redireciona o fluxo para a URL de login da aplicação para oferecer ao usuário a chance de entrar com outro provedor ou com seu nome de usuário e senha. A requisição para a URL de login terá um parametro “error” com o valor “multiple_users” para indicar o problema de forma que a página possa informar ao usuário. A URL padarão é “/signin” (relativa a raiz da aplicação), mas pode ser personalizada configurando a propriedadesignInUrl
. - Se algum error ocorra durante o processo de obtenção do token de acesso ou dos dados do usuário,
ProviderSignInController
redirecional o fluxo para a URL de login da aplicação. A requisição para essa URLterá um parametro “error” com o valor “provider” para indicar que ocorreu um erro durante a comunicação com o provedor. A URL padarão é “/signin” (relativa a raiz da aplicação), mas pode ser personalizada configurando a propriedadesignInUrl
.
- Se o ID do usuário não combinar com nenhum conta local, a classe
Como mostrado na configuração Java acima, ProviderSignInController
depende de vários outros objetos para fazer seu trabalhao.
- Um
ConnectionFactoryLocator
para localizar o ConnectionFactory usado para criar a conexão com o provedor. - Um
UsersConnectionRepository
para procurar pelo usuário local que tenha uma conexão com o usuário remoto tentando efetuar login. - Um
SignInAdapter
para logar o usuário na aplicação quando uma conexão é encontrada.
Quando estiver usando XML, não é necessário configurar explicitamente esses argumentos do construtor porque o construtor de ProviderSignInController
é anotado com @Inject
. Essas dependências será fornecidas ao ProviderSignInController
via autowiring. Você ainda precisa certificar de existirem beans disponíveis no contexto da aplicação Spring de forma que eles possam ser usados.
Quando você usa um ProviderSignInController
, você deve configurar esses beans para serem criados como scoped proxies:
@Bean @Scope(value="singleton", proxyMode=ScopedProxyMode.INTERFACES) public ConnectionFactoryLocator connectionFactoryLocator() { ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry(); registry.addConnectionFactory(new FacebookConnectionFactory( environment.getProperty("facebook.clientId"), environment.getProperty("facebook.clientSecret"))); registry.addConnectionFactory(new TwitterConnectionFactory( environment.getProperty("twitter.consumerKey"), environment.getProperty("twitter.consumerSecret"))); return registry; } @Bean @Scope(value="singleton", proxyMode=ScopedProxyMode.INTERFACES) public UsersConnectionRepository usersConnectionRepository() { return new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator(), textEncryptor); }
No caso da tentativa de login falhar, a tentativa de login será armazenada na sessão para ser usada para apresentar a página de cadastro ao usuário.
Pela configuração de ConnectionFactoryLocator e UsersConnectionRepository como scoped proxies, permite aos proxies serem passados adiante junto com a tentativa de login para a sessão ao invés de apenas os objetos.
A classe SignInAdapter
é usada exclusivamente para login via um provedor e dessa forma um bean SignInAdapter
precisará ser adicionado à configuração. Mas primeiro, você precisará criar uma implementação para a interface SignInAdapter
.
A interface SignInAdapter
é definida dessa forma:
public interface SignInAdapter { String signIn(String userId, Connection<?> connection, NativeWebRequest request); }
O método signIn()
recebe como parâmetro o ID do usuário local normalizado como uma String
. Nenhuma outra credencial é necessária porque nessa etapa que o método é chamado o usuário já terá sido autenticado com o provedor e sua conexão com esse provedor já foi usada para provar a identidade do usuário.
As implementações dessa interface deve usar esse ID para autenticar o usuário com a aplicação.
Aplicações diferentes irão ter métodos de autenticação diferentes, assim cada aplicação precisa implementar SignInAdapter
de uma forma que combine com seu esquema único.
Por exemplo, suponha que o esquema de autenticação de uma aplicação é baseada no Spring Security e simplesmente use um ID de usuário e seu principal.
Nesse caso, uma implementação de SignInAdapter
poderia parecer com isso:
@Service public class SpringSecuritySignInAdapter implements SignInAdapter { public String signIn(String localUserId, Connection<?> connection, NativeWebRequest request) { SecurityContextHolder.getContext().setAuthentication( new UsernamePasswordAuthenticationToken(localUserId, null, null)); return null; } }
Com o ProviderSignInController
e um SignInAdapter
configurado, o suporte interno para login com um provedor está pronto. A última coisa a ser feita é adicionar um botão de login em sua aplicação, que irá disparar o fluxo de autenticação com o ProviderSignInController
.
Por exemplo, o trecho de código HTML a seguir adiciona um botão “Signin with Twitter” em sua página:
<form id="tw_signin" action="<c:url value="/signin/twitter"/>" method="POST"> <button type="submit"> <img src="<c:url value="/resources/social/twitter/sign-in-with-twitter-d.png"/>" /> </button> </form>
Observe que o caminho usado no atribute action
do formulário é igual ao primeiro passo do fluxo do ProviderSignInController
. No caso, o provedor é identificado como “twitter”.
Clicando nesse botão irá disparar uma requisião POST para “/signin/twitter”, iniciando o fluxo de login com o Twitter. Se o usuário ainda não tiver sido autenticado com o Twitter, o usuário será direcionada para a página de autenticação do Twitter. Após a autenticação, o fluxo será direcionado de volta para a aplicação, que irá completar o processo de login.
Se o ProviderSignInController
não puder encontrar um usuário local associado com o usuário remoto que está tentando entrar no sistema,pode ser uma oportunidade para o usuário de cadastrar na aplicação.
Com a informação sobre o usuário recebida do provedor, o usuário pode ser direcionada para um formulário de cadastro já preenchido para se cadastrar explicitamente com a aplicação.
É possível também usar os dados do provedor para criar implicitamente um novo usuário local para a aplicação sem direcionar o usuário para um formulário de cadastro.
Por padrão, a URL de cadastro será “/signup”, relativa a raiz da aplicação. Você pode sobrepor o padrão pela configuração da propriedade signUpUrl
do controller.
Por exemplo, a configuração a seguir do ProviderSignInController
configurar a URL de cadastro para “/register”:
@Bean public ProviderSignInController providerSignInController() { ProviderSignInController controller = new ProviderSignInController(connectionFactoryLocator(), usersConnectionRepository(), new SimpleSignInAdapter()); controller.setSignUpUrl("/register"); return controller; }
Ou se estiver sendo usada a configuração XML:
<bean class="org.springframework.social.connect.signin.web.ProviderSignInController"> <property name="signUpUrl" value="/register" /> </bean>
Antes de redirecionar o fluxo para a página de cadastro, o ProviderSignInController
coleta algumas informação sobre a tentativa de autenticação. Essa informação pode ser usada para popular o formulário de cadastro e, depois de ser efetuado o cadastro, estabelecer uma conexão entre a nova conta e a conta remota.
Para poder preencher o formulário, você deve obter os dados do usuário de uma conexão recuperada de ProviderSignInUtils.getConnection()
. Por exemplo, considere esse método de um controller Spring MVC que mapeia o view do formulário com um SignupForm
que é conectado ao formulário:
@RequestMapping(value="/signup", method=RequestMethod.GET) public SignupForm signupForm(WebRequest request) { Connection<?> connection = ProviderSignInUtils.getConnection(request); if (connection != null) { return SignupForm.fromProviderUser(connection.fetchUserProfile()); } else { return new SignupForm(); } }
Se ProviderSignInUtils.getConnection()
retornar um conexão, isso significa que houve uma tentativa de login que pode ser completada se o usuário se cadastrar na aplicação. Nesse caso, o objeto SignupForm
é criado a partir dos dados do usuário obtido da conexão com o método fetchUserProfile()
. No método fromProviderUser()
, as propriedades de SignupForm
podems er configuradas dessa forma:
public static SignupForm fromProviderUser(UserProfile providerUser) { SignupForm form = new SignupForm(); form.setFirstName(providerUser.getFirstName()); form.setLastName(providerUser.getLastName()); form.setUsername(providerUser.getUsername()); form.setEmail(providerUser.getEmail()); return form; }
Aqui, o objecto SignupForm
é criado com o primeiro e último nome do usuário, além do nome de usuário e e-mail obtidos de UserProfile
. Além desses dados, UserProfile
também possui um método getName()
que retornará o nome completo do usuário fornecido pelo provedor.
A disponibilidade das propriedades de UserProfile
dependerá do provedor. O Twitter, por exemplo, não fornece o endereço de e-mail do usuário, assim o método getEmail()
sempre retornará null depois de uma tentativa de login com o Twitter.
Após o usuário completar o cadastro em sua aplicação, uma conexão pode ser criada entre a nova conta local e a conta remota. Para completar a conexão, chame ProviderSignInUtils.handlePostSignUp()
. Por exemplo, o método a seguir lida com a submissão do formulário de cadastro, cria uma conta e chama ProviderSignInUtils.handlePostSignUp()
para completar a conexão:
@RequestMapping(value="/signup", method=RequestMethod.POST) public String signup(@Valid SignupForm form, BindingResult formBinding, WebRequest request) { if (formBinding.hasErrors()) { return null; } Account account = createAccount(form, formBinding); if (account != null) { SignInUtils.signin(account.getUsername()); ProviderSignInUtils.handlePostSignUp(account.getUsername(), request); return "redirect:/"; } return null; }
Para permitir a criação implicita de uma conta de usuário. você precisa implementar a interface ConnectionSignUp
e injetar uma instância dessa classe ConnectionSignUp
ao repositório de conexões. A interface ConnectionSignUp
é simples, com apenas um método a ser implementada:
public interface ConnectionSignUp { String execute(Connection<?> connection); }
O método execute()
recebe uma Connection
que pode ser usada para recuperar a informação do usuário. Pode usar esse informação para criar um novo usuário local e retornar o ID desse usuário. Por exemplo, a implementação a seguir recupera os dados do usuário e usa para criar uma nova conta:
public class AccountConnectionSignUp implements ConnectionSignUp { private final AccountRepository accountRepository; public AccountConnectionSignUp(AccountRepository accountRepository) { this.accountRepository = accountRepository; } public String execute(Connection<?> connection) { UserProfile profile = connection.fetchUserProfile(); Account account = new Account(profile.getUsername(), profile.getFirstName(), profile.getLastName()); accountRepository.createAccount(account); return account.getUsername(); } }
Se ocorrer algum problema ao criar implicitamente o usuário (por exemplo, se o “username” escolhido implicitamente já existir) o método execute()
pode retornar null para indicar que o usuário não pôde ser criado implicitamente. Isso fará com que o ProviderSignInController
direcione o usuário para a página de cadastro.
Uma vez que tiver criado uma implementação de ConnectionSignUp
para sua aplicação. você precisa injetar ela em seu UsersConnectionRepository
. Usando uma configuração baseada em Java:
@Bean @Scope(value="singleton", proxyMode=ScopedProxyMode.INTERFACES) public UsersConnectionRepository usersConnectionRepository(AccountRepository accountRepository) { JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository( dataSource, connectionFactoryLocator(), Encryptors.noOpText()); repository.setConnectionSignUp(new AccountConnectionSignUp(accountRepository)); return repository; }
Traduzido de http://docs.spring.io/