spring security
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"
推荐阅读