zoukankan      html  css  js  c++  java
  • Spring Security 实现手机验证码登录

    思路:参考用户名密码登录过滤器链,重写认证和授权

    示例如下(该篇示例以精简为主,演示主要实现功能,全面完整版会在以后的博文中发出):

    由于涉及内容较多,建议先复制到本地工程中,然后在细细研究。

    1.   新建Maven项目  sms-code-validate

    2.   pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
            http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.java</groupId>
        <artifactId>sms-code-validate</artifactId>
        <version>1.0.0</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.5.RELEASE</version>
        </parent>
    
    
        <dependencies>
    
            <!-- Spring Boot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-oauth2</artifactId>
                <version>2.0.0.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.11</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
            </dependency>
    
    
            <!-- 热部署 -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>springloaded</artifactId>
                <version>1.2.8.RELEASE</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>provided</scope>
            </dependency>
    
        </dependencies>
    
        <build>
            <finalName>${project.artifactId}</finalName>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
    
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <goals>
                                <goal>repackage</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </project>

    3.   启动类  SmsCodeStarter.java

    package com.java;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * <blockquote><pre>
     * 
     * 主启动类
     * 
     * </pre></blockquote>
     * 
     * @author Logan
     *
     */
    @SpringBootApplication
    public class SmsCodeStarter {
    
        public static void main(String[] args) {
            SpringApplication.run(SmsCodeStarter.class, args);
        }
    
    }

    4.   ValidateCode.java

    package com.java.validate.code;
    
    import java.time.LocalDateTime;
    
    /**
     * 验证码封装类
     * 
     * @author Logan
     *
     */
    public class ValidateCode {
    
        /**
         * 验证码
         */
        private String code;
    
        /**
         * 过期时间
         */
        private LocalDateTime expireTime;
    
        /**
         * 指定验证码和有效分钟数的构造方法
         * 
         * @param code 验证码
         * @param validityMinutes 有效分钟数
         */
        public ValidateCode(String code, int validityMinutes) {
            this.code = code;
            this.expireTime = LocalDateTime.now().plusMinutes(validityMinutes);
        }
    
        /**
         * 指定验证码和过期时间的构造方法
         * 
         * @param code 验证码
         * @param expireTime 过期时间
         */
        public ValidateCode(String code, LocalDateTime expireTime) {
            this.code = code;
            this.expireTime = expireTime;
        }
    
        public String getCode() {
            return code;
        }
    
        public LocalDateTime getExpireTime() {
            return expireTime;
        }
    
    }

    5.   CodeGenerator.java

    package com.java.validate.generator;
    
    import org.apache.commons.lang3.RandomStringUtils;
    
    import com.java.validate.code.ValidateCode;
    
    /**
     * 验证码生成器
     * 
     * @author Logan
     *
     */
    public class CodeGenerator {
    
        /**
         * 验证码生成方法
         * 
         * @param length 验证码长度
         * @param validityMinutes 过期分钟数
         * @return
         */
        public static ValidateCode generate(int length, int validityMinutes) {
            String code = RandomStringUtils.randomNumeric(length);
            return new ValidateCode(code, validityMinutes);
        }
    
    }

    6.   ValidateCodeSender.java

    package com.java.validate.sender;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.stereotype.Component;
    
    /**
     * 验证码发送器
     * 
     * @author Logan
     *
     */
    @Component
    public class ValidateCodeSender {
    
        /**
         * 模拟发送手机验证码,此处发回浏览器,实际情况根据短信服务商做调整
         * 
         * @param response HTTP响应对象
         * @param mobile 手机号
         * @param code 验证码
         */
        public void sendSmsCode(HttpServletResponse response, String mobile, String code) {
            System.out.println(String.format("模拟向手机号【%s】发送验证码【%s】", mobile, code));
            write(response, "验证码为:" + code);
        }
    
        /**
         * 发送HTTP响应信息
         * 
         * @param response HTTP响应对象
         * @param message 信息内容
         */
        private void write(HttpServletResponse response, String message) {
            response.setContentType("text/html; charset=UTF-8");
    
            try (
                    PrintWriter writer = response.getWriter();
            ) {
                writer.write(message);
                writer.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }

     

    7.   ValidateCodeFilter.java

    package com.java.validate.filter;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.time.LocalDateTime;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.ServletRequestBindingException;
    import org.springframework.web.bind.ServletRequestUtils;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import com.java.controller.ValidateCodeController;
    import com.java.validate.code.ValidateCode;
    
    /**
     * 校验验证码过滤器
     * 
     * @author Logan
     * @createDate 2019-02-07
     *
     */
    @Component
    public class ValidateCodeFilter extends OncePerRequestFilter {
    
        /**
         * 需要校验短信验证码的请求
         */
        private List<String> smsCodeUrls = new ArrayList<>();
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
            /**
             * 如果需要校验短信验证码的请求集合中,包含当前请求,则进行短信验证码校验
             */
            if (smsCodeUrls.contains(request.getRequestURI())) {
                if (smsCodeValid(request, response)) {
    
                    // 校验通过,继续向后执行
                    filterChain.doFilter(request, response);
                }
    
            }
    
            // 其它请求,直接放过
            else {
                filterChain.doFilter(request, response);
            }
    
        }
    
        @Override
        protected void initFilterBean() throws ServletException {
    
            // 初始化添加需要校验的请求到集合中,可由配置文件中配置,此处为了简洁,直接添加
            smsCodeUrls.add("/login/mobile");
        }
    
        /**
         * 短信验证码是否有效
         * 
         * @param request HTTP请求对象
         * @param response HTTP响应对象
         * @return 有效,返回true;无效,返回false
         * @throws ServletRequestBindingException
         */
        private boolean smsCodeValid(HttpServletRequest request, HttpServletResponse response) throws ServletRequestBindingException {
            String smsCode = ServletRequestUtils.getStringParameter(request, "smsCode");
            ValidateCode validateCode = (ValidateCode) request.getSession().getAttribute(ValidateCodeController.SESSION_CODE_KEY);
            if (StringUtils.isBlank(smsCode)) {
                write(response, "验证码不能为空!");
                return false;
            } else if (null == validateCode) {
                write(response, "验证码不存在!");
                return false;
            } else if (LocalDateTime.now().isAfter(validateCode.getExpireTime())) {
                write(response, "验证码已过期!");
                return false;
            } else if (!StringUtils.equals(smsCode, validateCode.getCode())) {
                write(response, "验证码不正确!");
                return false;
            }
    
            // 验证成功,移除Session中验证码
            request.getSession().removeAttribute(ValidateCodeController.SESSION_CODE_KEY);
            return true;
        }
    
        /**
         * 发送HTTP响应信息
         * 
         * @param response HTTP响应对象
         * @param message 信息内容
         */
        private void write(HttpServletResponse response, String message) {
            response.setContentType("text/html; charset=UTF-8");
    
            try (
                    PrintWriter writer = response.getWriter();
            ) {
                writer.write(message);
                writer.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }

     

    8.   自定义UserDetailsService实现类,具体逻辑根据实际情况调整。  

    SecurityUserDetailsService.java

    package com.java.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.authority.AuthorityUtils;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Component;
    
    /**
     * UserDetailsService实现类
     * 
     * @author Logan
     *
     */
    @Component
    public class SecurityUserDetailsService implements UserDetailsService {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
            // 数据库存储密码为加密后的密文(明文为123456)
            String password = passwordEncoder.encode("123456");
    
            System.out.println("username: " + username);
            System.out.println("password: " + password);
    
            // 模拟查询数据库,获取属于Admin和Normal角色的用户
            User user = new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("Admin,Normal"));
    
            return user;
        }
    
    }

    9.   获取主机信息接口,模拟演示功能需要

    HostController.java

    package com.java.controller;
    
    import java.net.InetAddress;
    import java.net.UnknownHostException;
    import java.util.HashMap;
    import java.util.Map;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HostController {
    
        @GetMapping("/getHostMessage")
        public Map<String, Object> getHostMessage() {
            Map<String, Object> map = new HashMap<>();
            try {
                InetAddress serverHost = InetAddress.getLocalHost();
                map.put("hostname", serverHost.getHostName());
                map.put("hostAddress", serverHost.getHostAddress());
            } catch (UnknownHostException e) {
                e.printStackTrace();
                map.put("msg", e.getMessage());
            }
    
            return map;
    
        }
    
    }

    10.   验证码生成接口,可扩展集成图片验证码,后续博文中会发出。

    ValidateCodeController.java

    package com.java.controller;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.java.validate.code.ValidateCode;
    import com.java.validate.generator.CodeGenerator;
    import com.java.validate.sender.ValidateCodeSender;
    
    /**
     * 创建验证码接口
     * 
     * @author Logan
     *
     */
    @RestController
    @RequestMapping("/code")
    public class ValidateCodeController {
    
        /**
         * 验证码存放Session中的key
         */
        public static final String SESSION_CODE_KEY = "code";
    
        /**
         * 验证码长度,可以提取到配置中,此处只做演示,简单处理
         */
        private int length = 6;
    
        /**
         * 过期分钟数,可以提取到配置中,此处只做演示,简单处理
         */
        private int validityMinutes = 30;
    
        /**
         * 验证码发送器
         */
        @Autowired
        private ValidateCodeSender validateCodeSender;
    
        /**
         * 创建短信验证码接口
         * 
         * @param request 请求对象
         * @param response 响应对象
         * @param mobile 手机号
         */
        @GetMapping("/sms")
        public void createSmsCode(HttpServletRequest request, HttpServletResponse response, String mobile) {
            ValidateCode validateCode = CodeGenerator.generate(length, validityMinutes);
    
            // 存储验证码到Session中,登录时验证
            request.getSession().setAttribute(SESSION_CODE_KEY, validateCode);
    
            // 调用验证码发送器发送短信验证码
            validateCodeSender.sendSmsCode(response, mobile, validateCode.getCode());
        }
    
    }

    11.   AuthenticationSuccessHandler.java

    package com.java.authentication.handler;
    
    import java.io.IOException;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
    import org.springframework.stereotype.Component;
    
    /**
     * 授权成功处理器
     * 
     * @author Logan
     * @createDate 2019-02-07
     *
     */
    @Component
    public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
    
            // 设置默认跳转页面,当没有重定向页面时(例如:直接访问登录页面),此配置生效
            super.setDefaultTargetUrl("/main.html");
            super.onAuthenticationSuccess(request, response, authentication);
        }
    
    }

     

    12.   SmsCodeAuthenticationToken.java

    package com.java.authentication.mobile;
    
    import java.util.Collection;
    
    import org.springframework.security.authentication.AbstractAuthenticationToken;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.SpringSecurityCoreVersion;
    
    /**
     * <pre>
     * 
     * 短信验证码Token,封装短信验证码登录信息。
     * 
     * 参照{@link org.springframework.security.authentication.UsernamePasswordAuthenticationToken}
     * 
     * </pre>
     * 
     * @author Logan
     *
     */
    public class SmsCodeAuthenticationToken  extends AbstractAuthenticationToken {
    
        private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
    
        // ~ Instance fields
        // ================================================================================================
    
        private final Object principal;
    
        // ~ Constructors
        // ===================================================================================================
    
        /**
         * This constructor can be safely used by any code that wishes to create a
         * <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()}
         * will return <code>false</code>.
         *
         */
        public SmsCodeAuthenticationToken(String mobile) {
            super(null);
            this.principal = mobile;
            super.setAuthenticated(false);
        }
    
        /**
         * This constructor should only be used by <code>AuthenticationManager</code> or
         * <code>AuthenticationProvider</code> implementations that are satisfied with
         * producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
         * authentication token.
         *
         * @param mobile
         * @param authorities
         */
        public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
            super(authorities);
            this.principal = principal;
            super.setAuthenticated(true); // must use super, as we override
        }
    
        // ~ Methods
        // ========================================================================================================
    
        public Object getCredentials() {
            return null;
        }
    
        public Object getPrincipal() {
            return this.principal;
        }
    
    }

    13.   SmsCodeAuthenticationFilter.java

    package com.java.authentication.mobile;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.security.authentication.AuthenticationServiceException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
    import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
    import org.springframework.util.Assert;
    
    /**
     * <pre>
     * 
     * 短信验证码过滤器,
     * 
     * 参照{@link org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter}
     * 
     * </pre>
     * 
     * @author Logan
     *
     */
    public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    
        // ~ Static fields/initializers
        // =====================================================================================
    
        public static final String MOBILE_KEY = "mobile";
    
        private String mobileParameter = MOBILE_KEY;
        private boolean postOnly = true;
    
        // ~ Constructors
        // ===================================================================================================
    
        public SmsCodeAuthenticationFilter() {
            super(new AntPathRequestMatcher("/login/mobile", "POST"));
        }
    
        // ~ Methods
        // ========================================================================================================
    
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            if (postOnly && !request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
            }
    
            String mobile = StringUtils.trimToEmpty(request.getParameter(mobileParameter));
            SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);
    
            // Allow subclasses to set the "details" property
            setDetails(request, authRequest);
    
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    
        /**
         * Provided so that subclasses may configure what is put into the
         * authentication request's details property.
         *
         * @param request that an authentication request is being created for
         * @param authRequest the authentication request object that should have its
         *            details set
         */
        protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
            authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
        }
    
        /**
         * Sets the parameter name which will be used to obtain the mobile from the
         * login request.
         * 
         * @param mobileParameter the parameter name. Defaults to "mobile".
         */
        public void setMobileParameter(String mobileParameter) {
            Assert.hasText(mobileParameter, "mobile parameter must not be empty or null");
            this.mobileParameter = mobileParameter;
        }
    
        /**
         * Defines whether only HTTP POST requests will be allowed by this filter.
         * If set to true, and an authentication request is received which is not a
         * POST request, an exception will be raised immediately and authentication
         * will not be attempted. The <tt>unsuccessfulAuthentication()</tt> method
         * will be called as if handling a failed authentication.
         * <p>
         * Defaults to <tt>true</tt> but may be overridden by subclasses.
         */
        public void setPostOnly(boolean postOnly) {
            this.postOnly = postOnly;
        }
    
        public final String getMobileParameter() {
            return mobileParameter;
        }
    }

    14.   SmsCodeAuthenticationProvider.java

    package com.java.authentication.mobile;
    
    import org.springframework.security.authentication.AuthenticationProvider;
    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.UserDetailsService;
    
    /**
     * 短信验证码授权认证类
     * 
     * @author Logan
     * @createDate 2019-02-07
     *
     */
    public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
    
        private UserDetailsService userDetailsService;
    
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    
            // 未认证Token
            SmsCodeAuthenticationToken token = (SmsCodeAuthenticationToken) authentication;
            UserDetails user = userDetailsService.loadUserByUsername((String) token.getPrincipal());
    
            if (null == user) {
                throw new InternalAuthenticationServiceException("未绑定用户!");
            }
    
            // 已认证的Token
            SmsCodeAuthenticationToken authenticationToken = new SmsCodeAuthenticationToken(user, user.getAuthorities());
    
            // 复制之前的请求信息到认证后的Token中
            authenticationToken.setDetails(token.getDetails());
    
            return authenticationToken;
        }
    
        @Override
        public boolean supports(Class<?> authentication) {
            return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
        }
    
        public UserDetailsService getUserDetailsService() {
            return userDetailsService;
        }
    
        public void setUserDetailsService(UserDetailsService userDetailsService) {
            this.userDetailsService = userDetailsService;
        }
    
    }

    15.   ApplicationContextConfig.java

    package com.java.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    /**
     * 配置文件类
     * 
     * @author Logan
     *
     */
    @Configuration
    public class ApplicationContextConfig {
    
        /**
         * <blockquote><pre>
         * 
         * 配置密码编码器,Spring Security 5.X必须配置,否则登录时报空指针异常
         * 
         * </pre></blockquote>
         * 
         * @return
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
    }

    16.   RepositoryConfig.java

    package com.java.config;
    
    import javax.sql.DataSource;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
    import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
    
    /**
     * 数据库相关配置
     * 
     * @author Logan
     *
     */
    @Configuration
    public class RepositoryConfig {
    
        @Bean
        public PersistentTokenRepository tokenRepository(DataSource dataSource) {
            JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
            tokenRepository.setDataSource(dataSource);
            // tokenRepository.setCreateTableOnStartup(true); // 第一次启动时可使用此功能自动创建表,第二次要关闭,否则表已存在会启动报错
            return tokenRepository;
        }
    
    }

    17.   SmsCodeSecurityConfig.java

    package com.java.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.web.DefaultSecurityFilterChain;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    import org.springframework.stereotype.Component;
    
    import com.java.authentication.handler.AuthenticationSuccessHandler;
    import com.java.authentication.mobile.SmsCodeAuthenticationFilter;
    import com.java.authentication.mobile.SmsCodeAuthenticationProvider;
    
    /**
     * 短信验证码安全配置,串联自定义短信验证码验证流程
     * 
     * @author Logan
     * @createDate 2019-02-07
     *
     */
    @Component
    public class SmsCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Autowired
        private AuthenticationSuccessHandler successHandler;
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
    
            // 此处采用new的方式,而不是@Component和@Autowired结合,目的为了方便安装和卸载,可重用可移植性强
            SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
    
            // 设置AuthenticationManager
            smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
            smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(successHandler);
    
            // 短信验证码认证Provider类
            SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
            smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);
    
            // 设置短信验证码认证Provider类到AuthenticationManager管理集合中
            http.authenticationProvider(smsCodeAuthenticationProvider)
    
                    // 设置短信验证码在用户名密码验证过滤器之后验证
                    .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    
        }
    
    }

    18.   LoginConfig.java

    package com.java.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
    
    import com.java.validate.filter.ValidateCodeFilter;
    
    /**
     * 登录相关配置
     * 
     * @author Logan
     *
     */
    @Configuration
    public class LoginConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private PersistentTokenRepository tokenRepository;
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Autowired
        private ValidateCodeFilter validateCodeFilter;
    
        @Autowired
        private SmsCodeSecurityConfig smsCodeSecurityConfig;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http.apply(smsCodeSecurityConfig)
    
                    // 设置验证码过滤器到过滤器链中,在UsernamePasswordAuthenticationFilter之前执行
                    .and().addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
    
                    // 设置自定义表单登录页面
                    .formLogin().loginPage("/login.html")
    
                    // 设置登录验证请求地址为自定义登录页配置action ("/login/form")
                    .loginProcessingUrl("/login/form")
                    
                    // 设置默认登录成功跳转页面
                    .defaultSuccessUrl("/main.html")
    
                    /* 授权请求设置 */
                    .and().authorizeRequests()
    
                    // 设置不需要授权的请求
                    .antMatchers("/js/*", "/code/*", "/login.html").permitAll()
    
                    // 其它任何请求都需要验证权限
                    .anyRequest().authenticated()
    
                    /* 记住我功能设置 */
                    .and().rememberMe().tokenRepository(tokenRepository)
    
                    // 【记住我功能】有效期为两周
                    .tokenValiditySeconds(3600 * 24 * 14)
    
                    // 设置UserDetailsService
                    .userDetailsService(userDetailsService)
    
                    // 暂时停用csrf,否则会影响验证
                    .and().csrf().disable();
        }
    
    }

    19.   src/main/resources  下配置文件如下

    20.   application.properties

    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    spring.datasource.url=jdbc:mysql://192.168.32.10:3306/security?useUnicode=true&characterEncoding=UTF-8
    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    

    21.   login.html

    <!DOCTYPE html>
    <html>
    
        <head>
            <title>登录</title>
            <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
            <script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
            <script>
                function sendSmsCode() {
                    var mobile = $("#mobile").val().trim();
    
                    // 简单校验由11位数字组成
                    var reg = /^d{11}$/;
                    if(reg.test(mobile)) {
                        $.ajax({
                            type: "get",
                            url: "/code/sms?mobile=" + mobile,
                            async: true,
                            success: function(data) {
                                alert(data);
                            }
                        });
                    } else {
                        alert("手机号输入格式错误");
                    }
                }
            </script>
        </head>
    
        <body>
    
            <!--登录框-->
            <div align="center">
                <h2>用户自定义登录页面</h2>
                <fieldset style=" 390px;">
                    <legend>表单登录框</legend>
                    <form action="/login/form" method="post">
                        <table>
                            <tr>
                                <th>用户名:</th>
                                <td><input name="username" /> </td>
                            </tr>
                            <tr>
                                <th>密码:</th>
                                <td><input type="password" name="password" /> </td>
                            </tr>
                            <tr>
                                <th>记住我:</th>
                                <td><input type="checkbox" name="remember-me" value="true" checked="checked" /></td>
                            </tr>
                            <tr>
                                <th></th>
                                <td></td>
                            </tr>
                            <tr>
                                <td colspan="2" align="center"><button type="submit">登录</button></td>
                            </tr>
                        </table>
                    </form>
                </fieldset>
                <fieldset style=" 390px;margin-top: 30px;">
                    <legend>手机验证码登录框</legend>
                    <form action="/login/mobile" method="post">
                        <table>
                            <tr>
                                <th>手机号:</th>
                                <td><input id="mobile" name="mobile" value="13166668888" /></td>
                            </tr>
                            <tr>
                                <th>验证码:</th>
                                <td>
                                    <input id="smsCode" name="smsCode" />
                                    <button type="button" onclick="sendSmsCode()">发送手机验证码</button>
                                </td>
                            </tr>
                            <tr>
                                <td colspan="2" align="center"><button type="submit">登录</button></td>
                            </tr>
                        </table>
                    </form>
                </fieldset>
    
            </div>
    
        </body>
    
    </html>

     

    22.   main.html

    <!DOCTYPE html>
    <html>
    
        <head>
            <title>首页</title>
            <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
            <script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
            <script>
                function getHostMessage() {
                    $.ajax({
                        type: "get",
                        url: "/getHostMessage",
                        async: true,
                        success: function(data) {
                            $("#msg").val(JSON.stringify(data));
                        }
                    });
                }
            </script>
        </head>
    
        <body>
    
            <div>
                <h2>首页</h2>
                <table>
                    <tr>
                        <td><button onclick="getHostMessage()">获取主机信息</button></td>
                    </tr>
                </table>
    
            </div>
    
            <!--响应内容-->
            <div>
                <textarea id="msg" style=" 800px;height: 800px;"></textarea>
            </div>
    
        </body>
    
    </html>

    23.   js/jquery-3.3.1.min.js  可在官网下载

    https://code.jquery.com/jquery-3.3.1.min.js

     24.   创建数据库

    DROP DATABASE IF EXISTS security;
    CREATE DATABASE security;
    USE security;
    create table persistent_logins (
        username varchar(64) not null, 
        series varchar(64) primary key, 
        token varchar(64) not null, 
        last_used timestamp not null
    );

    25.   运行 SmsCodeStarter.java , 启动测试

    浏览器输入首页  http://localhost:8080/main.html

    地址栏自动跳转到登录页面,如下:

     

     表单登录可自行研究,只讲解手机验证码登录过程

    单击【发送手机验证码】按钮,控制台和浏览器都会显示生成验证码。

    输入正确的手机验证码,单击【登录】按钮。跳转到首页,如下所示:

    获取主机信息接口功能调用正常。

    验证码输入错误情况可自习研究。

    搭建完成!

     .

  • 相关阅读:
    js apply的用法
    js 键盘点击事件
    jquery 中 attr 和 prop 的区别
    css实现等高布局 两栏自适应布局 三栏自适应布局
    Python多线程同步命令行模拟进度显示
    Windows上Python3.5安装Scrapy(lxml)
    Ubuntu14.04右键菜单添加Sublime 2打开选项
    Git使用笔记(一)
    C# Winform程序本地化应用
    修改Windows系统的启动Shell
  • 原文地址:https://www.cnblogs.com/jonban/p/sms.html
Copyright © 2011-2022 走看看