将社交登录添加到Spring MVC Web应用程序:配置

过去,用户使用用户名和密码组合登录。 尽管如今有些人仍然偏爱传统方式,但越来越多的用户希望使用其社交媒体帐户登录。

这就是使Spring Social(及其子项目)成为Spring项目组合有用的补充的原因。 但是,将Spring Social与Spring Security集成起来有点麻烦。

Spring Social 1.1.0改变了这一切。 它提供了与Spring Security的无缝集成,并且Spring Security的Java配置支持使配置就像在公园里散步一样。

您不必相信我的话。 继续阅读,您将学到如何做到这一点。

我们解决方案的要求如下:

  • 必须可以使用常规注册表单创建用户帐户。
  • 必须有可能通过使用社交登录来创建用户帐户。
  • 必须可以使用用户名和密码登录。
  • 必须可以使用SaaS API提供程序进行登录。
  • 该应用程序必须支持Facebook和Twitter。
  • 应用程序必须使用“常规” Spring MVC控制器(无REST)。

让我们先看一下本教程的先决条件。

先决条件

本教程假定您已经创建了示例应用程序使用的Facebook和Twitter应用程序。 您可以通过以下链接创建这些应用程序:

如果您不知道该怎么做,可以查看以下链接:

让我们继续前进,了解如何使用Maven获得所需的依赖关系。

使用Maven获取所需的依赖关系

我们要做的第一件事是使用Maven获得所需的依赖关系。 为此,我们可以在POM文件中声明以下依赖关系:

  • Spring Security(版本3.2.0.RC1)。
    • 核心模块包含核心身份验证和访问控制组件。
  • Apache HttpClient(版本4.2.5)。 Apache HttpClient是Spring Social的可选依赖项(但建议使用)。 如果存在,Spring Social会将其用作HTTP客户端。 否则,Spring social将使用标准的Java SE组件。
  • Spring社交(版本1.1.0.BUILD-SNAPSHOT)。
    • 核心模块包含连接框架,并为OAuth客户端提供支持。
  • Spring Social Facebook(版本1.1.0.BUILD-SNAPSHOT)是Spring Social的扩展,它提供Facebook集成。
  • Spring Social Twitter(版本1.1.0.BUILD-SNAPSHOT)是对Social Social的扩展,它提供了Twitter集成。

pom.xml文件的相关部分如下所示:

<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>3.2.0.RC1</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>3.2.0.RC1</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>3.2.0.RC1</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>3.2.0.RC1</version>
</dependency>

<!-- Use Apache HttpClient as HTTP Client -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.2.5</version>
</dependency>

<!-- Spring Social -->
<dependency>
    <groupId>org.springframework.social</groupId>
    <artifactId>spring-social-core</artifactId>
    <version>1.1.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>    
    <groupId>org.springframework.social</groupId>
    <artifactId>spring-social-security</artifactId>
    <version>1.1.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>org.springframework.social</groupId>
    <artifactId>spring-social-web</artifactId>
    <version>1.1.0.BUILD-SNAPSHOT</version>
</dependency>

<!-- Spring Social Facebook -->
<dependency>
    <groupId>org.springframework.social</groupId>
    <artifactId>spring-social-facebook</artifactId>
    <version>1.1.0.BUILD-SNAPSHOT</version>
</dependency>

<!-- Spring Social Twitter -->
<dependency>
    <groupId>org.springframework.social</groupId>
    <artifactId>spring-social-twitter</artifactId>
    <version>1.1.0.BUILD-SNAPSHOT</version>
</dependency>

注意 :我们的应用程序还具有其他依赖项。 例如,它使用Spring Framework 3.2.4.RELEASE,Spring Data JPA 1.3.4和Hibernate 4.2.4.Final。 为了清楚起见,这些依赖项从依赖项列表中省略。 您可以从Github获取依赖项的完整列表

您可能还想阅读以下文档,这些文档为您提供了有关此博客文章(Spring Security和Spring Social)中讨论的框架的依赖性的更多信息:

接下来,我们必须为应用程序的配置属性创建一个属性文件。 让我们找出这是如何完成的。

创建属性文件

我们可以按照以下步骤创建属性文件:

  1. 创建一个名为application.properties的文件,并确保从类路径中找到它。
  2. 配置数据库连接。
  3. 配置休眠。
  4. 将Facebook应用程序ID和应用程序密钥添加到属性文件。
  5. 将Twitter使用者密钥和使用者密钥添加到属性文件中。

application.properties文件的内容如下所示:

#Database Configuration
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/socialtwitter
db.username=socialtwitter
db.password=password

#Hibernate Configuration
hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.format_sql=true
hibernate.hbm2ddl.auto=validate
hibernate.ejb.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy
hibernate.show_sql=false

#Facebook
facebook.app.id=foo
facebook.app.secret=bar

#Twitter
twitter.consumer.key=foo
twitter.consumer.secret=bar

在配置应用程序之前,我们必须创建一些通用组件。 让我们找出这些组件是什么,以及如何创建它们。

创建通用组件

我们必须创建在身份验证过程中使用的三个组件。 这些组件是:

  • 我们创建了一个类,其中包含经过身份验证的用户的用户详细信息。
  • 我们必须创建一个实现UserDetailsS​​ervice接口的类。 当用户使用表单登录时,此类用于加载用户信息。
  • 我们必须创建一个实现SocialUserDetailsS​​ervice接口的类。 当用户使用社交登录时,该类用于加载用户信息。

让我们继续前进,找出如何实现这些类。

创建用户详细信息类

在创建包含已认证用户的用户详细信息的类时,我们必须考虑以下要求:

  • 存储使用表单登录的用户的用户详细信息的类必须实现UserDetails接口。
  • 存储使用社交登录的用户的用户详细信息的类必须实现SocialUserDetails接口。

Spring Social具有满足这两个要求的SocialUser类。 但是,通常我们希望将特定于应用程序的信息添加到我们的用户详细信息类中。

我们可以按照以下步骤进行操作:

  1. 创建用户详细信息类。
  2. 扩展SocialUser类。
  3. 将应用程序特定的字段添加到创建的类。 我们的示例应用程序的特定于应用程序的字段是: idfirstNamelastNamerolesocialSignInProvider
  4. 创建一个构造函数,该构造函数将用户名,密码和授予的权限的集合作为参数。 将这些参数传递给SocialUser类的构造函数。
  5. 为特定于应用程序的字段创建吸气剂。
  6. 添加一个内部构建器类,该类用于构建新的ExampleUserDetails对象。

我们的用户详细信息类的源代码如下所示:

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.social.security.SocialUser;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

public class ExampleUserDetails extends SocialUser {

    private Long id;

    private String firstName;

    private String lastName;

    private Role role;

    private SocialMediaService socialSignInProvider;

    public ExampleUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
    }

    //Getters are omitted for the sake of clarity.

    public static class Builder {

        private Long id;

        private String username;

        private String firstName;

        private String lastName;

        private String password;

        private Role role;

        private SocialMediaService socialSignInProvider;

        private Set<GrantedAuthority> authorities;

        public Builder() {
            this.authorities = new HashSet<>();
        }

        public Builder firstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder id(Long id) {
            this.id = id;
            return this;
        }

        public Builder lastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public Builder password(String password) {
            if (password == null) {
                password = "SocialUser";
            }

            this.password = password;
            return this;
        }

        public Builder role(Role role) {
            this.role = role;

            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.toString());
            this.authorities.add(authority);

            return this;
        }

        public Builder socialSignInProvider(SocialMediaService socialSignInProvider) {
            this.socialSignInProvider = socialSignInProvider;
            return this;
        }

        public Builder username(String username) {
            this.username = username;
            return this;
        }

        public ExampleUserDetails build() {
            ExampleUserDetails user = new ExampleUserDetails(username, password, authorities);

            user.id = id;
            user.firstName = firstName;
            user.lastName = lastName;
            user.role = role;
            user.socialSignInProvider = socialSignInProvider;

            return user;
        }
    }
}

角色是一个简单的枚举,它指定示例应用程序的“合法”用户角色。 其源代码如下:

public enum Role {
    ROLE_USER
}

SocialMediaService是一个枚举,用于标识用户为我们的示例应用程序创建用户帐户时使用的SaaS API提供程序。 其源代码如下:

public enum SocialMediaService {
    FACEBOOK,
    TWITTER
}
实现UserDetailsS​​ervice接口

通过执行以下步骤,我们可以创建自己的UserDetailsS​​ervice接口实现:

  1. 创建一个实现UserDetailsS​​ervice接口的类。
  2. UserRepository字段添加到创建的类。
  3. 创建一个将UserRepository作为构造函数参数的构造函数,并使用@Autowired注释对构造函数进行注释。
  4. 实现UserDetailsS​​ervice接口的loadUserByUsername(String username)方法。 此方法的实现包括以下步骤:
    1. 通过调用UserRepository接口的findByEmail()方法来获取用户。 此方法返回其电子邮件与作为方法参数给出的用户名匹配的用户。
    2. 如果找不到用户,则抛出新的UsernameNotFoundException
    3. 创建一个新的ExampleUserDetails对象。
    4. 返回创建的对象。

RepositoryUserDetailsS​​ervice类的源代码如下所示:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class RepositoryUserDetailsService implements UserDetailsService {

    private UserRepository repository;

    @Autowired
    public RepositoryUserDetailsService(UserRepository repository) {
        this.repository = repository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = repository.findByEmail(username);

        if (user == null) {
            throw new UsernameNotFoundException("No user found with username: " + username);
        }

        ExampleUserDetails principal = ExampleUserDetails.getBuilder()
                .firstName(user.getFirstName())
                .id(user.getId())
                .lastName(user.getLastName())
                .password(user.getPassword())
                .role(user.getRole())
                .socialSignInProvider(user.getSignInProvider())
                .username(user.getEmail())
                .build();

        return principal;
    }
}

UserRepository是一个简单的Spring Data JPA存储库,其源代码如下所示:

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {

    public User findByEmail(String email);
}

用户是我们的示例应用程序的唯一实体,它包含为我们的示例应用程序创建了用户帐户的用户的信息。 其源代码的相关部分如下所示:

import javax.persistence.*;

@Entity
@Table(name = "users")
public class User extends BaseEntity<Long> {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "email", length = 100, nullable = false, unique = true)
    private String email;

    @Column(name = "first_name", length = 100,nullable = false)
    private String firstName;

    @Column(name = "last_name", length = 100, nullable = false)
    private String lastName;

    @Column(name = "password", length = 255)
    private String password;

    @Enumerated(EnumType.STRING)
    @Column(name = "role", length = 20, nullable = false)
    private Role role;

    @Enumerated(EnumType.STRING)
    @Column(name = "sign_in_provider", length = 20)
    private SocialMediaService signInProvider;

    public User() {

    }

    //Getters and other methods are omitted for the sake of clarity.
}
实现SocialUserDetailsS​​ervice接口

我们可以通过执行以下步骤来实现SocialUserDetailsS​​ervice接口:

  1. 创建一个实现SocialUserDetailsS​​ervice的类。
  2. UserDetailsS​​ervice字段添加到创建的类。
  3. 创建一个将UserDetailsS​​ervice对象作为构造函数参数的构造函数,并使用@Autowired注释对构造函数进行注释。
  4. 实现SocialUserDetailsInterfaceloadUserByUserId(String userId)方法。
  5. 通过调用loadUserByUsername()方法获取正确的UserDetails对象,并将用户ID作为方法参数传递。 我们可以这样做是因为我们的应用程序使用用户的用户名作为用户ID。
  6. 将返回的对象强制转换SocialUserDetails对象并返回。

SimpleSocialUserDetailsS​​ervice类的源代码如下所示:

import org.springframework.dao.DataAccessException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.social.security.SocialUser;
import org.springframework.social.security.SocialUserDetails;
import org.springframework.social.security.SocialUserDetailsService;

public class SimpleSocialUserDetailsService implements SocialUserDetailsService {

    private UserDetailsService userDetailsService;

    public SimpleSocialUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException, DataAccessException {
        UserDetails userDetails = userDetailsService.loadUserByUsername(userId);
        return (SocialUserDetails) userDetails;
    }
}

就这些。 现在,我们准备配置应用程序的应用程序上下文。 让我们找出如何做到这一点。

配置应用程序上下文

本节介绍如何使用Java配置来配置示例应用程序的应用程序上下文。 遵循以下准则,将应用程序上下文配置分为多个配置类:

  1. 每个配置类都包含与示例应用程序的特定部分相关联的配置。 如果我们必须在创建初始配置后的几个月(或几年)内检出某项内容或进行某些更改,则可以轻松找到相关的配置。
  2. 对配置进行了划分,使之可以通过使用Spring Test MVC轻松编写Web层的单元测试。 我们将在本教程的第三部分中进一步讨论这一点,在该部分中,我们将为应用程序的Web层编写单元测试。
  3. 当我们为应用程序编写集成测试时,使用该配置可以轻松删除对外部资源的依赖。 我们将在本教程的第四部分中进一步讨论这一点,该教程描述了如何为应用程序编写集成测试。

注意 :如果要使用XML配置,可以查看此博客文章的示例应用程序,该示例应用程序也具有有效的XML配置 (尽管没有web.xml)。

让我们从配置应用程序的持久层开始。

配置持久层

我们应用程序的持久层存储用户帐户信息,并提供一种访问此信息的方法。 这很重要,原因有两个:

  • 我们可以提供一种使用用户名和密码登录的方法。
  • 我们可以存储特定于应用程序的信息,并将此信息链接到使用社交登录的用户。

让我们找出如何通过使用两个Java配置类来配置它。

注意 :示例应用程序的持久层使用Spring Data JPA 1.3.4。 我将使该部分尽可能的薄。 如果您想了解有关Spring Data JPA的更多信息,可以阅读我的Spring Data JPA教程 。 我还写了一本关于Spring Data书,它应该可以帮助您立即开始使用。

我们可以按照以下步骤配置持久层:

  1. 创建配置类,并使用@Configuration注释对创建的类进行注释。
  2. @EnableJpaRepositories批注为类注解,并设置Spring Data JPA信息库的基本包。
  3. 通过使用@EnableTransactionManagement批注注释配置类来启用Spring事务管理。
  4. 在类中添加一个Environment字段,并使用@Autowired批注对该字段进行批注。 我们不需要使用@PropertySource批注来配置属性文件,因为已经在“父”应用程序上下文配置类中对其进行了配置。
  5. 配置数据源bean。 这个bean提供了到实体管理器的数据库连接,但是它还有另一个目的。 当Spring Social保持与数据库的连接并从数据库加载它们的连接时,将使用它。
  6. 配置事务管理器bean。
  7. 配置实体管理器工厂bean。

PersistenceContext类的源代码如下所示:

import com.jolbox.bonecp.BoneCPDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableJpaRepositories(basePackages = {
        "net.petrikainulainen.spring.social.signinmvc.user.repository"
})
@EnableTransactionManagement
public class PersistenceContext {

    @Resource
    private Environment env;

    @Bean
    public DataSource dataSource() {
        BoneCPDataSource dataSource = new BoneCPDataSource();

        dataSource.setDriverClass(env.getRequiredProperty("db.driver"));
        dataSource.setJdbcUrl(env.getRequiredProperty("db.url"));
        dataSource.setUsername(env.getRequiredProperty("db.username"));
        dataSource.setPassword(env.getRequiredProperty("db.password"));

        return dataSource;
    }

    @Bean
    public JpaTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return transactionManager;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();

        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        entityManagerFactoryBean.setPackagesToScan({
                "net.petrikainulainen.spring.social.signinmvc.common.model",
                "net.petrikainulainen.spring.social.signinmvc.user.model"
        });

        Properties jpaProperties = new Properties();
        jpaProperties.put("hibernate.dialect", env.getRequiredProperty("hibernate.dialect"));
        jpaProperties.put("hibernate.format_sql", env.getRequiredProperty("hibernate.format_sql"));
        jpaProperties.put("hibernate.hbm2ddl.auto", env.getRequiredProperty("hibernate.hbm2ddl.auto"));
        jpaProperties.put("hibernate.ejb.naming_strategy", env.getRequiredProperty("hibernate.ejb.naming_strategy"));
        jpaProperties.put("hibernate.show_sql", env.getRequiredProperty("hibernate.show_sql"));

        entityManagerFactoryBean.setJpaProperties(jpaProperties);

        return entityManagerFactoryBean;
    }
}

让我们继续研究如何为应用程序创建安全配置。

配置Spring Security

Spring Security为使用表单登录或社交登录的用户提供身份验证机制,并且还负责授权。

我们可以按照以下步骤配置Spring Security:

  1. 创建配置类,并使用@Configuration注释对创建的类进行注释。
  2. @EnableWebSecurity注释对类进行注释。 这样就可以通过实现WebSecurityConfigurer接口来配置Spring Security。
  3. 确保我们的配置类扩展了WebSecurityConfigurerAdapter类,该类是用于创建WebSecurityConfigurer实例的基类。 完成此操作后,我们可以通过覆盖方法来自定义安全配置。
  4. ApplicationContext字段添加到配置类,并使用@Autowired批注对该字段进行批注。
  5. UserRepository字段添加到配置中,并使用@Autowired注释对该字段进行注释。
  6. 重写WebSecurityConfigurerAdapter类的configure(WebSecurity web)方法。 确保Spring Security忽略对静态资源(例如CSS和Javascript文件)的请求。
  7. 重写WebSecurityConfigurerAdapter类的configure(HttpSecurity http)方法,并通过以下步骤实现它:
    1. 通过执行以下步骤配置表单登录:
      1. 将登录页面网址设置为“ / login”。
      2. 将处理登录表单提交的URL设置为“ / login / authenticate”。
      3. 将登录失败网址设置为“ / login?error = bad_credentials”。
    2. 通过执行以下步骤配置注销功能:
      1. 确保注销后删除名为JSESSIONID的cookie。
      2. 将注销网址设置为“ / logout”。
      3. 将注销成功URL设置为“ / login”。
    3. 配置基于url的授权。 此阶段的主要目的是确保匿名用户可以访问与登录/注册过程相关的所有URL,并保护应用程序的其余部分不受匿名用户的攻击。
    4. SocialAuthenticationFilter添加到Spring Security过滤器链。 为此,我们可以创建一个新的SpringSocialConfigurer对象,并确保在配置Spring Security时使用该对象。
    5. ApplicationContext字段的值设置为所有SecurityConfigurer实例共享的对象。
  8. 配置用来对用户密码进行哈希处理的PasswordEncoder bean(如果用户使用表单注册和登录)。 我们可以通过创建一个新的BCryptPasswordEncoder对象并返回创建的对象来做到这一点。
  9. 配置UserDetailsS​​ervice Bean。 我们可以通过创建一个新的RepositoryUserDetailsS​​ervice对象并将UserRepository作为构造函数参数来实现。
  10. 重写WebSecurityConfigurerAdapter类的registerAuthentication(AuthenticationManagerBuilder auth)方法。 如果用户使用表单登录,我们将使用此方法配置身份验证请求。 通过执行以下步骤来实现此方法:
    1. UserDetailsS​​ervice bean传递给作为方法参数给出的AuthenticationManagerBuilder对象。
    2. PasswordEncoder bean传递给作为方法参数给出的AuthenticationManagerBuilder对象。
  11. 配置SocialUserDetailsS​​ervice bean。 我们可以通过创建一个新的SimpleSocialUserDetailsS​​ervice对象并将UserDetailsS​​ervice bean作为构造函数参数来实现。 使用社交登录时,此bean加载用户特定的数据。

我们的应用程序上下文配置类的源代码如下所示:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.social.security.SocialUserDetailsService;
import org.springframework.social.security.SpringSocialConfigurer;

@Configuration
@EnableWebSecurity
public class SecurityContext extends WebSecurityConfigurerAdapter {

    @Autowired
    private ApplicationContext context;

    @Autowired
    private UserRepository userRepository;

    @Override
    public void configure(WebSecurity web) throws Exception {
        web
                //Spring Security ignores request to static resources such as CSS or JS files.
                .ignoring()
                    .antMatchers("/static/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //Configures form login
                .formLogin()
                    .loginPage("/login")
                    .loginProcessingUrl("/login/authenticate")
                    .failureUrl("/login?error=bad_credentials")
                //Configures the logout function
                .and()
                    .logout()
                        .deleteCookies("JSESSIONID")
                        .logoutUrl("/logout")
                        .logoutSuccessUrl("/login")
                //Configures url based authorization
                .and()
                    .authorizeRequests()
                        //Anyone can access the urls
                        .antMatchers(
                                "/auth/**",
                                "/login",
                                "/signin/**",
                                "/signup/**",
                                "/user/register/**"
                        ).permitAll()
                        //The rest of the our application is protected.
                        .antMatchers("/**").hasRole("USER")
                //Adds the SocialAuthenticationFilter to Spring Security's filter chain.
                .and()
                    .apply(new SpringSocialConfigurer())
                .and()
                    .setSharedObject(ApplicationContext.class, context);
    }

    @Override
    protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(10);
    }

    @Bean
    public SocialUserDetailsService socialUserDetailsService() {
        return new SimpleSocialUserDetailsService(userDetailsService());
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return new RepositoryUserDetailsService(userRepository);
    }
}

让我们继续前进,了解如何配置Spring Social。

配置Spring Social

Spring Social提供与Facebook和Twitter等SaaS API提供程序的集成。 我们可以按照以下步骤配置Spring Social:

  1. 创建实现SocialConfigurer接口的应用程序上下文配置类,并使用@Configuration注释对创建的类进行注释。 SocialConfigurer接口声明了可用于配置Spring Social的回调方法。
  2. @EnableSocial批注对类进行批注。 这将启用Spring Social并导入SocialConfiguration配置类。
  3. @Profile注释为类添加注释,并将字符串“ application”设置为其值。 这样可以确保仅在活动Spring概要文件为“ application”时使用此应用程序上下文配置类。 这很重要,因为它使我们无需依赖Facebook或Twitter即可为我们的应用程序编写集成测试。
  4. DataSource字段添加到配置类,并使用@Autowired批注对该字段进行批注。
  5. SocialConfigurer界面的addConnectionFactories()方法添加到创建的配置类中。 此方法采用以下两个方法参数:
    1. 第一个参数是ConnectionFactoryConfigurer对象,可用于注册连接工厂。
    2. 第二个参数是一个Environment对象,它代表示例应用程序在其中运行的环境。
  6. 通过执行以下步骤来实现addConnectionFactories()方法:
    1. 创建一个新的TwitterConnectionFactory对象,并将使用者密钥和使用者密钥作为构造函数参数传递。
    2. 通过调用ConnectionFactoryConfigurer接口的addConnectionFactory()方法来注册创建的TwitterConnectionFactory对象。 将创建的TwitterConnectionFactory对象作为方法参数传递。
    3. 创建一个新的FacebookConnectionFactory对象,并将应用程序ID和应用程序密钥作为构造函数参数传递。
    4. 通过调用ConnectionFactoryConfigurer接口的addConnectionFactory方法来注册创建的FacebookConnectionFactory对象。 将创建的FacebookConnectionFactory对象作为方法参数传递。
  7. SocialConfigurer接口的getUserIdSource()方法添加到创建的类中。 此方法返回的UserIdSource对象负责确定用户的正确帐户ID。 因为我们的示例应用程序使用用户的用户名作为帐户ID,所以我们必须通过返回新的AuthenticationNameUserIdSource对象来实现此方法。
  8. SocialConfigurer接口的getUsersConnectionRepository()方法添加到创建的类中。 此方法将ConnectionFactoryLocator对象作为方法参数,并返回UsersConnectionRepository对象。
  9. 通过执行以下步骤来实现getUsersConnectionRepository()方法:
    1. 创建一个新的JdbcUsersConnectionRepository对象,并将以下对象作为构造函数参数传递:
      1. 第一个参数是DataSource对象。 我们将dataSource字段的值作为第一个方法参数传递。
      2. 第二个参数是ConnectionFactoryLocator对象。 我们将connectionFactoryLocator方法参数的值作为第二个方法参数传递。
      3. 第三个参数是TextEncryptor对象,该对象加密SaaS API提供程序与我们的应用程序之间建立的连接的授权详细信息。 我们通过调用Encryptors类的noOpText()方法来创建此对象。 这意味着我们的示例应用程序将这些详细信息存储为纯文本。 这在开发阶段很方便,但是我们不应该在生产中使用它
    2. 返回创建的对象。
  10. 配置ConnectController bean。 配置此bean的方法有两个参数。 第一个参数是ConnectionFactoryLocator bean。 第二个参数是使用的ConnectionRepository bean。 在创建新的ConnectController对象时,将这些参数作为构造函数参数传递。

我们的配置类的源代码如下所示:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
import org.springframework.security.crypto.encrypt.Encryptors;
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.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.web.ConnectController;
import org.springframework.social.facebook.connect.FacebookConnectionFactory;
import org.springframework.social.security.AuthenticationNameUserIdSource;
import org.springframework.social.twitter.connect.TwitterConnectionFactory;

import javax.sql.DataSource;

@Configuration
@EnableSocial
@Profile("application")
public class SocialContext implements SocialConfigurer {

    @Autowired
    private DataSource dataSource;

    @Override
    public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
        cfConfig.addConnectionFactory(new TwitterConnectionFactory(
                env.getProperty("twitter.consumer.key"),
                env.getProperty("twitter.consumer.secret")
        ));
        cfConfig.addConnectionFactory(new FacebookConnectionFactory(
                env.getProperty("facebook.app.id"),
                env.getProperty("facebook.app.secret")
        ));
    }

    @Override
    public UserIdSource getUserIdSource() {
        return new AuthenticationNameUserIdSource();
    }

    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
        return new JdbcUsersConnectionRepository(
                dataSource,
                connectionFactoryLocator,
                Encryptors.noOpText()
        );
    }

    @Bean
    public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator, ConnectionRepository connectionRepository) {
        return new ConnectController(connectionFactoryLocator, connectionRepository);
    }
}

下一步是配置应用程序的Web层。 让我们开始工作。

配置Web层

我们可以按照以下步骤配置应用程序的Web层:

  1. 通过执行以下步骤来创建配置类:
    1. 扩展WebMvcConfigurerAdapter类。
    2. @Configuration注释对创建的类进行注释。
  2. 通过使用@ComponentScan批注注释该类并设置控制器的基本软件包,确保找到所有控制器类。
  3. 通过使用@EnableWebMvc注释对类进行注释来启用注释驱动的Web mvc。
  4. 确保容器的默认Servlet提供静态资源。
    1. 通过重写WebMvcConfigurerAdapter类的addResourceHandlers()方法来配置静态资源。
    2. 确保对静态资源的请求被转发到容器的默认Servlet。 这是通过重写WebMvcConfigurerAdapter类的configureDefaultServletHandling()方法来完成的。
  5. 配置异常解析器 bean。
  6. 配置ViewResolver bean。

WebAppContext类的源代码如下所示:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

import java.util.Properties;

@Configuration
@ComponentScan(basePackages = {
        "net.petrikainulainen.spring.social.signinmvc.common.controller",
        "net.petrikainulainen.spring.social.signinmvc.security.controller",
        "net.petrikainulainen.spring.social.signinmvc.user.controller"
})
@EnableWebMvc
public class WebAppContext extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("/static/");
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean
    public SimpleMappingExceptionResolver exceptionResolver() {
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();

        Properties exceptionMappings = new Properties();

        exceptionMappings.put("java.lang.Exception", "error/error");
        exceptionMappings.put("java.lang.RuntimeException", "error/error");

        exceptionResolver.setExceptionMappings(exceptionMappings);

        Properties statusCodes = new Properties();

        statusCodes.put("error/404", "404");
        statusCodes.put("error/error", "500");

        exceptionResolver.setStatusCodes(statusCodes);

        return exceptionResolver;
    }

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();

        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/WEB-INF/jsp/");
        viewResolver.setSuffix(".jsp");

        return viewResolver;
    }
}

让我们找出如何将所有这些结合在一起,并为我们的应用程序创建一个“父”应用程序上下文配置类。

绑在一起

最后一个应用程序上下文配置类具有三个职责:

  1. 它配置了整个示例应用程序中使用的常规组件。
  2. 它确保在类路径扫描期间找到我们应用程序的服务类。
  3. 它是我们应用程序的根应用程序上下文配置类。

我们可以按照以下步骤创建此配置类:

  1. 创建配置类,并使用@Configuration注释对创建的类进行注释。
  2. 通过使用@ComponentScan批注注释该类并设置我们的服务的基本包,确保在组件扫描期间找到我们的服务类。
  3. 通过使用@Import批注对类进行导入,以导入其他应用程序上下文配置类。
  4. @PropertySource注释对类进行注释,并将其配置为从类路径中查找名为application.properties的属性文件。 这样可以确保可以在导入的应用程序上下文配置类中访问配置属性。
  5. 配置MessageSource bean。
  6. 配置PropertyPlaceHolderConfigurer bean。

ExampleApplicationContext类的源代码如下所示:

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.*;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.context.support.ResourceBundleMessageSource;

@Configuration
@ComponentScan(basePackages = {
        "net.petrikainulainen.spring.social.signinmvc.user.service"
})
@Import({WebAppContext.class, PersistenceContext.class, SecurityContext.class, SocialContext.class})
@PropertySource("classpath:application.properties")
public class ExampleApplicationContext {

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();

        messageSource.setBasename("i18n/messages");
        messageSource.setUseCodeAsDefaultMessage(true);

        return messageSource;
    }

    @Bean
    public PropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

现在,我们已经配置了示例应用程序的应用程序上下文。 但是,我们仍然必须配置Web应用程序。 让我们看看如何使用Java配置来做到这一点。

配置Web应用程序

我们的最后一步是配置示例应用程序。 只要将应用程序部署到Servlet 3.0兼容容器中,我们就可以在没有web.xml的情况下执行此操作。

我们可以按照以下步骤配置Web应用程序:

  1. 创建一个实现WebApplicationInitializer接口的类。
  2. 通过覆盖WebApplicationInitializer接口的onStartup()方法来配置我们的应用程序。 我们可以通过执行以下步骤来实现此方法:
      1. 创建应用程序的根上下文,并将ExampleApplicationContext类注册到创建的根上下文。
      2. 配置调度程序servlet

    C)配置字符编码过滤器
    D)配置Spring Security过滤器链
    E)配置Sitemesh 。 F)将上下文加载器侦听器添加到Servlet上下文。

ExampleApplicationConfig类的源代码如下所示:

import org.sitemesh.config.ConfigurableSiteMeshFilter;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.*;
import java.util.EnumSet;

public class ExampleApplicationConfig implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(ExampleApplicationContext.class);

        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(rootContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");

        EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD);

        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);

        FilterRegistration.Dynamic characterEncoding = servletContext.addFilter("characterEncoding", characterEncodingFilter);
        characterEncoding.addMappingForUrlPatterns(dispatcherTypes, true, "/*");

        FilterRegistration.Dynamic security = servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy());
        security.addMappingForUrlPatterns(dispatcherTypes, true, "/*");

        FilterRegistration.Dynamic sitemesh = servletContext.addFilter("sitemesh", new ConfigurableSiteMeshFilter());
        sitemesh.addMappingForUrlPatterns(dispatcherTypes, true, "*.jsp");

        servletContext.addListener(new ContextLoaderListener(rootContext));
    }
}

下一步是什么?

现在,我们已经使用Java配置成功配置了示例应用程序。 本教程教了我们两件事:

  • 我们学习了如何实现Spring Security和Spring Social所需的组件。
  • 我们学习了通过使用Java配置来集成Spring Security和Spring Social。

本教程的下一部分描述了如何向示例应用程序添加注册和身份验证功能。

PS与往常一样,此博客文章的示例应用程序可在Github上获得


翻译自: https://www.javacodegeeks.com/2013/10/adding-social-sign-in-to-a-spring-mvc-web-application-configuration.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值