spring boot security(一)用户认证

系列教程观看地址我真的在B站学习

一、IOC容器结构说明

Spring容器与SpringMvc容器之间的关系
在这里插入图片描述

在Spring的具体实现上,子容器和父容器都是通过ServletContext的setAttribute方法放到ServletContext中的。但是,ContextLoaderListener会先于DispatcherServlet创建ApplicationContext,DispatcherServlet在创建时会先找到由ContextLoaderListener所创建的ApplicationContext,再将后者的ApplicationContext作为参数传给DispatcherServlet的ApplicationContext的setParent()方法。也就是说,子容器的创建依赖于父容器的创建,父容器先于子容器创建。在Spring源代码中,你可以在FrameServlet.Java中找到如下代码:
wac.setParent(parent);
其中,wac即为由DisptcherServlet创建的ApplicationContext,而parent则为有ContextLoaderListener创建的ApplicationContext。
此后,框架又会调用ServletContext的setAttribute()方法将wac加入到ServletContext中。

由上可知,为了提供更加安全的配置,我们需要将spring-security.xml 放置在applicationContext.xml中加载。

	<import resource="classpath:spring-security.xml" /> 

这样的话,就无法直接访问sercrity的内容,但是当我们开启了方法控制权限的时候,
参考文章地址:基于方法的权限控制

 <!--     开启权限控制的注解支持
        secured-annotations="enabled"     springSecurity内部的权限控制注解开关
        pre-post-annotations="enabled"     spring指定的权限控制的注解开关
        jsr250-annotations="enabled"      开启java250注解支持  
  --> 
  <security:global-method-security
   secured-annotations="enabled" 
   pre-post-annotations="enabled" 
   jsr250-annotations="enabled" /> 

相应的注释只能放在applicationContext层级别(即service层等,不能放在controller),但是我们 可以将上面上面的那段xml放在spring-mvc的配置文件中,就可以在controller层中使用注解了。

二、过滤器
2.1 常见过滤器
  1. org.springframework.security.web.context.SecurityContextPersistenceFilter

SecurityContextPersistenceFilter主要是使用SecurityContextRepository在session中保存或更新一个
SecurityContext,并将SecurityContext给以后的过滤器使用,来为后续filter建立所需的上下文。
SecurityContext中存储了当前用户的认证以及权限信息。

  1. org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter

此过滤器用于集成SecurityContext到Spring异步执行机制中的WebAsyncManager

  1. org.springframework.security.web.header.HeaderWriterFilter

向请求的Header中添加相应的信息,可在http标签内部使用security:headers来控制

  1. org.springframework.security.web.csrf.CsrfFilter

csrf又称跨域请求伪造,SpringSecurity会对所有post请求验证是否包含系统生成的csrf的token信息,
如果不包含,则报错。起到防止csrf攻击的效果。开启之后,登录和退出都是Post请求,需要添加Token

  1. org.springframework.security.web.authentication.logout.LogoutFilter

匹配URL为/logout的请求,实现用户退出,清除认证信息。

  1. org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter

认证操作全靠这个过滤器,默认匹配URL为/login且必须为POST请求。

  1. org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter

如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面。

  1. org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter

由此过滤器可以生产一个默认的退出登录页面

  1. org.springframework.security.web.authentication.www.BasicAuthenticationFilter

此过滤器会自动解析HTTP请求中头部名字为Authentication,且以Basic开头的头信息。

  1. org.springframework.security.web.savedrequest.RequestCacheAwareFilter

通过HttpSessionRequestCache内部维护了一个RequestCache,用于缓存HttpServletRequest

  1. org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter

针对ServletRequest进行了一次包装,使得request具有更加丰富的API

  1. org.springframework.security.web.authentication.AnonymousAuthenticationFilter

当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder中。
spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。

  1. org.springframework.security.web.session.SessionManagementFilter

SecurityContextRepository限制同一用户开启多个会话的数量

  1. org.springframework.security.web.access.ExceptionTranslationFilter

异常转换过滤器位于整个springSecurityFilterChain的后方,用来转换整个链路中出现的异常

  1. org.springframework.security.web.access.intercept.FilterSecurityInterceptor

获取所配置资源访问的授权信息,根据SecurityContextHolder中存储的用户信息来决定其是否有权
限。URL级别方法控制
读取数据库中url进行拦截

2.2 过滤器如何加载

springMVC过滤器超类-GenericFilterBean

我们在web.xml中配置了一个名称为springSecurityFilterChain的过滤器DelegatingFilterProxy,通过分析doFilter方法得知,通过springSecurityFilterChain这个名称,得到了一个FilterChainProxy过滤器,最终在第三步执行了这个过滤器。该类中有 List<SecurityFilterChain> filterChains 成员变量,通过执行doFilterInternal方法可以获得的所有的过滤器。
在这里插入图片描述

三、认证过程解析
3.1 配置文件示例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xmlns:security="http://www.springframework.org/schema/security"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
			    http://www.springframework.org/schema/beans/spring-beans.xsd
			    http://www.springframework.org/schema/context
			    http://www.springframework.org/schema/context/spring-context.xsd
			    http://www.springframework.org/schema/aop
			    http://www.springframework.org/schema/aop/spring-aop.xsd
			    http://www.springframework.org/schema/tx
			    http://www.springframework.org/schema/tx/spring-tx.xsd
			    http://www.springframework.org/schema/mvc
			    http://www.springframework.org/schema/mvc/spring-mvc.xsd
                http://www.springframework.org/schema/security
			    http://www.springframework.org/schema/security/spring-security.xsd">

    <!--释放静态资源-->
    <security:http pattern="/css/**" security="none"/>
    <security:http pattern="/img/**" security="none"/>
    <security:http pattern="/plugins/**" security="none"/>
    <security:http pattern="/failer.jsp" security="none"/>
    <!--配置springSecurity-->
    <!--
    auto-config="true"  表示自动加载springsecurity的配置文件
    use-expressions="true" 表示使用spring的el表达式来配置springsecurity
    -->
    <security:http auto-config="true" use-expressions="true">
        <!--让认证页面可以匿名访问-->
        <security:intercept-url pattern="/login.jsp" access="permitAll()"/>
        <!--拦截资源-->
        <!--
        pattern="/**" 表示拦截所有资源
        access="hasAnyRole('ROLE_USER')" 表示只有ROLE_USER角色才能访问资源
        -->
        <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')"/>
        <!--配置认证信息-->
        <security:form-login login-page="/login.jsp"
                             login-processing-url="/login"
                             default-target-url="/index.jsp"
                             authentication-failure-url="/failer.jsp"/>
        <!--配置退出登录信息-->
        <security:logout logout-url="/logout"
                         logout-success-url="/login.jsp"/>
        <!--去掉csrf拦截的过滤器-->
        <!--<security:csrf disabled="true"/>-->
    </security:http>

    <!--把加密对象放入的IOC容器中-->
    <bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

    <!--设置Spring Security认证用户信息的来源-->
    <!--
    springsecurity默认的认证必须是加密的,加上{noop}表示不加密认证。
    -->
    <security:authentication-manager>
        <security:authentication-provider user-service-ref="userServiceImpl">
            <security:password-encoder ref="passwordEncoder"/>
        </security:authentication-provider>
    </security:authentication-manager>

<!--     开启权限控制的注解支持
        secured-annotations="enabled"     springSecurity内部的权限控制注解开关
        pre-post-annotations="enabled"     spring指定的权限控制的注解开关
        jsr250-annotations="enabled"      开启java250注解支持  
  --> 
  <security:global-method-security
   secured-annotations="enabled" 
   pre-post-annotations="enabled" 
   jsr250-annotations="enabled" /> 
</beans>
3.2 认证流程解析

发送一个/loginpost请求,被 UsernamePasswordAuthenticationFilter,该类 继承了AbstractAuthenticationProcessingFilter

public class UsernamePasswordAuthenticationFilter extends
	AbstractAuthenticationProcessingFilter {

// 	AbstractAuthenticationProcessingFilter doFilter中执行了该方法,后续会执行一些通过认证或者认证失败后的操作(记住我,保存session信息)	
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 对象,此时的对象没有权限信息,username和password都是前端传递的值
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		// 通过 authenticate()方法去认证,实现类为 ProviderManager
		return this.getAuthenticationManager().authenticate(authRequest);
	}
}
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
		InitializingBean {
// 执行认证操作的父类接口 AuthenticationProvider 实现类通过 supports方法来判断使用哪个类来认证
private List<AuthenticationProvider> providers = Collections.emptyList();
public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
	Class<? extends Authentication> toTest = authentication.getClass();
//  通过for循环去执行判断各个实现类的supports方法,因为我们前面封装的是
//UsernamePasswordAuthenticationToken 对象,查看其中一个实现类的
//AbstractUserDetailsAuthenticationProvider的supports方法可以看出,返回为ture
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 e) {
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			}
			catch (InternalAuthenticationServiceException e) {
				prepareException(e, authentication);
				throw e;
			}
			catch (AuthenticationException e) {
				lastException = e;
			}
		}
	}
}
public abstract class AbstractUserDetailsAuthenticationProvider implements
		AuthenticationProvider, InitializingBean, MessageSourceAware {

	// 该方法,传递来UsernamePasswordAuthenticationToken类型的对象,返回为Ture
	public boolean supports(Class<?> authentication) {
		return (UsernamePasswordAuthenticationToken.class
				.isAssignableFrom(authentication));
	}

	// 真正执行认证的方法
	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		···
		// 该方法会调用 userDetailsService.loadUserByUsername(username)方法,即我们自己实现的
		// 调用数据库查询,并且有权限信息的 UserDetails 对象
		user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
		// 验证账号是否可用,对应接口 UserDetails类中的四个可用属性判断
		preAuthenticationChecks.check(user);
		// 验证账号,密码是否正确
		additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
		···		
		// 如果验证正确,返回 一个 UsernamePasswordAuthenticationToken对象,此时的对象会有权限信息
		return createSuccessAuthentication(principalToReturn, authentication, user)
	}
}
  1. 此时,如果认证成功,并且返回一个带权限信息的 UsernamePasswordAuthenticationToken对象到了AbstractAuthenticationProcessingFilter拦截器的doFilter方法中,执行successfulAuthentication(request, response, chain, authResult)
  2. successfulAuthentication 将认证信息存储到了SecurityContext中。并调用了loginSuccess方法,这就是
    常见的“记住我”功能。
  3. 如果认证失败,抛出的议程也会被doFilter方法捕捉,执行unsuccessfulAuthentication(request, response, failed);
四、杂七杂八
  1. 关闭basic认证
  2. springSecurity加密认证,默认使用SCryptPasswordEncoder
  3. X-Frame-Options头:主要为了防止被别的站点劫持,做iframe引入
  4. . CSRF:跨站请求伪造&CORS:跨站资源共享
  5. CORS配置:
    https://blog.csdn.net/niugang0920/article/details/79817763
    https://www.cnblogs.com/famary/p/10336223.html
  6. JWT跨域认证
  7. 系列讲解
  8. addFilterAt() & addFilterBefore()
  9. Security限制多个用户登陆
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值