关于权限控制,一开始感觉比较难,后来先是接触了Spring Security 学起来也比较吃力,再是学习了Shiro,感觉简单很多。
总体来说这些框架,主要做了两个事情
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
还做了一些事情,我也没有深入,主要就这么。
从以下配置文件可以看出。
<beans:bean id="myFilter" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<beans:property name="accessDecisionManager" >
<beans:bean class="org.springframework.security.access.vote.AffirmativeBased">
<beans:property name="decisionVoters">
<beans:list>
<beans:bean class="org.springframework.security.access.vote.RoleVoter" />
<beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
</beans:list>
</beans:property>
</beans:bean>
</beans:property>
<!-- resourceService在applicationContext*.xml中或注解定义 -->
<beans:property name="securityMetadataSource" ref="securityMetadataSource" />
</beans:bean>
这里有一个authenticationManager是做身份认证的,有一个accessDecisionManager是做授权管理的。还有一个是securityMetadataSource这里主要做的从数据库取出资源与角色的关系,为第二个授权做准备,授权又是根据用户的身份找出他的角色,根据角色判断是否有访问资源的权限。
先介绍这个 securityMetadataSource这里需要实现FilterInvocationSecurityMetadataSource接口,大致如下:
/**
* resourceMap 存储的资源为<Url,Collection<ConfigAttribute>>,其中Collection<ConfigAttribute>为资源对应的角色集合,其构造方式为new SecurityConfig("ROLE_" + role.getRoleId().toString())
* resourceMap 里面存储的内容为<1个URL,对应得角色的集合>
* 加载所有的资源与角色的关系
*/
public void init() throws Exception {
resourceMap = new HashMap<String,Collection<ConfigAttribute>>();
List<Resource> resources = resourceDao.findAll();
for(Resource item:resources) {
String url = item.getUrlpath();
if(url != null) {
List<Role> roles = roleDao.getRolesByResourceId(item.getResourceid());
if(roles !=null && roles.size()>0) {
if(resourceMap.containsKey(item.getUrlpath())) {
Collection<ConfigAttribute> old = resourceMap.get(item.getUrlpath());
old.addAll(listToCollection(roles));
} else {
resourceMap.put(url, listToCollection(roles));
}
}
}
}
}
/**
* 将角色转化为ConfigAttribute
* @param roles
* @return
*/
private Collection<ConfigAttribute> listToCollection(Collection<Role> roles) {
Collection<ConfigAttribute> list = new ArrayList<ConfigAttribute>();
for(Role role :roles) {
ConfigAttribute configAttribute = new SecurityConfig("ROLE_"+role.getRoleid().toString());
//list.add(new SecurityConfig("ROLE_" + role.getRoleid().toString()));
list.add(configAttribute);
}
return list;
}
/**
* 将资源与权限的对应关系Map转化为Security需要的Collection
*/
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
for(Map.Entry<String, Collection<ConfigAttribute>> entry:resourceMap.entrySet()) {
allAttributes.addAll(entry.getValue());
}
return allAttributes;
}
//返回所请求资源所需要的权限
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
String requestUrl = ((FilterInvocation) object).getRequestUrl();
System.out.println("requestUrl is " + requestUrl);
if(resourceMap == null) {
try { init(); }
catch (Exception e)
{ e.printStackTrace(); } }
return resourceMap.get(requestUrl);
}
上面前几个方法是在程序启动的时候执行,最后一个方法是在你要访问某个资源的时候自动调用的最终返回的是 Collection<ConfigAttribute> 权限集合。
accessDecisionManager授权管理,根据上面返回的权限集合,和用户登录存储的用户权限进行对比判断用户是否有访问权限,自己写授权管理的话需要实现AccessDecisionManager 接口,如下:
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
return;
}
//所请求的资源拥有的权限(一个资源对多个权限)
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while(iterator.hasNext()) {
ConfigAttribute configAttribute = iterator.next();
//访问所请求资源所需要的权限
String needPermission = configAttribute.getAttribute();
System.out.println("needPermission is " + needPermission);
//用户所拥有的权限authentication
for(GrantedAuthority ga : authentication.getAuthorities()) {
if(needPermission.equals(ga.getAuthority())) {
return;
}
}
}
//没有权限让我们去捕捉
throw new AccessDeniedException(" 没有权限访问!");
}
authenticationManager身份认证,主要包含登陆这些,框架一般会给我们提供form表单提交的处理接口,我们需要实现接口和配置一些相关配置。
<http auto-config="true" access-denied-page="/accessDenied.jsp" use-expressions="true">
always-use-default-target="true" authentication-failure-url="/index.jsp?login_error=true"
default-target-url="/frame.jsp"
/>
<logout logout-success-url="/index.jsp"/>
<intercept-url pattern="/style/**" filters="none" />
<intercept-url pattern="/js/**" filters="none" />
<intercept-url pattern="/index.jsp" filters="none" />
<intercept-url pattern="/login.action" filters="none"/>
<intercept-url pattern="/frame.jsp" access="isAuthenticated()"/>
<anonymous />
<session-management invalid-session-url="/accessDenied.jsp">
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
<!-- 将自己的过滤器加入到过滤器链中, 放在FILTER_SECURITY_INTERCEPTOR之前-->
<custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
</http>
<authentication-manager alias="autheticationManager">
<!-- 使用自定义UserDetailsService -->
<authentication-provider user-service-ref="userService"/>
</authentication-manager>
以上是Spring Security的配置,还需要在userService 登录验证里面实现UserDetailsService 接口。Shiro实现方式如下 :
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(
user.getUsercode(), EncryptUtils.encryptMD5(user.getPassword()));
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (AuthenticationException e) {
modelView.addObject("message", "login errors");
modelView.setViewName("/login");
e.printStackTrace();
}
accessDecisionManager