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日

相关文章

  • 使用JS获取页面上的所有标签

    获取页面上的所有标签是一个常见的任务,我们可以使用JavaScript来完成这个任务。下面是几个步骤,讲解如何使用JS获取页面上的所有标签。 获取Html页面中的所有标签 可以使用 document.getElementsByTagName(‘tagname’) 方法来获取指定标签名的所有标签,其中 tagname 是标签名。例如,以下代码将获取所有的 &l…

    Java 2023年6月15日
    00
  • SpringMVC 重定向参数RedirectAttributes实例

    下面我将详细讲解“SpringMVC 重定向参数RedirectAttributes实例”的完整攻略。 1. 概述 在SpringMVC中,通过重定向(Redirect)实现页面的跳转是常见的做法。但有时可能需要将一些参数传递到重定向后的页面中。例如,一个操作成功后,我们需要将提示消息传递给下一个页面。这时,就需要使用到RedirectAttributes这…

    Java 2023年6月15日
    00
  • Spring Cloud zuul自定义统一异常处理实现方法

    来详细讲解一下“Spring Cloud zuul自定义统一异常处理实现方法”的完整攻略。 1. 背景介绍 Zuul 是 Netflix 出品的一个基于 JVM 用于构建可伸缩的微服务架构的 API 网关服务器。Zuul 的主要功能是路由转发和过滤器。路由功能是微服务的一部分,它将请求路由到相应的服务。Zuul 还能够对请求进行过滤,其中最常用的是安全过滤器…

    Java 2023年5月27日
    00
  • Java中System.currentTimeMillis()计算方式与时间单位转换讲解

    下面是Java中System.currentTimeMillis()计算方式与时间单位转换讲解的完整攻略。 1. System.currentTimeMillis()计算方式 在Java中,我们可以通过System.currentTimeMillis()方法获取当前的毫秒数,这个毫秒数表示从1970年1月1日00:00:00 GMT起到现在的时间间隔。 这个…

    Java 2023年5月20日
    00
  • SQL 注入式攻击的本质

    SQL注入式攻击指的是攻击者通过在应用程序的输入框中插入恶意的SQL代码,让数据库执行攻击者所期望的操作。SQL注入攻击通常被用来窃取敏感信息、修改数据库数据、或者进行其他恶意操作。 攻击者会尝试在表单、搜索框、登录框等应用程序的输入框中插入SQL代码。如果输入框没有进行正确的数据过滤与转义,攻击者就可以通过输入特定的SQL语句来修改数据库中的数据,这种攻击…

    Java 2023年6月15日
    00
  • Java使用JSONObject操作json实例解析

    下面我将为你详细讲解Java使用JSONObject操作json实例解析的完整攻略。 什么是JSONObject 在Java中操作json数据需要用到第三方库,其中一个流行的库是JSON-java。而JSONObject就是JSON-java库中的一个类,用于操作Json格式的数据。 导入JSON-java库 在使用JSON-java库前,需要先将其导入到项…

    Java 2023年5月26日
    00
  • Hibernate 基本操作、懒加载以及缓存

    前言 上一篇咱们介绍了 Hibernate 以及写了一个 Hibernate 的工具类,快速入门体验了一波 Hibernate 的使用,我们只需通过 Session 对象就能实现数据库的操作了。 现在,这篇介绍使用 Hibernate 进行基本的 CRUD、懒加载以及缓存的知识。 提示:如果你还没看上一篇,那么建议你看完上一篇再来看这篇。 上一篇:一文快速入…

    Java 2023年5月11日
    00
  • java实现建造者模式(Builder Pattern)

    下面我就详细讲解一下“Java实现建造者模式(Builder Pattern)”的完整攻略。 什么是建造者模式? 建造者模式是一种对象创建型设计模式,它允许你创建不同风格的对象,同时避免构造器污染问题。在该模式中,我们将构造过程分步进行,使得在创建对象时能够更加灵活地控制每个构造步骤,从而创建不同类型的对象。 建造者模式的角色 建造者模式中有以下几个角色: …

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