SpringBoot Security工作原理

一、概述

Spring Security是解决安全访问控制的问题,说白了就是认证和授权两个问题。而至于像之前示例中页面控件的查看权限,是属于资源具体行为。SpringSecurity虽然也提供了类似的一些支持,但是这些不是Spring Security控制的重点。Spring Security功能的重点是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。而Spring Security对Web资源的保护是通过Filter来实现的,所以要从Filter入手,逐步深入Spring Security原理。
当初始化Spring Security时,org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration中会往Spring容器中注入一个名为SpringSecurityFilterChain的Servlet过滤器,类型为org.springframework.security.web.FilterChainProxy。它实现了javax.servlet.Filter,因此外部的请求都会经过这个类。
在这里插入图片描述

而FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时,这些Filter都已经注入到Spring容器中,他们是Spring Security的核心,各有各的职责。但是他们并不直接处理用户的认证和授权,而是把他们交给了认证管理器(AuthenticationManager)和决策管理器(AccessDecisionManager)进行处理。下面是FilterChainProxy相关类的UML图示:
在这里插入图片描述
Spring Security的功能实现主要就是由一系列过滤器链相互配合完成的。在启动过程中可以看到有info日志。
在这里插入图片描述
下面介绍过滤器链中主要的几个过滤器及其作用:

SecurityContextPersistenceFilter 这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截器),会在请求开始时从配置好的SecurityContextRepository 中获取 SecurityContext,然后把它设置给SecurityContextHolder。在请求完成后将SecurityContextHolder 持有的SecurityContext 再保存到配置好的 SecurityContextRepository,同时清除securityContextHolder 所持有的 SecurityContext;

UsernamePasswordAuthenticationFilter 用于处理来自表单提交的认证。该表单必须提供对应的用户名和密码,其内部还有登录成功或失败后进行处理的AuthenticationSuccessHandler 和 AuthenticationFailureHandler,这些都可以根据需求做相关改变;

FilterSecurityInterceptor 是用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问,前面已经详细介绍过了;

ExceptionTranslationFilter 能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常:AuthenticationException 和AccessDeniedException,其它的异常它会继续抛出。

二、认证流程

在这里插入图片描述
具体流程:
(1)、用户提交用户名、密码被SecurityFilterChain中的UsernamePasswordAuthenticationFilter 过滤器获取到,封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
(2)、 然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证
(3)、认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication 实例。
(4)、SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication,通过 SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个List 列表,存放多种认证方式,最终实际的认证工作是由AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终
AuthenticationProvider将UserDetails填充至Authentication。

几个核心的组件的调用流程:
在这里插入图片描述

1、AuthenticationProvider接口认证处理器

public interface AuthenticationProvider {
	//认证的方法
	Authentication authenticate(Authentication authentication) throws
	AuthenticationException;
	//支持哪种认证
	boolean supports(Class<?> var1);
}

这里对于AbstractUserDetailsAuthenticationProvider,他的support方法就表明他可以处理用户名密码这样的认证

public boolean supports(Class<?> authentication) {
	   return    UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}

2、Authentication认证信息

	继承自Principal类,代表一个抽象主体身份。继承了一个getName()方法来表示主体的名称
public interface Authentication extends Principal, Serializable {
	//获取权限信息列表
	 Collection<? extends GrantedAuthority> getAuthorities();
	 //获取凭证信息。用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
	 Object getCredentials();
	//细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地 址和sessionId的值。
	 Object getDetails();
	 //身份信息,大部分情况下返回的是UserDetails接口的实现类
	 Object getPrincipal();
	boolean isAuthenticated();
	void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

3、UserDetailsService接口: 获取用户信息

  获取用户信息的基础接口,只有一个根据用户名获取用户信息的方法。
public interface UserDetailsService {
	UserDetails loadUserByUsername(String var1) throws
	UsernameNotFoundException;
}

在DaoAuthenticationProvider的retrieveUser方法中,会获取spring容器中的UserDetailsService。如果我们没有自己注入UserDetailsService对象,那么在UserDetailsServiceAutoConfiguration类中,会在启动时默认注入一个带user用户
的UserDetailsService。我们可以通过注入自己的UserDetailsService来实现加载自己的数据。

4、UserDetails: 用户信息实体

代表了一个用户实体,包括用户、密码、权限列表,还有一些状态信息,包括账号过期、认证过期、是否启用

public interface UserDetails extends Serializable {

	Collection<? extends GrantedAuthority> getAuthorities();
	String getPassword();
	String getUsername();
	boolean isAccountNonExpired();
	boolean isAccountNonLocked();
	boolean isCredentialsNonExpired();
	boolean isEnabled();

}

5、PasswordEncoder 密码解析器


public interface PasswordEncoder {
    String encode(CharSequence rawPassword);

    boolean matches(CharSequence rawPassword, String encodedPassword);

    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

DaoAuthenticationProvider在additionalAuthenticationChecks方法中会获取Spring容器中的PasswordEncoder来对用户输入的密码进行比较。

6、BCryptPasswordEncoder

这是SpringSecurity中最常用的密码解析器。他使用BCrypt算法。他的特点是加密可以加盐sault,但是解密不需要盐。因为盐就在密文当中。这样可以通过每次添加不同的盐,而给同样的字符串加密出不同的密文。
密文形如:
$2a 10 10 10vTUDYhjnVb52iM3qQgi2Du31sq6PRea6xZbIsKIsmOVDnEuGb/.7K
其中:$是分割符,无意义;2a是bcrypt加密版本号;10是cost的值;而后的前22位是salt值;再然后的字符串就是密码的密文了

三、授权流程

1、整体流程

  授权是在用户认证通过后,对访问资源的权限进行检查的过程。Spring Security可以通过http.authorizeRequests()对web请求进行授权保护。Spring Security使用标准Filter建立了对web请求的拦截,最终实现对资源的授权访问

在这里插入图片描述
授权的流程:
1、拦截请求,已认证用户访问受保护的web资源将被SecurityFilterChain中(实现类为DefaultSecurityFilterChain)的 FilterSecurityInterceptor 的子类拦截。
2、获取资源访问策略,FilterSecurityInterceptor会从 SecurityMetadataSource的子类DefaultFilterInvocationSecurityMetadataSource 获取要访问当前资源所需要的权限Collection 。
SecurityMetadataSource其实就是读取访问策略的抽象,而读取的内容,其实就是我们配置的访问规则,读取访问策略如:

	http.csrf().disable()//关闭csrg跨域检查
	 //这里注意matchere是有顺序的。
	 .authorizeRequests()
	 .antMatchers("/mobile/**").hasAuthority("mobile")
	 .antMatchers("/salary/**").hasAuthority("salary")
	 .antMatchers("/common/**").permitAll() //common下的请求直接通过
	 .anyRequest().authenticated() //其他请求需要登录
	 .and() //并行条件
	
	.formLogin().defaultSuccessUrl("/main.html").failureUrl("/common/loginFailed");

3、最后,FilterSecurityInterceptor会调用 AccessDecisionManager 进行授权决策,若决策通过,则允许访问资源,否则将禁止访问。

关于AccessDecisionManager接口,最核心的就是其中的decide方法。这个方法就是用来鉴定当前用户是否有访问对应受保护资源的权限。

public interface AccessDecisionManager {
     //通过传递的参数来决定用户是否有访问对应受保护资源的权限
	 void decide(Authentication authentication, Object object,	
	Collection<ConfigAttribute> configAttributes) throws
	AccessDeniedException,
	InsufficientAuthenticationException;
}

这里着重说明一下decide的参数:authentication:要访问资源的访问者的身份,
object:要访问的受保护资源,web请求对应FilterInvocation。
configAttributes:是受保护资源的访问策略,通过SecurityMetadataSource获取。

2、决策流程

在AccessDecisionManager的实现类ConsensusBased中,是使用投票的方式来确定是否能够访问受保护的资源。
在这里插入图片描述
AccessDecisionManager中包含了一系列的AccessDecisionVoter讲会被用来对Authentication是否有权访问受保护对象进行投票,AccessDecisionManager根据投票结果,做出最终角色**(为什么要投票? 因为权限可以从多个方面来进行配置,有角色但是没有
资源怎么办?这就需要有不同的处理策略)**

AccessDecisionVoter是一个接口,定义了三个方法:

public interface AccessDecisionVoter<S> {
	 int ACCESS_GRANTED = 1;
	 int ACCESS_ABSTAIN = 0;
	 int ACCESS_DENIED = -1;
	boolean supports(ConfigAttribute attribute);
	boolean supports(Class<?> clazz);
	int vote(Authentication authentication, S object,
	Collection<ConfigAttribute> attributes);
}

vote()就是进行投票的方法。投票可以表示赞成、拒绝、弃权。
Spring Security内置了三个基于投票的实现类,分别是AffirmativeBased,ConsensusBasesd和UnanimaousBased

AffirmativeBased是Spring Security默认使用的投票方式,他的逻辑是只要有一
个投票通过,就表示通过。
1、只要有一个投票通过了,就表示通过。
2、如果全部弃权也表示通过。
3、如果没有人投赞成票,但是有人投反对票,则抛出AccessDeniedException.

ConsensusBased的逻辑是:多数赞成就通过
1、如果赞成票多于反对票则表示通过
2、如果反对票多于赞成票则抛出AccessDeniedException
3、如果赞成票与反对票相同且不等于0,并且属性allowIfEqualGrantedDeniedDecisions的值为true,则表示通过,否则抛出
AccessDeniedException。参数allowIfEqualGrantedDeniedDecisions的值默认是true。
4、如果所有的AccessDecisionVoter都弃权了,则将视参数allowIfAllAbstainDecisions的值而定,如果该值为true则表示通过,否则将抛出异
常AccessDeniedException。参数allowIfAllAbstainDecisions的值默认为false。

UnanimousBased相当于一票否决。
1、如果受保护对象配置的某一个ConfifigAttribute被任意的
AccessDecisionVoter反对了,则将抛出AccessDeniedException。
2、如果没有反对票,但是有赞成票,则表示通过。
3、如果全部弃权了,则将视参数allowIfAllAbstainDecisions的值而定,true则
通过,false则抛出AccessDeniedException。
Spring Security默认是使用的AffirmativeBased投票器,我们同样可以通过往
Spring容器里注入的方式来选择投票决定器

@Bean
public AccessDecisionManager accessDecisionManager() {

	List<AccessDecisionVoter<? extends Object>> decisionVoters
	= Arrays.asList(
	 new WebExpressionVoter(),
	 new RoleVoter(),
	 new AuthenticatedVoter(),
	 new MinuteBasedVoter());
	return new UnanimousBased(decisionVoters);
}

然后在configure中配置

@Override
protected void configure(HttpSecurity http) throws Exception {
 http
 ...
 .anyRequest()
 .authenticated()
 .accessDecisionManager(accessDecisionManager());

}

四、自定义认证

1、自定义登录页面及登录过程

//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
	http
	 .authorizeRequests()
	 .antMatchers("/r/**").authenticated()
	 .anyRequest().permitAll()
	 .and()
	 .formLogin()//允许表单登录
	 .loginPage("/login‐view")//自定义登录页面
	.loginProcessingUrl("/login")//自定义登录处理地址
	 .defaultSuccessUrl("/main.html")//指定登录成功后的跳转地址-页面重定向
	 // .successForwardUrl("/login‐success")//指定登录成功后的跳转URL - 后端跳转
	.permitAll();
}

2、将数据源改为从数据库获取数据

      修改UserDetails,从数据库加载用户信息。修改HttpSecurity,从数据库加载授权配置

3、配置方法与资源绑定关系

(1)、代码方式配置
Spring Security可以通过HttpSecurity配置URL授权信息,保护URL常用的方法有:

authenticated() 保护URL,需要用户登录
 permitAll() 指定URL无需保护,一般应用与静态资源文件
 hasRole(String role) 限制单个角色访问。角色其实相当于一个"ROLE_"+role的资源。
 hasAuthority(String authority) 限制单个权限访问
 hasAnyRole(String... roles)允许多个角色访问.
 hasAnyAuthority(String... authorities) 允许多个权限访问.
 access(String attribute) 该方法使用 SpEL表达式, 所以可以创建复杂的限制.
 hasIpAddress(String ipaddressExpression) 限制IP地址或子网

(2)、注解方式配置
Spring Security除了可以通过HttpSecurity配置授权信息外,还提供了注解方式对方法进行授权。注解方式需要先在启动加载的类中打开
@EnableGlobalMethodSecurity(securedEnabled=true) 注解,然后在需要权限管理的方法上使用@Secured(Resource)的方式配合权限

		@EnableGlobalMethodSecurity(securedEnabled=true) 开启@Secured 注解过滤权限
		打开后@Secured({"ROLE_manager","ROLE_admin"}) 表示方法需要有manager和admin
		两个角色才能访问
		另外@Secured注解有些关键字,比如IS_AUTHENTICATED_ANONYMOUSLY 表示可以匿名登
		录。
		@EnableGlobalMethodSecurity(jsr250Enabled=true) 开启@RolesAllowed 注解过滤权限
		@EnableGlobalMethodSecurity(prePostEnabled=true) 使用表达式时间方法级别的安全性,
		打开后可以使用一下几个注解。
		@PreAuthorize 在方法调用之前,基于表达式的计算结果来限制对方法的访问。例如
		@PreAuthorize("hasRole('normal') AND hasRole('admin')")
		@PostAuthorize 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常。此注释支持使用returnObject来表示返回的对象。例如 @PostAuthorize("returnObject!=null &&returnObject.username == authentication.name")
		@PostFilter 允许方法调用,但必须按照表达式来过滤方法的结果
		@PreFilter 允许方法调用,但必须在进入方法之前过滤输入值

五、会话控制

1、获取用户信息

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。spring security提供会话管
理,认证通过后将身份信息放入SecurityContextHolder上下文,SecurityContext与当前线程进行绑定,方便获取用户身份。可以通过为SecurityContextHolder.getContext().getAuthentication()获取当前登录用户信息。

@RestController
@RequestMapping("/common")
public class LoginController {

	 @GetMapping("/getLoginUserByPrincipal")
	 public String getLoginUserByPrincipal(Principal principal){
	
		return principal.getName();
	 }
	 @GetMapping(value = "/getLoginUserByAuthentication")
	public String currentUserName(Authentication authentication) {
	
			return authentication.getName();
	 }
	 @GetMapping(value = "/username")
	 public String currentUserNameSimple(HttpServletRequest request) {
		 Principal principal = request.getUserPrincipal();
		 return principal.getName();
	 }
	 @GetMapping("/getLoginUser")
	 public String getLoginUser(){
		   Principal principal =
		  (Principal)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
		
		   return principal.getName();
	
	}

}

2、会话控制

可以通过配置sessonCreationPolicy参数来了控制如何管理Session。
@Override
protected void configure(HttpSecurity http) throws Exception {

http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) }

这个属性有几个选项:
机制 描述
always 如果没有Session就创建一个
ifRequired如果需要就在登录时创建一个,默认策略

never SpringSecurity将不会创建Session。但是如果应用中其他地方创建了
Session,那么Spring Security就会使用。
stateless SpringSecurity将绝对不创建Session,也不使用。适合于一些REST API
的无状态场景。

3、会话超时

会话超时时间可以通过spring boot的配置直接审定。

server.servlet.session.timeout=3600s

session超时后,可以通过SpringSecurity的http配置跳转地址

http.sessionManagement()
 .expiredUrl("/login‐view?error=EXPIRED_SESSION")
.invalidSessionUrl("/login‐view?error=INVALID_SESSION");

expired是指session过期,invalidSession指传入的sessionId失效。

4、安全会话cookie

我们可以使用httpOnly和secure标签来保护我们的会话cookie:
httpOnly: 如果为true,那么浏览器脚本将无法访问cookie
secure: 如果为true,则cookie将仅通过HTTPS连接发送
spring boot 配置文件:

server.servlet.session.cookie.http‐only=true
server.servlet.session.cookie.secure=true

5、退出

Spring Security默认实现了logout退出,直接访问/logout就会跳转到登出页面,而ajax访问/logout就可以直接退出。
在WebSecurityConfifig的config(HttpSecurity http)中,也是可以配置退出的一些属性,例如自定义退出页面、定义推出后的跳转地址。

http
 .and()
 .logout() //提供系统退出支持,使用 WebSecurityConfigurerAdapter 会自动被应用
 .logoutUrl("/logout") //默认退出地址
 .logoutSuccessUrl("/login‐view?logout") //退出后的跳转地址

.addLogoutHandler(logoutHandler) //添加一个LogoutHandler,用于实现用户退出时
的清理工作.默认 SecurityContextLogoutHandler 会被添加为最后一个 LogoutHandler.invalidateHttpSession(true);
//指定是否在退出时让HttpSession失效,默认是true

在退出操作时,会做以下几件事情:
1、使HTTP Session失效。
2、清除SecurityContextHolder
3、跳转到定义的地址。

logoutHandler
一般来说, LogoutHandler 的实现类被用来执行必要的清理,因而他们不应该抛出
异常。
下面是Spring Security提供的一些实现:
PersistentTokenBasedRememberMeServices 基于持久化token的RememberMe功能的相关清理
TokenBasedRememberMeService 基于token的RememberMe功能的相关清

CookieClearingLogoutHandler 退出时Cookie的相关清理
CsrfLogoutHandler 负责在退出时移除csrfTokenSecurityContextLogoutHandler 退出时SecurityContext的相关清理
链式API提供了调用相应的 LogoutHandler 实现的快捷方式,比如
deleteCookies()

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值