上次发过一个springsecurity的文章但都是比较简单的,角色,权限什么的都是配置在配置文件中的,在现实中不是很实用,这次就更深入的弹下springsecurity
1)登录部分
在springsecurity中提供了各种的认证,表单认证,基本认证等,只要通过配置一下就可以实现你想要的认证方式.这里只说下表单验证
<http auto-config="true">
<intercept-url pattern="/lndex.action" filters="none"/>
<form-login login-page="/lndex.action" default-target-url='/com/yecg/security/main/MainFrame.jsp' always-use-default-target="true"/>
</http>login-page指的是你的登录页面.default-target-url是你登录以后成功的页面,always-use-default-target是你登录成功后后是不是始终跳转到default-target-url.因为有时候你想访问一个url,但因为没有权限而被弹到了都登录页面,你登录后想直接到你刚访问的页面的话,就需要把它设为false.
再来看下/lndex.action所对应的登录页面:
这个页面就要一个用户名的输入,一个密码的输入以及一个提交的按钮,只贴部分代码:
<tr>
<td>
<s:textfield name="j_username" label="User Name"></s:textfield>
</td>
</tr>
<tr>
<td>
<s:textfield name="j_password" label="password"></s:textfield>
</td>
</tr>
<tr>
<td>
<input class="button" type="button" value="login" onClick="doSubmit(this.form,'j_spring_security_check');">
</td>
</tr>
name的属性必须是j_username和j_password哦,提交的action名字是j_spring_security_check,然后springsecurity会帮你完成剩下的事情.当然你也可以自己定制,那么你就不能用springsecurity默认的过滤器链了,要自己改造下..
想下登录的过程,用户到登录页面--->到数据库中查看是否有此用户---->如果有,把这个用户的用户名阿,角色之类的保存到session中,一般都是这个做法.当然在这个过程中你可以加入自己的功能,比如你想在登录的时候把所有的资源权限都加载入内存,以提高效率....
要用springsecurity从你的数据库中取得用户名密码,以及角色那么你要在后台做些改动.首先是数据库的设计,一般建个user表以及role表和他们的中间表.
你的应用中应该要有个对应user的Javabean来模拟用户,这个javabean要实现UserDetails这个类:
public class User implements UserDetails{
private String username;
private String password;
private List<Role> roles;
/* (non-Javadoc)
* @see org.springframework.security.userdetails.UserDetails#getAuthorities()
*/
public GrantedAuthority[] getAuthorities() {
List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>(roles.size());
for(Role role : roles) {
grantedAuthorities.add(new GrantedAuthorityImpl(role.getRoleName()));
}
return grantedAuthorities.toArray(new GrantedAuthority[roles.size()]);
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
/* (non-Javadoc)
* @see org.springframework.security.userdetails.UserDetails#isAccountNonExpired()
*/
public boolean isAccountNonExpired() {
return true;
}
/* (non-Javadoc)
* @see org.springframework.security.userdetails.UserDetails#isAccountNonLocked()
*/
public boolean isAccountNonLocked() {
return true;
}
/* (non-Javadoc)
* @see org.springframework.security.userdetails.UserDetails#isCredentialsNonExpired()
*/
public boolean isCredentialsNonExpired() {
return true;
}
/* (non-Javadoc)
* @see org.springframework.security.userdetails.UserDetails#isEnabled()
*/
public boolean isEnabled() {
return true;
}
}
public GrantedAuthority[] getAuthorities() 方法主要是把这个用户的角色都放到 GrantedAuthority的数组中返回.
你还需要实现UserDetailsService,以模拟用户的读出.
public class SecurityUserDetailService implements UserDetailsService {
private ISuperDAO superDAO;
public ISuperDAO getSuperDAO() {
return superDAO;
}
public void setSuperDAO(ISuperDAO superDAO) {
this.superDAO = superDAO;
}
public UserDetails loadUserByUsername(String userName)throws
UsernameNotFoundException, DataAccessException{
User user=null;
try {
user =superDAO.getObject("getUserByName", userName);
if(user ==null) {
throw new UsernameNotFoundException("User " + userName + " has no GrantedAuthority");
}
List<Role> roleList =superDAO.getList("getRolesByUser", userName);
user.setRoles(roleList);
} catch (DaoException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return user;
}
//get a map ,key is url ,value is roles in the url seprated by ","
public Map<String, String> loadUrlAuthorities() {
Map<String, String> urlAuthorities = new HashMap<String, String>();
try {
List<Resource> resourceList =superDAO.getList("getResourceValues", null);
for(Resource resource : resourceList){
List<Role> roles = superDAO.getList("getRolesByResource", resource.getValue());
resource.setRoles(roles);
urlAuthorities.put(resource.getValue(), resource.getRoleAuthorities());
}
} catch (DaoException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return urlAuthorities;
}
public List<Resource> getResourceByUser(String userName,String resourceType){
Map<String,String> paramMap = new HashMap<String,String>();
paramMap.put("userName", userName);
paramMap.put("type", resourceType);
List<Resource> resourceAllList =new ArrayList<Resource>();
try {
List<Role> roleList =superDAO.getList("getRolesByUser", paramMap);
for(Role role : roleList){
String roleName =role.getRoleName();
paramMap.put("roleName", roleName);
List<Resource> resourceList=superDAO.getList("getResourceByRole", paramMap);
resourceAllList.addAll(resourceList);
}
} catch (DaoException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return resourceAllList;
}
}
loadUserByUsername这个方法就是读取用户的过程了.最后在配置文件中
<authentication-provider user-service-ref="securityManager">
</authentication-provider>
<bean id="securityManager" class="com.yecg.security.service.SecurityUserDetailService">
<property name="superDAO" ref="DAO" ></property>
</bean>
<bean id="DAO" class="com.yecg.security.dao.SuperDAO"/>这样就可以实现正常的登录了.
这里有个值得借鉴的地方就是一般我们登录后会把登录的信息放在session中,而springsecurity是放在threadlocal中的,节约了session的空间.
2)权限部分
权限部分指每个角色可以访问哪些Url,对某些资源可以有哪些操作.数据库中建个resource的表,维护各种需要认证才能访问的资源,比如url,数据什么的.简单的例子:
create table SECURITY_RESOURCE
(
RESOURCE_KEY VARCHAR2(20) not null,
RESOURCE_TYPE VARCHAR2(20),
RESOURCE_VALUE VARCHAR2(60)
)比如大部分的系统都会有个要求就是,用户登进来以后显示用户有权限看的那部分菜单.那么就可以在这张表中存入左右的菜单信息,再建个与role的中间表.维护哪些角色可以访问哪些url.
让我们再来想想资源的验证应该要包含哪些内容呢:
1)首先,用户一登录看到各自权限可以看到的菜单,这个比较好实现.
2)aop实现权限的验证,用户访问某个资源时,先看下数据库用户是否有这个资源的权限,没有就报个没有权限的错
3)页面上实现权限的验证,用户没有权限则看不到按钮.
我们看下第2项在springsecurity中如何实现,先来配置文件:
<authentication-manager alias="authenticationManager"/>
<beans:bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
<beans:property name="allowIfAllAbstainDecisions" value="false"/>
<beans:property name="decisionVoters">
<beans:list>
<beans:bean class="org.springframework.security.vote.RoleVoter"/>
<beans:bean class="org.springframework.security.vote.AuthenticatedVoter"/>
</beans:list>
</beans:property>
</beans:bean>
<beans:bean id="resourceSecurityInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
<beans:property name="authenticationManager" ref="authenticationManager"/>
<beans:property name="accessDecisionManager" ref="accessDecisionManager"/>
<beans:property name="objectDefinitionSource" ref="secureResourceFilterInvocationDefinitionSource" />
<beans:property name="observeOncePerRequest" value="false" />
<custom-filter after="LAST" />
</beans:bean>
<beans:bean id="secureResourceFilterInvocationDefinitionSource" class="com.yecg.security.interceptor.SecureResourceFilterInvocationDefinitionSource" />
我们实现的是 FilterSecurityInterceptor,实际上在过滤链中已经包含了springsecurity默认实现的FilterSecurityInterceptor,只不过我们要实现自己的验证机制,那么就重新实现了FilterSecurityInterceptor,然后通过<custom-filter after="LAST" />把它加入过滤链的尾端.
secureResourceFilterInvocationDefinitionSource是我们自己的实现,它需要实现FilterInvocationDefinitionSource接口,这个类主要是返回受保护的url:
public class SecureResourceFilterInvocationDefinitionSource implements FilterInvocationDefinitionSource{
private static final Logger logger = Logger.getLogger(SecureResourceFilterInvocationDefinitionSource.class);
// get the request url and find the roles of the url in database.
public ConfigAttributeDefinition getAttributes(Object filter)
throws IllegalArgumentException{
FilterInvocation filterInvocation = (FilterInvocation) filter;
String requserUrl = filterInvocation.getRequestUrl();
logger.debug("the requser url is --- "+requserUrl);
SecurityUserDetailService userDetailService =(SecurityUserDetailService) ApplicationContextUtil.getBean("securityManager");
Map<String, String> urlAuthorities =userDetailService.loadUrlAuthorities();
ActionUtil.showMap(urlAuthorities, "urlAuthorities");
UrlMatcher urlMatcher =new AntUrlPathMatcher();
String grantedAuthorities=null;
for(Iterator<Map.Entry<String, String>> iter = urlAuthorities.entrySet().iterator(); iter.hasNext();) {
Map.Entry<String, String> entry = iter.next();
String url = entry.getKey();
if(ActionUtil.isNotBlank(url)){
if(urlMatcher.pathMatchesUrl(url, requserUrl)) {
logger.debug("requserUrl is match the url in the database, get roles ........");
grantedAuthorities = entry.getValue();
logger.debug("grantedAuthorities is --- "+grantedAuthorities);
break;
}
}
}
if(ActionUtil.isNotBlank(grantedAuthorities)){
ConfigAttributeEditor configAttrEditor = new ConfigAttributeEditor();
configAttrEditor.setAsText(grantedAuthorities);
return (ConfigAttributeDefinition) configAttrEditor.getValue();
}
return null;
}
/* (non-Javadoc)
* @see org.springframework.security.intercept.ObjectDefinitionSource#getConfigAttributeDefinitions()
*/
@SuppressWarnings("unchecked")
public Collection getConfigAttributeDefinitions() {
return null;
}
/* (non-Javadoc)
* @see org.springframework.security.intercept.ObjectDefinitionSource#supports(java.lang.Class)
*/
@SuppressWarnings("unchecked")
public boolean supports(Class clazz) {
return true;
}
}注意getAttributes这个方法,他实际上就是根据请求的url然后到数据库去找这个url对应的role,如果找不到就返回空.
之后springsecurity会把这个方法返回的role列表和登录的用户属于的role做对比,决定用户是否有权限访问这个url
这样就实现的基本的权限控制,当然在springsecurity中你还可以实现对方法的保护.以及领域对象的保护(acl)
总的来说springsecurity的灵活性非常的强,基本可以满足所有对权限访问的需求.而且基于aop实现,你可以方便的在不同的项目中移植,但灵活性的代价就是学习springsecurity不是那么容易上手,需要一定时间才可以灵活的运用
















