Spring boot + Spring Security 多种登录认证方式配置(一)

Spring boot + Spring Security 多种登录认证方式配置(一)

欢迎大家去我的个人网站踩踩 点这里哦

一、前言

最近项目上用到Spring Security作为权限认证,项目是Spring boot项目,刚开始只用到本地数据库账号密码登录一种认证方式,后来需求修改,客户有个第三方接口提供登录,为了方便用户,修改为同时支持两种登录方式,在网上多番查找资料,加上看了源码后终于弄出来了,也对Spring Security认证有了更深入的了解,鉴于网上对于多种登录认证方式的资料都不是太完整齐全,所以有了这篇,一来作为记录、二来自己也梳理一下知识点,废话到此为止。

二、单认证方式

在说多种认证方式之前,咱们先简单过下单认证方式是如何配置的,也说下Spring Security的各个配置类的作用。

1、UsernamePasswordAuthenticationFilter

Spring Security 默认认证过滤器,处于拦截器链当中,继承AbstractAuthenticationProcessingFilter,咱们看一下源码Spring boot + Spring Security 多种登录认证方式配置(一)

 

Spring boot + Spring Security 多种登录认证方式配置(一)

可以看出里面构造方法指定了默认拦截地址 /login,attemptAuthentication是父类AbstractAuthenticationProcessingFilter抽象方法的实现方法,在父类中doFilter方法里调用,可以看到方法实现是从request里取得用户名密码,最后构建成UsernamePasswordAuthenticationToken,然后调用AuthenticationManager的 authenticate 方法作为参数传进去进行认证。

2、UsernamePasswordAuthenticationToken

Spring boot + Spring Security 多种登录认证方式配置(一)

UsernamePasswordAuthenticationToken没什么好讲的,在其实就是对认证参数用户名密码的封装,当然后续登录成功之后会作为用户认证信息的封装。

3、AuthenticationManager

authenticationManager是AbstractAuthenticationProcessingFilter的一个成员变量,从上面可以看出,这个参数是必须赋值的,采用默认的过滤器认证,spring security会默认给一个实现类ProviderManager,看下代码

Spring boot + Spring Security 多种登录认证方式配置(一)

Spring boot + Spring Security 多种登录认证方式配置(一)

从源码看到,管理器会遍历所有注册的认证器集合,调用每个认证器的authenticate认证,此时会有疑惑,如果多个登录方式,肯定会有多个认证器,每次都遍历认证所有的认证器是否不太合理?关键在于以下这个判断代码

这个 toTest 参数就是过滤器传进来的 UsernamePasswordAuthenticationToken

Class<? extends Authentication> toTest = authentication.getClass();

if (!provider.supports(toTest)) {
     continue;
}

Spring boot + Spring Security 多种登录认证方式配置(一)

 

会调用每个认证器的supports方法,只有此方法返回true,才会执行认证,(由此想到如果自定义认证器,此方法一定要重写),此方法如何实现,咱们看一下此方法的默认实现,会判断
  

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

由此看出,参数必须为 UsernamePasswordAuthenticationToken 类或者其子类的字节码,此参数又是由UsernamePasswordAuthenticationFilter 里传过来的

由此得知,每个过滤器都需要一个AbstractAuthenticationToken的子类绑定

4、AuthenticationProvider

这个是重点配置,具体认证方法都是在这里实现的,因此我们要自定义我们的认证方法,都需要实现这个接口,这个接口只有两个方法,authenticate用来认证,supports 用来决定启用条件

Spring boot + Spring Security 多种登录认证方式配置(一)

Spring boot + Spring Security 多种登录认证方式配置(一)

具体实现方法根据自己的业务需要,一般是查询数据库,对比密码,看下我的实现类,如下

Spring boot + Spring Security 多种登录认证方式配置(一)

一般我们会注入一个自定义的UserDetailService实现类,重写 loadUserByUsername,具体根据用户名查询用户信息,认证成功将用户信息包装成 Authentication 返回

Spring boot + Spring Security 多种登录认证方式配置(一)

5、AuthenticationSuccessHandler、AuthenticationFailureHandler

Spring boot + Spring Security 多种登录认证方式配置(一)

看过滤器源码,认证结束后,会根据认证成功或失败,分别调用两个成功失败处理器

successHandler.onAuthenticationSuccess(request, response, authResult);

failureHandler.onAuthenticationFailure(request, response, failed);

因此,我们可以自定义这两个处理器,来自己处理认证成功失败

Spring boot + Spring Security 多种登录认证方式配置(一)

6、配置文件

最后,咱们来看下Spring Security的配置文件

@Configuration
@Slf4j
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private MyAuthenticationProvider defaultProvider;  //默认本地用户名密码登录AuthenticationProvider
    @Autowired
    private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler myAuthenticationFailHander;
    @Autowired
    private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> myAuthenticationDetailsSource;
    @Autowired
    private ValidateCodeFilter validateCodeFilter;

    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
       //super.configure(http);
       http.cors();
       http.csrf().disable();
       
       http.addFilterBefore(validateCodeFilter, AbstractPreAuthenticatedProcessingFilter.class);
       
       
       http.formLogin().loginPage("/toLogin")
           .loginProcessingUrl("/doLogin") 
           .failureUrl("/loginError")
           .authenticationDetailsSource(myAuthenticationDetailsSource)
           .successHandler(myAuthenticationSuccessHandler)
           .failureHandler(myAuthenticationFailHander)
           .permitAll()  //表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
           .and()
           .logout().permitAll().invalidateHttpSession(true)
           .deleteCookies("JSESSIONID").logoutSuccessHandler(logoutSuccessHandler())
           .and()
           .authorizeRequests()
           .antMatchers("/swagger-ui.html","/webjars/**","/v2/**","/swagger-resources/**","/favicon.ico","/css/**","/common/**","/js/**","/images/**",
               "/captcha.jpg","/login","/doLogin","/doCitictLogin","/loginError","/getAllTenant","/sessionExpired","/sessionInvalid","/code/*").permitAll()
           .anyRequest().authenticated()
           .and()
           .sessionManagement().invalidSessionUrl("/sessionInvalid")
           .maximumSessions(10)
           // 当达到最大值时,是否保留已经登录的用户
           .maxSessionsPreventsLogin(false)
           // 当达到最大值时,旧用户被踢出后的操作
           //.expiredSessionStrategy(customExpiredSessionStrategy());
           //在上一句过期策略里配置
           .expiredUrl("/sessionExpired");
    }
      
    
     @Override
     protected void configure(AuthenticationManagerBuilder auth) throws Exception{

         //注册认证处理器
         auth.authenticationProvider(defaultProvider);

     }
     
     @Override
     @Bean
     public AuthenticationManager authenticationManagerBean() throws Exception {
         return super.authenticationManagerBean();
     }

    @Bean
     public LogoutSuccessHandler logoutSuccessHandler() { //登出处理
         return new LogoutSuccessHandler() {
             @Override
             public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                 try {
                     UserInfo user = (UserInfo) authentication.getPrincipal();
                     log.info("USER : " + user.getUsername() + " LOGOUT SUCCESS !  ");
                 } catch (Exception e) {
                     log.info("LOGOUT EXCEPTION , e : " + e.getMessage());
                 }
                 httpServletResponse.sendRedirect("/toLogin");
             }
         };
     }

     @Bean
     public SessionInformationExpiredStrategy customExpiredSessionStrategy() {
         
         return new SessionInformationExpiredStrategy() {
            
            private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
             
            @Override
            public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
                
                // 如果是跳转html页面,url代表跳转的地址
                redirectStrategy.sendRedirect(event.getRequest(), event.getResponse(), "/sessionExpired");
                
            }
        };
     }
     
     @Bean("sessionStrategy")
     public SessionStrategy sessionStrategy() {
         SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
         return sessionStrategy;
     }
   }

三、总结

最后我们总结一下配置流程,要实现一个登录认证,首先要自定义一个过滤器 AbstractAuthenticationProcessingFilter,注入一个认证管理器 AuthenticationManager,然后需要绑定一个AbstractAuthenticationToken,注册一个认证处理器 AuthenticationProvider,如果使用默认认证过滤器,则只需要自定义认证处理器进行认证即可.

下一篇,我们将进入正题,说下多种登录方式的配置。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/111319.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 阿里巴巴中间件团队_阿里的中间件和库

    阿里巴巴中间件团队_阿里的中间件和库实践出真知,阿里中间件博客入口

  • 【详细】3分钟教会你使用USDT钱包「建议收藏」

    【详细】3分钟教会你使用USDT钱包「建议收藏」小编为您介绍一款安全系数非常高的USDT钱包,之所以安全系数非常高是因为去中心化模式,USDT钱包文件或私钥可以脱离网络储存,比如可以备份在手机里,硬盘里,U盘里,网盘里,甚至拿笔写在纸上。我们经常听说用户一般会把大部分数字资产存储到冷钱包,主要是为了安全的考虑,而把少量的数字资产存储在交易所热钱包,以方便交易,现在USDT钱包居多的是中心化钱包,用户的数字货币储存在交易所里,换句话说非常不安全,因为你不知道你的USDT钱包文件与私钥,交易所被黑客攻击或者自行关闭你是无能为力只能眼睁睁看这你的币子任由他

  • centos挖矿程序解决

    centos挖矿程序解决centos挖矿程序解决第一种办法:1.top找到cup占比最高的程序2.ps-aux|grepCOMMAND3.crontab-l查看定时任务4.然后删除挖矿脚本和定时任务脚本5.如果删不掉chattr-i脚本6.然后再删7.然后crontab-e清除掉脚本内容…

  • mysql中字符转数字,MYSQL字符数字转换为数字「建议收藏」

    mysql中字符转数字,MYSQL字符数字转换为数字「建议收藏」1、将字符的数字转成数字,比如’0’转成0可以直接用加法来实现例如:将user表中的uid进行排序,可uid的定义为varchar,可以这样解决select*fromuserorderby(uid+0)2、在进行ifnull处理时,比如ifnull(a/b,’0′)这样就会导致a/b成了字符串,因此需要把’0’改成0,即可解决此困扰3、比较数字和varchar时,比如a=11,…

  • 浅析瀑布流布局及其原理视频_jquery瀑布流布局

    浅析瀑布流布局及其原理视频_jquery瀑布流布局一、什么是瀑布流很多时候我们会看到一些Vlog网站或者展示图片的网站,它们的图片明明每一张的高度大小都不同,但是却能自动地适应,排成一行一行地展示,并且下拉到底的时候,加载的图片也会自动适应,这就是瀑布流,比如下图。…

    2022年10月30日
  • Latex插入图片却不显示问题小结

    Latex插入图片却不显示问题小结1、首先检查有没有导入宏包\usepackage{graphicx}2、检查图片路径有无问题3、可以尝试将Tex的同名文件删除,重新编译生成4、若是文章分栏\begin{multicols}{2}那是因为multicols环境不能识别figure环境,重新定义一个环境即可\newenvironment{figurehere}{\def\@captype{figure}}{}\makeatother%用于连接公式编号在文中,用figureher

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号