文章目录

  • 实现AbstractAuthenticationToken自定义验证对象
  • 定义登录filter
  • 实现AbstractAuthenticationProcessingFilter的attemptAuthentication方法
  • 自定义身份验证处理器
  • 定义认证成功处理器
  • 定义认证失败处理器
  • 配置登录过滤器到springSecurity


实现AbstractAuthenticationToken自定义验证对象

在SpringSecurity认证过程中,最核心的对象为Authentication,这个对象用于在认证过程中存储主体的各种基本信息(例如:用户名,密码,所属部门等等)和主体的认证信息(例如,接口权限)。

我们可以通过继承AbstractAuthenticationToken来自定义的Authentication对象

public class MyUserNamePassToken extends AbstractAuthenticationToken {


    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

		//一般用于存储主体信息,
    private Object principal;
		//一般用于储存验证主体信息的凭据
    private Object credentials;

		
    public MyUserNamePassToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }

    public MyUserNamePassToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(true);
    }


    @Override
    public Object getCredentials() {
        return credentials;
    }

    @Override
    public Object getPrincipal() {
        return principal;
    }
}

我们可能还是比较懵逼,定义这个对象有啥用,别急。对于当前这个对象的各个属性存储啥其实都不重要,你想要怎么存就怎么存,我们重点关注第二个构造方法中的入参authorities,这个参数是在AbstractAuthenticationToken中定义的,代表该用户所拥有的所有权限的集合。

定义登录filter

自定义filter的方法有很多种,通过阅读springSecurity自带的UsernamePasswordAuthenticationFilter的源码我们对其进行模仿,通过继承AbstractAuthenticationProcessingFilter来实现一个Filter,AbstractAuthenticationProcessingFilter是由springSecurity定义的一个过滤器抽象类,它基于认证流程做了一个非常好的扩充,下面是AbstractAuthenticationProcessingFilter的dofilter方法源码。

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);
			return;
		}
		try {
      //先调用钩子方法尝试进行身份认证
			Authentication authenticationResult = attemptAuthentication(request, response);
      //如果没有认证成功,直接返回
			if (authenticationResult == null) {
				// return immediately as subclass has indicated that it hasn't completed
				return;
			}
      //session相关功能,现在都不用session,这里不管
			this.sessionStrategy.onAuthentication(authenticationResult, request, response);
			//认证成功时候继续执行过滤器链剩余的过滤器,默认时false不执行; 
			if (this.continueChainBeforeSuccessfulAuthentication) {
				chain.doFilter(request, response);
			}
      //执行认证成功处理器
			successfulAuthentication(request, response, chain, authenticationResult);
		}
    //认证发生异常则执行认证失败处理器
		catch (InternalAuthenticationServiceException failed) {
			this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
			unsuccessfulAuthentication(request, response, failed);
		}
		catch (AuthenticationException ex) {
			unsuccessfulAuthentication(request, response, ex);
		}
	}

通过阅读源码发现,只要我们继承了AbstractAuthenticationProcessingFilter我们就只需要实现attemptAuthentication(request, response)方法来处理具体的认证流程,再定义一个AuthenticationSuccessHandler处理认证成功和定义一个AuthenticationFailureHandler来处理认证失败这三个就可以实现一个完整的登录filter。

实现AbstractAuthenticationProcessingFilter的attemptAuthentication方法

public class MyLoginFilter extends AbstractAuthenticationProcessingFilter {
		//定义过滤器拦截的请求映射
    private final static String matchUrl = "/login";

    public MyLoginFilter() {
        super(new AntPathRequestMatcher(matchUrl));
    }

	  //定义身份验证逻辑
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        //初始化一个我们自定义的未经认证的Authentication对象
        MyUserNamePassToken userNamePassToken = new MyUserNamePassToken(request.getParameter("userName"), request.getParameter("UserPass"));
        //调用所有的身份验证处理器循环进行验证,返回一个经过验证的Authentication对象,想看源码的同学可以看ProviderManager这个类
      	return this.getAuthenticationManager().authenticate(userNamePassToken);
    }
}

在代码的最后一行我们调用this.getAuthenticationManager().authenticate(userNamePassToken),这行代码的意思是调用所有的身份验证处理器对当前的认证信息进行处理,认证成功则返回Authentication,我们通过实现AuthenticationProvider自定义一个处理器来单独处理我们的MyUserNamePassToken。

自定义身份验证处理器

public class MyUserNamePassProvider implements AuthenticationProvider {

	  
    private final UserDetailsService userDetailsService;

    public MyUserNamePassProvider(UserDetailsService MyUserDetailsService) {
        this.userDetailsService = MyUserDetailsService;
    }

		//认证方法,这里只是简单实现验证,具体实现根据具体业务而定
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        MyUserNamePassToken myUserNamePassToken = (MyUserNamePassToken) authentication;
        User user = null;
        try {
            user = (User) userDetailsService.loadUserByUsername(myUserNamePassToken.getName());
            if (user == null) {
                throw new Exception("用户不存在");
            }
            if (!myUserNamePassToken.getCredentials().equals(user.getPassword())) {
                throw new Exception("用户密码不正确");
            }
            String s = UUID.randomUUID().toString();
            LoginToken.tokens.put(s, user);
            myUserNamePassToken.setDetails(s);
            return myUserNamePassToken;
        } catch (Exception e) {
            throw new InternalAuthenticationServiceException(e.getMessage());
        }
    }
	  //这里定义支持处理那种类型的authentication
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.isAssignableFrom(MyUserNamePassToken.class);
    }
}

这个类只实现了两个方法,第一个方法authenticate用于进行权限验证,如果验证成功则返回一个

定义认证成功处理器

我们直接返回登录成功

public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().write(objectMapper.writeValueAsString(R.ok(authentication.getDetails(), "登录成功")));
    }
}

定义认证失败处理器

认证失败直接返回认证失败

public class LoginFailHandler implements AuthenticationFailureHandler {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().write(objectMapper.writeValueAsString(R.failed(exception.getMessage(), "登录失败")));
    }
}

配置登录过滤器到springSecurity

构建一个springSecurity配置类

@Configuration
public class LoginAuthenticationConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    @Autowired
    @Qualifier("MyUserDetailsService")
    UserDetailsService userDetailsService;

    @Bean
    public LoginSuccessHandler loginSuccessHandler() {
        return new LoginSuccessHandler();
    }

    @Bean
    public LoginFailHandler loginFailHandler() {
        return new LoginFailHandler();
    }

    @Override
    public void configure(HttpSecurity builder) throws Exception {
        builder.authenticationProvider(new MyUserNamePassProvider(userDetailsService));
        MyLoginFilter loginFilter = new MyLoginFilter();
        loginFilter.setAuthenticationManager(builder.getSharedObject(AuthenticationManager.class));
        loginFilter.setAuthenticationSuccessHandler(loginSuccessHandler());
        loginFilter.setAuthenticationFailureHandler(loginFailHandler());
        builder.addFilterAfter(loginFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

新建一个SpringSecurity总配置类,将上面的登录认证配置添加。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    final LoginAuthenticationConfig loginAuthenticationConfig;

    public SecurityConfig(LoginAuthenticationConfig loginAuthenticationConfig) {
        this.loginAuthenticationConfig = loginAuthenticationConfig;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //禁用 CSRF
                .csrf().disable()
                //去除session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .logout().disable()
                .apply(loginAuthenticationConfig).and()
                .authorizeRequests().anyRequest().authenticated();
    }
}

至此我们就完成一个登录验证的配置