spring security 3.0 实现认证与授权

先看一下spring security 官方对以下几个类或接口的解释,因为这个几个类在程序中会使用到;
ConfigAttribute:Stores a security system related configuration attribute.
SecurityConfig:ConfigAttribute的实现类。
GrantedAuthority:Represents an authority granted to an Authentication object.
GrantedAuthorityImpl:GrantedAuthority的实现类。
UserDetails:Provides core user information.
Authentication:Represents the token for an authentication request or for an authenticated principal once the request has been processed by the AuthenticationManager.authenticate(Authentication) method.
UserDetailsService:Core interface which loads user-specific data.
FilterInvocationSecurityMetadataSource:Marker interface for SecurityMetadataSource implementations that are designed to perform lookups keyed on FilterInvocations.
AccessDecisionManager:Makes a final access control (authorization) decision.

定义四张表:用户表、角色表、资源表、组织机构表(可选)

首先需要在web.xml文件中添加以下配置:

<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>

接着配置spring security的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<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.0.xsd">

	<!-- access-denied-page:定义没有权限时跳转的页面 ;use-expressions:true表示使用表达式定义忽略拦截资源列表-->
	<http auto-config="true" access-denied-page="/accessDenied.do" use-expressions="true">
		<!-- 忽略拦截资源定义 -->
		<intercept-url pattern="/showLogin.do*" filters="none" />
		<intercept-url pattern="/scripts/**" filters="none" />
		<intercept-url pattern="/images/**" filters="none" />
		<intercept-url pattern="/styles/**" filters="none" />
		<intercept-url pattern="/dwr/**" filters="none" />
		<!-- isAuthenticated()表示只有通过验证的用户才能访问 -->
		<intercept-url pattern="/**" access="isAuthenticated()" />
		<intercept-url pattern="/main.do*" access="isAuthenticated()" />
		
		<!-- max-sessions=1:禁止2次登陆;ession-fixation-protection="none":防止伪造sessionid攻击,用户登录成功后会销毁用户当前的session,-->
		<!-- 创建新的session并把用户信息复制到新session中 ;invalid-session-url:定义session超时跳转页面 -->
		 <session-management invalid-session-url="/timeout.jsp" session-fixation-protection="none">
            <concurrency-control max-sessions="1" />  
        </session-management>
        
		<!-- 表单验证 -->
		<form-login login-page="/showLogin.do" authentication-failure-url="/showLogin.do?error=true" always-use-default-target="true" default-target-url="/login.do?error=false" />
		<!-- 退出系统跳转页面 -->	
		<logout invalidate-session="true" logout-success-url="/showLogin.do" />
		<!-- 免登陆验证 -->
		<remember-me />
		<!-- 自定义拦截器(这里和spring security2的配置有点不同) -->
		<custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="securityFilter" />
	</http>
	
	<beans:bean id="securityFilter" class="xxx.xxx.xxx.commons.permissionengine.web.security.FilterSecurityInterceptor">
		<beans:property name="authenticationManager" ref="authenticationManager" />
		<beans:property name="accessDecisionManager" ref="securityAccessDecisionManager" />
		<beans:property name="securityMetadataSource" ref="securityMetadataSource" />
	</beans:bean>
	
	<authentication-manager alias="authenticationManager">
		<authentication-provider user-service-ref="securityUserDetailService">
			<!-- 密码采用MD5算法加密 -->
			<password-encoder hash="md5" />
		</authentication-provider>
	</authentication-manager>
	
	<!-- 用户拥有的权限 -->
	<beans:bean id="securityUserDetailService" class="xxx.xxx.xxx.commons.permissionengine.web.security.SecurityUserDetailService" />
	<!-- 用户是否拥有所请求资源的权限 -->
	<beans:bean id="securityAccessDecisionManager" class="xxx.xxx.xxx.commons.permissionengine.web.security.SecurityAccessDecisionManager" />
	<!-- 资源与权限对应关系 -->
	<beans:bean id="securityMetadataSource" class="xxx.xxx.xxx.commons.permissionengine.web.security.SecurityMetadataSource" />
</beans:beans>

接着需要写一个类实现UserDetails接口,这个并不是我们系统的用户,它只是一个VO,
用于保存从spring security上下文环境中获取到登录用户,因为从spring security上下文环境中获取登录用户的返回值就是UserDetails的实现类:

package xxx.xxx.xxx.commons.permissionengine.entity;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

public class UserDetailInfo implements UserDetails {

	private static final long serialVersionUID = 6137804370301413132L;
	
	private String userName;
	
	private String password;
	
	private Collection<GrantedAuthority> authorities;
	
	public UserDetailInfo() {}

	@Override
	public Collection<GrantedAuthority> getAuthorities() {
		return authorities;
	}

	@Override
	public String getPassword() {
		return password;
	}

	@Override
	public String getUsername() {
		return userName;
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public void setAuthorities(Collection<GrantedAuthority> authorities) {
		this.authorities = authorities;
	}
}

接着自定义一个继承了AbstractSecurityInterceptor的filter:

package xxx.xxx.xxx.commons.permissionengine.web.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 FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
	
	private FilterInvocationSecurityMetadataSource securityMetadataSource;

	@Override
	public void destroy() {
		// TODO Auto-generated method stub
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
		FilterInvocation invocation = new FilterInvocation(request, response, filterChain);
		invoke(invocation);
	}
	
	public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
		InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
		try {
			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
		} finally {
			super.afterInvocation(token, null);
		}
	}

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

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

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

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

	public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
		return this.securityMetadataSource;
	}
}

接着定义一个类,实现了UserDetailsService接口,主要用于获取登录用户信息和用户所具有的角色

package xxx.xxx.xxx.commons.permissionengine.web.security;


import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;

import javax.annotation.Resource;

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 xxx.xxx.xxx.commons.permissionengine.entity.Role;
import xxx.xxx.xxx.commons.permissionengine.entity.User;
import xxx.xxx.xxx.commons.permissionengine.entity.UserDetailInfo;
import xxx.xxx.xxx.commons.permissionengine.service.IUserService;

public class SecurityUserDetailService implements UserDetailsService {

	private IUserService userService;
	
	/**
	 * 获取登录用户信息并添加到security上下文环境
	 */
	@Override
	public UserDetails loadUserByUsername(String name)throws UsernameNotFoundException, DataAccessException {
		//定义存放用户角色信息的集合
		Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
		//通过已经经过验证的登录用户的用户名查找登录用户信息
		User user = userService.findUserByProperty("name", name);
		//定义一个userDetailInfo对象,该类实现了spring security UserDetails接口,因为已经经过验证的登录用户会保持在
		//spring security上下文环境中,通过该上下文环境获取登录用户信息返回的是UserDetails类型,因此需要定义一个类实现该接口
		UserDetailInfo userDetailInfo = null;
		if(user != null) {
			//获取登录用户信息的角色列表
			Set<Role> roles = user.getRoles();
			for(Role role : roles) {
				//利用角色用户具有的角色的编号构造一个GrantedAuthority对象,并把该对象添加到集合中
				GrantedAuthorityImpl grantedAuthorityImpl = new GrantedAuthorityImpl("ROLE_"+role.getNo());
				authorities.add(grantedAuthorityImpl);
			}
			userDetailInfo = new UserDetailInfo();
			userDetailInfo.setUserName(user.getName());
			userDetailInfo.setPassword(user.getPassword());
			//把角色信息添加到userDetailInfo对象中
			userDetailInfo.setAuthorities(authorities);
		}
		return userDetailInfo;
	}
	
	@Resource(name = "userService")
	public void setUserService(IUserService userService) {
		this.userService = userService;
	}
}

再接着定义一个实现了FilterInvocationSecurityMetadataSource的类:

package xxx.xxx.xxx.commons.permissionengine.web.security;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

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

import xxx.xxx.xxx.commons.permissionengine.service.IMenuService;

/**
 * 资源源数据管理器
 * @author Keven
 *
 */
public class SecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
	
	private IMenuService menuService;
	//定义一个url匹配工具类
	private UrlMatcher urlMatcher = new AntUrlPathMatcher();
	
	private static Map<String, Collection<ConfigAttribute>> menuMap = null;
	
	//该构造方法由spring容器调用
	public SecurityMetadataSource() {
		loadMenuDefine();
	}
	
	private void loadMenuDefine() {
		menuMap = new HashMap<String, Collection<ConfigAttribute>>();
		//初始化匿名用户所拥有的权限
		Collection<ConfigAttribute> guestAtts = new ArrayList<ConfigAttribute>();
		ConfigAttribute guestAttribute = new SecurityConfig("ROLE_Guest");
		guestAtts.add(guestAttribute);
		menuMap.put("/showLogin.do*", guestAtts);
	}

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

	@Override
	public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
		//获取请求url
		String url = ((FilterInvocation)object).getRequestUrl();
		//从数据库获取资源与角色的对应关系,并设置初始化的资源_角色到该Map
		menuService.setMenuMap(menuMap);
		//获取资源列表
		Iterator<String> iter = menuMap.keySet().iterator();
		while(iter.hasNext()) {
			String menuUrl = iter.next();
			//防止把null值加入到map,报空指针异常
			if(menuUrl != null) {
				//请求url与角色所拥有的权限做匹配
				if(urlMatcher.pathMatchesUrl(menuUrl, url))
					return menuMap.get(menuUrl);
			}
		}
		return null;
	}

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

	@Resource(name = "menuService")
	public void setMenuService(IMenuService menuService) {
		this.menuService = menuService;
	}
}

再接着定义一个实现了AccessDecisionManager的类:

package xxx.xxx.xxx.commons.permissionengine.web.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;

/**
 * 决策管理器,用于判断用户需要访问的资源与用户所拥有的角色是否匹配
 * @author Keven
 *
 */
public class SecurityAccessDecisionManager implements AccessDecisionManager {

	@Override
	public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
		if(configAttributes == null)
			return;
		//获取资源与角色对应关系列表
		Iterator<ConfigAttribute> iter = configAttributes.iterator();
		while(iter.hasNext()) {
			ConfigAttribute configAttribute = iter.next();
			//获取访问该资源需要的角色
			String needRole = ((SecurityConfig)configAttribute).getAttribute();
			//从上下文环境获取用户所具有的角色
			for(GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
				//判断用户拥有的角色是否与访问该资源所需要的角色匹配
				if(needRole.equals(grantedAuthority.getAuthority()))
					return;
			}
		}
		throw new AccessDeniedException("权限不足!");
	}

	@Override
	public boolean supports(ConfigAttribute arg0) {
		return true;
	}

	@Override
	public boolean supports(Class<?> arg0) {
		return true;
	}
}

 spring security的基本的登录认证和授权就这样完成了,有关spring security的方法和领域对象的权限控制、单点登录和分布式登录请看下集。

猜你喜欢

转载自bh-keven.iteye.com/blog/1121205