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)


相关推荐

  • Java Calendar 日历类的时间操作 Timestamp Date Calendar 相互转换

    Java Calendar 日历类的时间操作 Timestamp Date Calendar 相互转换JavaCalendar日历类的时间操作,这也许是Java环境下创建和管理日历及操作时间最简单的一个方案了,示范代码也很简单。演示了获取时间,日期时间的累加和累减,以及日期时间的比较。原文地址:blog.csdn.net/joyous/article/details/9630893注意事项:Calendar的month从0开始,也就是全年12个月由0~1…

  • eclipse中svn_git打补丁解决冲突

    eclipse中svn_git打补丁解决冲突1.为什么会出现冲突&lt;1&gt;两个开发人员,Harry和Sally,分别从服务器端下载了文件A。&lt;2&gt;Harry修改之后,A变成了A’,Sally修改之后,A变成了A”。&lt;3&gt;Harry先一步提交,使服务器端文件的版本也变成了A’&lt;4&gt;Sally本地的文件A”已经过时了,此时她已无法提交文件,服务器会要求她先进行一次更新操作。&lt;…

    2022年10月14日
  • Mac Quicktime 录屏带声音[通俗易懂]

    Mac Quicktime 录屏带声音[通俗易懂]最近有录屏的需求,但是Mac大多数录屏软件都收费,之前用Windows时用EV录屏,免费好用,可惜没有Mac版。Mac自带的QuickTime软件虽然能录屏,但是不能录制声音,很苦恼。直到我发现了SoundFlower软件。1、下载安装soundflower给个链接:http://mysoft.6h5.cn/Soundflower-2.0b2.dmg安…

  • 学习Python之路之ipython的使用及Pycharm的安装[通俗易懂]

    学习Python之路之ipython的使用及Pycharm的安装[通俗易懂]一、Python的基础知识1、概念Python是一种面向对象的解释型计算机程序设计语言,由荷兰人GuidovanRossum于1989年发明,第一个公开发行版发行于1991年。2、Python的特点优点:简单、优雅、明确 有强大的第三方库模块 可跨平台移植 一种面向对象的语言缺点:代码执行速度慢,相比C语言,不过现在python的异步并发框架导致执行速度慢 p…

  • Java数组及二维数组初始化与赋值方法总结

    Java数组及二维数组初始化与赋值方法总结1.定义数组直接赋值inta[]={1,2,3,4,5};2.用new在数组中创建元素inta[]=newint[]{1,2,3};注意:后面的[]不要忘了3.创建指定大小数组,但不赋值 intc[]=newint[3]; System.out.println(Arrays.toString(c));注意:基本类型会自动初始化为空值,int型则为0.输出结果为[0,0,0],Arrays.toString产生一维数组的可打印版本。4.先声明再赋值 intc[

  • flex 词法分析_c语言词法分析器的简单实现

    flex 词法分析_c语言词法分析器的简单实现为什么80%的码农都做不了架构师?>>>…

    2022年10月28日

发表回复

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

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