下面是 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
方法中配置创建 PasswordEncoder
的 bean
。
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
接口,实现了其中的 authenticate
和 supports
方法。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。
其中示例的邮件验证码登录方式和手机验证码登录方式都已实现。具体实现代码可以查看 EmailAuthenticationProvider
和 SmsAuthenticationProvider
两个类。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring Security 实现多种登录方式(常规方式外的邮件、手机验证码登录) - Python技术站