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

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

 

 




  • 微信
  • 赶快加我聊天吧
  • QQ
  • 相逢即是有缘
  • weinxin
三尾鱼

发表评论 取消回复

    • avatar 幻想世界

      有用,可以实现