spring-security学习记录

spring security - 学习记录

简介

首先,它是能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,基于 Servlet 过滤器、 IoC和AOP , 为 Web 请求和方法调用提供身份确认和授权处理,避免了代码耦合,减少了大量重复代码工作。

接口

  • AuthenticationManager :处理来自于框架其他部分的认证请求。
  • AuthenticationProvider :用于认证的,可以通过实现这个接口来定制我们自己的认证逻辑,它的实现类有很多,默认的是JaasAuthenticationProvider
  • AccessDecisionManager : 用于访问控制的,它决定用户是否可以访问某个资源,实现这个接口可以定制我们自己的授权逻辑
  • AccessDecisionVoter : 投票器,在授权的时通过投票的方式来决定用户是否可以访问,这里涉及到投票规则
  • UserDetailsService : 用于加载特定用户信息的,它只有一个接口通过指定的用户名去查询用户
  • UserDetails : 代表用户信息,即主体,相当于Shiro中的Subject,User是它的一个实现

授权表决方式与投票器

AccessDecisionManager 接口有一个套接的抽象实现类AbstractAccessDecisionManager,和三个实现类,即,三种表决方式,如下

  • AffirmativeBased (默认):根据javadoc,该类是AccessDecisionManager的简单具体实现,如果任何AccessDecisionVoter返回肯定响应,则授予访问权限。
    • 这个具体实现只是轮询所有已配置的AccessDecisionVoters,并在任何AccessDecisionVoter肯定投票时授予访问权限。只有在拒绝投票且没有肯定投票的情况下才能拒绝访问。如果每个AccessDecisionVoter都弃权,则决定将基于isAllowIfAllAbstainDecisions()属性(默认为
      false)。
      • 源码:
for (AccessDecisionVoter voter : getDecisionVoters()) {
	int result = voter.vote(authentication, object, configAttributes);
	if (logger.isDebugEnabled()) {
		logger.debug("Voter: " + voter + ", returned: " + result);
	}
	//检查投票结果
	switch (result) {
	//允许
	case AccessDecisionVoter.ACCESS_GRANTED:
		return;
	//被拒绝
	case AccessDecisionVoter.ACCESS_DENIED:
		deny++;

		break;

	default:
		break;
	}
}
//如果存在被拒绝。则抛出异常
if (deny > 0) {
	throw new AccessDeniedException(messages.getMessage(
			"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}

//全部弃权则调用方法,该方法源码如下
checkAllowIfAllAbstainDecisions();
protected final void checkAllowIfAllAbstainDecisions() {
//isAllowIfAllAbstainDecisions()实际返回 allowIfAllAbstainDecisions(默认false)
if (!this.isAllowIfAllAbstainDecisions()) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
}
  • ConsensusBased:根据javadoc,该类是使用基于共识的方法的AccessDecisionManager的简单具体实现。 “共识”在这里意味着多数人统治(无视弃权)而非一致同意(无视弃权)。如果您需要一致同意,请参阅UnanimousBased。
    • 这个具体实现只是简单地轮询所有已配置的AccessDecisionVoters,并在完成时确定对拒绝响应的一致性。如果有相同数量的授予和拒绝投票,则决定将基于isAllowIfEqualGrantedDeniedDecisions()属性(默认为true)。如果每个AccessDecisionVoter都弃权,则决定将基于isAllowIfAllAbstainDecisions()属性(默认为false)。
    • 源码:
for (AccessDecisionVoter voter : getDecisionVoters()) {
 int result = voter.vote(authentication, object, configAttributes);

 if (logger.isDebugEnabled()) {
  logger.debug("Voter: " + voter + ", returned: " + result);
 }
   //检查投票结果
 switch (result) {
 //同意计数
 case AccessDecisionVoter.ACCESS_GRANTED:
  grant++;
  break;
   //拒绝计数
 case AccessDecisionVoter.ACCESS_DENIED:
  deny++;
  break;
 default:
  break;
 }
}
//同意大于拒绝
if (grant > deny) {
	return;
}
//反之
if (deny > grant) {
	throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
//相同且不是弃权,根据allowIfEqualGrantedDeniedDecisions(默认true)
if ((grant == deny) && (grant != 0)) {
if (this.allowIfEqualGrantedDeniedDecisions) {
return;
}else {
		throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
	}
}
//弃权处理(默认false,抛出异常)
checkAllowIfAllAbstainDecisions();
  • UnanimousBased:根据javadoc,该类是AccessDecisionManager的简单具体实现,要求所有选民弃权或授予访问权限。
    • 此具体实现轮询每个ConfigAttribute的所有已配置的AccessDecisionVoters,并在仅收到授予(或弃权)投票时授予访问权限。其他投票实现通常将整个ConfigAttribute列表传递给AccessDecisionVoter。此实现的不同之处在于,每个AccessDecisionVoter一次只知道一个ConfigAttribute。如果每个AccessDecisionVoter都弃权,则决定将基于isAllowIfAllAbstainDecisions()属性(默认为false)。
    • 源码差不了多少

两种默认投票器

  • RoleVoter
    • 如果任何ConfigAttribute#getAttribute()以指示它是角色的前缀开头,则投票。默认前缀字符串是ROLE_,但可以覆盖任何值。它也可以设置为空,这意味着基本上任何属性都将被投票。如下面进一步描述的,空前缀的效果可能不是非常期望的。如果没有配置属性以角色前缀开始,则弃权。如果与角色前缀开头的ConfigAttribute存在完全匹配的GrantedAuthority,则表示授予访问权限。如果与角色前缀不同的ConfigAttribute没有完全匹配的GrantedAuthority,则表示拒绝访问。空角色前缀意味着投票人将为每个ConfigAttribute投票。当使用不同类别的ConfigAttributes时,这将不是最佳的,因为选民将投票表示不代表角色的属性。但是,当使用没有前缀的预先存在的角色名称时,此选项可能有一些用处,并且在读取它们时不存在为它们添加角色前缀的能力,例如在JdbcDaoImpl中提供的。所有比较和前缀都区分大小写。
  • AuthenticatedVoter
    • 如果存在IS_AUTHENTICATED_FULLY或IS_AUTHENTICATED_REMEMBERED或IS_AUTHENTICATED_ANONYMOUSLY的ConfigAttribute#getAttribute(),则投票。此列表按照最严格的检查顺序进行最严格的检查。将检查当前的身份验证以确定主体是否具有特定级别的身份验证。 “FULLY”认证选项意味着用户被完全认证(即AuthenticationTrustResolver#isAnonymous(Authentication)为false,AuthenticationTrustResolver#isRememberMe(Authentication)为false)。如果通过记住我或者经过完全认证,则“REMEMBERED”将授予访问权限。如果委托人通过记住我,匿名或通过完全身份验证进行身份验证,则“匿名”将授予访问权限。所有比较和前缀都区分大小写。

springboot 简单集成 security

  • 实现接口UserDetailsService ,实现loadUserByUsername方法,自定义查询用户信息逻辑,如下:
@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        SysUser sysUser = userService.getUserByName(s);
        if(sysUser == null){
            throw new UsernameNotFoundException("not found username");
        }
        List authorities = new ArrayList<>();
        for (SysRole role : sysUser.getRoleList()){
            for (SysPermission permission : role.getPermissionList()){
                authorities.add(new SimpleGrantedAuthority(permission.getCode()));
            }
        }
        return new User(sysUser.getAccount(),sysUser.getPassword(),authorities);
    }
}
  • 继承WebSecurityConfigurerAdapter 配置SecurityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)  //  启用方法级别的权限认证
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //  允许所有用户访问"/index.html"
        http.authorizeRequests()
                .antMatchers( "/index").permitAll()
                .anyRequest().authenticated()   // 其他地址的访问均需验证权限
                .and()
                .formLogin()
                .loginPage("/login")   //  登录页
                .failureUrl("/login-error").permitAll()
                .and()
                .logout()
                .logoutSuccessUrl("/index");
    }

	//配置加密方式
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

	//生成security提供的加密类bean
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
  • BCryptPasswordEncoder 加密据说比MD5安全,测试类里尝试了密码生成,发现相同的密码会有不同的密文,很神奇
    • 有两个接口方法实现:
      • encode(CharSequence rawPassword) (加密)
      • boolean matches(CharSequence rawPassword, String encodedPassword)(比较明文与密文)
    • 测试类
      @RunWith(SpringRunner.class)
      @SpringBootTest
      public class SecurityDemoApplicationTests {
          @Autowired
          private PasswordEncoder passwordEncoder;
      
          @Test
          public void contextLoads() throws Exception {
              String hide1 = passwordEncoder.encode("xxxx");
              String hide2 = passwordEncoder.encode("123456");
              String hide3 = passwordEncoder.encode("123456");
              boolean flag1 = passwordEncoder.matches("123456" , hide1);
              boolean flag2 = passwordEncoder.matches("123456" , hide2);
              System.out.println(hide1);
              System.out.println(hide2);
              System.out.println(hide3);
              System.out.println(flag1);
              System.out.println(flag2);
          }
      
      }
      
      -----------------------------------------
      $2a$10$L01l3Bhlnt4LvwbSW/en.e7Lru/jndY6yODwTyZq5EKXl82kC5P16
      $2a$10$yUj7RJrryKKdOpf.DCuf8OIqN7iipoLsw0SIHdXVYY/d5GI17cOoa
      $2a$10$DaZjZlW3OHPtoypiSTiNS.VLjjKbLUuGNvIygKNZLNxbNGjXQHBNW
      false
      true

过程(借鉴https://www.cnblogs.com/lexiaofei/p/7027336.html)

  • 用户使用用户名和密码进行登录。
  • Spring Security 将获取到的用户名和密码封装成一个实现了 Authentication 接口的 UsernamePasswordAuthenticationToken。
  • 将上述产生的 token 对象传递给 AuthenticationManager 进行登录认证。
  • AuthenticationManager 认证成功后将会返回一个封装了用户权限等信息的 Authentication 对象。
  • 通过调用 SecurityContextHolder.getContext().setAuthentication(…) 将 AuthenticationManager 返回的 Authentication 对象赋予给当前的 SecurityContext。
  • 在认证成功后,跳转到指定的成功页面,默认是应用的根路径。

此后,用户就可以继续操作去访问其它受保护的资源了,但是在访问的时候将会使用保存在 SecurityContext 中的 Authentication 对象进行相关的权限鉴定。

详细参考http://www.importnew.com/20612.html

你可能感兴趣的:(security)