信息发布→ 登录 注册 退出

Spring Security 实现多种登录方式(常规方式外的邮件、手机验证码登录)

发布时间:2026-01-11

点击量:
目录
  • 前言:
  • 一、‍♂️理论知识
  • 二、EmailCodeAuthenticationFilter
  • 三、EmailCodeAuthenticationToken
  • 四、EmailCodeAuthenticationProvider
    • 4.1、先看看AbstractUserDetailsAuthenticationProvider,我们再来模仿
    • 4.2、抄作业啦
  • 五、在配置类中进行配置
    • 六、测试及源代码
      • 七、自言自语

         不知道, 你在用Spring Security的时候,有没有想过,用它实现多种登录方式勒,这次我的小伙伴就给我提了一些登录方面的需求,需要在原有账号密码登录的基础上,另外实现电话验证码以及邮件验证码登录,以及在实现之后,让我能够做到实现第三方登录,如gitee、github等。

        本文主要是讲解Security在实现账号密码的基础上,并且不改变原有业务情况下,实现邮件、电话验证码登录。

        前言:

        上一篇文章我写了 Security登录详细流程详解有源码有分析。掌握这个登录流程,我们才能更好的做Security的定制操作。

        我在写这篇文章之前,也看过很多博主的文章,写的非常好,有对源码方面的解析,也有对一些相关设计理念的理解的文章。

        这对于已经学过一段时间,并且对Security已经有了解的小伙伴来说,还是比较合适的,但是对于我以及其他一些急于解决当下问题的小白,并不是那么友善。

        一、‍♂️理论知识

        我们先思考一下这个流程大致是如何的?

        • 填写邮件号码,获取验证码
        • 输入获取到的验证码进行登录(登录的接口:/email/login,这里不能使用默认的/login,因为我们是扩展)
        • 在自定义的过滤器 EmailCodeAuthenticationFilter 中获取发送过来的邮件号码及验证码,判断验证码是否正确,邮件账号是否为空等
        • 封装成一个需要认证的 Authentication ,此处我们自定义实现为 EmailCodeAuthenticationToken。
        • 将 Authentiction 传给 AuthenticationManager 接口中 authenticate 方法进行认证处理
        • AuthenticationManager 默认是实现类为 ProviderManager ,ProviderManager 又委托给 AuthenticationProvider 进行处理
        • 我们自定义一个 EmailCodeAuthenticationProvider 实现 AuthenticationProvider ,实现身份验证。
        • 自定义的 EmailCodeAuthenticationFilter 继承了 AbstractAuthenticationProcessingFilter 抽象类, AbstractAuthenticationProcessingFilter 在 successfulAuthentication 方法中对登录成功进行了处理,通过 SecurityContextHolder.getContext().setAuthentication() 方法将 Authentication 认证信息对象绑定到 SecurityContext即安全上下文中。
        • 其实对于身份验证通过后的处理,有两种方案,一种是直接在过滤器重写successfulAuthentication,另外一种就是实现AuthenticationSuccessHandler来处理身份验证通过。
        • 身份验证失败也是一样,可重写unsuccessfulAuthentication方法,也可以实现 AuthenticationFailureHandler来对身份验证失败进行处理。

        大致流程就是如此。从这个流程中我们可以知道,需要重写的组件有以下几个:

        • EmailCodeAuthenticationFilter:邮件验证登录过滤器
        • EmailCodeAuthenticationToken:身份验证令牌
        • EmailCodeAuthenticationProvider:邮件身份认证处理
        • AuthenticationSuccessHandler:处理登录成功操作
        • AuthenticationFailureHandler:处理登录失败操作

        接下来,我是模仿着源码写出我的代码,建议大家可以在使用的时候,多去看看,我这里去除了一些不是和这个相关的代码。

        来吧!!

        二、EmailCodeAuthenticationFilter

        我们需要重写的 EmailCodeAuthenticationFilter,实际继承了AbstractAuthenticationProcessingFilter抽象类,我们不会写,可以先看看它的默认实现UsernamePasswordAuthenticationFilter是怎么样的吗,抄作业这是大家的强项的哈。

        public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
            
            public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
        
            public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
        
            private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
                    "POST");
            //从前台传过来的参数
            private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
        
            private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
        
            private boolean postOnly = true;
            
            //  初始化一个用户密码 认证过滤器  默认的登录uri 是 /login 请求方式是POST
            public UsernamePasswordAuthenticationFilter() {
                super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
            }
        
            public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
                super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
            }
        
            /**
            执行实际身份验证。实现应执行以下操作之一:
            1、为经过身份验证的用户返回填充的身份验证令牌,表示身份验证成功
            2、返回null,表示认证过程还在进行中。 在返回之前,实现应该执行完成流程所需的任何额外工作。
            3、如果身份验证过程失败,则抛出AuthenticationException
            */
            @Override
            public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
                    throws AuthenticationException {
                if (this.postOnly && !request.getMethod().equals("POST")) {
                    throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
                }
                String username = obtainUsername(request);
                username = (username != null) ? username : "";
                username = username.trim();
                String password = obtainPassword(request);
                password = (password != null) ? password : "";
                //生成 UsernamePasswordAuthenticationToken 稍后交由AuthenticationManager中的authenticate进行认证
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
                // 可以放一些其他信息进去
                setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            }
        
            @Nullable
            protected String obtainPassword(HttpServletRequest request) {
                return request.getParameter(this.passwordParameter);
            }
        
            @Nullable
            protected String obtainUsername(HttpServletRequest request) {
                return request.getParameter(this.usernameParameter);
            }
        
            protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
                authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
            }
        
            //set、get方法
        }

        接下来我们就抄个作业哈:

        package com.crush.security.auth.email_code;
        
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.security.authentication.AuthenticationManager;
        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.security.web.util.matcher.RequestMatcher;
        
        import javax.servlet.FilterChain;
        import javax.servlet.ServletException;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import javax.servlet.http.HttpSession;
        import java.io.IOException;
        import java.util.ArrayList;
        
        /**
         * @Author: crush
         * @Date: 2025-09-08 21:13
         * version 1.0
         */
        public class EmailCodeAuthenticationFilter  extends AbstractAuthenticationProcessingFilter {
            /**
             * 前端传来的 参数名 - 用于request.getParameter 获取
             */
            private final String DEFAULT_EMAIL_NAME="email";
        
            private final String DEFAULT_EMAIL_CODE="e_code";
        
            @Autowired
            @Override
            public void setAuthenticationManager(AuthenticationManager authenticationManager) {
                super.setAuthenticationManager(authenticationManager);
            }
            /**
             * 是否 仅仅post方式
             */
            private boolean postOnly = true;
        
            /**
             * 通过 传入的 参数 创建 匹配器
             * 即 Filter过滤的url
             */
            public EmailCodeAuthenticationFilter() {
                super(new AntPathRequestMatcher("/email/login","POST"));
            }
        
        
            /**
             * filter 获得 用户名(邮箱) 和 密码(验证码) 装配到 token 上 ,
             * 然后把token 交给 provider 进行授权
             */
            @Override
            public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
                if(postOnly && !request.getMethod().equals("POST") ){
                    throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
                }else{
                    String email = getEmail(request);
                    if(email == null){
                        email = "";
                    }
                    email = email.trim();
                    //如果 验证码不相等 故意让token出错 然后走springsecurity 错误的流程
                    boolean flag = checkCode(request);
                    //封装 token
                    EmailCodeAuthenticationToken token = new EmailCodeAuthenticationToken(email,new ArrayList<>());
                    this.setDetails(request,token);
                    //交给 manager 发证
                    return this.getAuthenticationManager().authenticate(token);
                }
            }
        
            /**
             * 获取 头部信息 让合适的provider 来验证他
             */
            public void setDetails(HttpServletRequest request , EmailCodeAuthenticationToken token ){
                token.setDetails(this.authenticationDetailsSource.buildDetails(request));
            }
        
            /**
             * 获取 传来 的Email信息
             */
            public String getEmail(HttpServletRequest request ){
                String result=  request.getParameter(DEFAULT_EMAIL_NAME);
                return result;
            }
        
            /**
             * 判断 传来的 验证码信息 以及 session 中的验证码信息
             */
            public boolean checkCode(HttpServletRequest request ){
                String code1 = request.getParameter(DEFAULT_EMAIL_CODE);
                System.out.println("code1**********"+code1);
                // TODO 另外再写一个链接 生成 验证码 那个验证码 在生成的时候  存进redis 中去
                //TODO  这里的验证码 写在Redis中, 到时候取出来判断即可 验证之后 删除验证码
                if(code1.equals("123456")){
                    return true;
                }
                return false;
            }
        	// set、get方法...
        }
        

        三、EmailCodeAuthenticationToken

        我们EmailCodeAuthenticationToken是继承AbstractAuthenticationToken的,按照同样的方式,我们接着去看看AbstractAuthenticationToken的默认实现是什么样的就行了。

        /**
        
         */
        public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
        
            private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
        
            // 这里指的账号密码哈
            private final Object principal;
        
            private Object credentials;
        
            /**
            没经过身份验证时,初始化权限为空,setAuthenticated(false)设置为不可信令牌
             */
            public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
                super(null);
                this.principal = principal;
                this.credentials = credentials;
                setAuthenticated(false);
            }
        
            /**
            经过身份验证后,将权限放进去,setAuthenticated(true)设置为可信令牌
             */
            public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
                    Collection<? extends GrantedAuthority> authorities) {
                super(authorities);
                this.principal = principal;
                this.credentials = credentials;
                super.setAuthenticated(true); // must use super, as we override
            }
        
            @Override
            public Object getCredentials() {
                return this.credentials;
            }
        
            @Override
            public Object getPrincipal() {
                return this.principal;
            }
        
            @Override
            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);
            }
        
            @Override
            public void eraseCredentials() {
                super.eraseCredentials();
                this.credentials = null;
            }
        
        }

        日常抄作业哈:

        /**
         * @Author: crush
         * @Date: 2025-09-08 21:13
         * version 1.0
         */
        public class EmailCodeAuthenticationToken extends AbstractAuthenticationToken {
        
        
            /**
             * 这里的 principal 指的是 email 地址(未认证的时候)
             */
            private final Object principal;
        
            public EmailCodeAuthenticationToken(Object principal) {
                super((Collection) null);
                this.principal = principal;
                setAuthenticated(false);
            }
        
            public EmailCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
                super(authorities);
                this.principal = principal;
                super.setAuthenticated(true);
            }
        
            @Override
            public Object getCredentials() {
                return null;
            }
        
            @Override
            public Object getPrincipal() {
                return this.principal;
            }
        
            @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");
                } else {
                    super.setAuthenticated(false);
                }
            }
        
        }

        这个很简单的哈

        四、EmailCodeAuthenticationProvider

        自定义的EmailCodeAuthenticationProvider是实现了AuthenticationProvider接口,抄作业就得学会看看源码。我们接着来。

        4.1、先看看AbstractUserDetailsAuthenticationProvider,我们再来模仿

        AuthenticationProvider 接口有很多实现类,不一一说明了,直接看我们需要看的AbstractUserDetailsAuthenticationProvider, 该类旨在响应 UsernamePasswordAuthenticationToken 身份验证请求。但是它是一个抽象类,但其实就一个步骤在它的实现类中实现的,很简单,稍后会讲到。

        在这个源码中我把和检查相关的一些操作都给删除,只留下几个重点,我们一起来看一看哈。

        //该类旨在响应UsernamePasswordAuthenticationToken身份验证请求。
        public abstract class AbstractUserDetailsAuthenticationProvider
                implements AuthenticationProvider, InitializingBean, MessageSourceAware {
        
            protected final Log logger = LogFactory.getLog(getClass());
        
            private UserCache userCache = new NullUserCache();
        
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                        () -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
                                "Only UsernamePasswordAuthenticationToken is supported"));
                //获取用户名
                String username = determineUsername(authentication);
                //判断缓存中是否存在
                boolean cacheWasUsed = true;
                UserDetails user = this.userCache.getUserFromCache(username);
                if (user == null) {
                    cacheWasUsed = false;
                    try {
                        // 缓存中没有 通过字类实现的retrieveUser 从数据库进行检索,返回一个 UserDetails 对象
                        user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
                    }
                    catch (UsernameNotFoundException ex) {
                        this.logger.debug("Failed to find user '" + username + "'");
                        if (!this.hideUserNotFoundExceptions) {
                            throw ex;
                        }
                        throw new BadCredentialsException(this.messages
                                .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                    }
                    Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
                }
                try {
                    //进行相关检查  因为可能是从缓存中取出来的 并非是最新的
                    this.preAuthenticationChecks.check(user);
                    additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
                }
                catch (AuthenticationException ex) {
                    if (!cacheWasUsed) {
                        throw ex;
                    }
                    // 没有通过检查, 重新检索最新的数据
                    cacheWasUsed = false;
                    user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
                    this.preAuthenticationChecks.check(user);
                    additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
                }
                // 再次进行检查
                this.postAuthenticationChecks.check(user);
                // 存进缓存中去
                if (!cacheWasUsed) {
                    this.userCache.putUserInCache(user);
                }
                Object principalToReturn = user;
                if (this.forcePrincipalAsString) {
                    principalToReturn = user.getUsername();
                }
                //创建一个可信的身份令牌返回
                return createSuccessAuthentication(principalToReturn, authentication, user);
            }
        
            private String determineUsername(Authentication authentication) {
                return (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
            }
        
            /**
        简而言之就是创建了一个通过身份验证的UsernamePasswordAuthenticationToken
             */
            protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
                    UserDetails user) {
                UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
                        authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
                result.setDetails(authentication.getDetails());
                this.logger.debug("Authenticated user");
                return result;
            }
        
        
            /**
        允许子类从特定于实现的位置实际检索UserDetails ,如果提供的凭据不正确,则可以选择立即抛出AuthenticationException (如果需要以用户身份绑定到资源以获得或生成一个UserDetails )
             */
            protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
                    throws AuthenticationException;
            //...
            
        
            //简而言之:当然有时候我们有多个不同的 `AuthenticationProvider`,它们分别支持不同的 `Authentication`对象,那么当一个具体的 `AuthenticationProvier`传进入 `ProviderManager`的内部时,就会在 `AuthenticationProvider`列表中挑选其对应支持的provider对相应的 Authentication对象进行验证
            @Override
            public boolean supports(Class<?> authentication) {
                return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
            }
        
        }

        关于 protected abstract UserDetails retrieveUser 的实现,AbstractUserDetailsAuthenticationProvider实现是DaoAuthenticationProvider.

        DaoAuthenticationProvider主要操作是两个,第一个是从数据库中检索出相关信息,第二个是给检索出的用户信息进行密码的加密操作。

        public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
        
            private UserDetailsService userDetailsService;
            
            @Override
            protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
                    throws AuthenticationException {
                prepareTimingAttackProtection();
                try {
                    // 检索用户,一般我们都会实现 UserDetailsService接口,改为从数据库中检索用户信息 返回安全核心类 UserDetails
                    UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
                    if (loadedUser == null) {
                        throw new InternalAuthenticationServiceException(
                                "UserDetailsService returned null, which is an interface contract violation");
                    }
                    return loadedUser;
                }
                catch (UsernameNotFoundException ex) {
                    mitigateAgainstTimingAttack(authentication);
                    throw ex;
                }
                catch (InternalAuthenticationServiceException ex) {
                    throw ex;
                }
                catch (Exception ex) {
                    throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
                }
            }
        
            @Override
            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);
            }
        
        }

        4.2、抄作业啦

        看完源码,其实我们如果要重写的话,主要要做到以下几个事情:

        重写public boolean supports(Class<?> authentication)方法。

        有时候我们有多个不同的 AuthenticationProvider,它们分别支持不同的 Authentication对象,那么当一个具体的 AuthenticationProvier 传进入 ProviderManager的内部时,就会在 AuthenticationProvider列表中挑选其对应支持的 provider 对相应的 Authentication对象进行验证

        简单说就是指定AuthenticationProvider验证哪个 Authentication 对象。如指定DaoAuthenticationProvider认证UsernamePasswordAuthenticationToken,

        所以我们指定EmailCodeAuthenticationProvider认证EmailCodeAuthenticationToken。

        检索数据库,返回一个安全核心类UserDetail。

        创建一个经过身份验证的Authentication对象

        了解要做什么事情了,我们就可以动手看看代码啦。

        /**
         * @Author: crush
         * @Date: 2025-09-08 21:14
         * version 1.0
         */
        @Slf4j
        public class EmailCodeAuthenticationProvider implements AuthenticationProvider {
        
            ITbUserService userService;
        
            public EmailCodeAuthenticationProvider(ITbUserService userService) {
                this.userService = userService;
            }
        
        
            /**
             * 认证
             */
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                if (!supports(authentication.getClass())) {
                    return null;
                }
                log.info("EmailCodeAuthentication authentication request: %s", authentication);
                EmailCodeAuthenticationToken token = (EmailCodeAuthenticationToken) authentication;
        
                UserDetails user = userService.getByEmail((String) token.getPrincipal());
        
                System.out.println(token.getPrincipal());
                if (user == null) {
                    throw new InternalAuthenticationServiceException("无法获取用户信息");
                }
                System.out.println(user.getAuthorities());
                EmailCodeAuthenticationToken result =
                        new EmailCodeAuthenticationToken(user, user.getAuthorities());
                        /*
                        Details 中包含了 ip地址、 sessionId 等等属性 也可以存储一些自己想要放进去的内容
                        */
                result.setDetails(token.getDetails());
                return result;
            }
        
            @Override
            public boolean supports(Class<?> aClass) {
                return EmailCodeAuthenticationToken.class.isAssignableFrom(aClass);
            }
        }

        五、在配置类中进行配置

        主要就是做下面几件事:将过滤器、认证器注入到spring中
        将登录成功处理、登录失败处理器注入到Spring中,或者在自定义过滤器中对登录成功和失败进行处理。
        添加到过滤链中

            @Bean
            public EmailCodeAuthenticationFilter emailCodeAuthenticationFilter() {
                EmailCodeAuthenticationFilter emailCodeAuthenticationFilter = new EmailCodeAuthenticationFilter();
                emailCodeAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
                emailCodeAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
                return emailCodeAuthenticationFilter;
            }
        
            @Bean
            public EmailCodeAuthenticationProvider emailCodeAuthenticationProvider() {
                return new EmailCodeAuthenticationProvider(userService);
            }
        
            /**
             * 因为使用了BCryptPasswordEncoder来进行密码的加密,所以身份验证的时候也的用他来判断哈、,
             *
             * @param auth
             * @throws Exception
             */
            @Override
            protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
                //authenticationProvider 根据传入的自定义AuthenticationProvider添加身份AuthenticationProvider 。
                auth.authenticationProvider(emailCodeAuthenticationProvider());
            }
        .and()
            .authenticationProvider(emailCodeAuthenticationProvider())
            .addFilterBefore(emailCodeAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class)
        
            .authenticationProvider(mobileCodeAuthenticationProvider())
            .addFilterBefore(mobileCodeAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class)

        六、测试及源代码

        项目具体的配置、启动方式、环境等、都在github及gitee的文档上有详细说明。

        源代码中包含sql文件、配置文件以及相关博客链接,源代码中也加了很多注释,尽最大程度让大家能够看明白。

        在最大程度上保证大家都能正确的运行及测试。
        源码:gitee-Security

        七、自言自语

        如果这篇存在不太懂的内容,可以先看我的另一篇文章:

        SpringBoot集成Security实现安全控制,使用Jwt制作Token令牌。

        之后再回过头来看这一篇文章,应该会更加容易理解。

        在线客服
        服务热线

        服务热线

        4008888355

        微信咨询
        二维码
        返回顶部
        ×二维码

        截屏,微信识别二维码

        打开微信

        微信号已复制,请打开微信添加咨询详情!