上次发过一个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不是那么容易上手,需要一定时间才可以灵活的运用