Spring Security权限框架理论与实战(二)
Spring Security提供了一个实现了可以缓存UserDetails的UserDetailsService实现类,CachingUserDetailsService
该类的构造接收一个用于真正加载UserDetails的UserDetailsService实现类
当需要加载UserDetails时,其首先会从缓存中获取,如果缓存中没有对应的UserDetails存在,则使用持有的UserDetailsService实现类进行加载,然后将加载后的结果存放在缓存中。
UserDetails与缓存的交互是通过UserCache接口来实现的
CachingUserDetailsService默认拥有UserCache的一个空实现引用NullUserCache
当缓存中不存在对应的UserDetails时将使用引用的UserDetailsService类型的delegate进行加载
加载后再把它存放到Cache中并进行返回
除了NullUserCache之外,Spring Security还为我们提供了一个基于Ehcache的UserCache实现类
public class EhCacheBasedUserCache implements UserCache, InitializingBean { // ~ Static fields/initializers
// =====================================================================================
private static final Log logger = LogFactory.getLog(EhCacheBasedUserCache.class); // ~ Instance fields
// ================================================================================================
private Ehcache cache; // ~ Methods
// ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(cache, "cache mandatory");
} public Ehcache getCache() { return cache;
} public UserDetails getUserFromCache(String username) {
Element element = cache.get(username); if (logger.isDebugEnabled()) {
logger.debug("Cache hit: " + (element != null) + "; username: " + username);
} if (element == null) { return null;
} else { return (UserDetails) element.getValue();
}
} public void putUserInCache(UserDetails user) {
Element element = new Element(user.getUsername(), user); if (logger.isDebugEnabled()) {
logger.debug("Cache put: " + element.getKey());
}
cache.put(element);
} public void removeUserFromCache(UserDetails user) { if (logger.isDebugEnabled()) {
logger.debug("Cache remove: " + user.getUsername());
} this.removeUserFromCache(user.getUsername());
} public void removeUserFromCache(String username) {
cache.remove(username);
} public void setCache(Ehcache cache) { this.cache = cache;
}
}
核心方法
其中的决策类类型-投票器
看一下最常见的投票器
定义了权限前缀
核心方法自然为选举方法
三大投票器
一票通过
/**
* Simple concrete implementation of
* {@link org.springframework.security.access.AccessDecisionManager} that grants access if
* any <code>AccessDecisionVoter</code> returns an affirmative response.
*/
public class AffirmativeBased extends AbstractAccessDecisionManager {
public AffirmativeBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) {
super(decisionVoters);
}
// ~ Methods
// ========================================================================================================
/**
* This concrete implementation simply polls all configured
* {@link AccessDecisionVoter}s and grants access if any
* <code>AccessDecisionVoter</code> voted affirmatively. Denies access only if there
* was a deny vote AND no affirmative votes.
* <p>
* If every <code>AccessDecisionVoter</code> abstained from voting, the decision will
* be based on the {@link #isAllowIfAllAbstainDecisions()} property (defaults to
* false).
* </p>
*
* @param authentication the caller invoking the method
* @param object the secured object
* @param configAttributes the configuration attributes associated with the method
* being invoked
*
* @throws AccessDeniedException if access is denied
*/
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0;
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"));
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
}
一半以上功能选举通过
/**
* Simple concrete implementation of
* {@link org.springframework.security.access.AccessDecisionManager} that uses a
* consensus-based approach.
* <p>
* "Consensus" here means majority-rule (ignoring abstains) rather than unanimous
* agreement (ignoring abstains). If you require unanimity, please see
* {@link UnanimousBased}.
*/
public class ConsensusBased extends AbstractAccessDecisionManager {
// ~ Instance fields
// ================================================================================================
private boolean allowIfEqualGrantedDeniedDecisions = true;
public ConsensusBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) {
super(decisionVoters);
}
// ~ Methods
// ========================================================================================================
/**
* This concrete implementation simply polls all configured
* {@link AccessDecisionVoter}s and upon completion determines the consensus of
* granted against denied responses.
* <p>
* If there were an equal number of grant and deny votes, the decision will be based
* on the {@link #isAllowIfEqualGrantedDeniedDecisions()} property (defaults to true).
* <p>
* If every <code>AccessDecisionVoter</code> abstained from voting, the decision will
* be based on the {@link #isAllowIfAllAbstainDecisions()} property (defaults to
* false).
*
* @param authentication the caller invoking the method
* @param object the secured object
* @param configAttributes the configuration attributes associated with the method
* being invoked
*
* @throws AccessDeniedException if access is denied
*/
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int grant = 0;
int deny = 0;
int abstain = 0;
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:
abstain++;
break;
}
}
if (grant > deny) {
return;
}
if (deny > grant) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
if ((grant == deny) && (grant != 0)) {
if (this.allowIfEqualGrantedDeniedDecisions) {
return;
}
else {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
public boolean isAllowIfEqualGrantedDeniedDecisions() {
return allowIfEqualGrantedDeniedDecisions;
}
public void setAllowIfEqualGrantedDeniedDecisions(
boolean allowIfEqualGrantedDeniedDecisions) {
this.allowIfEqualGrantedDeniedDecisions = allowIfEqualGrantedDeniedDecisions;
}
}
全票通过才可
/**
* Simple concrete implementation of
* {@link org.springframework.security.access.AccessDecisionManager} that requires all
* voters to abstain or grant access.
*/
public class UnanimousBased extends AbstractAccessDecisionManager {
public UnanimousBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) {
super(decisionVoters);
}
// ~ Methods
// ========================================================================================================
/**
* This concrete implementation polls all configured {@link AccessDecisionVoter}s for
* each {@link ConfigAttribute} and grants access if <b>only</b> grant (or abstain)
* votes were received.
* <p>
* Other voting implementations usually pass the entire list of
* <tt>ConfigAttribute</tt>s to the <code>AccessDecisionVoter</code>. This
* implementation differs in that each <code>AccessDecisionVoter</code> knows only
* about a single <code>ConfigAttribute</code> at a time.
* <p>
* If every <code>AccessDecisionVoter</code> abstained from voting, the decision will
* be based on the {@link #isAllowIfAllAbstainDecisions()} property (defaults to
* false).
*
* @param authentication the caller invoking the method
* @param object the secured object
* @param attributes the configuration attributes associated with the method being
* invoked
*
* @throws AccessDeniedException if access is denied
*/
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) throws AccessDeniedException {
int grant = 0;
int abstain = 0;
List<ConfigAttribute> singleAttributeList = new ArrayList<>(1);
singleAttributeList.add(null);
for (ConfigAttribute attribute : attributes) {
singleAttributeList.set(0, attribute);
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, singleAttributeList);
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
grant++;
break;
case AccessDecisionVoter.ACCESS_DENIED:
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied",
"Access is denied"));
default:
abstain++;
break;
}
}
}
// To get this far, there were no deny votes
if (grant > 0) {
return;
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
}
生成
启动
结果
新建 Config 类
Config
http://localhost:8080/hello
访问受限, SS 起作用了!
常见 Case 实现
只要能登录即可
添加用户权限
logout接口测试
角色登录
要使用 ROLE 前缀
指定多角色用户
无管理员权限用户登录失败
数据库管理用户
实现类
由于密码机制的特殊性,需要自定义密码验证机制
(本文版权归原作者所有。转载文章仅为传播更多信息之目的,如有侵权请与我们联系,我们将及时处理。)
- End -
推荐阅读