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

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

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

一、前言

上篇文章,我们简单讲了一下单认证方式的配置,以及各个spring security配置文件的作用

https://blog.csdn.net/qq_36521507/article/details/103365805

本篇则讲一下多种认证方式的配置

二、多认证

1、自定义认证过滤器

由上篇文章,我们知道了要配置登录认证,需要先自定义一个过滤器,我们参考默认过滤器自定义一个

public class CitictAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
    

    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

    private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
    private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
    private boolean postOnly = true;

    
    public CitictAuthenticationProcessingFilter() {
         super(new AntPathRequestMatcher("/citict/doLogin", "POST"));
     }

    @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());
        }

        String username = obtainUsername(request);
        String password = obtainPassword(request);

        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();

        CitictAuthenticationToken authRequest = new CitictAuthenticationToken(
                username, password);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);
    }

2、自定义AuthenticationToken实现类

在此之前,我们需要先自定义一个AuthenticationToken的实现类,用来封装认证参数,以及绑定认证器

public class CitictAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = -6437322217156360297L;

    private final Object principal;
    private Object credentials;

    /**
     * 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 CitictAuthenticationToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        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 principal
     * @param credentials
     * @param authorities
     */
    public CitictAuthenticationToken(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;
    }
    
    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");
        }

        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
        credentials = null;
    }

   

}

3、自定义一个认证处理器AuthenticationProvider


@Slf4j
@Component
public class CitictAuthenticationProvider implements AuthenticationProvider {
    /**
     * 注入我们自己定义的用户信息获取对象
     */
    @Autowired
    private UserDetailsService userDetailService;
    
    private UserDetailsChecker userDetailsChecker = new MyPreAuthenticationChecks();


    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // TODO Auto-generated method stub
        String userName = authentication.getName();// 这个获取表单输入中返回的用户名;
        String password = (String)authentication.getCredentials();// 这个是表单中输入的密码;
        MyWebAuthenticationDetails details = (MyWebAuthenticationDetails)authentication.getDetails();
        
        // 这里构建来判断用户是否存在和密码是否正确
        UserInfo userInfo = (UserInfo)userDetailService.loadUserByUsername(userName); // 这里调用我们的自己写的获取用户的方法;
      
        
        userDetailsChecker.check(userInfo);
        
        
        /**
         * 登录认证
         */
        try {
            LoginUtil.login(userName, password);
        } catch (Exception e) {
             e.printStackTrace();
             throw new BadCredentialsException(e.getMessage());
        }
        

        Collection<? extends GrantedAuthority> authorities = userInfo.getAuthorities();
        // 构建返回的用户登录成功的token
        return new CitictAuthenticationToken(userInfo, password, authorities);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        
        /**
         * providerManager会遍历所有
         * securityconfig中注册的provider集合
         * 根据此方法返回true或false来决定由哪个provider
         * 去校验请求过来的authentication
         */
        return (CitictAuthenticationToken.class
            .isAssignableFrom(authentication));
    }

}

注意supports方法,只有这样判断,才会在自定义过滤器调用认证管理器认证时,只调用CitictAuthenticationProvider我们自定义的认证方法,排除其他认证器,具体原因参考上篇文章。

4、最后看下spring security配置文件

@Configuration
@Slf4j
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private MyAuthenticationProvider defaultProvider;  //默认本地用户名密码登录AuthenticationProvider
    @Autowired
    private CitictAuthenticationProvider citictProvider;  //自定义登录AuthenticationProvider
    @Autowired
    private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler myAuthenticationFailHander;
    @Autowired
    private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> myAuthenticationDetailsSource;
    @Autowired
    private ValidateCodeFilter validateCodeFilter;
    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
       //super.configure(http);
       http.cors();
       http.csrf().disable();
       
       //拦截器需要在此注册
       http.addFilterBefore(validateCodeFilter, AbstractPreAuthenticatedProcessingFilter.class);
       http.addFilterBefore(citictAuthenticationProcessingFilter(), AbstractPreAuthenticatedProcessingFilter.class);
       
       /**
        * 此处配置的过滤器拦截地址、认证成功失败处理器,需要在下面过滤器单独配置
        */
       http.formLogin().loginPage("/toLogin")  
           .failureUrl("/loginError")


            //.loginProcessingUrl("/doLogin") //改为在过滤器处配置
           //.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);
         auth.authenticationProvider(citictProvider);

     }
     
     //这个必须重写,才能使用AuthenticationManager,在成员变量注入进来,再注入过滤器中
     @Override
     @Bean
     public AuthenticationManager authenticationManagerBean() throws Exception {
         return super.authenticationManagerBean();
     }

    //下面就是默认的过滤器UsernamePasswordAuthenticationFilter
    //配置一下拦截地址、认证成功失败处理器、authenticationManager
     
     /**
      * 默认用户名密码认证过滤器
      * @Author guomh 2019/12/02
      * @return
      */
     @Bean
     public UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() {
         UsernamePasswordAuthenticationFilter filter = new UsernamePasswordAuthenticationFilter();
         
         filter.setAuthenticationManager(authenticationManager);
         filter.setAuthenticationDetailsSource(myAuthenticationDetailsSource);
         filter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
         filter.setAuthenticationFailureHandler(myAuthenticationFailHander);
         filter.setFilterProcessesUrl("/doLogin");
         return filter;
     }
     
     //下面就是自定义的过滤器,配置一下拦截地址、认证成功失败处理器、authenticationManager
     //如果还有其他认证过滤器,则再这样写一个
     /**
      * 自定义登录过滤器
      * @Author guomh 2019/12/02
      * @return
      */
     @Bean
     public CitictAuthenticationProcessingFilter citictAuthenticationProcessingFilter() {
         CitictAuthenticationProcessingFilter filter = new CitictAuthenticationProcessingFilter();
         filter.setAuthenticationManager(authenticationManager);
         filter.setAuthenticationDetailsSource(myAuthenticationDetailsSource);
         filter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
         filter.setAuthenticationFailureHandler(myAuthenticationFailHander);
         filter.setFilterProcessesUrl("/doCitictLogin");
         return filter;
     }
     
     @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;
     }
   
}

三、总结

这样配置,易于扩展,如果我们要再加一个手机登录认证器,我们只需要写

MobileAuthenticationProcessingFilter、MobileAuthenticationToken、MobileAuthenticationProvider

再配置文件中注册拦截器bean, MobileAuthenticationProcessingFilter

加入拦截器链,http.addFilterBefore(MobileAuthenticationProcessingFilter(), AbstractPreAuthenticatedProcessingFilter.class);

注册认证器 auth.authenticationProvider(mobileProvider) 即可

每个拦截器都可根据需要自定义配置。

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

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

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

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

(0)


相关推荐

  • JAVA队列( Queue ) 详解[通俗易懂]

    JAVA队列( Queue ) 详解[通俗易懂]什么是队列?队列是一种特殊的线性表,遵循先入先出、后入后出的基本原则,一般来说,它只允许在表的前端进行删除操作,而在表的后端进行插入操作,但是java的某些队列运行在任何地方插入删除;比如我们常用的LinkedList集合,它实现了Queue接口,因此,我们可以理解为LinkedList就是一个队列;java队列特性队列主要分为阻塞和非阻塞,有界和无界、单向链表和双向链表之分;阻塞和非阻塞阻塞队列入列(删除元素)时,如果元素数量超过队列总数…

  • Visual Studio 2008 序列号 激活 vs2008[通俗易懂]

    Visual Studio 2008 序列号 激活 vs2008[通俗易懂]
    VisualStudio2008简体中文试用版(90天)变永久正式版的两种方法:
    一、先安装试用版,然后在“添加或删除程序”里找到VS2008,点“更改/删除”就会看到一个输入序列号的地方,把下面这个序列号输进去即可,TeamSuite和Professional通用。
    二、把Setupsetup.sdb文件中的[ProductKey]项中对应的序列号即可。
    因为九十天试用版本已经是rtm版本。所以改变序列号以后的升级或者安装,就会变成正式版,不再

  • accessors 作用_EasyExcel与@Accessors(chain = true)不兼容分析

    accessors 作用_EasyExcel与@Accessors(chain = true)不兼容分析EasyExcelEasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel.github地址:https://github.com/alibaba/easyexcelAccessors@Accessors注解用来配置lombok如何产生和显示getters和setters的方法。public@interfaceAcce…

    2022年10月31日
  • Windows 7 连接 Windows 10 共享打印机,Windows 无法连接打印机,操作失败,错误为0x0000011b 的终极解决办法

    Windows 7 连接 Windows 10 共享打印机,Windows 无法连接打印机,操作失败,错误为0x0000011b 的终极解决办法Windows7连接Windows10共享打印机出现错误0x000001b,无法通过卸载KB5005565安全更新来解决该问题,正确的处理方法是手工添加一个本地打印机,本方法稳定可靠。本文详述了该方法的操作步骤。

  • navicat永久激活码最新2021【在线注册码/序列号/破解码】

    navicat永久激活码最新2021【在线注册码/序列号/破解码】,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • java目录删除_java删除文件及目录[通俗易懂]

    java目录删除_java删除文件及目录[通俗易懂]java中删除目录事先要删除目录下的文件或子目录。用递归就可以实现。publicvoiddel(Stringfilepath)throwsIOException{Filef=newFile(filepath);//定义文件路径if(f.exists()&&f.isDirectory()){//判断是文件还是目录if(f.listFiles().length==…

发表回复

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

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