SpringSecurity自定义认证

这篇具有很好参考价值的文章主要介绍了SpringSecurity自定义认证。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一. 前言

​ 学习了SpringSecurity的使用,以及跟着源码分析了一遍认证流程,掌握了这个登录认证流程,才能更方便我们做自定义操作。

​ 下面我们来学习下怎么实现多种登录方式,比如新增加一种邮箱验证码登录的形式,但SpringSecurity默认的Usernamepassword方式不影响。

二. 自定义邮件验证码认证

0. 说明

自定义一个邮箱验证码的认证,将邮箱号码作为key,验证码作为value存放到Redis中缓存。

1. 回顾

首先回顾下之前源码分析的认证流程,如下图:

SpringSecurity自定义认证

2. 设计思路

  1. 首先前端是填写邮箱,点击获取验证码

  2. 输入获取到的验证码,点击登录按钮,发送登录接口(/emial/login,此处不能使用默认的/login,因为我们属于扩展)

  3. 自定义过滤器EmailCodeAuthenticationFilter(类似UsernamepasswordAuthenticationFilter),获取邮箱号码与验证码

  4. 将邮箱号码与验证码封装为一个需要认证的自定义Authentication对象EmailCodeAuthenticationToken(类似UsernamepasswordAuthenticationToken)

  5. EmailCodeAuthenticationToken传给AuthenticationManager接口的authenticate方法认证

  6. 因为AuthenticationManager的默认实现类为ProviderManager,而ProviderManager又是委托给了AuthenticationProvider,因此

    自定义一个AuthenticationProvider接口的实现类EmailCodeAuthenticationProvider,实现authenticate方法认证

  7. 认证成功与认证失败的处理:一种是直接在过滤器EmailCodeAuthenticationFilter中重写successfulAuthenticationunsuccessfulAuthentication,另一种是实现AuthenticationSuccessHandlerAuthenticationFailureHandler进行处理

  8. 总归一句:照猫画瓢

总结

需要实现以下几个类:

  • 过滤器EmailCodeAuthenticationFilter
  • Authentication对象EmailCodeAuthenticationToken
  • AuthenticationProvider类EmailCodeAuthenticationProvider
  • 自定义认证成功与认证失败的Handler

3. 代码实现

  1. 自定义Authentication对象(这里是EmailCodeAuthenticationToken)

    public class EmailCodeAuthenticationToken extends AbstractAuthenticationToken {
    
        private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
        // 邮箱账号
        private final Object principal;
        // 邮箱验证码
        private Object credentials;
    
        /**
         * 没有经过验证时,权限位空,setAuthenticated设置为不可信令牌
         * @param principal
         * @param credentials
         */
        public EmailCodeAuthenticationToken(Object principal, Object credentials) {
            super(null);
            this.principal = principal;
            this.credentials = credentials;
            setAuthenticated(false);
        }
    
        /**
         * 已认证后,将权限加上,setAuthenticated设置为可信令牌
         * @param principal
         * @param credentials
         * @param authorities
         */
        public EmailCodeAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
            super(authorities);
            this.principal = principal;
            this.credentials = credentials;
            super.setAuthenticated(true);
        }
    
        @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;
        }
    }
    

    说明:

    ​ 模仿UsernamepasswordAuthenticationToken定义,继承AbstractAuthenticationToken,这里注意的是要定义两个构造器,分别对应未认证和已认证的Token,已认证的调用super.setAuthenticated(true);

  2. 自定义Filter(这里是EmailCodeAuthenticationFilter)

    public class EmailCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
        // 前端传来的参数名
        private final String SPRING_SECURITY_EMAIL_KEY = "email";
        private final String SPRING_SECURITY_EMAIL_CODE_KEY = "email_code";
    
        // 自定义的路径匹配器,拦截Url为:/email/login
        private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/email/login",
                                                                                                                "POST");
    
        // 是否仅POST方式
        private boolean postOnly = true;
    
        public EmailCodeAuthenticationFilter() {
            super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
        }
    
        /**
         * 认证方法,在父类的doFilter中调用
         * @param request
         * @param response
         * @return
         * @throws AuthenticationException
         * @throws IOException
         * @throws ServletException
         */
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
            if (this.postOnly && !request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException("Authentication method not support : " + request.getMethod());
            }
            System.out.println("email attemptAuthentication");
            // 获取邮箱号码
            String email = obtainEmail(request);
            email = (email != null) ? email : "";
            email = email.trim();
            // 获取邮箱验证码
            String emailCode = obtainEmailCode(request);
            emailCode = (emailCode != null) ? emailCode : "";
            // 构造Token
            EmailCodeAuthenticationToken authRequest = new EmailCodeAuthenticationToken(email, emailCode);
            setDetails(request, authRequest);
            // 使用AuthenticationManager来进行认证
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    
        /**
         * 获取请求中email参数
         * @param request
         * @return
         */
        @Nullable
        protected String obtainEmail(HttpServletRequest request) {
            return request.getParameter(this.SPRING_SECURITY_EMAIL_KEY);
        }
    
        /**
         * 获取请求中验证码参数email_code
         * @param request
         * @return
         */
        @Nullable
        protected String obtainEmailCode(HttpServletRequest request) {
            return request.getParameter(this.SPRING_SECURITY_EMAIL_CODE_KEY);
        }
    
        protected void setDetails(HttpServletRequest request, EmailCodeAuthenticationToken authRequest) {
            authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
        }
    }
    

    说明:

    ​ 模仿UsernamepasswordAuthentionFilter实现自定义的过滤器,核心是attemptAuthentication方法.

  3. 自定义AuthenticationProvider(这里是EmailCodeAuthenticationProvider)

    public class EmailCodeAuthenticationProvider implements AuthenticationProvider {
        protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    
        private  EmailCodeUserDetailsService emailCodeUserDetailsService;
    
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            Assert.isInstanceOf(EmailCodeAuthenticationToken.class, authentication,
                                () -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
                                                               "Only UsernamePasswordAuthenticationToken is supported"));
            // 此时的authentication还没认证,获取邮箱号码
            EmailCodeAuthenticationToken unAuthenticationToken = (EmailCodeAuthenticationToken) authentication;
            // 做校验
            UserDetails user = this.emailCodeUserDetailsService.loadUserByEmail(unAuthenticationToken);
            if (user == null) {
                throw new InternalAuthenticationServiceException("EmailCodeUserDetailsService returned null, which is an interface contract violation");
            }
            System.out.println("authentication successful!");
    
            Object principalToReturn = user;
            return createSuccessAuthentication(principalToReturn, authentication, user);
        }
    
        @Override
        public boolean supports(Class<?> authentication) {
            return EmailCodeAuthenticationToken.class.isAssignableFrom(authentication);
        }
    
        protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
            EmailCodeAuthenticationToken result = new EmailCodeAuthenticationToken(principal,
                                                                                   authentication.getCredentials(), user.getAuthorities());
            result.setDetails(authentication.getDetails());
            return result;
        }
    
        public void setEmailCodeUserDetailsService(EmailCodeUserDetailsService emailCodeUserDetailsService) {
            this.emailCodeUserDetailsService = emailCodeUserDetailsService;
        }
    }
    

    说明:

    ​ Provider是真正做认证的地方,这里调用emailCodeUserDetailsService服务去执行验证,因为要用到这个Service,所以提供了一个set方法setEmailCodeUserDetailsService用于注入。这里的这个service是我们自定义的,可以不用实现UserDetailsService, Service里的逻辑可以自定义

  4. 自定义认证成功与失败的Handler

    public class EmailCodeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            response.setContentType("text/plain;charset=UTF-8");
            response.getWriter().write(authentication.getName());
        }
    }
    
    public class EmailCodeAuthenticationFailureHandler implements AuthenticationFailureHandler {
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
            response.setContentType("text/plain;charset=UTF-8");
            response.getWriter().write("邮箱验证码错误!");
        }
    }
    

    说明:

    ​ 这里的是认证成功或失败后的处理,需要实现对应的接口以及方法。这里的逻辑只是简单测试,具体逻辑以后根据业务逻辑去编写。

  5. 添加自定义认证的配置

    ​ 为了让我们自定义的认证生效,需要将我们的Filter和Provider加入到SpringSecurity的配置中。这里我们使用apply这个方法将其他一些配置合并到SpringSecurity的配置中,形成插件化。比如: httpSecurity.apply(new xxxxConfig());

    ​ 因此我们可以将我们的配置单独放到一个配置类中。

    public class EmailCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    
        // 注入email验证服务
        @Autowired
        private EmailCodeUserDetailsService emailCodeUserDetailsService;
    
        @Override
        public void configure(HttpSecurity http) {
            // 配置Filter
            EmailCodeAuthenticationFilter emailCodeAuthenticationFilter = new EmailCodeAuthenticationFilter();
            // 设置AuthenticationManager
            emailCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
            // 设置认证成功处理Handler
            emailCodeAuthenticationFilter.setAuthenticationSuccessHandler(new EmailCodeAuthenticationSuccessHandler());
            // 设置认证失败处理Handler
            emailCodeAuthenticationFilter.setAuthenticationFailureHandler(new EmailCodeAuthenticationFailureHandler());
    
            // 配置Provider
            EmailCodeAuthenticationProvider emailCodeAuthenticationProvider = new EmailCodeAuthenticationProvider();
            // 设置email验证服务
            emailCodeAuthenticationProvider.setEmailCodeUserDetailsService(emailCodeUserDetailsService);
    
            // 将过滤器添加到过滤器链路中
            http.authenticationProvider(emailCodeAuthenticationProvider).addFilterAfter(emailCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        }
    }
    

    注意:

    ​ 这里需要注意的是,一定要将AuthenticationManager提供给Filter,如果没有这一步,那么在Filter中进行认证的时候无法找到对应的Provider,因为AuthenticationManger就是管理Provider的。
    http.getSharedObject(AuthenticationManager.class)解释:

    SharedObject是在配置中进行共享的一些对象,HttpSecurity共享了一些非常有用的对象可以供外部使用,比如AuthenticationManager

    最后在SpringSecurity的主配置中加入我们的自定义配置:

    @Configuration
    public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private EmailCodeAuthenticationSecurityConfig emailCodeAuthenticationSecurityConfig;
    
        @Autowired
        private DefaultUserDetailsService defaultUserDetailsService;
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        @Override
        protected AuthenticationManager authenticationManager() throws Exception {
            return super.authenticationManager();
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(defaultUserDetailsService);
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/getEmailCode", "/**/*.html");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/logout")
                .and()
                .apply(emailCodeAuthenticationSecurityConfig)
                .and()
                .csrf()
                .disable();
        }
    }
    

    说明:

    ​ 因为这里使用了数据库保存用户信息,所以在SpringSecurity的默认表单登录里,修改了UserDetailService,在这里进行校验,所以在主配置中要设置UserDetailService:auth.userDetailsService(defaultUserDetailsService);

  6. 其他一些文件

    查看我上传的gitee源码吧,整个工程都上传了。

  7. 前端页面实现

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>登录</title>
    
            <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
            <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
            <script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
            <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
            <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
    
            <style>
                body {
                    background-color: gray;
                }
                .login-div {
                    width: 400px;
                    /* height: 200px; */
                    margin: 0 auto;
                    margin-top: 200px;
                    border: 1px solid black;
                    padding: 10px;
                }
            </style>
        </head>
        <body>
            <div class="login-div">
                <ul class="nav nav-tabs" role="tablist">
                    <li class="active">
                        <a href="#usernameLogin"   data-toggle="tab">用户名登录</a>
                    </li>
                    <li>
                        <a href="#emailLogin" data-toggle="tab">邮箱验证码登录</a>
                    </li>
                </ul>
    
                <!-- 用户名登录 -->
                <div class="tab-content">
                    <div class="tab-pane active" id="usernameLogin">
                        <form action="/login" method="POST">
                            <div class="form-group">
                                <label>用户名</label>
                                <input type="text" class="form-control" placeholder="Username" name="username">
                            </div>
                            <div class="form-group">
                                <label>密码</label>
                                <input type="password" class="form-control"  placeholder="Password" name="password">
                            </div>
    
                            <div class="checkbox">
                                <label>
                                    <input type="checkbox" name="rememberType"> 记住我
                                </label>
                            </div>
                            <button type="submit" class="btn btn-default">登录</button>
                        </form>
                    </div>
                    <!-- 邮箱登录 -->
                    <div class="tab-pane" id="emailLogin">            
                        <form action="/email/login" method="POST">
                            <div class="form-group" >
                                <label>邮箱地址</label>
                                <input type="email" class="form-control" placeholder="Email" name="email" id="email">
                            </div>
                            <div class="form-group">
                                <label>验证码</label>
                                <input type="text" class="form-control"  placeholder="Code" name="email_code">
                            </div>
    
                            <div class="form-group">
                                <label>
                                    <button type="button" class="btn btn-default" id="getCode">获取验证码</button>
                                    <span id="showCode" style="margin-left: 20px;"></span>
                                </label>
                            </div>
                            <button type="submit" class="btn btn-default">登录</button>
                        </form>
                    </div>
                </div>
    
            </div>
    
            <script>
                $('#nav a').on('click', function(e) {
                    e.preventDefault();
                    $(this).tab('show');
                }); 
    
                $('#getCode').on('click', function() {
                    $.ajax({
                        type: "GET",
                        url: "/getEmailCode",
                        data: {
                            email: $('#email').val()
                        },
                        // dataType: "dataType",
                        success: function (response) {
                            $('#showCode').text(response);
                        }
                    });
                });
            </script>
        </body>
    </html>
    

    说明:

    前端页面只是简单的显示使用两种方式来登录的操作,一些输入校验什么的没有详细实现,所以这里默认各位大佬都是正常操作哈。

    这个前端支持两种登录方式,用户名密码登录方式使用的SpringSecurity默认的UsernamepasswordAuthenticationFilter,邮箱验证码使用的是自定义的EmailCodeAuthenticationFilter,在邮箱登录页面,点击获取验证码按钮,会请求服务器获取一个随机的字符串作为验证码,并且存入Redis中,有效期60s(记住我功能在这里没有实现)

    SpringSecurity自定义认证

    SpringSecurity自定义认证

  8. 数据库操作

    因为目前只是自定义认证,不涉及授权,所以只有一个用户表

    CREATE TABLE `user` (
      `id` INT(11) NOT NULL AUTO_INCREMENT,
      `username` VARCHAR(32) DEFAULT NULL,
      `password` VARCHAR(255) DEFAULT NULL,
      `email` VARCHAR(255) DEFAULT NULL,
      `enabled` TINYINT(1) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=INNODB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
    
    INSERT INTO `user` VALUES ('1', 'root', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq','123456@qq.com', '1');
    

    随便插入一个用户,密码是123,数据库的是经过加密的。文章来源地址https://www.toymoban.com/news/detail-470240.html

到了这里,关于SpringSecurity自定义认证的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringSecurity认证和授权流程详解

    Spring Security是一个Java框架,用于保护应用程序的安全性。它提供了一套全面的安全解决方案,包括身份验证、授权、防止攻击等功能。Spring Security基于过滤器链的概念,可以轻松地集成到任何基于Spring的应用程序中。它支持多种身份验证选项和授权策略,开发人员可以根据需

    2024年04月08日
    浏览(50)
  • SpringSecurity安全框架 ——认证与授权

    目录  一、简介 1.1 什么是Spring Security 1.2 工作原理 1.3 为什么选择Spring Security 1.4 HttpSecurity 介绍🌟 二、用户认证 2.1 导入依赖与配置 2.2 用户对象UserDetails 2.3 业务对象UserDetailsService 2.4 SecurityConfig配置 2.4.1 BCryptPasswordEncoder密码编码器 2.4.2 RememberMe 记住登录信息 2.4.3 CSR

    2024年02月04日
    浏览(43)
  • 【 SpringSecurity】第三方认证&方法级别安全

    在登录网页时,时常有用其他账号登录的方式,它们能够让用户避免在Web站点特定的登录页上自己输入凭证信息。这样的Web站点提供了一种通过其他网站(如Facebook)登录的方式,用户可能已经在这些其他的网站登录过了 这种类型的认证是基于OAuth2或OpenID Connect(OIDC)的。OAut

    2024年02月13日
    浏览(39)
  • SpringSecurity+JWT前后端分离架构登录认证

    目录 1. 数据库设计 2. 代码设计 登录认证过滤器 认证成功处理器AuthenticationSuccessHandler 认证失败处理器AuthenticationFailureHandler AuthenticationEntryPoint配置 AccessDeniedHandler配置 UserDetailsService配置 Token校验过滤器 登录认证过滤器接口配置 Spring Security全局配置 util包 测试结果   在Spri

    2024年01月21日
    浏览(69)
  • SpringSecurity入门demo(一)集成与默认认证

    一、集成与默认认证: 1、说明:在引入 Spring Security 项目之后,没有进行任何相关的配置或编码的情况下,Spring Security 有一个默认的运行状态,要求在经过 HTTP 基本认证后才能访问对应的 URL 资源,其默认使用的用户名为 user, 密码则是动态生成并打印到控制台的一串随机码

    2024年02月02日
    浏览(31)
  • SpringSecurity入门demo(三)多用户身份认证

    方法中完成身份认证。前面的demo都只有一个用户,security中使用 UserDetailsService做为用户数据源  ,所以可以实现 UserDetailsService  接口来自定义用户。实现方法可以有几下几种: 1)内容用户 2)JDBC读取 3)自定义UserDetailsService 4)自定义AuthenticationProvider 一、使用内存用户验证

    2024年01月17日
    浏览(40)
  • SpringSecurity实现前后端分离登录token认证详解

    目录 1. SpringSecurity概述 1.1 权限框架 1.1.1 Apache Shiro 1.1.2 SpringSecurity 1.1.3 权限框架的选择 1.2 授权和认证 1.3 SpringSecurity的功能 2.SpringSecurity 实战 2.1 引入SpringSecurity 2.2 认证 2.2.1 登录校验流程  2.2.2 SpringSecurity完整流程  2.2.3 认证流程详解 2.3 思路分析 2.4 代码实战 2.4.1  自定义

    2024年02月08日
    浏览(50)
  • 3.SpringSecurity基于数据库的认证与授权

    承接:2.SpringSecurity - 处理器简单说明-CSDN博客 我们之前学习的用户的信息都是配置在代码中,如下段代码所示 但是我们并不希望上面这样写,我们希望把用户的信息存入到数据库 用户实体类需要实现UserDetails接口,并实现该接口中的7个方法, UserDetails 接口的7个方法如下图

    2024年02月07日
    浏览(41)
  • SpringSecurity的安全认证的详解说明(附完整代码)

    SpringSecurity 登录认证和请求过滤器以及安全配置详解说明 系统环境:win10 Maven环境:apache-maven-3.8.6 JDK版本:1.8 SpringBoot版本:2.7.8 根据用户名和密码登录,登录成功后返回 Token 数据,将 token放到请求头中 ,每次请求后台携带 token 数据 携带token请求后台 ,后台认证成功,过滤

    2024年02月08日
    浏览(36)
  • Postman测试 - SpringSecurity用户名和密码认证访问后台请求

    Postman测试SpringSecurity用户名和密码认证访问后台请求 1. 浏览器测试 1、启动后台项目,consoal控制台得到一个默认的登录密码 fd16e894-172e-4f20-976d-0b07fb7a2cbb 2、在浏览器访问请求:http://localhost:8080/login,得到一个登录页面,填入默认用户名和密码 fd16e894-172e-4f20-976d-0b07fb7a2cbb ,即

    2024年02月16日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包