Soziale Anmeldung mit Spring Boot
In diesem Beitrag zeige ich, wie man Social Login in einer Spring Boot-Anwendung verwendet. Also erstellen wir eine Anwendung, aber wir verwenden eine formularbasierte Anmeldung, die der einfachste und unsicherste Authentifizierungsmechanismus ist, den es gibt. Wie können wir diese Vermutung überwinden und den neuesten sichereren Mechanismus verwenden?
Social Login – Tada.
Ja, mit einer zunehmenden Anzahl sozialer Netzwerke wird es immer beliebter und einfacher, einen OAuth-basierten Anmeldemechanismus mithilfe sozialer Netzwerke zu erstellen. Mit anderen Worten, Spring Boot bietet eine Lösung mit einem Social-Login-Plugin und in diesem Beitrag zeigen wir, wie Sie Social-Login verwenden, um Ihre Benutzer zu authentifizieren.
Was brauchen Sie
- IntelliJ
- Java 8
- Twitter-/Facebook-/Google-/Linkedin-/Github-Konten
- Frühlingsstiefel
- Gradle
Spring Social Core
Der Frühling bietet einen spring-social-core
an Projekt, das APIs enthält, um eine Verbindung zu den sozialen Konten des Benutzers herzustellen. Dennoch enthält diese Bibliothek ein Connect-Framework, das eine Lösung zur Verwaltung von Verbindungen mit Anbietern sozialer Dienste bietet. Es bietet Unterstützung für OAuth1a und OAuth2. Der einfachste Weg, diese Bibliothek zu verstehen, besteht darin, dass Sie eine Verbindungsfactory für jeden sozialen Anbieter erstellen. Ein Verbindungsfactory-Locator findet eine Factory zum Erstellen eines Anmeldeanbieters. Ich werde weitere Details bereitstellen, wenn wir dieses Modul implementieren.
Erstelle ein soziales Login-Gradle-Projekt
Falls Sie es aus meinen Blog-Beiträgen noch nicht mitbekommen haben, ich bin von Eclipse auf IntelliJ für den Programmiereditor umgestiegen. Intellij ist einfach intelligenter und einfach zu schreibender Code-Editor. Erstellen Sie also zuerst ein Gradle-Projekt für Spring Boot. (Nebenbemerkung – wenn Sie IntelliJ Ultimate Edition verwenden, bietet es eine Funktion zum Erstellen eines Spring-Projekts.) Wir werden die neueste Version von Spring Boot (2.0.3.RELEASE) verwenden, um dieses Projekt zu erstellen.
Die Gradle-Datei sieht wie folgt aus:
buildscript { ext { springBootVersion = '2.0.3.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' group = 'com.betterjavacode' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 repositories { mavenCentral() maven { url 'https://repo.spring.io/libs-milestone' } } dependencies { compile('org.springframework.boot:spring-boot-starter-thymeleaf') compile("org.springframework.boot:spring-boot-starter-web") compile("org.springframework.social:spring-social-security:1.1.6.RELEASE") compile("org.springframework.social:spring-social-config:1.1.6.RELEASE") compile("org.springframework.social:spring-social-core:1.1.6.RELEASE") compile('org.springframework.boot:spring-boot-starter-security') compile('org.springframework.boot:spring-boot-starter-data-jpa') compile("com.fasterxml.jackson.core:jackson-databind:2.9.6") compile('mysql:mysql-connector-java:5.1.6') compile("org.springframework.social:spring-social-twitter:1.1.2.RELEASE") compile("org.springframework.social:spring-social-facebook:2.0.3.RELEASE") compile("org.springframework.social:spring-social-google:1.0.0.RELEASE") compile("org.springframework.social:spring-social-github:1.0.0.M4") compile("org.springframework.social:spring-social-linkedin:1.0.2.RELEASE") testCompile('org.springframework.boot:spring-boot-starter-test') }
Ich werde im weiteren Verlauf jede in der Gradle-Datei hinzugefügte Abhängigkeit erklären.
Erstellen Sie eine Entitätsklasse
Wir werden eine einfache Entitätsklasse für Benutzer verwenden mit nur einem Feld name
. Dies sieht wie folgt aus:
@JsonIgnoreProperties(ignoreUnknown = true) public class User { public String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
Erstellen Sie eine soziale Konfiguration, um die Spring Social-Bibliothek anzupassen
Zuerst werden wir eine Schnittstelle SocialConfigurer
implementieren die Spring Social Library anbietet. Im Rahmen dieser Implementierung werden wir beispielsweise Verbindungsfabriken für verschiedene soziale Dienstleister erstellen. Auch für dieses Modul verwenden wir InMemoryUsersConnectionRepository
. Sie können jederzeit ein JDBC-basiertes Repository für Datenbankbenutzerverbindungen implementieren. Diese Klasse sieht wie folgt aus:
package com.betterjavacode.reusablesociallogin; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.*; import org.springframework.core.env.Environment; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.encrypt.Encryptors; import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.social.UserIdSource; import org.springframework.social.config.annotation.ConnectionFactoryConfigurer; import org.springframework.social.config.annotation.EnableSocial; import org.springframework.social.config.annotation.SocialConfigurer; import org.springframework.social.config.annotation.SocialConfigurerAdapter; import org.springframework.social.connect.Connection; import org.springframework.social.connect.ConnectionFactoryLocator; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.connect.UsersConnectionRepository; import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository; import org.springframework.social.connect.mem.InMemoryUsersConnectionRepository; import org.springframework.social.connect.support.ConnectionFactoryRegistry; import org.springframework.social.connect.web.ProviderSignInController; import org.springframework.social.facebook.connect.FacebookConnectionFactory; import org.springframework.social.github.connect.GitHubConnectionFactory; import org.springframework.social.google.connect.GoogleConnectionFactory; import org.springframework.social.linkedin.connect.LinkedInConnectionFactory; import org.springframework.social.security.AuthenticationNameUserIdSource; import org.springframework.social.twitter.api.Twitter; import org.springframework.social.twitter.api.impl.TwitterTemplate; import org.springframework.social.twitter.connect.TwitterConnectionFactory; import javax.inject.Inject; import javax.sql.DataSource; @Configuration @PropertySource("classpath:application.properties") @EnableSocial public class SocialConfig implements SocialConfigurer { @Autowired private DataSource dataSource; @Override public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer, Environment environment) { connectionFactoryConfigurer.addConnectionFactory(new TwitterConnectionFactory(environment.getProperty("spring.social.twitter.consumerKey"), environment.getProperty("spring.social.twitter.consumerSecret"))); connectionFactoryConfigurer.addConnectionFactory(new FacebookConnectionFactory(environment.getProperty("spring.social.facebook.appId"),environment.getProperty("spring.social.facebook.appSecret"))); GoogleConnectionFactory googleConnectionFactory = new GoogleConnectionFactory(environment.getProperty("spring.social.google.appId"),environment.getProperty("spring.social.google.appSecret")); googleConnectionFactory.setScope("profile"); connectionFactoryConfigurer.addConnectionFactory(googleConnectionFactory); connectionFactoryConfigurer.addConnectionFactory(new GitHubConnectionFactory(environment.getProperty("spring.social.github.appId"), environment.getProperty("spring.social.github.appSecret"))); connectionFactoryConfigurer.addConnectionFactory(new LinkedInConnectionFactory(environment.getProperty("spring.social.linkedin.appId"), environment.getProperty("spring.social.linkedin.appSecret"))); } @Override public UserIdSource getUserIdSource() { return new UserIdSource() { @Override public String getUserId() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in"); } return authentication.getName(); } }; } @Override public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) { InMemoryUsersConnectionRepository usersConnectionRepository = new InMemoryUsersConnectionRepository( connectionFactoryLocator); return usersConnectionRepository; } }
Wie Sie in dieser Klasse sehen, beziehe ich mich auf application.properties
. Die application.properties
wird wie folgt aussehen:
spring.social.twitter.consumerKey=[Twitter consumer key] spring.social.twitter.consumerSecret=[Twitter consumer secret] spring.social.facebook.appId=[Facebook client id] spring.social.facebook.appSecret=[Facebook client secret] spring.social.google.appId=[Google client id] spring.social.google.appSecret=[Google client secret] spring.social.github.appId=[Github client id] spring.social.github.appSecret=[Github client secret] spring.social.linkedin.appId=[Linkedin client id] spring.social.linkedin.appSecret=[Linkedin client secret] server.port = 8448
Mit anderen Worten, um clientid
zu erhalten und clientsecret
müssen Sie Ihren Antrag bei jedem Sozialleistungsträger registrieren. Wir werden das in diesem Beitrag nicht behandeln.
Erstellen Sie eine Spring-Web-Sicherheitskonfiguration
In dieser Klasse erweitern wir websecurityconfigureradapter
und HTTP-Sicherheit als Teil der Spring-Sicherheitsimplementierung konfigurieren. Wir fügen auch einen bean
hinzu um Anmeldeanbieter zu erstellen, die Teil von Spring Social sind. Darüber hinaus werden wir diesen Anmeldeanbieter implementieren, um Benutzern die Möglichkeit zu bieten, sich bei ihrem sozialen Anbieter anzumelden.
package com.betterjavacode.reusablesociallogin; import com.betterjavacode.reusablesociallogin.social.SocialConnectionSignup; import com.betterjavacode.reusablesociallogin.social.SocialSignInAdapter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.social.connect.ConnectionFactoryLocator; import org.springframework.social.connect.UsersConnectionRepository; import org.springframework.social.connect.mem.InMemoryUsersConnectionRepository; import org.springframework.social.connect.web.ProviderSignInController; import org.springframework.social.security.SpringSocialConfigurer; @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private ConnectionFactoryLocator connectionFactoryLocator; @Autowired private UsersConnectionRepository usersConnectionRepository; @Autowired private SocialConnectionSignup socialConnectionSignup; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/","/socialloginhome","/signin/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } @Bean public ProviderSignInController providerSignInController() { ((InMemoryUsersConnectionRepository) usersConnectionRepository) .setConnectionSignUp(socialConnectionSignup); return new ProviderSignInController( connectionFactoryLocator, usersConnectionRepository, new SocialSignInAdapter()); } }
Wie Sie in dieser Klasse sehen, haben wir einen bean ProviderSignInController
was SocialSignInAdapter
verwendet .
Implementieren Sie einen Anmeldeadapter
Dies ist vor allem das Herzstück unserer Implementierung, in der die Authentifizierung stattfindet und dem Benutzer eine Rolle für den Zugriff auf die Anwendung zugewiesen wird. Der Benutzer wird zur Anwendung umgeleitet, wenn sich der Benutzer erfolgreich authentifiziert. Diese Klasse sieht wie folgt aus:
package com.betterjavacode.reusablesociallogin.social; import com.betterjavacode.reusablesociallogin.util.ConnectionHelper; import com.betterjavacode.reusablesociallogin.util.ConnectionType; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.social.connect.Connection; import org.springframework.social.connect.web.SignInAdapter; import org.springframework.stereotype.Service; import org.springframework.web.context.request.NativeWebRequest; import java.util.ArrayList; import java.util.List; @Service public class SocialSignInAdapter implements SignInAdapter { @Override public String signIn(String userId, Connection<?> connection, NativeWebRequest request) { Authentication authentication = getAuthentication(userId, connection); SecurityContextHolder.getContext().setAuthentication(authentication); return "/socialloginsuccess"; } private Authentication getAuthentication(String localUserId, Connection<?> connection) { List<GrantedAuthority> roles = getRoles(connection); String password = null; Authentication authentication = new UsernamePasswordAuthenticationToken(localUserId, password, roles); return authentication; } private List<GrantedAuthority> getRoles(Connection<?> connection) { List<GrantedAuthority> roles = new ArrayList<GrantedAuthority>(); ConnectionType type = ConnectionHelper.getConnectionType(connection); String role = type.toString(); roles.add(new SimpleGrantedAuthority(role)); return roles; } }
Wie Sie in getAuthentication
sehen übergeben wir userId
und Rollen für die tokenbasierte Authentifizierung.
Wenn sich der Benutzer zuvor noch nicht bei einem sozialen Anbieter angemeldet hat, wird er aufgefordert, sich anzumelden, und wird nach der ersten Anmeldung zur Anwendung weitergeleitet.
package com.betterjavacode.reusablesociallogin.social; import com.betterjavacode.reusablesociallogin.entity.User; import com.betterjavacode.reusablesociallogin.util.UserHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.social.connect.Connection; import org.springframework.social.connect.ConnectionSignUp; import org.springframework.stereotype.Service; @Service public class SocialConnectionSignup implements ConnectionSignUp { @Autowired UserHelper userHelper; @Override public String execute(Connection<?> connection) { User user = userHelper.getUser(connection); return user.getName(); } }
Wie Sie in dieser Klasse sehen, haben wir Autowired
ein userHelper
Klasse, diese Klasse wird eine Implementierung haben, um Benutzerdetails von jedem sozialen Anbieter zu erhalten.
Daher dieser UserHelper
wird wie folgt aussehen:
package com.betterjavacode.reusablesociallogin.util; import com.betterjavacode.reusablesociallogin.entity.User; import org.springframework.social.connect.Connection; import org.springframework.social.facebook.api.Facebook; import org.springframework.social.github.api.GitHub; import org.springframework.social.google.api.Google; import org.springframework.social.linkedin.api.LinkedIn; import org.springframework.social.twitter.api.Twitter; import org.springframework.stereotype.Component; @Component public class UserHelper { public User getUser(Connection<?> connection) { User user = null; //get the connection type ConnectionType type = ConnectionHelper.getConnectionType(connection); if (type.equals(ConnectionType.TWITTER)) { user = getTwitterUser(connection); } else if (type.equals(ConnectionType.FACEBOOK)) { user = getFacebookUser(connection); } else if (type.equals(ConnectionType.GOOGLE)) { user = getGoogleUser(connection); } else if (type.equals(ConnectionType.GITHUB)) { user = getGithubUser(connection); } else if (type.equals(ConnectionType.LINKEDIN)){ user = getLinkedInUser(connection); } return user; } private User getTwitterUser(Connection<?> connection) { User user = new User(); Twitter twitterApi = (Twitter)connection.getApi(); String name = twitterApi.userOperations().getUserProfile().getName(); user.setName(name); return user; } private User getFacebookUser(Connection<?> connection) { User user = new User(); Facebook facebookApi = (Facebook)connection.getApi(); String [] fields = { "name" }; User userProfile = facebookApi.fetchObject("me", User.class, fields); String name = userProfile.getName(); user.setName(name); return user; } private User getGoogleUser(Connection<?> connection) { User user = new User(); Google googleApi = (Google) connection.getApi(); String name = googleApi.plusOperations().getGoogleProfile().getDisplayName(); user.setName(name); return user; } private User getGithubUser(Connection<?> connection) { User user = new User(); GitHub githubApi = (GitHub) connection.getApi(); String name = githubApi.userOperations().getUserProfile().getName(); user.setName(name); return user; } private User getLinkedInUser(Connection<?> connection) { User user = new User(); LinkedIn linkedInApi = (LinkedIn) connection.getApi(); String name = linkedInApi.profileOperations().getUserProfile().getFirstName(); user.setName(name); return user; } }
Einen Controller und Ansichten implementieren
In ähnlicher Weise besteht das letzte Stück in diesem Puzzle darin, einen Controller und entsprechende Ansichten hinzuzufügen, damit der Benutzer beim Zugriff auf die Anwendung zur Authentifizierung aufgefordert wird.
Wir werden jedoch einen Login-Controller hinzufügen, der drei Ansichten für login
hat , sociallogin
und socialloginsuccess
. Dies sieht wie folgt aus:
@Controller public class LoginController { @RequestMapping(value="/login", method= RequestMethod.GET) public String login(Model model) { return "login"; } @RequestMapping(value ="/socialloginhome", method = RequestMethod.GET) public String socialloginhome(Model model) { return "socialloginhome"; } @RequestMapping(value="/socialloginsuccess", method= RequestMethod.GET) public String socialloginsuccess(Model model) { return "socialloginsuccess"; } }
Anwendung ausführen
Nachdem ich die Anwendung erstellt und ausgeführt habe, sieht der Ablauf wie folgt aus:
Sie klicken auf here
Sie gelangen zum folgenden Social-Login-Bildschirm:
Ich wähle Facebook und der serverseitige Code leitet mich zum Facebook-Anmeldebildschirm weiter. Sobald ich meine Anmeldeinformationen eingegeben habe, leitet mich Facebook wie folgt zu meiner Anwendung zurück:
Daher haben wir eine erfolgreiche soziale Authentifizierung gezeigt. Schließlich ist Social Login Teil jeder SaaS-Anwendung, die Sie erstellen.
Schlussfolgerung
Abschließend haben wir gezeigt, wie man ein Social-Login-Modul mit der Social-Funktion von Spring Boot erstellt. Außerdem wird der Code dafür hier zum Download zur Verfügung stehen.
Referenzen
- Spring Social Überblick – Spring Social
- Soziale Entwicklung im Frühling – Soziale Entwicklung im Frühling
- Frühlingssozial-Tutorial – Frühlingssozial-Tutorial