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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • java实现十进制转十六进制_十进制转十六进制java代码

    java实现十进制转十六进制_十进制转十六进制java代码基础练习十六进制转十进制时间限制:1.0s内存限制:512.0MB问题描述  从键盘输入一个不超过8位的正的十六进制数字符串,将它转换为正的十进制数后输出。注:十六进制数中的10~15分别用大写的英文字母A、B、C、D、E、F表示。样例输入FFFF样例输出65535太奇葩了,拿到这道题受上道题的影响,自己写了进制转化函数,结果,25分。。。。imp…

    2025年5月27日
    5
  • anaconda和python版本对照表

    anaconda和python版本对照表python2 python3 anaconda2/3 2.7.14 3.6.5 5.2.0 2.7.14 3.6.4 5.1.0 2.7.14 3.6.3 5.0.1 2.7.13 3.6.2 5.0.0 2.7.13 3.6.1 4.4.0 2.7.13 3.6.0 4.3.1 2….

    2022年5月28日
    487
  • SpringBoot整合RabbitMQ之实战

    SpringBoot整合RabbitMQ之实战实战前言RabbitMQ作为目前应用相当广泛的消息中间件,在企业级应用、微服务应用中充当着重要的角色。特别是在一些典型的应用场景以及业务模块中具有重要的作用,比如业务服务模块解耦、异步通信、高并发限流、超时业务、数据延迟处理等。其中课程的学习链接地址:https://edu.csdn.net/course/detail/9314RabbitMQ官网拜读首先,让我们先拜读Ra…

    2022年5月14日
    39
  • 【NOIP2011】聪明的质检员

    【NOIP2011】聪明的质检员2.聪明的质监员(qc.cpp/c/pas)小T是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有n个矿石,从1到n逐一编号,每个矿石都有自己的重量wi以及价值vi。检验矿产的流程是:1、给定m个区间[Li,Ri];2、选出一个参数W;3、对于一个区间[Li,Ri],计算矿石在这个区间上的检验值Yi:这批矿产的检验结果Y为各个区间

    2025年12月3日
    5
  • mysql fulltext搜索_mysql 全文搜索 FULLTEXT

    mysql fulltext搜索_mysql 全文搜索 FULLTEXT到3.23.23时,MySQL开始支持全文索引和搜索。全文索引在MySQL中是一个FULLTEXT类型索引。FULLTEXT索引用于MyISAM表,可以在CREATETABLE时或之后使用ALTERTABLE或CREATEINDEX在CHAR、VARCHAR或TEXT列上创建。对于大的数据库,将数据装载到一个没有FULLTEXT索引的表中,然后再使…

    2025年7月8日
    2
  • ArcEngine 中的-2147467259错误

    ArcEngine 中的-2147467259错误ArcEngine中因数据不合规导致的-2147467259错误

    2022年7月13日
    28

发表回复

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

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