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日

相关文章

  • Java实现分布式系统限流

    Java实现分布式系统限流攻略 本文主要介绍如何在Java分布式系统中实现限流功能。限流是一种保护系统稳定性的重要手段,可以有效地避免系统被过量流量攻击、系统资源被耗尽等问题。 什么是限流? 限流是一种系统资源保护机制,通过对系统请求流量进行控制,保证系统能够承受的负载范围内运行。限流可以在短时间内有效地防止系统被过量流量冲垮,保障系统的可用性和稳定性。 常…

    Java 2023年5月30日
    00
  • 如何进行Java代码混淆?

    下面是Java代码混淆的完整使用攻略: 什么是代码混淆? 代码混淆是一种技术,用于在不改变代码功能的情况下,通过改变代码的结构和逻辑,使之变得更难理解和分析,从而增加反向工程的难度。 为什么要进行代码混淆? 在Java应用程序中,代码是以明文形式存在的。如果黑客轻易地分析出加密算法,则攻击者可以使用相同的算法和密钥来破解系统。因此,代码混淆是为了增加系统的安…

    Java 2023年5月11日
    00
  • Jdbctemplate多数据源配置方法详解

    下面就具体讲解“Jdbctemplate多数据源配置方法详解”。 1. 什么是JdbcTemplate多数据源配置 JdbcTemplate多数据源配置是指在一个项目中使用多个数据源,通过JdbcTemplate进行数据操作的方法。JdbcTemplate是Spring框架提供的一个JDBC工具类,可以方便地进行JDBC操作,而多数据源配置是指在一个项目中使…

    Java 2023年6月16日
    00
  • MyBatis入门学习教程-MyBatis快速入门

    对于”MyBatis入门学习教程-MyBatis快速入门”,我们可以按照如下步骤来进行学习,包括环境搭建、对象映射、CRUD操作等内容。 一、环境搭建 下载MyBatis在MyBatis的官方网站上下载最新版本的MyBatis(https://github.com/mybatis/mybatis-3/releases),解压后将其中的mybatis-3.x.…

    Java 2023年5月20日
    00
  • Java动态代理四种实现方式详解

    《Java动态代理四种实现方式详解》是一篇详细介绍Java动态代理技术的文章,本文将从以下几个方面逐一介绍: 什么是Java动态代理 Java动态代理的特点 Java动态代理的四种实现方式 实现示例 总结 1. 什么是Java动态代理 Java动态代理是指在程序运行过程中动态生成代理类的技术。相比于静态代理需要手动编写代理类,动态代理可以让程序更加灵活,更容…

    Java 2023年5月18日
    00
  • 瑞吉外卖day1

    项目整体介绍 项目介绍 本项目(瑞吉外卖)是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括系统管理后台和移动端应用两部分。其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的菜品、套餐、订单等进行管理维护。移动端应用心主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单等。本项目共分为3期进行开发:第一期主要实现基本需求,其中移动端应用通过…

    Java 2023年5月6日
    00
  • spring-data-redis 2.0 的使用示例代码

    Spring Data Redis是一个Spring Data项目的一部分,它提供了与Redis key-value数据库进行交互的一些功能,如分布式面向连接池的Jedis客户端、RedisTemplate、Repository等。 Spring Data Redis 2.0的使用示例代码主要分为以下几个步骤: 1. 添加依赖 在pom.xml中添加如下依赖…

    Java 2023年5月20日
    00
  • Java设计模式之装饰者模式详解和代码实例

    Java设计模式之装饰者模式详解和代码实例 什么是装饰者模式? 装饰者模式是一种结构型设计模式,以动态的方式将责任附加到对象上。装饰者提供了与继承相比更为灵活的替代方案,以扩展功能。 装饰者模式的组成 抽象构件(Component):定义装饰者和被装饰者的公共接口。 具体构件(ConcreteComponent):这是被装饰者,这是需要进行功能扩展的对象。 …

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