Spring Security(学习笔记)--用户名密码登录与短信验证码(模拟短信)登录同时支持怎么弄?

重点标识

思路:先输入手机号,再生成验证码,存到session或者redis,发给手机,然后登陆验证。

准备数据库

先准备一张user表,如下


SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(0) NULL DEFAULT NULL,
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `enabled` tinyint(1) NULL DEFAULT NULL,
  `accountNonExpired` tinyint(1) NULL DEFAULT NULL,
  `accountNonLocked` tinyint(1) NULL DEFAULT NULL,
  `credentialsNonExpired` tinyint(1) NULL DEFAULT NULL,
  `phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

数据库这样就行了,接下来我们来构建一个Spring Boot项目,加入依赖如下:

  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

准备User类,这里简单点,就不专门搞那些锁定了,直接给true。


public class User implements UserDetails {
    private Integer id;
    private String username;

    private String password;

    private String phone;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

准备UserService:


@Service
public class UserService implements UserDetailsService {


    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User user = userMapper.loadUserByUsername(username);
        if(user == null){
            throw new UsernameNotFoundException("用户名找不到!");
        };
        return user;
    }
}

最后,准备一个mapper


@Mapper
public interface UserMapper {
    @Select("SELECT * FROM USER WHERE USERNAME=#{username}")
    public User loadUserByUsername(String username) ;
}

别忘了,配置数据库地址!


spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql:///security_mybatis

配置Security


@Configuration
public class SecurityConfig {

    @Bean
    WebSecurityCustomizer webSecurityCustomizer(){
        return web -> web.ignoring().requestMatchers("*.js");
    }


    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http.authorizeHttpRequests(a->a
                        .requestMatchers("/sendSms").permitAll()
                        .anyRequest().authenticated())
                //这里注意,不配置就是默认的登陆地址login,但是如果配置了登录页面,则后面的登录地址也要配置
                .formLogin(f->f.loginPage("/login.html").loginProcessingUrl("/login").permitAll())
                .csrf(c->c.disable());
        return http.build();
    }
}

登录页面也放这里吧:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="jquery-3.7.1.min.js"></script>
</head>
<body>
<form action="/login" method="post">

    <table>
        <tr>
            <td>用户名</td>
            <td><input type="text" name="username" value="admin"></td>
        </tr>
        <tr>
            <td>密码</td>
            <td><input type="password" name="password" value="123"></td>
        </tr>
        <tr>
            <td><input type="submit" name="登录"></td>
        </tr>
    </table>
</form>

<h1>验证码</h1>


<form action="/login" method="post">

    <table>
        <tr>
            <td>手机号</td>
            <td><input type="text" name="phone" value="" id="phone"></td>
        </tr>
        <tr>
            <td>验证码</td>
            <td><input type="text" name="code" value=""></td>
            <td><button type="button" onclick="sendsmS()"  name="">发送验证码</button></td>
        </tr>
        <tr>
            <td><input type="submit" name="登录"></td>
        </tr>
    </table>
</form>
</body>

<script>

    function sendsmS(){
        let phone = $("#phone").val();
        $.get("/sendSms",{phone:phone})
    }
</script>
</html>

再来一个验证登录和发送验证码的接口就ok了。



@RestController
public class UserController {

    SecureRandom secureRandom = new SecureRandom();

    @GetMapping("/user")
    public String username(){
        return SecurityContextHolder.getContext().getAuthentication().getName();
    }


    /**
     * 调用这个方法发送验证码
     */
    @GetMapping("/sendSms")
    public void sendSms(HttpSession httpSession,String phone){
        //1000-9000的数字
       int code= secureRandom.nextInt(9000)+ 1000;
        System.out.println(code);

    }
}

简单测试一下,用户密码可以登录,点击发送验证码,可以在后台打印出来,这样,前期的准备工作就OK了

验证验证码

在之前,我们看过用户名,密码验证是通过UsernamePasswordAuthenticationFilter过滤器来实现的,在这个过滤器里面,主要是UsernamePasswordAuthenticationToken来进行认证标识的。

那就好办了,我们模仿他,也写一个Sms的token。

我们知道,每一种认证方式,都有自己的token,短信验证码也不列外。

认证逻辑我们之前看过,登录是交给AuthenticationManager处理的,AuthenticationManager又交给AuthenticationProvider,而AuthenticationProvider则是ProviderManager统一管理的

每一个AuthenticationProvider都有一个supports方法,就例如用户名密码验证的DaoAuthenticationProvider,判断是不是UsernamePasswordAuthenticationToken这个token。(在它的父类里面实现的)

在这里插入图片描述

可以看到AbstractUserDetailsAuthenticationProvider这个抽象类是实现了AuthenticationProvider的一些接口,然后再交给DaoAuthenticationProvider,那我们也这样来。
先创建一个SmsAuthenticationToken

package org.tongzhou.sms_login.config;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;

import java.util.Collection;

public class SmsAuthenticationToken extends AbstractAuthenticationToken{
    private static final long serialVersionUID = 620L;
    private final Object principal;
    private Object credentials;

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

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

    public static SmsAuthenticationToken unauthenticated(Object principal, Object credentials) {
        return new SmsAuthenticationToken(principal, credentials);
    }

    public static SmsAuthenticationToken authenticated(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        return new SmsAuthenticationToken(principal, credentials, authorities);
    }

    public Object getCredentials() {
        return this.credentials;
    }

    public Object getPrincipal() {
        return this.principal;
    }

    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        Assert.isTrue(!isAuthenticated, "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        super.setAuthenticated(false);
    }

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

有了token还不够,还要把他交给Provider进行管理,看一下DaoAuthenticationProvider这个怎么实现的,继承自一个抽象类,然后重写additionalAuthenticationChecks方法,那我们也这样弄。

先创建一个AbstractSmsAuthenticationProvider

package org.tongzhou.sms_login.config;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.util.Assert;

public abstract class AbstractSmsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {

    protected final Log logger = LogFactory.getLog(this.getClass());
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    private UserCache userCache = new NullUserCache();
    private boolean forcePrincipalAsString = false;
    protected boolean hideUserNotFoundExceptions = true;
    private UserDetailsChecker preAuthenticationChecks = new AbstractSmsAuthenticationProvider.DefaultPreAuthenticationChecks();
    private UserDetailsChecker postAuthenticationChecks = new AbstractSmsAuthenticationProvider.DefaultPostAuthenticationChecks();
    private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

    public AbstractSmsAuthenticationProvider() {
    }

    protected abstract void additionalAuthenticationChecks(UserDetails userDetails, SmsAuthenticationToken authentication) throws AuthenticationException;

    public final void afterPropertiesSet() throws Exception {
        Assert.notNull(this.userCache, "A user cache must be set");
        Assert.notNull(this.messages, "A message source must be set");
        this.doAfterPropertiesSet();
    }

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(SmsAuthenticationToken.class, authentication, () -> {
            return this.messages.getMessage("AbstractSmsAuthenticationProvider.onlySupports", "Only SmsAuthenticationToken is supported");
        });
        String username = this.determineUsername(authentication);
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                user = this.retrieveUser(username, (SmsAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("Failed to find user '" + username + "'");
                if (!this.hideUserNotFoundExceptions) {
                    throw var6;
                }

                throw new BadCredentialsException(this.messages.getMessage("AbstractSmsAuthenticationProvider.badCredentials", "Bad credentials"));
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (SmsAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            if (!cacheWasUsed) {
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (SmsAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (SmsAuthenticationToken)authentication);
        }

        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

    private String determineUsername(Authentication authentication) {
        return authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
    }

    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
        SmsAuthenticationToken result = SmsAuthenticationToken.authenticated(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());
        this.logger.debug("Authenticated user");
        return result;
    }

    protected void doAfterPropertiesSet() throws Exception {
    }

    public UserCache getUserCache() {
        return this.userCache;
    }

    public boolean isForcePrincipalAsString() {
        return this.forcePrincipalAsString;
    }

    public boolean isHideUserNotFoundExceptions() {
        return this.hideUserNotFoundExceptions;
    }

    protected abstract UserDetails retrieveUser(String username, SmsAuthenticationToken authentication) throws AuthenticationException;

    public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
        this.forcePrincipalAsString = forcePrincipalAsString;
    }

    public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
        this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
    }

    public void setMessageSource(MessageSource messageSource) {
        this.messages = new MessageSourceAccessor(messageSource);
    }

    public void setUserCache(UserCache userCache) {
        this.userCache = userCache;
    }

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

    protected UserDetailsChecker getPreAuthenticationChecks() {
        return this.preAuthenticationChecks;
    }

    public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) {
        this.preAuthenticationChecks = preAuthenticationChecks;
    }

    protected UserDetailsChecker getPostAuthenticationChecks() {
        return this.postAuthenticationChecks;
    }

    public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) {
        this.postAuthenticationChecks = postAuthenticationChecks;
    }

    public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
        this.authoritiesMapper = authoritiesMapper;
    }

    private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
        private DefaultPreAuthenticationChecks() {
        }

        public void check(UserDetails user) {
            if (!user.isAccountNonLocked()) {
                AbstractSmsAuthenticationProvider.this.logger.debug("Failed to authenticate since user account is locked");
                throw new LockedException(AbstractSmsAuthenticationProvider.this.messages.getMessage("AbstractSmsAuthenticationProvider.locked", "User account is locked"));
            } else if (!user.isEnabled()) {
                AbstractSmsAuthenticationProvider.this.logger.debug("Failed to authenticate since user account is disabled");
                throw new DisabledException(AbstractSmsAuthenticationProvider.this.messages.getMessage("AbstractSmsAuthenticationProvider.disabled", "User is disabled"));
            } else if (!user.isAccountNonExpired()) {
                AbstractSmsAuthenticationProvider.this.logger.debug("Failed to authenticate since user account has expired");
                throw new AccountExpiredException(AbstractSmsAuthenticationProvider.this.messages.getMessage("AbstractSmsAuthenticationProvider.expired", "User account has expired"));
            }
        }
    }

    private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
        private DefaultPostAuthenticationChecks() {
        }

        public void check(UserDetails user) {
            if (!user.isCredentialsNonExpired()) {
                AbstractSmsAuthenticationProvider.this.logger.debug("Failed to authenticate since user account credentials have expired");
                throw new CredentialsExpiredException(AbstractSmsAuthenticationProvider.this.messages.getMessage("AbstractSmsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
            }
        }
    }
}


然后,在创建它的继承类SmsAuthenticationProvider

package org.tongzhou.sms_login.config;

import jakarta.servlet.http.HttpSession;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.tongzhou.sms_login.service.UserService;

public class SmsAuthenticationProvider extends AbstractSmsAuthenticationProvider{

    private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
    private PasswordEncoder passwordEncoder;
    private volatile String userNotFoundEncodedPassword;
    private UserDetailsService userDetailsService;
    private UserDetailsPasswordService userDetailsPasswordService;

    public SmsAuthenticationProvider() {
        this(PasswordEncoderFactories.createDelegatingPasswordEncoder());
    }

    public SmsAuthenticationProvider(PasswordEncoder passwordEncoder) {
        this.setPasswordEncoder(passwordEncoder);
    }

    /**
     *
     * 这里是做密码校验的,我们可以在这做验证码校验
     * @param userDetails
     * @param authentication
     * @throws AuthenticationException
     */
    protected void additionalAuthenticationChecks(UserDetails userDetails, SmsAuthenticationToken authentication) throws AuthenticationException {

        //获取到session
        HttpSession session = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession();

        //拿到session中的验证码
        String code = String.valueOf(session.getAttribute((String) authentication.getPrincipal()));

        //用户输入的验证码
        String credentials = (String) authentication.getCredentials();
        //不相等,
        if(!code.equalsIgnoreCase(credentials)){
            throw new BadCredentialsException("验证码输入错误!");
        }
        if (authentication.getCredentials() == null) {
            this.logger.debug("Failed to authenticate since no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        } else {
            String presentedPassword = authentication.getCredentials().toString();
//            if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
//                this.logger.debug("Failed to authenticate since password does not match stored value");
//                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
//            }
        }
    }

    protected void doAfterPropertiesSet() {
        Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
    }

    /**
     *
     * 这里原本是根据用户名去查询的,修改下,让他根据手机号查询
     * 用户登录的时候,手机号和验证码被封装成authentication对象
     * @param username
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    protected final UserDetails retrieveUser(String username, SmsAuthenticationToken authentication) throws AuthenticationException {
        this.prepareTimingAttackProtection();
        UserDetails loadedUser = null;
        try {
           
//            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            UserDetailsService us = this.getUserDetailsService();
            if(us instanceof  UserService userService){

                loadedUser = userService.loadUserByPhone((String)authentication.getPrincipal());
            }

            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
            } else {
                return loadedUser;
            }
        } catch (UsernameNotFoundException var4) {
            this.mitigateAgainstTimingAttack(authentication);
            throw var4;
        } catch (InternalAuthenticationServiceException var5) {
            throw var5;
        } catch (Exception var6) {
            throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
        }
    }

    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
        boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(user.getPassword());
        //密码升级,这里不需要升级,先注释掉。
//        if (upgradeEncoding) {
//            String presentedPassword = authentication.getCredentials().toString();
//            String newPassword = this.passwordEncoder.encode(presentedPassword);
//            user = this.userDetailsPasswordService.updatePassword(user, newPassword);
//        }

        return super.createSuccessAuthentication(principal, authentication, user);
    }

    private void prepareTimingAttackProtection() {
        if (this.userNotFoundEncodedPassword == null) {
            this.userNotFoundEncodedPassword = this.passwordEncoder.encode("userNotFoundPassword");
        }

    }

    private void mitigateAgainstTimingAttack(SmsAuthenticationToken authentication) {
        if (authentication.getCredentials() != null) {
            String presentedPassword = authentication.getCredentials().toString();
            this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
        }

    }

    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
        this.passwordEncoder = passwordEncoder;
        this.userNotFoundEncodedPassword = null;
    }

    protected PasswordEncoder getPasswordEncoder() {
        return this.passwordEncoder;
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    protected UserDetailsService getUserDetailsService() {
        return this.userDetailsService;
    }

    public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
        this.userDetailsPasswordService = userDetailsPasswordService;
    }
}

最后,我们定义一个过滤器,把这个token放进去

public class SmsAuthenticationFilter extends UsernamePasswordAuthenticationFilter {


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        String phone = request.getParameter("phone");
        if(phone != null && !"".equals(phone)){
            SmsAuthenticationToken authenticationToken = SmsAuthenticationToken.unauthenticated(phone,request.getParameter("code"));
            return this.getAuthenticationManager().authenticate(authenticationToken);
        }

        return super.attemptAuthentication(request, response);
    }
}

最后,别忘了配置。


package org.tongzhou.sms_login.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.tongzhou.sms_login.service.UserService;

@Configuration
public class SecurityConfig {

    @Autowired
    UserService userService;


    @Bean
    SmsAuthenticationFilter smsAuthenticationFilter(){
        SmsAuthenticationFilter smsAuthenticationFilter = new SmsAuthenticationFilter();
        smsAuthenticationFilter.setFilterProcessesUrl("/login");
        smsAuthenticationFilter.setAuthenticationSuccessHandler(((request, response, authentication) -> {
            response.getWriter().write(authentication.getName());
        }));

        smsAuthenticationFilter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());

        smsAuthenticationFilter.setAuthenticationManager(authenticationManager());
        return smsAuthenticationFilter;
    }

    @Bean
    AuthenticationManager authenticationManager(){
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        SmsAuthenticationProvider smsAuthenticationProvider = new SmsAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userService);
        smsAuthenticationProvider.setUserDetailsService(userService);
        ProviderManager providerManager = new ProviderManager(daoAuthenticationProvider, smsAuthenticationProvider);
        return providerManager;
    }

    @Bean
    WebSecurityCustomizer webSecurityCustomizer(){
        return web -> web.ignoring().requestMatchers("*.js");
    }


    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http.authorizeHttpRequests(a->a
                        .requestMatchers("/sendSms").permitAll()
                        .anyRequest().authenticated())
                //这里注意,不配置就是默认的登陆地址login,但是如果配置了登录页面,则后面的登录地址也要配置
                .formLogin(f->f.loginPage("/login.html").loginProcessingUrl("/login").permitAll())
                .csrf(c->c.disable())
                .addFilterBefore(smsAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
}

这样,就大功告成了,把请求页面和请求接口也放出来:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="jquery-3.7.1.min.js"></script>
</head>
<body>
<form action="/login" method="post">

    <table>
        <tr>
            <td>用户名</td>
            <td><input type="text" name="username" value="admin"></td>
        </tr>
        <tr>
            <td>密码</td>
            <td><input type="password" name="password" value="123"></td>
        </tr>
        <tr>
            <td><input type="submit" name="登录"></td>
        </tr>
    </table>
</form>

<h1>验证码</h1>


<form action="/login" method="post">

    <table>
        <tr>
            <td>手机号</td>
            <td><input type="text" name="phone" value="" id="phone"></td>
        </tr>
        <tr>
            <td>验证码</td>
            <td><input type="text" name="code" value=""></td>
            <td><button type="button" onclick="sendsmS()"  name="">发送验证码</button></td>
        </tr>
        <tr>
            <td><input type="submit" name="登录"></td>
        </tr>
    </table>
</form>
</body>

<script>

    function sendsmS(){
        let phone = $("#phone").val();
        $.get("/sendSms",{phone:phone})
    }
</script>
</html>

@RestController
public class UserController {

    SecureRandom secureRandom = new SecureRandom();

    @GetMapping("/user")
    public String username(){
        return SecurityContextHolder.getContext().getAuthentication().getName();
    }


    /**
     * 调用这个方法发送验证码
     */
    @GetMapping("/sendSms")
    public void sendSms(HttpSession httpSession,String phone){
        //1000-9000的数字
       int code= secureRandom.nextInt(9000)+ 1000;
       httpSession.setAttribute(phone,code);
        System.out.println(code);

    }
}

感兴趣的同学可以试一下,下一篇,我们来看看怎么基于阿里云真正的发短信到手机上进行验证。

结语

夜深人静,好思考!

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值