欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring security

程序员文章站 2022-06-19 11:30:00
...

spring security框架出来很久了,之前也研究过一段时间,现在有时间刚好整理一下。

这次做的,是全数据库配置,5张表。用户,角色,资源,用户-角色映射,角色资源映射。当然还可以进一步的扩充,菜单表,用户菜单表。

 

jar包什么的,可以直接从官网下,就不再说了,先从web.xml配置说起。

 

security 的filter,拦截全部的请求:

<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

 

security的listenter:

<listener>
	<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>

 

如果想要用到srping的mvc的话,可以配置servlet:

<servlet>
	<servlet-name>mvc</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
<servlet-mapping>
	<servlet-name>mvc</servlet-name>
	<url-pattern>*.do</url-pattern>
</servlet-mapping>

 当然,这里可以配置多个servlet来处理类型的不同的请求,比如.do结尾的,比如.action结尾的,比如spring rest的URL的等等,只需要<servlet-name>不同就可以了,以及注意<load-on-startup>的先后顺序。

注意的是如果配置了org.springframework.web.servlet.DispatcherServlet,需要有一个与该<servlet-name>名字匹配的xml配置文件,系统会去默认加载,如<servlet-name>mvc</servlet-name>,则对应mvc-servlet.xml,里面可以没有实质的内容。

 

接下来就是最总要的applicationContext-sercurity.xml配置文件了:

<!-- auto-config="true" 设置为true时,安照默认的filter顺序加载,详见官方文档 -->
<http auto-config="true" use-expressions="true" access-denied-page="/error/403.jsp">

<!-- 允许访问commons下的资源以及登陆界面 -->
<intercept-url pattern="/commons/**" filters="none" />
<!-- 错误信息页面 -->
<intercept-url pattern="/error/**" filters="none" />
<!-- 指定登录页面及登录失败跳转页 -->
<form-login login-page="/error/404.jsp"
	authentication-failure-url="/manager/login.jsp?login_error=1"
	default-target-url="/manager/manager.jsp" />
<!-- 登出之后跳转页面 -->
<logout invalidate-session="true" logout-success-url="/manager/login.jsp" 
logout-url="/j_spring_security_logout" />

<!-- 保证一个用户同时只能登入一次 -->
<session-management>
	<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>

<!-- 检测失效的sessionId,超时时定位到另外一个URL -->
<session-management invalid-session-url="/sessionTimeout.jsp" />

<!-- 增加一个自定义的filter,放在FILTER_SECURITY_INTERCEPTOR之前,实现用户、角色、权限、资源的数据库管理。 -->
<custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR" />
</http>

<!-- 一个自定义的filter,必须包含authenticationManager,accessDecisionManager,securityMetadataSource三个属性。 -->
<beans:bean id="myFilter" class="com.core.security.MyFilterSecurityInterceptor">
	<beans:property name="authenticationManager" ref="authenticationManager" />
	<beans:property name="accessDecisionManager" ref="myAccessDecisionManager" />
	<beans:property name="securityMetadataSource" ref="securityMetadataSource" />
</beans:bean>

<!-- 认证管理器 -->
<!-- 注意能够为authentication-manager 设置alias别名 -->
<authentication-manager alias="authenticationManager">
	<authentication-provider user-service-ref="myUserDetailService">
		<!-- 加密 -->
		<password-encoder hash="md5" />
	</authentication-provider>
</authentication-manager>

<!-- 接口实现 -->
<!-- 用户验证,注入userService -->
<beans:bean id="myUserDetailService" class="com.core.security.MyUserDetailService">
	<beans:property name="userService" ref="userService" />
</beans:bean>
<!-- 访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->
<beans:bean id="myAccessDecisionManager" class="com.core.security.MyAccessDecisionManager" />
<!-- 资源源数据定义,即定义某一资源可以被哪些角色访问 -->
<!-- 无法注入,采用构造器加载 -->
<beans:bean id="securityMetadataSource"
	class="com.core.security.MyInvocationSecurityMetadataSource">
	<beans:constructor-arg>
		<beans:ref bean="roleService" />
	</beans:constructor-arg>
</beans:bean>

<!-- 加载JDBC properties文件 -->
<!-- 原加载类org.springframework.beans.factory.config.PropertyPlaceholderConfigurer -->
<!-- 配置文件中数据库配置为加密之后内容,加载时需要对其进行解密 -->
<beans:bean id="propertyConfigurer" class="com.core.commons.DBEncryption">
	<beans:property name="locations">
		<beans:list>
			<beans:value>classpath*:/com/conf/jdbc.properties</beans:value>
		</beans:list>
	</beans:property>
</beans:bean>

 其他的就是一些数据源等等配置文件了。

 

接下来是需要自己实现的几个接口:

MyAccessDecisionManager:

package com.core.security;

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.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

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

	// In this method, need to compare authentication with configAttributes.
	// 1, A object is a URL, a filter was find permission configuration by this
	// URL, and pass to here.
	// 2, Check authentication has attribute in permission configuration
	// (configAttributes)
	// 3, If not match corresponding authentication, throw a
	// AccessDeniedException.
	// 权限判断 检查用户是否拥有访问该URL资源所需的角色
	public void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes)
			throws AccessDeniedException, InsufficientAuthenticationException {
		if (configAttributes == null) {
			return;
		}
		//System.out.println("cause MyAccessDecisionManager decide " + object.toString()); // object is a URL.
		Iterator<ConfigAttribute> ite = configAttributes.iterator();
		while (ite.hasNext()) {
			ConfigAttribute ca = ite.next();
			String needRole = ((SecurityConfig) ca).getAttribute();
			for (GrantedAuthority ga : authentication.getAuthorities()) {
				
				//System.out.println("cause " +needRole+":"+ga.getAuthority());
				
				if (needRole.equals(ga.getAuthority())) { // ga is user's role.
					return;
				}
			}
		}
		throw new AccessDeniedException("no right");
	}

	public boolean supports(ConfigAttribute attribute) {
		return true;
	}

	public boolean supports(Class<?> clazz) {
		return true;
	}

}

 

MyFilterSecurityInterceptor:

package com.core.security;

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 MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

	private FilterInvocationSecurityMetadataSource securityMetadataSource;

	// ~ Methods
	// ========================================================================================================

	/**
	 * Method that is actually called by the filter chain. Simply delegates to
	 * the {@link #invoke(FilterInvocation)} method.
	 * 
	 * @param request
	 *            the servlet request
	 * @param response
	 *            the servlet response
	 * @param chain
	 *            the filter chain
	 * 
	 * @throws IOException
	 *             if the filter chain fails
	 * @throws ServletException
	 *             if the filter chain fails
	 */
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		
		//System.out.println("cause FilterInvocationSecurityMetadataSource doFilter");
		
		FilterInvocation fi = new FilterInvocation(request, response, chain);
		invoke(fi);
	}

	public Class<? extends Object> getSecureObjectClass() 
	{
		return FilterInvocation.class;
	}

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

	public SecurityMetadataSource obtainSecurityMetadataSource()
	{
		return this.securityMetadataSource;
	}
	

	public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() 
	{
		return securityMetadataSource;
	}

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

	//@Override
	public void destroy() {
		
	}

	//@Override
	public void init(FilterConfig arg0) throws ServletException {
		
	}

}

 

MyInvocationSecurityMetadataSource:

package com.core.security;

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 org.apache.log4j.Logger;
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.security.web.util.AntUrlPathMatcher;
import org.springframework.security.web.util.UrlMatcher;

import com.core.role.model.Role;
import com.core.role.service.RoleService;
import com.core.user.service.UserService;

/**
 * 
 * 此类在初始化时,应该取到所有资源及其对应角色的定义
 * 
 * @author cause
 * 
 */
public class MyInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
	private UrlMatcher urlMatcher = new AntUrlPathMatcher();
	private static Map<String, Collection<ConfigAttribute>> resourceMap = null;

	private RoleService roleService;
	private UserService userService;

	Logger log = Logger.getLogger(MyInvocationSecurityMetadataSource.class.getName());
	
	public MyInvocationSecurityMetadataSource(RoleService roleService) {
		this.roleService = roleService;
		loadResourceDefine();
	}

	// 初始化角色-资源信息
	public void loadResourceDefine() {

		resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
		Collection<ConfigAttribute> atts;
		ConfigAttribute ca;

		List<Role> roleList = roleService.getAllRole();

		List<String> rescList;
		
		for (int i = 0; i < roleList.size(); i++) {
			
			ca = new SecurityConfig(roleList.get(i).getName());
			atts = new ArrayList<ConfigAttribute>();
			atts.add(ca);
			
			rescList = roleService.getRescByRoleID(roleList.get(i).getId());

			for (int j = 0; j < rescList.size(); j++) {
				
				//System.out.println(roleList.get(i).getName()+":"+rescList.get(j));
				resourceMap.put(rescList.get(j), atts);
			}
		}
		
//		 ca = new SecurityConfig("ROLE_ADMIN");
//		 atts = new ArrayList<ConfigAttribute>();
//		 atts.add(ca);
//		 //resourceMap.put("/**", atts);
//		 //resourceMap.put("/manager/**", atts);
//		 
//		 ca = new SecurityConfig("ROLE_USER");
//		 atts = new ArrayList<ConfigAttribute>();
//		 atts.add(ca);
//		 //resourceMap.put("/test/user/**", atts);
//		 resourceMap.put("/manager/**", atts);
//		 //resourceMap.put("/user.do**", atts);
		
	}

	// According to a URL, Find out permission configuration of this URL.
	// 获取访问的URL资源所需要的角色
	// 项目启动时加载,需要注入数据源,相关信息从数据库读取
	public Collection<ConfigAttribute> getAttributes(Object object)
			throws IllegalArgumentException {

		// System.out.println("cause MyInvocationSecurityMetadataSource getAttributes");

		// guess object is a URL.
		String url = ((FilterInvocation) object).getRequestUrl();
		
		log.info("user acces URL = "+url);
		
		Iterator<String> ite = resourceMap.keySet().iterator();
		while (ite.hasNext()) {
			String resURL = ite.next();

			// System.out.println("pathMatchesUrl1 "+resURL+":"+url+":"+urlMatcher.pathMatchesUrl(resURL,
			// url));

			// urlMatcher.pathMatchesUrl(url, resURL)

			// resURL-资源串(根据该资源串查询角色) url-当前访问的资源
			if (urlMatcher.pathMatchesUrl(resURL, url)) {
				Collection<ConfigAttribute> ca = resourceMap.get(resURL);
				// System.out.println("cause check "+ca);
				return ca;
			}
		}

		// 避免因为返回空导致decide不执行
		Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
		ConfigAttribute ca = new SecurityConfig("ROLE_NO_USER");
		atts.add(ca);
		return atts;
	}

	public boolean supports(Class<?> clazz) {
		return true;
	}

	public Collection<ConfigAttribute> getAllConfigAttributes() {
		return null;
	}

	public UserService getUserService() {
		return userService;
	}

	public void setUserService(UserService userService) {
		this.userService = userService;
	}

	public RoleService getRoleService() {
		return roleService;
	}

	public void setRoleService(RoleService roleService) {
		this.roleService = roleService;
	}

}

 

MyUserDetailService:

package com.core.security;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.core.user.model.MyUser;
import com.core.user.service.UserService;

public class MyUserDetailService implements UserDetailsService {

	private UserService userService;

	// @Override
	// 加载用户信息
	// 需要数据源,在这里加载用户的信息,包括用户的密码、状态、角色、权限等
	public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException, DataAccessException {

		Collection<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();

		GrantedAuthorityImpl auth;

		// admin = new GrantedAuthorityImpl("ROLE_ADMIN");
		// GrantedAuthorityImpl auth_manager = new
		// GrantedAuthorityImpl("ROLE_MANAGER");
		// GrantedAuthorityImpl auth_user = new
		// GrantedAuthorityImpl("ROLE_USER");
		// User user = null;

		// get user
		MyUser myUser = userService.getUserByUserName(username);
		// get role
		if (myUser != null) {
			List<String> roleList = userService.getRoleByUserID(myUser.getId());

			for (int i = 0; i < roleList.size(); i++) {
				// System.out.println(username+":"+roleList.get(i));
				auth = new GrantedAuthorityImpl(roleList.get(i));
				auths.add(auth);
			}

			myUser.setAuthorities(auths);
		}
		else
		{
			throw new UsernameNotFoundException("usernameNotFound");
		}
		
		// 硬编码,且该user为security的user
		// if (username.toUpperCase().equals("ADMIN")) {
		// auths = new ArrayList<GrantedAuthority>();
		// auths.add(auth_admin);
		// auths.add(auth_manager);
		// auths.add(auth_user);
		// user = new User(username, "admin", true, true, true, true, auths);
		// myUser.setAuthorities(auths);
		// } else if (username.toUpperCase().equals("USER")) {
		// auths = new ArrayList<GrantedAuthority>();
		// auths.add(auth_user);
		// user = new User(username, "user", true, true, true, true, auths);
		// myUser.setAuthorities(auths);
		// }

		return myUser;
	}

	public void setUserService(UserService userService) {
		this.userService = userService;
	}

}

 因为我的采用的是hibernate,以及spring的依赖注入,所以上述代码中,userService是service层,自己可以通过其他方式实现。

至此,核心的配置就完成了。

附带表结构:

CREATE TABLE `menu` (
  `id` bigint(20) NOT NULL auto_increment,
  `name` varchar(255) NOT NULL default '',
  `url` varchar(255) default '',
  `parentId` bigint(20) NOT NULL default '-1',
  `sort` int(11) default '0',
  PRIMARY KEY  (`id`),
  UNIQUE KEY `unique` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `menu_role` (
  `menu_id` bigint(20) NOT NULL default '0',
  `role_id` bigint(20) NOT NULL default '0',
  UNIQUE KEY `unique` (`menu_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `resc` (
  `id` bigint(20) NOT NULL auto_increment,
  `name` varchar(255) default NULL,
  `resc_type` varchar(255) default NULL,
  `resc_string` varchar(255) default NULL,
  `priority` int(11) default NULL,
  `descn` varchar(255) default NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `unique` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `resc_role` (
  `resc_id` bigint(20) NOT NULL default '0',
  `role_id` bigint(20) NOT NULL default '0',
  UNIQUE KEY `unique` (`resc_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `user_role` (
  `user_id` bigint(20) NOT NULL default '0',
  `role_id` bigint(20) NOT NULL default '0',
  UNIQUE KEY `unique` (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `role` (
  `id` bigint(20) NOT NULL auto_increment,
  `name` varchar(255) NOT NULL default '',
  `descn` varchar(255) NOT NULL default '',
  PRIMARY KEY  (`id`),
  UNIQUE KEY `unique` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `user` (
  `id` bigint(20) NOT NULL auto_increment,
  `username` varchar(255) NOT NULL default '',
  `password` varchar(255) NOT NULL default '',
  `status` tinyint(1) NOT NULL default '1',
  `descn` varchar(255) NOT NULL default '',
  PRIMARY KEY  (`id`),
  UNIQUE KEY `unique` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `user_role` (
  `user_id` bigint(20) NOT NULL default '0',
  `role_id` bigint(20) NOT NULL default '0',
  UNIQUE KEY `unique` (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

登录的action:<%=request.getContextPath()%>/j_spring_security_check" 

登出的action:<%=request.getContextPath()%>//j_spring_security_logout"