Spring Security 实现多种登录方式(常规方式外的邮件、手机验证码登录)

下面是 Spring Security 实现多种登录方式的完整攻略:

概述

Spring Security 是 Spring 生态中的一个安全框架,它提供了许多安全方面的功能,如认证、授权和攻击防护等。其中认证功能就是判断用户是否合法,并确定用户是否具有相关资源的访问权限。

常规方式的登录是通过用户名和密码进行认证,而本文要讨论的是除常规方式外的邮件、手机验证码登录。

邮件和手机验证码登录方式需要先获取用户的邮箱和手机号码,然后向邮箱或者手机发送验证码,用户输入正确的验证码后进行登录。这种方式相对于直接输入用户名和密码登录更加快捷和安全。

实现步骤

下面我们来详细讲解如何使用 Spring Security 实现多种登录方式。

1. 添加依赖

首先需要在 Spring Boot 项目中添加 Spring Security 依赖,在 pom.xml 文件中添加如下配置:

<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2. 配置 Spring Security

在 Spring Boot 项目中,我们需要创建一个 Security 配置类,该类需要继承 WebSecurityConfigurerAdapter 类,并在类上加上 @EnableWebSecurity 注解。配置类的代码如下:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private DataSource dataSource;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/").permitAll()
            .antMatchers("/login").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login")
            .defaultSuccessURL("/hello")
            .permitAll()
            .and()
            .logout()
            .permitAll();
    }

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

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl = new JdbcTokenRepositoryImpl();
        jdbcTokenRepositoryImpl.setDataSource(dataSource);
        return jdbcTokenRepositoryImpl;
    }

}

上述代码中,我们配置了 Spring Security 的认证方式和授权方式。用户认证通过后,将通过 defaultSuccessURL 指定的 URL 来到达主页。

注意在 configure 方法中配置创建 PasswordEncoderbean

3. 实现邮件验证码登录

在实现邮件验证码登录之前,首先需要添加一个邮件服务依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

然后,我们需要实现一个 AuthenticationProvider,该类主要是用于验证用户输入的邮件验证码是否与预期的验证码一致。代码实现如下:

@Component
public class EmailAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserService userService;

    @Autowired
    private JavaMailSender javaMailSender;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String email = authentication.getName(); // 获取用户输入的邮箱
        String code = (String) authentication.getCredentials(); // 获取用户输入的验证码

        User user = userService.getUserByEmail(email);
        if (user == null) {
            throw new UsernameNotFoundException("该邮箱还未注册");
        }

        // TODO: 从缓存中获取邮件验证码,并比较验证码是否正确

        return new EmailAuthenticationToken(email, code, user.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return EmailAuthenticationToken.class.isAssignableFrom(authentication);
    }

    private void sendEmail(String email, String code) {
        MimeMessage message = javaMailSender.createMimeMessage();
        try {
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            helper.setFrom("example@mail.com");
            helper.setTo(email);
            helper.setSubject("邮件验证码");
            helper.setText("您的验证码是 " + code, true);
        } catch (MessagingException e) {
            e.printStackTrace();
        }
        javaMailSender.send(message);
    }

}

上述代码中,我们实现了 EmailAuthenticationProvider 类,该类继承了 AuthenticationProvider 接口,实现了其中的 authenticatesupports 方法。authenticate 方法用于验证输入的邮箱、验证码是否正确; supports 方法用于判断是否支持该类型的验证。

然后,我们定义了一个 sendEmail 方法,该方法用于发送邮件验证码,这里采用了 JavaMailSender 来发送邮件。

接下来,我们需要实现 EmailAuthenticationToken,该类用于封装邮件验证码的相关信息。

public class EmailAuthenticationToken extends AbstractAuthenticationToken {

    private final Object principal;
    private Object credentials;

    public EmailAuthenticationToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }

    public EmailAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);
    }

    @Override
    public Object getPrincipal() {
        return principal;
    }

    @Override
    public Object getCredentials() {
        return credentials;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }
        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
        credentials = null;
    }

}

上述代码中,我们定义了一个 EmailAuthenticationToken 类,该类继承了 AbstractAuthenticationToken 并实现了其中的方法。

最后,在 SecurityConfig 类的配置方法中增加以下代码:

.authenticator(new EmailAuthenticationProvider())

即可使用邮件验证码登录方式。

4. 实现手机验证码登录

实现手机验证码登录方式涉及到短信服务,目前提供短信服务的云平台有阿里大鱼、云片等。本篇文章以阿里大鱼为例(参考阿里云短信服务)。

首先,需要在 pom.xml 文件中添加以下配置:

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>4.0.3</version>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
    <version>1.0.0</version>
</dependency>

然后,在代码中实现 AuthenticationProvider,该类用于验证用户输入的手机号码和验证码是否正确。

@Component
public class SmsAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserService userService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final String KEY_PREFIX = "sms:";

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String mobileNumber = authentication.getName();
        String code = (String) authentication.getCredentials();

        User user = userService.getUserByMobileNumber(mobileNumber);
        if (user == null) {
            throw new UsernameNotFoundException("该手机号码还未注册");
        }

        String key = KEY_PREFIX + mobileNumber;
        String expectedCode = redisTemplate.opsForValue().get(key);
        if (StringUtils.isEmpty(expectedCode)) {
            throw new BadCredentialsException("验证码已过期");
        }

        if (!expectedCode.equals(code)) {
            throw new BadCredentialsException("验证码不正确");
        }

        return new SmsAuthenticationToken(mobileNumber, code, user.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return SmsAuthenticationToken.class.isAssignableFrom(authentication);
    }

    private void sendSms(String mobileNumber, String code) {
        String accessKeyId = "yourAccessKeyId";
        String accessKeySecret = "yourAccessSecret";
        String signName = "yourSignName";
        String templateCode = "yourTemplateCode";

        DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
        IAcsClient client = new DefaultAcsClient(profile);

        CommonRequest request = new CommonRequest();
        request.setMethod(MethodType.POST);
        request.setDomain("dysmsapi.aliyuncs.com");
        request.setVersion("2017-05-25");
        request.setAction("SendSms");
        request.putQueryParameter("RegionId", "cn-hangzhou");
        request.putQueryParameter("PhoneNumbers", mobileNumber);
        request.putQueryParameter("SignName", signName);
        request.putQueryParameter("TemplateCode", templateCode);
        request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}");

        try {
            CommonResponse response = client.getCommonResponse(request);
            System.out.println(response.getData());
        } catch (ClientException e) {
            e.printStackTrace();
        }

    }

}

上述代码中,我们定义了一个 SmsAuthenticationProvider 类,用于验证用户输入的手机号码和验证码是否正确。在 sendSms 方法中,我们使用阿里云短信服务发送短信验证码。

然后,我们定义一个 SmsAuthenticationToken 类,该类用于封装手机验证码的相关信息。

public class SmsAuthenticationToken extends AbstractAuthenticationToken {

    private final Object principal;
    private Object credentials;

    public SmsAuthenticationToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }

    public SmsAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);
    }

    @Override
    public Object getPrincipal() {
        return principal;
    }

    @Override
    public Object getCredentials() {
        return credentials;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }
        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
        credentials = null;
    }

}

最后,在 SecurityConfig 类的配置方法中增加以下代码:

.authenticator(new SmsAuthenticationProvider())

即可使用手机验证码登录方式。

示例

具体实现可以参考本项目的源码:spring-security-demo

其中示例的邮件验证码登录方式和手机验证码登录方式都已实现。具体实现代码可以查看 EmailAuthenticationProviderSmsAuthenticationProvider 两个类。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring Security 实现多种登录方式(常规方式外的邮件、手机验证码登录) - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • ServletContext中常用方法介绍

    下面是关于“ServletContext中常用方法介绍”的完整攻略。 一、ServletContext概述 在Java Web中,ServletContext表示Servlet上下文,是一个Web应用的全局上下文环境。每个Web应用都会有一个ServletContext,在应用启动的时候创建,并且在应用停止的时候销毁。 ServletContext中的信息可…

    Java 2023年6月2日
    00
  • java编程实现并查集的路径压缩代码详解

    Java编程实现并查集的路径压缩代码详解 什么是并查集? 并查集(Union-Find)是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。 为什么需要路径压缩? 在并查集的运行过程中,当进行多次find操作时,可能出现树深度太深的问题,导致find操作的时间复杂度增加。在这种情况下,就需要使…

    Java 2023年5月26日
    00
  • Java线程使用同步锁交替执行打印奇数偶数的方法

    Java线程使用同步锁交替执行打印奇数偶数的方法,可以使用以下步骤: 使用一个共享变量来记录当前需要打印的数字。例如,初始化该变量为1,表示需要打印的是第一个奇数。 定义两个线程,一个奇数线程和一个偶数线程。奇数线程打印奇数,偶数线程打印偶数。 在线程中使用while循环,每次判断当前需要打印的数字是否超过了指定范围(比如100),如果超过则结束循环。如果未…

    Java 2023年5月18日
    00
  • Java File类的详解及简单实例

    Java File类的详解及简单实例 简介 Java中的File类是一个用于操作文件和文件夹的类,可以用于检查文件和文件夹的状态、进行文件和文件夹的删除、重命名等操作。File类中包含的方法较多,它与Java IO的输入输出流中的类相互支持,是进行Java操作文件的重要一环。 File类的构造函数 File(String pathname) 用指定的路径na…

    Java 2023年5月20日
    00
  • 安装Java时怎么拦截推广软件?

    下面是“安装Java时怎么拦截推广软件”的完整攻略: 1. 下载Java安装程序 首先,我们需要从Oracle官网上下载Java的安装程序,选择适合自己系统的版本,然后双击运行安装程序。 2. 进入安装向导 在开始安装Java之前,系统会询问你是否同意Oracle的许可协议,同意后点击“下一步”按钮。接着你会看到“选择安装选项”界面,我们需要选择“自定义安装…

    Java 2023年5月26日
    00
  • 基于@JsonSerialize和@JsonInclude注解使用方法

    这里为您详细讲解关于“基于@JsonSerialize和@JsonInclude注解使用方法”的完整攻略。 什么是@JsonSerialize注解和@JsonInclude注解? 在介绍使用方法之前,我们先来简单了解一下这两个注解的概念。 @JsonSerialize注解是用于指定Java对象序列化为JSON数据的类或者具体实例的序列化方式。 @JsonIn…

    Java 2023年5月26日
    00
  • MyBatis动态SQL特性详解

    MyBatis动态SQL特性详解 什么是动态SQL 动态SQL是指在运行时根据不同的条件来动态生成SQL语句的技术,MyBatis支持动态SQL。 使用动态SQL可以在不同的查询条件下进行灵活的SQL组合,提高SQL语句的复用性和灵活性。 动态SQL实现方式 MyBatis提供了两种方式来实现动态SQL:使用XML实现和使用注解实现。 使用XML实现 if元…

    Java 2023年5月19日
    00
  • java 读写文件[多种方法]

    Java 读写文件攻略 在 Java 中,提供了多种读写文件的方法,本文将介绍最常用的几种方法,以及两条示例。 使用 FileInputStream 和 FileOutputStream Java 的 FileInputStream 和 FileOutputStream 分别表示字节流的输入输出流,可以用于读写二进制文件。以下是使用这种方法读写文件的示例代码…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部