Spring-Security入门(配合数据库设置权限,验证码功能)

spring-security(基于spring-security3)

  • 由于这次的项目需要对权限进行限制,所以在网上对security进行了学习,刚开始看的时候也是头脑非常混乱的,不过终归还是要一边写一边学,这样才能更好的理解,光是看是看不会的,接下来进入正题。

  • 首先来看看security工作的流程图(取自网上的截图) Spring-Security入门(配合数据库设置权限,验证码功能)_第1张图片

  • 一开始看不懂没关系,等把整个代码写完,在各个地方打上断点再来对照这个图就能理清头绪

  • 这里我一共使用到了4张表(t_manager,t_role,t_power,t_role_power)

    -- ----------------------------
      -- Table structure for t_manager
      -- ----------------------------
      DROP TABLE IF EXISTS `t_manager`;
      CREATE TABLE `t_manager` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT,
        `manager_name` varchar(20) DEFAULT NULL,
        `account` varchar(20) DEFAULT NULL,
        `password` varchar(32) DEFAULT NULL,
        `tel` varchar(13) DEFAULT NULL,
        `email` varchar(100) DEFAULT NULL,
        `fk_role_id` bigint(20) DEFAULT NULL,
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    
      
      -- ----------------------------
      -- Table structure for t_role
      -- ----------------------------
      DROP TABLE IF EXISTS `t_role`;
      CREATE TABLE `t_role` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT,
        `role_name` varchar(20) DEFAULT NULL,
        `roleType` int(11) DEFAULT NULL,
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8;
    
      
      -- ----------------------------
      -- Table structure for t_power
      -- ----------------------------
      DROP TABLE IF EXISTS `t_power`;
      CREATE TABLE `t_power` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT,
        `power_name` varchar(50) DEFAULT NULL,
        `url` varchar(50) DEFAULT NULL,
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
    
      -- ----------------------------
      -- Table structure for t_role_power
      -- ----------------------------
      DROP TABLE IF EXISTS `t_role_power`;
      CREATE TABLE `t_role_power` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT,
        `fk_role_power` bigint(20) DEFAULT NULL,
        `fk_power_role` bigint(20) DEFAULT NULL,
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=96 DEFAULT CHARSET=utf8;
  • 表建好之后,导入security需要的jar包,然后开始配置

web.xml中的配置(只记录了与security相关的部分)

<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/classes/spring-security.xmlparam-value>
context-param>

    <listener>
      	<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
    listener>
    

 <filter>  
     <filter-name>springSecurityFilterChainfilter-name>  
     <filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>  
 filter>  
  
    <filter-mapping>  
      <filter-name>springSecurityFilterChainfilter-name>  
      <url-pattern>/*url-pattern>  
    filter-mapping>

接下来是security的配置文件

  
<beans:beans xmlns="http://www.springframework.org/schema/security"  
  xmlns:beans="http://www.springframework.org/schema/beans"  
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://www.springframework.org/schema/beans  
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
           http://www.springframework.org/schema/security  
           http://www.springframework.org/schema/security/spring-security-3.1.xsd">  


      
    <http pattern="/jsp/power/userLogin.jsp" security="none" />
   	<http pattern="/jsp/power/managerLogin.jsp" security="none"/>
   	<http pattern="/login/imgCode" security="none"/>
    <http pattern="/resource/**" security="none"/> 
  	<http pattern="/jsp/power/error.jsp" security="none"/>
  	<http pattern="/jsp/common/*" security="none"/>
  	


                                 
    <http auto-config='true' access-denied-page="/jsp/power/error.jsp" use-expressions="true">  
       
          
        <form-login login-page="/jsp/power/userLogin.jsp" default-target-url="/login/controller" 
always-use-default-target="true"/>  
        
        
        <custom-filter ref="securityFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
        <custom-filter ref="ImgCodeFilter" before="FORM_LOGIN_FILTER"/>
    http>
   
   =   
    <beans:bean id="securityFilter" class="com.lovo.netctoss.filter.MySecurityFilter">  
          
        <beans:property name="authenticationManager" ref="myAuthenticationManager" />  
          
        <beans:property name="accessDecisionManager" ref="myAccessDecisionManager" />  
          
        <beans:property name="securityMetadataSource" ref="mySecurityMetadataSource" />  
    beans:bean>  
  
  <authentication-manager alias="myAuthenticationManager">  
          
        <authentication-provider user-service-ref="myUserDetailsService"/>  
    authentication-manager>  
beans:beans>
   
    
 
   <beans:bean id="ImgCodeFilter" class="com.lovo.netctoss.filter.ImgCodeFilter">
	
	<beans:property name="authenticationManager" ref="myAuthenticationManager" /> 
	
	<beans:property name="authenticationSuccessHandler">
		<beans:bean class="org.springframework.security.web.authentication.SavedReq
uestAwareAuthenticationSuccessHandler">  
       		 <beans:property name="defaultTargetUrl" value="/login/controller">beans:property>
		beans:bean>
	beans:property> 
	
	
	<beans:property name="authenticationFailureHandler">
		<beans:bean class="org.springframework.security.web.authentication.SimpleUrlA
uthenticationFailureHandler">  
       		 <beans:property name="defaultFailureUrl" value="/jsp/power/userLogin.jsp">beans:property>
		beans:bean>
	beans:property>   

beans:bean>

myUserDetailsService(登录验证)

注:这个类用户登录使用的,并取得用户拥有的所有权限,通过用户名到数据库进行查询,如果没有查到这个用户,
则登录失败留在原网页,如果查出这个用户,则把用户的账号密码和查出的用户拥有的所有权限,封装到security
自己的User类中返回(得到的形式是这样的UserDetail: Username: wangnima; Password: [PROTECTED];
 Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true;
 Granted Authorities:ROLE_ADMIN),接下来security会把前台发过来的密码和查到的密码进行比较,
看是否登录成功,失败停留在原网页,这一步框架自己解决,不需要我们来判断,只需要返回User即可。
import java.util.HashSet;
import java.util.Set;

import javax.annotation.Resource;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.lovo.netctoss.operatesys.managermag.beans.ManagerBean;
import com.lovo.netctoss.operatesys.managermag.service.IManagerService;

@Service("myUserDetailsService")
public class MyUserDetailsService implements UserDetailsService{

@Resource
private IManagerService managerServiceImpl;

@Override
public UserDetails loadUserByUsername(String account)
		throws UsernameNotFoundException {
	// TODO Auto-generated method stub
	ManagerBean manager = managerServiceImpl.selectManagerByAccount(account);
	
	Set grantedAuths=obtionGrantedAuthorities(manager);
	boolean enables = true;  
    boolean accountNonExpired = true;  
    boolean credentialsNonExpired = true;  
    boolean accountNonLocked = true;
	
  //封装成spring security的user  
    User userdetail = new User(manager.getManagerAccount(), manager.getManagerPwd(),
manager.getManagerName(), enables, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuths);  
   
    
    return userdetail;
	
}

//查找用户权限  
public Set obtionGrantedAuthorities(ManagerBean manager){  
    Set authSet=new HashSet();  
   String [] powerNames  = manager.getRole().getPowerLists().split(",");
   
   for(String powerName:powerNames){
	   authSet.add(new SimpleGrantedAuthority(powerName));
   }
    return authSet;  
}

}

mySecurityMetadataSource(资源与权限的关系)

注:我们发送的请求会进入mySecurityMetadataSource这个类中的getAttributes()方法中,
String requestUrl = ((FilterInvocation) object).getRequestUrl();可以得到我们刚才发送
请求的路径,然后通过loadResourceDefine()方法到数据库中查询所有的权限与对应可访问资源的关系,
因为项目管理的权限较少,所以这里采用的是权限和可访问资源之间11的关系,如果复杂的话可以采用1对多
,取出存入到resourceMap集合中(得到的是这样的{/jsp/user/*=[ROLE_USER], /jsp/power/*=
[ROLE_POWER],/jsp/expense/*=[ROLE_MONEY],/jsp/log/*=[ROLE_LOG],/jsp/manager/*=
[ROLE_MANAGER],/jsp/query/*=[ROLE_CHECK],/jsp/**=[ROLE_ADMIN]}),接下来验证用户请求的资
源是否需要某种权限,如果需要则返回相应的权限(也就是这一句return resourceMap.get(URLChangeTool.subString(requestUrl)))
这个地方通过请求url作为键得到的值是资源对应的权限集合,如果不需要则返回null。



import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.annotation.Resource;

import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;

import com.lovo.netctoss.operatesys.powermag.dao.IRoleDao;
import com.lovo.netctoss.operatesys.powermag.dao.impl.RoleDaoImpl;
import com.lovo.netctoss.pojos.URLResource;
import com.lovo.netctoss.util.URLChangeTool;

@Service("mySecurityMetadataSource")
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource{

@Resource
private IRoleDao roleDaoImpl;

private static Map<String, Collection<ConfigAttribute>> resourceMap = null;

private List<URLResource> findResources;
//加载所有资源与权限的关系  
private void loadResourceDefine() {
	
	if(resourceMap==null){
		resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
		
		findResources = roleDaoImpl.findResource();
	
		for(URLResource url_resources:findResources){
			Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
			//configAttribute放入权限名称(ROLE_xxxx    这种形式的,security默认的形式如果想改可以百度)
			ConfigAttribute configAttribute = new SecurityConfig(url_resources.getRole_name());
				configAttributes.add(configAttribute);
				resourceMap.put(url_resources.getAccess_url(), configAttributes);
				
			
		}
		
		//以权限名封装为Spring的security Object
		
		Set<Entry<String, Collection<ConfigAttribute>>> resourceSet = resourceMap.entrySet();  
        Iterator<Entry<String, Collection<ConfigAttribute>>> iterator = resourceSet.iterator();
	}
	
}

 //返回所请求资源所需要的权限
	@Override
public Collection<ConfigAttribute> getAttributes(Object object)
		throws IllegalArgumentException {
	// TODO Auto-generated method stub
	String requestUrl = ((FilterInvocation) object).getRequestUrl();
	
	
	if(resourceMap == null) {  
        loadResourceDefine();  
    }
	
	
	if(requestUrl.equals("/jsp/main/main.jsp")){
		Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
		for(URLResource url_resources:this.findResources){
			ConfigAttribute configAttribute = new SecurityConfig(url_resources.getRole_name());
			configAttributes.add(configAttribute);
		}
		
		return configAttributes;
		
	}
	
	
	return resourceMap.get(URLChangeTool.subString(requestUrl));
}


@Override
	public boolean supports(Class arg0) {
		// TODO Auto-generated method stub
		return true ;
	}

@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
	// TODO Auto-generated method stub
	return null;
}

}

MySecurityFilter

注:这个类在中间起到的作用是无论是从登录页面发送的请求,还是直接访问你想要的资源,都会被这个过滤器拦截
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;


public class MySecurityFilter extends AbstractSecurityInterceptor implements Filter{


private FilterInvocationSecurityMetadataSource securityMetadataSource;





@Override
public void doFilter(ServletRequest req, ServletResponse res,
		FilterChain chain) throws IOException, ServletException {
	// TODO Auto-generated method stub
	FilterInvocation fi=new FilterInvocation(req,res,chain);  

    invok(fi);
}

private void invok(FilterInvocation fi) throws IOException, ServletException{
	
	InterceptorStatusToken token = null;
	
	token = super.beforeInvocation(fi);
	
	try {  
        fi.getChain().doFilter(fi.getRequest(), fi.getResponse());  
    } finally {  
        super.afterInvocation(token, null);  
    }
	
}




@Override
public Class getSecureObjectClass() {
	// TODO Auto-generated method stub
	return FilterInvocation.class;
}

@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
	// TODO Auto-generated method stub
	return this.securityMetadataSource;
}



public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
	return securityMetadataSource;
}

public void setSecurityMetadataSource(
		FilterInvocationSecurityMetadataSource securityMetadataSource) {
	this.securityMetadataSource = securityMetadataSource;
}



@Override
public void init(FilterConfig arg0) throws ServletException {
	// TODO Auto-generated method stub
	
}
@Override
public void destroy() {
	// TODO Auto-generated method stub
	
}




}

myAccessDecisionManager(验证用户是否有权限访问该资源)

注authentication是用户拥有的权限,就是我们刚才返回User里面的,configAttributes是请求资源所需要的权限集合,
然后循环进行比较,我这里是只要有其中一个就可以通过,如果该用户没有这个权限则抛出AccessDeniedException异常,
这时后台会报错,但是会跳转到我们配置的失败页面中,这样一个流程就完成了。
    import java.util.Collection;
    import java.util.Iterator;
    
    import org.springframework.security.access.AccessDecisionManager;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.access.ConfigAttribute;
    import org.springframework.security.authentication.InsufficientAuthenticationException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.stereotype.Service;

@Service("myAccessDecisionManager")
public class MyAccessDecisionManager implements AccessDecisionManager{

@Override
public void decide(Authentication authentication, Object object,
		Collection configAttributes) throws AccessDeniedException,
		InsufficientAuthenticationException {
	// TODO Auto-generated method stub
	//如果请求的资源没有找到权限则放行,表示该资源为公共资源,都可以访问  
    if(configAttributes == null){  
          
        return ;  
    }
    
    Iterator ite=configAttributes.iterator();  
    while(ite.hasNext()){  
        ConfigAttribute ca =ite.next(); 
        String needRole = ca.getAttribute();  
        for(GrantedAuthority ga:authentication.getAuthorities()){  
            
        	if(ga.getAuthority().equals("ROLE_ADMIN")){
        		return;
        	}
        	
        	if(needRole.equals(ga.getAuthority())){    
                return;  
            }  
        }  
    }  
    throw new AccessDeniedException("没有权限!!!");
    
    
	
}

@Override
public boolean supports(ConfigAttribute arg0) {
	// TODO Auto-generated method stub
	return true;
}

@Override
public boolean supports(Class arg0) {
	// TODO Auto-generated method stub
	return true;
}

}
  • 这样也就完成了一个流程,接下来是后面加的验证码功能(自己写一个过滤器对验证码进验证)
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

    import org.springframework.security.authentication.AuthenticationServiceException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    public class ImgCodeFilter extends UsernamePasswordAuthenticationFilter {
    	
    	@Override  
    	public Authentication attemptAuthentication(HttpServletRequest request,  
    	        HttpServletResponse response) throws AuthenticationException {
    		
    		String inputCode = request.getParameter("yzCode");
    		
    		HttpSession session = request.getSession();
    		String generateCode = (String) session.getAttribute("code");
    		if(!inputCode.equals(generateCode)){
    			
    			throw  new AuthenticationServiceException("验证码错误!");
    	    }
    		
    		
    		
    		
    		return super.attemptAuthentication(request, response);
    	}
    }
  • 页面代码
这里要提一句的就是提交的路径必须是j_spring_security_check,输入框name必须是j_username和j_password,
这个是security规定好的
    
    <form id="ff" action="/security/j_spring_security_check" onsubmit="return lgcheck()"  method="post" >
        <div style="margin-bottom:30px;padding-left:100px;color: rgba(151, 156, 249, 0.98) ">
            <label style="font-size: 20px;">欢迎登录!label>
        
    div>
    <div style="margin-bottom:25px">
    
   		<input id="j_username" class="easyui-textbox" name="j_username" style="width:100%"  
data-options="validType:'length[5,20]',label:'用户名:',required:true">
    div>
    <div style="margin-bottom:25px">
        <input id="j_password"  class="easyui-textbox" type="password"  name="j_password" style="width:100%" 
 data-options="validType:'length[5,10]',label:'密码:',required:true">
    div>
    <div style="margin-bottom:25px;position: relative; ">
        <input id="yzCode1" class="easyui-textbox" name="yzCode" style="width:55%" data-options="label:'验证码:'">
        <img id="img" src="login/imgCode" style="width:30%; height: 20px;position: absolute; margin-left: 10px">
        
		<img id="img2" onclick="javascript:rf()" src="resource/imgs/reload.png" 
style="margin-left:100px;width:18px;">
    div>

	<input type="submit" value="登录" class="easyui-linkbutton" style="width: 78px;
height: 26px;  float:left;  margin-top: 5px;">
form>
  • URLResource类

    public class URLResource {
    
    private String role_name;//权限名
    private String access_url;//该权限可以访问的资源
    
    
    public String getRole_name() {
    	return role_name;
    }
    public void setRole_name(String role_name) {
    	this.role_name = role_name;
    }
    
    @Override
    public String toString() {
    	return "URLResource [role_name=" + role_name + ", access_url="
    			+ access_url + "]";
    }
    
    public String getAccess_url() {
    	return access_url;
    }
    public void setAccess_url(String access_url) {
    	this.access_url = access_url;
    }
    public URLResource() {
    	super();
    	// TODO Auto-generated constructor stub
    }
    
      }
  • dao实现类中

    public List findResource() {
    
    ArrayList powers = roleServiceMapper.selectAllPower();
    ArrayList urlRes = new ArrayList();
    String urls = null;
    for(PowerBean power :powers){
    	URLResource urlresources = new URLResource();
    	
    			urls = power.getUrl();
    			urlresources.setRole_name(power.getPowerName());
    			urlresources.setAccess_url(urls);
    	urlRes.add(urlresources);
    	}
    
    
    return urlRes;
  • 最后是我自己写的转换路径的工具类仅供参考

    public class URLChangeTool {
    
    
    
    public static String subString(String reqUrl){
    System.out.println(reqUrl);
    
    if(reqUrl.matches("/[0-9a-zA-Z]{0,}") || reqUrl.matches("/jsp/[0-9a-zA-Z]{0,}")){
    	//任意返回一个需要权限的资源url,这样因为还未登录所以回到默认登录页面
    	return "/jsp/**";
    }
    
    
    return reqUrl.substring(0, reqUrl.lastIndexOf("/"))+"/*";
        }
      }

总结

  • spring security是对url进行限制的(没有登录时,UserDetail默认为字符串"anonymousUser")
  1. 未登录时访问不需要权限的资源
  • request---->MySecurityMetadataSource时,调用getAttribute方法,验证request是否需要权限(这里假定是不需要权限的资源)对应数据库中就没有相应的 权限,此时将 return null相当于这个资源是公共资源,只要登录过就能访问,不会进入MyAccessDecisionManager这个类,但是因为还没有登录所以如果要直接返回默认登录页面这时可以任意返回一个需要权限的资源(URLChangeTool中的有写到),这样就可以回到登录页面。
  1. 未登录时访问需要权限的资源
  • 与上面类似,不过由于return 了某一个权限,所以进入MyAccessDecisionManager这个类进行判断是否能通过,但authentication.getAuthorities()这个方法因为没有登录,所以 得到null,然后抛出异常 throw new AccessDeniedException("没有权限!!!");由于未登录,所以返回登录页面
  1. 登录后访问不需要权限的资源
  • 此时因为getAttribute方法返回null,不进入MyAccessDecisionManager,但经过MyUserDetailsService类后,由于该用户登录过,所以就通过,相当于访问公共资源
  1. 登录后访问需要权限的资源
  • 此时getAttribute方法返回该请求需要的所有权限,进入MyAccessDecisionManager,然后在needRole.equals(authentication.getAuthority()) 如果需要的权限和用户拥有的权限相对应 则通过这个过滤器,如果用户没有相应权限则抛出异常,此时由于登录过所以进入配置文件中配置的access-denied-page没有权限页面

你可能感兴趣的:(java)