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) 即可

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

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Dreamweaver 2019安装教程

    Dreamweaver 2019安装教程安装前先关闭杀毒软件和360卫士,注意安装路径不能有中文,安装包路径也不要有中文。1.选中【DreamweaverCC2019】压缩包,鼠标右击选择【解压到DreamweaverCC2019】。2.双击打开【DreamweaverCC2019】文件夹。3.选中【Set-up.exe】可执行文件,鼠标右击选择【以管理员身份运行】。4.点击文件夹小图标,然后选择【更改位置】。5.建议安装到除C盘以外的磁盘,可以在D盘或其他盘新建一个【DwCC2…

    2022年5月20日
    37
  • mac navicat激活码[最新免费获取]

    (mac navicat激活码)好多小伙伴总是说激活码老是失效,太麻烦,关注/收藏全栈君太难教程,2021永久激活的方法等着你。IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html1STL5S9V8F-eyJsaWNlbnNlSWQi…

    2022年3月27日
    108
  • python创建新数组_python怎么创建数组

    python创建新数组_python怎么创建数组一 直接定义法:1.直接定义matrix=[0,1,2,3]2.间接定义matrix=[0foriinrange(4)]  print(matrix)二 Numpy方法:Numpy内置了从头开始创建数组的函数:zeros(shape)将创建一个用指定形状用0填充的数组。默认的dtype是float64。下面是几种常用的创建方法:#coding=utf-8importnumpyasnp…

    2022年6月7日
    54
  • sql中的嵌套查询_sql的多表数据嵌套查询

    sql中的嵌套查询_sql的多表数据嵌套查询今天纠结了好长时间,才解决的一个问题,问题原因是求得多条数据中, 时间和日期是最大的一条数据先前是以为只要msx函数就可以解决的,Select* fromtableNamewheredate=(selectmax(date)fromTableName) andtime=(selectmax(time)fromTableName)

    2022年8月10日
    6
  • 最新QT下载和安装 指南教程「建议收藏」

    最新QT下载和安装 指南教程「建议收藏」原文地址:http://c.biancheng.net/view/3851.htmlQt体积很大,有1GB~3GB,官方下载通道非常慢,相信很多读者会崩溃,所以建议大家使用国内的镜像网站(较快),或者使用迅雷下载(很快)。作为Qt下载教程,本文会同时讲解以上三种下载方式。Qt官方下载(非常慢)Qt官网有一个专门的资源下载网站,所有的开发环境和相关工具都可以从这里下载,具体地址是:http://download.qt.io/图1:Qt官方下载网站截图对目录结构的…

    2022年5月17日
    37
  • javascript 数组排序(三种方法)

    javascript 数组排序(三种方法)一、用sort()方法进行数组排序vararr1=[20,80,54,78,8,67,27]; functionmy(a,b){ returna-b; } console.log(arr1.sort(my));//8,20,27,54,67,78,80二、冒泡排序vararr1=[20,80,54,78,8,67,27]; functionmys(arr1){ for(vari=0;i<arr1.length-1;

    2022年6月11日
    30

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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