首先,它是能够为基于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”将授予访问权限。如果委托人通过记住我,匿名或通过完全身份验证进行身份验证,则“匿名”将授予访问权限。所有比较和前缀都区分大小写。
@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);
}
}
@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();
}
}
@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
此后,用户就可以继续操作去访问其它受保护的资源了,但是在访问的时候将会使用保存在 SecurityContext 中的 Authentication 对象进行相关的权限鉴定。