springboot整合springsecurity 实现前后端分离项目中的用户认证登录及权限管理(源码分析)(2)

SpringSecurity 提供的 Csrf 防御机制

至此,我们已经实现了,在用户未登录认证的情况下对请求进行拦截,并且对登录请求”/login“ 放行。接下来要实现登录接口的自定义配置。在实际环境中一般会将用户信息放在数据库中。我们还是先用默认的用户名密码跑一遍登录接口,看默认情况下,springSecurity 是怎么处理登录请求的。
启动项目。使用PostMan 发送 Post 请求至”/login“
在这里插入图片描述
发现还是报”用户未登录“。之前明明已经实现了对”/login“请求的放行,唯一不同的是之前发送的是Get请求,而在Post请求下还是被拦截了。看控制台信息:
在这里插入图片描述
可以看到login请求只经过了4个过滤器就报错了,然后默认跳转到了”/error“,最后被自定义的 entryPoint 拦截。
Invalid CSRF token found for http://localhost:8080/login 大概意是说 CSRF token 找不到。

CSRF(跨站请求伪造),是一种常见的 Web 攻击方式。其主要是借助了浏览器默认发送 Cookie 的这一机制,在用户登录某一网站时,浏览器会自动记录登录cookie信息,然后危险网站会有一个超链接,超链接的地址指向了用户刚刚登陆过的正常网站,当用户被诱导点击该链接时,由于浏览器会自动带上cookie信息,能够正常访问,这样就达到了伪造请求的目的,给用户带来风险和损失。

简单了解csrf攻击的机制以后我们来看一下 csrfFilter 的实现。

@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
					throws ServletException, IOException {
		request.setAttribute(HttpServletResponse.class.getName(), response);

		CsrfToken csrfToken = this.tokenRepository.loadToken(request);
		final boolean missingToken = csrfToken == null;
		if (missingToken) {
			csrfToken = this.tokenRepository.generateToken(request);
			this.tokenRepository.saveToken(csrfToken, request, response);
		}
		request.setAttribute(CsrfToken.class.getName(), csrfToken);
		request.setAttribute(csrfToken.getParameterName(), csrfToken);

		if (!this.requireCsrfProtectionMatcher.matches(request)) {
			filterChain.doFilter(request, response);
			return;
		}

		String actualToken = request.getHeader(csrfToken.getHeaderName());
		if (actualToken == null) {
			actualToken = request.getParameter(csrfToken.getParameterName());
		}
		if (!csrfToken.getToken().equals(actualToken)) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Invalid CSRF token found for "
						+ UrlUtils.buildFullRequestUrl(request));
			}
			if (missingToken) {
				this.accessDeniedHandler.handle(request, response,
						new MissingCsrfTokenException(actualToken));
			}
			else {
				this.accessDeniedHandler.handle(request, response,
						new InvalidCsrfTokenException(csrfToken, actualToken));
			}
			return;
		}

		filterChain.doFilter(request, response);
	}

CsrfFilter 中的doFilter 是从 它的父类 OncePerRequestFilter 继承来的,OncePerRequestFilter 过滤器的作用是避免同一请求被同一过滤器过滤多次。在它的doFilter 方法中调用了 CsrfFilter 的 doFilterInternal 方法 其中requireCsrfProtectionMatcher 属性
默认值是 DefaultRequiresCsrfMatcher 类的实例

private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
		private final HashSet<String> allowedMethods = new HashSet<>(
				Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));

		/*
		 * (non-Javadoc)
		 *
		 * @see
		 * org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.
		 * servlet.http.HttpServletRequest)
		 */
		@Override
		public boolean matches(HttpServletRequest request) {
			return !this.allowedMethods.contains(request.getMethod());
		}
	}

这个类中的 allowedMethods 是一个set 集合,包含了 “GET”, “HEAD”, “TRACE”, “OPTIONS” 也就是说 如果请求类型是其中之一的话,matches 方法返回 false,不会对 csrftoken 进行校验,也就不会报之前的错,因此 CsrfFilter 只对POST 请求校验 csrfToken,之前的测试结果也证明了这一点。

接着看 doFilterInternal 方法,actualToken 变量的值是从 请求头,或者请求参数中获取,显然之前用 PostMan 发送Post 请求并没有带上这个token ,因此会才会报错。

实际上springSecurity 为了防止跨站请求伪造攻击,会要求post 请求携带一个额外的 csrf token 参数,而参数的值是在渲染登录页面时自动给到前端的,在正常提交请求时,页面需要将该 token 值一同携带,后台通过 csrfFilter 校验该 token 值是否合法,而伪造链接虽然能够获取 cookie 但并不知道如何传递 token 参数,因而避免了csrf 攻击。

对于前后端分离的项目来说,如果前端页面是小程序,app之类的移动应用,不涉及浏览器应用的话,是无需考虑 csrf 攻击的。这种情况下就需要考虑如何关闭 csrf token 验证功能。

从 doFilterInternal 方法的程序逻辑来看,只要是Post 请求 就必然会涉及到 token 验证这一环节,因此要想关闭 csrf 功能 只有将 csrfFilter 从过滤器链中剔除这一种方法。
打断点,看程序调用堆栈
在这里插入图片描述
从调用堆栈来看 最先调用的过滤器是 DelegatingFilterProxy 其中有一个delegate 属性 存放了其代理的 filter 对象,然后在 dofilter 方法中 调用了该代理对象的 dofilter 方法 ,此时这个代理过滤器类是 FilterChainProxy, 它的 dofiler 方法中调用了 doFilterInternal 方法 ,我们重点看这个方法:

private void doFilterInternal(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {

		FirewalledRequest fwRequest = firewall
				.getFirewalledRequest((HttpServletRequest) request);
		HttpServletResponse fwResponse = firewall
				.getFirewalledResponse((HttpServletResponse) response);

		List<Filter> filters = getFilters(fwRequest);

		if (filters == null || filters.size() == 0) {
			if (logger.isDebugEnabled()) {
				logger.debug(UrlUtils.buildRequestUrl(fwRequest)
						+ (filters == null ? " has no matching filters"
								: " has an empty filter list"));
			}

			fwRequest.reset();

			chain.doFilter(fwRequest, fwResponse);

			return;
		}

		VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
		vfc.doFilter(fwRequest, fwResponse);
	}

其中 getFilters 方法 返回了 一个Filter 的集合。在这里打断点debug可以发现,getFilters 方法 其实是获取到了过滤器链表中所有的过滤器类。默认情况下共有13个过滤器,其中也包含了 CsrfFilter。 然后把 filters 集合传入 VirtualFilterChain 的构造器中,设置其 additionalFilters 属性值。

private VirtualFilterChain(FirewalledRequest firewalledRequest,
				FilterChain chain, List<Filter> additionalFilters) {
			this.originalChain = chain;
			this.additionalFilters = additionalFilters;
			this.size = additionalFilters.size();
			this.firewalledRequest = firewalledRequest;
		}

最后调用 VirtualFilterChain 的 dofilter 方法:

@Override
		public void doFilter(ServletRequest request, ServletResponse response)
				throws IOException, ServletException {
			if (currentPosition == size) {
				if (logger.isDebugEnabled()) {
					logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
							+ " reached end of additional filter chain; proceeding with original chain");
				}

				// Deactivate path stripping as we exit the security filter chain
				this.firewalledRequest.reset();

				originalChain.doFilter(request, response);
			}
			else {
				currentPosition++;

				Filter nextFilter = additionalFilters.get(currentPosition - 1);

				if (logger.isDebugEnabled()) {
					logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
							+ " at position " + currentPosition + " of " + size
							+ " in additional filter chain; firing Filter: '"
							+ nextFilter.getClass().getSimpleName() + "'");
				}

				nextFilter.doFilter(request, response, this);
			}
		}

这个方法比较关键,currentPosition 的默认值为0,而 size 的值在构造方法中被设置为 additionalFilters 中filter 的个数,也就是 13 ,因此当 currentPosition 小于 13 时,会从 additionalFilters 中获取下一个 filter,调用其 dofilter 方法,并且把当前 VirtualFilterChain 对象 传入dofilter 方法中,由于 VirtualFilterChain 是 FilterChain 的实现类,因此,additionalFilters 中的每一个filter 的 dofilter 方法执行完成后都会返回到 VirtualFilterChain 的 dofilter 方法中,然后 currentPosition 的值加一,以 currentPosition 为索引 获取下一个filter,并执行其dofilter 方法。直至 13个filter 都执行完成。

到此为止,整个过滤器链的执行过程我们已经基本了解了,接着来看 getFilters 方法是如何获取到默认的13个过滤器的。

private List<Filter> getFilters(HttpServletRequest request) {
		for (SecurityFilterChain chain : filterChains) {
			if (chain.matches(request)) {
				return chain.getFilters();
			}
		}

		return null;
	}
public interface SecurityFilterChain {

	boolean matches(HttpServletRequest request);

	List<Filter> getFilters();
}

遍历了 filterChains 只要其中的 SecurityFilterChain实现类 和 request 请求匹配,那么返回一个filter 集合。
因此重点还是在FilterChainProxy 类的 filterChains 属性上。而 filterChains属性 是在构造方法中注入的:

public FilterChainProxy(List<SecurityFilterChain> filterChains) {
		this.filterChains = filterChains;
	}

还是老方法,在构造方法打断点,启动项目,看调用堆栈:

protected Filter performBuild() throws Exception {
		Assert.state(
				!securityFilterChainBuilders.isEmpty(),
				() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
						+ "Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
						+ "More advanced users can invoke "
						+ WebSecurity.class.getSimpleName()
						+ ".addSecurityFilterChainBuilder directly");
		int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
		List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
				chainSize);
		for (RequestMatcher ignoredRequest : ignoredRequests) {
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		}
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		if (httpFirewall != null) {
			filterChainProxy.setFirewall(httpFirewall);
		}
		filterChainProxy.afterPropertiesSet();

		Filter result = filterChainProxy;
		if (debugEnabled) {
			logger.warn("\n\n"
					+ "********************************************************************\n"
					+ "**********        Security debugging is enabled.       *************\n"
					+ "**********    This may include sensitive information.  *************\n"
					+ "**********      Do not use in a production system!     *************\n"
					+ "********************************************************************\n\n");
			result = new DebugFilter(filterChainProxy);
		}
		postBuildAction.run();
		return result;
	}

根据调用栈 找到 WebSecurity 类的 performBuild 方法,在该方法中 利用 securityFilterChains 构造了一个 FilterChainProxy 实例,securityFilterChainBuilders 是 SecurityBuilder 实现类的list集合,此时只有一个元素即 HttpSecurity ,重点看 HttpSecurity 的 build() 方法
在这里插入图片描述
httpSecurity 继承了 AbstractConfiguredSecurityBuilder,它的 build() 方法最终是调用了 AbstractConfiguredSecurityBuilder 的 doBuild() 方法:

@Override
	protected final O doBuild() throws Exception {
		synchronized (configurers) {
			buildState = BuildState.INITIALIZING;

			beforeInit();
			init();

			buildState = BuildState.CONFIGURING;

			beforeConfigure();
			configure();

			buildState = BuildState.BUILDING;

			O result = performBuild();

			buildState = BuildState.BUILT;

			return result;
		}
	}

重点看其中的 configure 方法:

@SuppressWarnings("unchecked")
	private void configure() throws Exception {
		Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

		for (SecurityConfigurer<O, B> configurer : configurers) {
			configurer.configure((B) this);
		}
	}
private Collection<SecurityConfigurer<O, B>> getConfigurers() {
		List<SecurityConfigurer<O, B>> result = new ArrayList<>();
		for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {
			result.addAll(configs);
		}
		return result;
	}

configure() 方法 调用 getConfigurers() 获取 SecurityConfigurer 的集合,然后遍历其中的 configurer 对象 依次调用其 configurer() 方法。在这边打断点可以看到 一共有 13个 configurer 对象 依次对应于13 个过滤器。
在这里插入图片描述
以 CsrfConfigurer 为例。它的 configurer() 方法主要就是 构造一个 CsrfFilter,然后对其进行一些处理,最终调用 httpSecurity 的 addFilter() 方法 将 CsrfFilter 加入到 httpSecurity 的 filters 属性中

@SuppressWarnings("unchecked")
	@Override
	public void configure(H http) {
		CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository);
		RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();
		if (requireCsrfProtectionMatcher != null) {
			filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
		}
		AccessDeniedHandler accessDeniedHandler = createAccessDeniedHandler(http);
		if (accessDeniedHandler != null) {
			filter.setAccessDeniedHandler(accessDeniedHandler);
		}
		LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
		if (logoutConfigurer != null) {
			logoutConfigurer
					.addLogoutHandler(new CsrfLogoutHandler(this.csrfTokenRepository));
		}
		SessionManagementConfigurer<H> sessionConfigurer = http
				.getConfigurer(SessionManagementConfigurer.class);
		if (sessionConfigurer != null) {
			sessionConfigurer.addSessionAuthenticationStrategy(
					getSessionAuthenticationStrategy());
		}
		filter = postProcess(filter);
		http.addFilter(filter);
	}
public HttpSecurity addFilter(Filter filter) {
		Class<? extends Filter> filterClass = filter.getClass();
		if (!comparator.isRegistered(filterClass)) {
			throw new IllegalArgumentException(
					"The Filter class "
							+ filterClass.getName()
							+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
		}
		this.filters.add(filter);
		return this;
	}

到这里不难发现,其实过滤器链中的每一个过滤器都由其对应的 SecurityConfigurer 对象去创建并放到 httpSecurity 的 filters 属性中。而 这些SecurityConfigurer 对象 是从 httpSecurity 的 configurers 属性中获取的,我们在 httpSecurity 的 构造方法上打断点,查看调用堆栈,发现是在 WebSecurityConfigAdapter 类 中的 getHttp() 方法中 构造了一个 httpSecurity 实例:

protected final HttpSecurity getHttp() throws Exception {
		if (http != null) {
			return http;
		}

		DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
				.postProcess(new DefaultAuthenticationEventPublisher());
		localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

		AuthenticationManager authenticationManager = authenticationManager();
		authenticationBuilder.parentAuthenticationManager(authenticationManager);
		authenticationBuilder.authenticationEventPublisher(eventPublisher);
		Map<Class<?>, Object> sharedObjects = createSharedObjects();

		http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
				sharedObjects);
		if (!disableDefaults) {
			// @formatter:off
			http
				.csrf().and()
				.addFilter(new WebAsyncManagerIntegrationFilter())
				.exceptionHandling().and()
				.headers().and()
				.sessionManagement().and()
				.securityContext().and()
				.requestCache().and()
				.anonymous().and()
				.servletApi().and()
				.apply(new DefaultLoginPageConfigurer<>()).and()
				.logout();
			// @formatter:on
			ClassLoader classLoader = this.context.getClassLoader();
			List<AbstractHttpConfigurer> defaultHttpConfigurers =
					SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

			for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
				http.apply(configurer);
			}
		}
		configure(http);
		return http;
	}

这个类我们就很熟悉了,就是我们自定义配置类的父类。在构造了 httpSecurity 实例后 调用了 csrf() 方法:

public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
		ApplicationContext context = getContext();
		return getOrApply(new CsrfConfigurer<>(context));
	}

又是 getOrApply() 方法 这个方法在上一篇的分析中出现过多次,其实就是在这里创建了 CsrfConfigurer 对象并将其放入 httpSecurity 的 configurers 属性中。其它的 configurer 对象 也是由类似的途径创建的。

接着回去看 httpSecurity 的 构造方法调用堆栈:
在这里插入图片描述
跟随调用堆栈往上找,找到 WebSecurityConfiguration 类的 springSecurityFilterChain 方法:

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

webSecurity.build() 方法最终调用了 AbstractConfiguredSecurityBuilder 的 doBuild() 方法 这个方法我们之前分析过,是一个通用的方法,通过一系列标准化的流程,来初始化和配置当前的 SecurityBuilder 实现类,WebSecurity 和HttpSecurity 都是 SecurityBuilder 的实现类,它们的build() 方法最终都会调用这个 doBuild() 方法。
在doBuild() 方法中 会调用 init() 方法:

private void init() throws Exception {
		Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

		for (SecurityConfigurer<O, B> configurer : configurers) {
			configurer.init((B) this);
		}

		for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
			configurer.init((B) this);
		}
	}
private Collection<SecurityConfigurer<O, B>> getConfigurers() {
		List<SecurityConfigurer<O, B>> result = new ArrayList<>();
		for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {
			result.addAll(configs);
		}
		return result;
	}

在init() 方法中首先获取 webSecurity 的 configurers 属性的values 集合 也就是 SecurityConfigurer 列表 而此时 values 集合中只有一个元素,就是我们自定义的 webSecurityConfig 类的实例:
在这里插入图片描述
然后遍历 SecurityConfigurer 列表依次调用其 init() 方法 webSecurityConfig 继承了 WebSecurityConfigurerAdapter,这里会调用 WebSecurityConfigurerAdapter 的init() 方法,然后在这个方法中,调用 getHttp() 方法 创建一个HttpSecurity 实例。 getHttp() 方法中创建完13个过滤器对应的 SecurityConfig 对象之后,会调用 configure() 方法配置httpSecurity,此时的 configure() 方法调用的是 webSecurityConfig 类中重写的 configure(HttpSecurity http) 方法,因此我们可以在这个方法中对 CsrfConfigurer 进行配置。

接着我们来分析一下 CsrfConfigurer 类 看有没有相关方法来配置 csrfFilter 失效。
CsrfConfigurer 继承了 AbstractHttpConfigurer 类:

public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
		extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {

	/**
	 * Disables the {@link AbstractHttpConfigurer} by removing it. After doing so a fresh
	 * version of the configuration can be applied.
	 *
	 * @return the {@link HttpSecurityBuilder} for additional customizations
	 */
	@SuppressWarnings("unchecked")
	public B disable() {
		getBuilder().removeConfigurer(getClass());
		return getBuilder();
	}

	@SuppressWarnings("unchecked")
	public T withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
		addObjectPostProcessor(objectPostProcessor);
		return (T) this;
	}
}

其中有一个 disabled() 方法,看方法的注释,意思大概是该方法可以移除当前 configurer 对象,移除了 CsrfConfigurer 对象,Csrffilter 对象就不会被创建,正好满足了我们的需求。

来分析一下它的源码,首先调用 getBuilder() 方法,这个方法在 AbstractHttpConfigurer 的父类 SecurityConfigurerAdapter 中:

protected final B getBuilder() {
		if (securityBuilder == null) {
			throw new IllegalStateException("securityBuilder cannot be null");
		}
		return securityBuilder;
	}

返回了 CsrfConfigurer 类的 securityBuilder 属性对象。这个属性是在 CsrfConfigurer 初始化的时候进行赋值的,根据上文的分析, CsrfConfigurer 是在 HttpSecurity 的 csrf() 方法中调用 getOrApply() 方法进行初始化的。

private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(
			C configurer) throws Exception {
		C existingConfig = (C) getConfigurer(configurer.getClass());
		if (existingConfig != null) {
			return existingConfig;
		}
		return apply(configurer);
	}
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
			throws Exception {
		configurer.addObjectPostProcessor(objectPostProcessor);
		configurer.setBuilder((B) this);
		add(configurer);
		return configurer;
	}

看到在 apply() 方法中 调用 setBuilder() 方法设置了 CsrfConfigurer 类的 securityBuilder 属性值为当前 this 对象,也就是 HttpSecurity 对象。
因此 getBuilder() 方法返回值就是 HttpSecurity,然后又调用了 removeConfigurer(getClass()) 方法,参数是当前 class 对象 也就是 CsrfConfigurer,最终调用的是 HttpSecurity 的父类 AbstractConfiguredSecurityBuilder 类中的 removeConfigurer(Class clazz)方法, 看一下它的源码:

public <C extends SecurityConfigurer<O, B>> C removeConfigurer(Class<C> clazz) {
		List<SecurityConfigurer<O, B>> configs = this.configurers.remove(clazz);
		if (configs == null) {
			return null;
		}
		if (configs.size() != 1) {
			throw new IllegalStateException("Only one configurer expected for type "
					+ clazz + ", but got " + configs);
		}
		return (C) configs.get(0);
	}

很明显,这个方法就是把 HttpSecurity 的 configurers 属性中的 CsrfConfigurer 对象 移除掉。
通过以上分析,我们找到了让 csrf 校验功能失效的方法,即在 webSecurityConfig 的 configure(HttpSecurity http) 方法中增加如下配置:

 @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http.csrf().disable();
    }

重启项目,此时再用 PostMan 发送 Post 请求至 “/login” 就不会再提示用户未登录了。

通过一步步分析源码,我们不仅找到了如何关闭 SpringSecurity 的 Csrf 校验功能,也对框架内部过滤器链的初始化过程,以及各个过滤器的配置方法都有了一定的了解,这些知识也有助于我们之后对框架做出更多个性化的改动。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Spring Boot整合Spring Security可以实现登录认证和数据权限管理。下面是简单的步骤: 1. 添加依赖 在pom.xml文件添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` 2. 配置Spring Security 在主配置类添加@EnableWebSecurity注解,开启Spring Security,并且创建一个继承自WebSecurityConfigurerAdapter的配置类,用于配置Spring Security。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // 配置登录页面和登录请求的路径 http.formLogin() .loginPage("/login") .loginProcessingUrl("/login") .defaultSuccessURL("/home") .and() // 配置退出登录的路径和跳转页面 .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login") .and() // 配置拦截规则 .authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and() // 关闭CSRF保护 .csrf().disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 配置用户信息和密码加密方式 auth.inMemoryAuthentication() .passwordEncoder(new BCryptPasswordEncoder()) .withUser("admin") .password(new BCryptPasswordEncoder().encode("admin")) .roles("ADMIN"); } } ``` 3. 配置数据权限管理 如果需要实现数据权限管理,可以在配置类添加一个实现了FilterInvocationSecurityMetadataSource接口的类,用于获取当前请求所需的权限信息,并且在配置类添加一个实现了AccessDecisionManager接口的类,用于判断当前用户是否有访问该资权限。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource; @Autowired private CustomAccessDecisionManager customAccessDecisionManager; @Override protected void configure(HttpSecurity http) throws Exception { // 配置拦截规则 http.authorizeRequests() .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O object) { object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource); object.setAccessDecisionManager(customAccessDecisionManager); return object; } }) .and() // 关闭CSRF保护 .csrf().disable(); } } ``` 参考资料: 1. Spring Security官方文档:https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/ 2. Spring Boot整合Spring Security实现登录认证和数据权限管理:https://www.jianshu.com/p/04d848c9cb8d

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值