SpringSecurity学习笔记(二)加载的Filter,默认配置

参考视频,编程不良人

SpringSecurity中的过滤器

在这里插入图片描述
以上图片来自官网,Security过滤器通过SecurityFilterChain API插入FilterChainProxy。过滤器的顺序很重要。通常不需要知道Spring Security过滤器的顺序。然而,有时了解顺序是有益的。以下是Spring Security Filter顺序的详细列表:

ForceEagerSessionCreationFilter

ChannelProcessingFilter//过滤请求协议http、https。默认不加载

WebAsyncManagerIntegrationFilter//将WebAsyncManager与SpringSecurity上下文进行集成,默认加载。

SecurityContextPersistenceFilter//在处理请求前将安全信息加载到SpringContextHolder中。默认加载

HeaderWriterFilter//处理头信息加入响应中,默认加载

CorsFilter//处理跨域问题,默认不加载

CsrfFilter//处理CSRF攻击,默认加载

LogoutFilter//处理注销登录,默认加载

OAuth2AuthorizationRequestRedirectFilter//处理OAuth2认证重定向,NO

Saml2WebSsoAuthenticationRequestFilter//处理SAML认证,NO

X509AuthenticationFilter//处理X509认证,NO

AbstractPreAuthenticatedProcessingFilter//处理预认证问题,NO

CasAuthenticationFilter//处理CAS单点登录

OAuth2LoginAuthenticationFilter//处理OAuth2认证

Saml2WebSsoAuthenticationFilter//处理SAML认证

UsernamePasswordAuthenticationFilter//处理表单登录

OpenIDAuthenticationFilter//处理OpenId认证

DefaultLoginPageGeneratingFilter//配置默认登录页面

DefaultLogoutPageGeneratingFilter//配置默认注销页面

ConcurrentSessionFilter//处理session有效期

DigestAuthenticationFilter//处理HTTP摘要认证

BearerTokenAuthenticationFilter//处理OAuth2认证的AccessToken

BasicAuthenticationFilter//处理HTTP basic登录

RequestCacheAwareFilter//处理请求缓存

SecurityContextHolderAwareRequestFilter//包装原始请求

JaasApiIntegrationFilter//处理JAAS认证

RememberMeAuthenticationFilter//处理RememberMe登录

AnonymousAuthenticationFilter//配置匿名认证

OAuth2AuthorizationCodeGrantFilter//处理OAuth2认证中的授权码

SessionManagementFilter//处理session并发问题

ExceptionTranslationFilter//处理认证授权中的异常

FilterSecurityInterceptor//处理授权相关

SwitchUserFilter//处理账户切换

默认情况下SpringBootSpringSecurity进行自动化配置时,会创建一个名字为SpringSecurityFilterChain的过滤器,并且注入到Spring容器中,这个过滤器将负责所有的安全管理,包括用户的授权、认证、重定向等等,这里可以参考WebSecurityConfiguraction的源码。
在这里插入图片描述
里面有这么一段代码

 @Bean(
        name = {"springSecurityFilterChain"}
    )
    public Filter springSecurityFilterChain() throws Exception {
        boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
        if (!hasConfigurers) {
            WebSecurityConfigurerAdapter adapter = (WebSecurityConfigurerAdapter)this.objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {
            });
            this.webSecurity.apply(adapter);
        }

        return (Filter)this.webSecurity.build();
    }

这里就会加载默认的filter
(这里如果没有源码就使用maven命令: mvn dependency:sources下载源码!)

默认对所有的请求做验证

我们在WebSecurityConfigAdapter类中可以发现下面这个方法,这也就是为什么我们加入了启动jar之后原来的请求就都会被过滤了。

protected void configure(HttpSecurity http) throws Exception {
		logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");

		http
			.authorizeRequests()
				.anyRequest().authenticated()
				.and()
			.formLogin().and()
			.httpBasic();
	}

在这里插入图片描述

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)//类路径下存在这个类的时候才会生效
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)//只要我们自定义了bean就可以覆盖上面默认的配置,
@ConditionalOnWebApplication(type = Type.SERVLET)//在servlet容器下才会生效
public class SpringBootWebSecurityConfiguration {

	@Configuration(proxyBeanMethods = false)
	@Order(SecurityProperties.BASIC_AUTH_ORDER)
	static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {

	}

}

默认的登录界面

在源码中存在这样一个类DefaultLoginPageGeneratingFilter,这个类就生成了登录界面
在这里插入图片描述

默认登录用户的生成

protected void configure(HttpSecurity http) throws Exception {
		logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");

		http
			.authorizeRequests()
				.anyRequest().authenticated()
				.and()
			.formLogin().and()
			.httpBasic();
	}

我们点进去formLogin()

public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
		return getOrApply(new FormLoginConfigurer<>());
	}

看一下这个类FormLoginConfigurer<>()

public FormLoginConfigurer() {
		super(new UsernamePasswordAuthenticationFilter(), null);
		usernameParameter("username");
		passwordParameter("password");
	}

里面有这么一个类UsernamePasswordAuthenticationFilter,这个类里面有这么一个方法attemptAuthentication,这里就对登录进行了一些验证。

public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
		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();

		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);

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

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

点进最后的authenticate方法,里面有一个循环判断的逻辑

for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}

			if (debug) {
				logger.debug("Authentication attempt using "
						+ provider.getClass().getName());
			}

			try {
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException e) {
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			} catch (AuthenticationException e) {
				lastException = e;
			}
		}

我们进入这一行代码result = provider.authenticate(authentication);这个子类AbstractUserDetailsAuthenticationProvider里面的实现

public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> messages.getMessage(
						"AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));

		// Determine username
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
				: authentication.getName();

		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);

		if (user == null) {
			cacheWasUsed = false;

			try {
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
//						这块代码是一个真正实现验证逻辑的代码
			}
			catch (UsernameNotFoundException notFound) {
				logger.debug("User '" + username + "' not found");

				if (hideUserNotFoundExceptions) {
					throw new BadCredentialsException(messages.getMessage(
							"AbstractUserDetailsAuthenticationProvider.badCredentials",
							"Bad credentials"));
				}
				else {
					throw notFound;
				}
			}

			Assert.notNull(user,
					"retrieveUser returned null - a violation of the interface contract");
		}

		try {
			preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException exception) {
			if (cacheWasUsed) {
				// There was a problem, so try again after checking
				// we're using latest data (i.e. not from the cache)
				cacheWasUsed = false;
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
				preAuthenticationChecks.check(user);
				additionalAuthenticationChecks(user,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			else {
				throw exception;
			}
		}

		postAuthenticationChecks.check(user);

		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}

		Object principalToReturn = user;

		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}

		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

我们点进代码retrieveUser的实现

protected final UserDetails retrieveUser(String username,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
		//下面就是验证的实现
			//这里根据username去查询user用户
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		}
		catch (UsernameNotFoundException ex) {
			mitigateAgainstTimingAttack(authentication);
			throw ex;
		}
		catch (InternalAuthenticationServiceException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}

我们仔细阅读这个方法的实现this.getUserDetailsService().loadUserByUsername(username);

public interface UserDetailsService {
	// ~ Methods
	// ========================================================================================================

	/**
	 * Locates the user based on the username. In the actual implementation, the search
	 * may possibly be case sensitive, or case insensitive depending on how the
	 * implementation instance is configured. In this case, the <code>UserDetails</code>
	 * object that comes back may have a username that is of a different case than what
	 * was actually requested..
	 *
	 * @param username the username identifying the user whose data is required.
	 *
	 * @return a fully populated user record (never <code>null</code>)
	 *
	 * @throws UsernameNotFoundException if the user could not be found or the user has no
	 * GrantedAuthority
	 */
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

这个UserDetailsService 这个接口有四个实现类
在这里插入图片描述
这里是基于内存实现的,默认使用实现类是InMemoryUserDetailsManager,我们可以看到这里面有一个HashMap,这里存储了对应的用户名和密码。
在这里插入图片描述

public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException {
			//这里直接使用users获取,就是获取hashmap里面的key对应的value。
		UserDetails user = users.get(username.toLowerCase());

		if (user == null) {
			throw new UsernameNotFoundException(username);
		}

		return new User(user.getUsername(), user.getPassword(), user.isEnabled(),
				user.isAccountNonExpired(), user.isCredentialsNonExpired(),
				user.isAccountNonLocked(), user.getAuthorities());
	}

关于这里为什么默认是这个类InMemoryUserDetailsManager,需要看以下这个自动配置类UserDetailsServiceAutoConfiguration
在这里插入图片描述
如果ConditionalOnMissingBean里面的类都没有就会使用基于内存的InMemoryUserDetailsManager类。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(
		value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class },
		type = { "org.springframework.security.oauth2.jwt.JwtDecoder",
				"org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector" })
public class UserDetailsServiceAutoConfiguration {

	private static final String NOOP_PASSWORD_PREFIX = "{noop}";

	private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");

	private static final Log logger = LogFactory.getLog(UserDetailsServiceAutoConfiguration.class);

	@Bean
	@ConditionalOnMissingBean(
			type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
	@Lazy
	public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
			ObjectProvider<PasswordEncoder> passwordEncoder) {
		SecurityProperties.User user = properties.getUser();
		List<String> roles = user.getRoles();
		return new InMemoryUserDetailsManager(
				User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
						.roles(StringUtils.toStringArray(roles)).build());
	}

	private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
		String password = user.getPassword();
		if (user.isPasswordGenerated()) {
			logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
		}
		if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
			return password;
		}
		return NOOP_PASSWORD_PREFIX + password;
	}

}

这段代码里面有这个SecurityProperties.User user = properties.getUser();

我们看一下这个类,这里面可以配置用户名和密码,如果没有配置就使用默认的。我们还可以在配置文件里面进行配置然后读取

spring:
  security:
    user:
      name: root
      password: root
      roles:
        - 
@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {

	/**
	 * Order applied to the WebSecurityConfigurerAdapter that is used to configure basic
	 * authentication for application endpoints. If you want to add your own
	 * authentication for all or some of those endpoints the best thing to do is to add
	 * your own WebSecurityConfigurerAdapter with lower order.
	 */
	public static final int BASIC_AUTH_ORDER = Ordered.LOWEST_PRECEDENCE - 5;

	/**
	 * Order applied to the WebSecurityConfigurer that ignores standard static resource
	 * paths.
	 */
	public static final int IGNORED_ORDER = Ordered.HIGHEST_PRECEDENCE;

	/**
	 * Default order of Spring Security's Filter in the servlet container (i.e. amongst
	 * other filters registered with the container). There is no connection between this
	 * and the {@code @Order} on a WebSecurityConfigurer.
	 */
	public static final int DEFAULT_FILTER_ORDER = OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100;

	private final Filter filter = new Filter();

	private User user = new User();

	public User getUser() {
		return this.user;
	}

这个类里面有一个静态内部类User

public static class User {

		/**
		 * Default user name.
		 */
		private String name = "user";

		/**
		 * Password for the default user name.
		 */
		private String password = UUID.randomUUID().toString();

		/**
		 * Granted roles for the default user name.
		 */
		private List<String> roles = new ArrayList<>();

		private boolean passwordGenerated = true;

		public String getName() {
			return this.name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getPassword() {
			return this.password;
		}

		public void setPassword(String password) {
			if (!StringUtils.hasLength(password)) {
				return;
			}
			this.passwordGenerated = false;
			this.password = password;
		}

		public List<String> getRoles() {
			return this.roles;
		}

		public void setRoles(List<String> roles) {
			this.roles = new ArrayList<>(roles);
		}

		public boolean isPasswordGenerated() {
			return this.passwordGenerated;
		}

	}

从上面的分析可以知道,如果我们集成了数据库可以直接实现UserDetailsService 接口然后重写验证逻辑即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北海冥鱼未眠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值