SpringSecurity 3.2入门(9)自定义权限控制代码实现

时间:2023-03-09 20:19:35
SpringSecurity 3.2入门(9)自定义权限控制代码实现

1、 一个自定义的filter,必须包含authenticationManager,accessDecisionManager,securityMetadataSource三个属性,我们的所有控制将在这三个类中实现 。

package cn.jxufe.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 { protected Logger logger = LoggerFactory.getLogger(getClass()); // 与applicationContext-security.xml里的myFilter的属性securityMetadataSource对应,
// 其他的两个组件,已经在AbstractSecurityInterceptor定义
private FilterInvocationSecurityMetadataSource securityMetadataSource; @Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
} public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
logger.debug("------------MyFilterSecurityInterceptor.doFilter()-----------开始拦截了....");
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
} private void invoke(FilterInvocation fi) throws IOException,
ServletException {
// object为FilterInvocation对象
// 1.获取请求资源的权限
// 执行Collection<ConfigAttribute> attributes =
// SecurityMetadataSource.getAttributes(object); logger.debug("--------------用户发送请求--------------");
InterceptorStatusToken token = null;
token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}catch (Exception e) {
}finally {
super.afterInvocation(token, null);
} logger.debug("------------MyFilterSecurityInterceptor.doFilter()-----------拦截结束了....");
} public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return securityMetadataSource;
} public void setSecurityMetadataSource(
FilterInvocationSecurityMetadataSource securityMetadataSource) {
this.securityMetadataSource = securityMetadataSource;
} public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
} public void destroy() {
// TODO Auto-generated method stub
} public Class<? extends Object> getSecureObjectClass() {
// 下面的MyAccessDecisionManager的supports方面必须放回true,否则会提醒类型错误
return FilterInvocation.class;
}
}

MySecurityFilter.java

2、用于启动时加载资源列表,还拥有判断是否拥有请求访问资源权限的方法。

package cn.jxufe.core.security;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.util.AntPathMatcher; import cn.jxufe.core.dao.BaseDao;
import cn.jxufe.core.entiry.Resource;
import cn.jxufe.core.entiry.Role; public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { protected Logger logger = LoggerFactory.getLogger(getClass()); private AntPathMatcher urlMatcher = new AntPathMatcher(); private static Map<String, Collection<ConfigAttribute>> resourceMap = null; private BaseDao baseDao; public BaseDao getBaseDao() {
return baseDao;
} public void setBaseDao(BaseDao baseDao) {
this.baseDao = baseDao;
} //由spring调用
public MyFilterInvocationSecurityMetadataSource(BaseDao baseDao) {
this.baseDao=baseDao;
loadResourceDefine();
} //加载所有资源
private void loadResourceDefine() {
logger.debug("容器启动(MySecurityMetadataSource:loadResourceDefine)");
logger.debug("--------------开始加载系统资源与权限列表数据--------------");
resourceMap = new HashMap<String,Collection<ConfigAttribute>>();
String sql="select * from t_system_resource_info";
List<Resource> resources = this.baseDao.findListBeanByArray(sql, Resource.class);
for(Resource resource : resources){
Collection<ConfigAttribute> configAttributes = null;
ConfigAttribute configAttribute = new SecurityConfig(resource.getName());
if(resourceMap.containsKey(resource.getPath())){
configAttributes = resourceMap.get(resource.getPath());
configAttributes.add(configAttribute);
}else{
configAttributes = new ArrayList<ConfigAttribute>() ;
configAttributes.add(configAttribute);
resourceMap.put(resource.getPath(), configAttributes);
}
}
logger.debug("--------------系统资源与权限列表数据加载结束--------------");
} /**
* 根据请求的资源地址,获取它所拥有的权限
*/
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
//获取请求的url地址
String requestUrl = ((FilterInvocation) object).getRequestUrl();
logger.debug("--------------取得请求资源所需权限(MySecurityMetadataSource:getAttributes)--------------");
logger.debug("--------------请求地址为:"+requestUrl+"--------------");
Iterator<String> it = resourceMap.keySet().iterator();
while(it.hasNext()){
String _url = it.next();
if(_url.indexOf("?")!=-1){
_url = _url.substring(0, _url.indexOf("?"));
}
if(urlMatcher.match(_url,requestUrl))
return resourceMap.get(_url);
}
return null; //如果是想做到没配的资源默认可以访问的话,那么就返回空或者NULL /**
* 使用下面的写法代替return null;没配的资源则不可以访问,建议开发的时候还是用上面的为好。
*/
// Collection<ConfigAttribute> returnCollection = new ArrayList<ConfigAttribute>();
// returnCollection.add(new SecurityConfig("ROLE_NO_USER"));
// return returnCollection;
} public Collection<ConfigAttribute> getAllConfigAttributes() {
Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
for (Entry<String, Collection<ConfigAttribute>> entry : resourceMap.entrySet()) {
allAttributes.addAll(entry.getValue());
}
logger.debug(allAttributes.toString());
return allAttributes;
} public Collection<ConfigAttribute> getConfigAttributes(String...value) {
return SecurityConfig.createList(value);
} public boolean supports(Class<?> clazz) {
return true;
} }

MyFilterInvocationSecurityMetadataSource.java

3、访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 ;做最终的访问控制决定。

package cn.jxufe.core.security;

import java.util.Collection;
import java.util.Iterator; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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; public class MyAccessDecisionManager implements AccessDecisionManager { protected Logger logger = LoggerFactory.getLogger(getClass()); /**
* 认证用户是否具有权限访问该url地址
*
*/
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
logger.debug("--------------匹配用户拥有权限和请求权限(MyAccessDecisionManager:decide)--------------");
logger.debug("--------------验证用户是否具有一定的权限--------------");
if(configAttributes == null) {
return;
}
//所请求的资源拥有的权限(一个资源对多个权限)
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while(iterator.hasNext()) {
ConfigAttribute configAttribute = iterator.next();
//访问所请求资源所需要的权限
String needPermission = configAttribute.getAttribute();
logger.debug("--------------访问所请求资源所需要的权限为 " + needPermission+"--------------");
//用户所拥有的权限authentication
for(GrantedAuthority ga : authentication.getAuthorities()) {
if(needPermission.equals(ga.getAuthority())) {
logger.debug("--------------权限验证通过--------------");
return;
}
}
}
//没有权限让我们去捕捉
logger.debug("--------------权限验证未通过--------------");
throw new AccessDeniedException(" 没有权限访问!");
} /**
* 启动时候被AbstractSecurityInterceptor调用,决定AccessDecisionManager是否可以执行传递ConfigAttribute。
*/
public boolean supports(ConfigAttribute attribute) {
// System.out.println("MyAccessDescisionManager.supports()------------"+attribute.getAttribute());
return true;
} /**
* 被安全拦截器实现调用,包含安全拦截器将显示的AccessDecisionManager支持安全对象的类型
*/
public boolean supports(Class<?> clazz) {
logger.debug("MyAccessDescisionManager.supports()--------------------------------");
return true;
} }

MyAccessDecisionManager.java

4、验证登录信息。

package cn.jxufe.core.security;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import cn.jxufe.core.dao.BaseDao;
import cn.jxufe.core.entiry.User; public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final String USERNAME = "username";
private static final String PASSWORD = "password";
private static final String KAPTCHA = "kaptcha"; private BaseDao baseDao; public BaseDao getBaseDao() {
return baseDao;
} public void setBaseDao(BaseDao baseDao) {
this.baseDao = baseDao;
} @Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} String username = obtainUsername(request);
String password = obtainPassword(request);
String kaptcha = obtainKaptcha(request);
logger.debug("--------------用户:" + username +"正在登录---"+kaptcha+"-----------");
// 验证码校验
String kaptchaExpected = (String) request.getSession().getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
if(!kaptcha.equals(kaptchaExpected)){
BadCredentialsException exception = new BadCredentialsException("验证码不匹配!");
throw exception;
} // 账号与密码校验
username = username.trim();
String sql="select * from t_system_user_info where username=? and password=?";
Object[] args=new Object[]{ username, password }; User users = (User) this.baseDao.findUniqueBeanByArray(sql, User.class, args);
if (users == null || !users.getPassword().equals(password)) {
BadCredentialsException exception = new BadCredentialsException("用户名或密码不匹配!");
throw exception;
}
// 实现 Authentication
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// 允许子类设置详细属性
setDetails(request, authRequest);
// 运行UserDetailsService的loadUserByUsername 再次封装Authentication
return this.getAuthenticationManager().authenticate(authRequest);
} @Override
protected String obtainUsername(HttpServletRequest request) {
Object obj = request.getParameter(USERNAME);
return null == obj ? "" : obj.toString();
} @Override
protected String obtainPassword(HttpServletRequest request) {
Object obj = request.getParameter(PASSWORD);
return null == obj ? "" : obj.toString();
} protected String obtainKaptcha(HttpServletRequest request) {
Object obj = request.getParameter(KAPTCHA);
return null == obj ? "" : obj.toString();
} @Override
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
super.setDetails(request, authRequest);
}
}

MyUsernamePasswordAuthenticationFilter.java

5、登录成功后,根据用户名,返回一个Userdetail。

package cn.jxufe.core.security;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
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.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component; import cn.jxufe.core.dao.BaseDao;
import cn.jxufe.core.entiry.Resource; @Component("userDetailsService")
public class MyUserDetailsService implements UserDetailsService { protected Logger logger = LoggerFactory.getLogger(getClass()); @Autowired
private JdbcTemplate jdbcTemplate; @Autowired
private BaseDao baseDao; //登录验证
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
logger.debug("------------验证并授权(MyUserDetailsServiceImpl:loadUserByUsername)------------"); // 根据用户名获取帐户和权限信息
String sql = "SELECT username,password,enabled,name rname " +
"FROM t_system_user_info u,t_system_role_info r,t_system_user_role ur " +
"WHERE u.id=ur.user_id AND r.id=ur.role_id AND username= ?";
// 如果一个用户具有多个权限,连接查询会返回一个List
List list =this.jdbcTemplate.queryForList(sql, new Object[] { username }); // 取出帐户和权限信息填充到User中返回
if (list == null || list.size() <= 0)
// spring-security定义的异常
throw new UsernameNotFoundException("用户不存在!");
// 如果用户存在
Map<String, Object> map = (Map<String, Object>) list.get(0);
// 密码
String password = (String) map.get("password");
// 帐户是否可用
boolean enabled = ((Integer) map.get("enabled") == 1);
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
// 帐户所具有的权限 Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();
List<Resource> resources=this.getResourceByUsername(username);
logger.debug("------------用户所拥有权限------------");
for (int i=0;i<resources.size();i++) {
GrantedAuthority authority = new SimpleGrantedAuthority(resources.get(i).getName());
authSet.add(authority);
}
logger.debug("------------"+authSet.toString()+"------------");
// spring-security提供的类
User userdetail = new User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authSet);
return userdetail; } private List<Resource> getResourceByUsername(String username){
String sql ="SELECT * FROM t_system_resource_info WHERE id IN(" +
"SELECT DISTINCT resource_id FROM t_system_authority_resource WHERE authority_id IN(" +
"SELECT authority_id FROM t_system_role_authority WHERE role_id IN(" +
"SELECT role_id FROM t_system_user_role WHERE user_id =( " +
"SELECT id FROM t_system_user_info WHERE username= ? ))))";
List<Resource> list =this.baseDao.findListBeanByArray(sql, Resource.class, new Object[] { username });
return list;
} }

MyUserDetailsService.java

6、配置信息

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
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.2.xsd"> <!-- 配置不过滤的资源(静态资源及登录相关) -->
<security:http pattern="/**/*.css" security="none" />
<security:http pattern="/**/*.js" security="none" />
<security:http pattern="/**/*.jpg" security="none" />
<security:http pattern="/**/*.jpeg" security="none" />
<security:http pattern="/**/*.gif" security="none" />
<security:http pattern="/**/*.png" security="none" />
<security:http pattern="/favicon.ico" security="none" />
<!-- 不过滤验证码 -->
<security:http pattern="/captcha-image.htm" security="none" />
<!-- 不过滤登录页面 -->
<security:http pattern="/login.htm" security="none" />
<security:http pattern="/login.jsp" security="none" />
<!-- 不过滤首页 -->
<security:http pattern="/index.htm" security="none" />
<security:http pattern="/index.jsp" security="none" /> <!-- 配置SpringSecurity的http安全服务 -->
<!-- 使用了 use-expressions="true 则 需使用hasRole('ROLE_USER')-->
<!-- 配置了auto-config="true"loginFilter报错,如果你没有自定义的登录页面,它就会跳转到security默认的登录页面中。 -->
<security:http access-denied-page="/accessDenied.jsp" entry-point-ref="authenticationProcessingFilterEntryPoint">
<security:session-management>
<security:concurrency-control
max-sessions="1" />
</security:session-management> <!-- 检测失效的sessionId,session超时时,定位到另外一个URL -->
<security:session-management
invalid-session-url="/sessionTimeOut.jsp" /> <!-- 配置登出信息,指定退出系统后,跳转页面 -->
<security:logout logout-url="/logout"
logout-success-url="/login.htm" invalidate-session="true" /> <!-- 认证和授权 -->
<security:custom-filter ref="myLoginFilter" position="FORM_LOGIN_FILTER" />
<security:custom-filter ref="securityFilter" before="FILTER_SECURITY_INTERCEPTOR"/> </security:http> <!-- 认证管理器,配置SpringSecutiry的权限信息 -->
<security:authentication-manager>
<security:authentication-provider>
<!-- 使用数据库中的用户名和密码 -->
<security:jdbc-user-service
data-source-ref="dataSource" />
</security:authentication-provider>
</security:authentication-manager> <!-- 验证配置 , 认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 -->
<security:authentication-manager alias="myAuthenticationManager">
<!-- 使用自己数据库中的用户和角色表,获取用户拥有的权限 -->
<security:authentication-provider
user-service-ref="myUserDetailsServiceImpl" />
</security:authentication-manager> <!-- 登录验证器 -->
<bean id="myLoginFilter"
class="cn.jxufe.core.security.MyUsernamePasswordAuthenticationFilter">
<!-- 处理登录 -->
<property name="filterProcessesUrl" value="/j_spring_security_check"></property>
<property name="usernameParameter" value="username"></property>
<property name="passwordParameter" value="password"></property>
<property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler"></property>
<property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"></property>
<property name="authenticationManager" ref="myAuthenticationManager"></property>
<property name="baseDao" ref="baseDao"></property>
</bean> <bean id="loginLogAuthenticationSuccessHandler"
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/index.jsp"></property>
</bean>
<bean id="simpleUrlAuthenticationFailureHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<property name="defaultFailureUrl" value="/login.jsp"></property>
</bean> <!-- 认证过滤器 -->
<bean id="securityFilter" class="cn.jxufe.core.security.MySecurityFilter">
<!-- 用户拥有的权限 -->
<property name="authenticationManager" ref="myAuthenticationManager" />
<!-- 用户是否拥有所请求资源的权限 -->
<property name="accessDecisionManager" ref="myAccessDecisionManager" />
<!-- 资源与权限对应关系 -->
<property name="securityMetadataSource" ref="myFilterInvocationSecurityMetadataSource" />
</bean> <bean id="myUserDetailsServiceImpl" class="cn.jxufe.core.security.MyUserDetailsService" />
<bean id="myAccessDecisionManager" class="cn.jxufe.core.security.MyAccessDecisionManager"/>
<bean id="myFilterInvocationSecurityMetadataSource" class="cn.jxufe.core.security.MyFilterInvocationSecurityMetadataSource">
<constructor-arg name="baseDao" ref="baseDao" />
</bean> <bean id="baseDao" class="cn.jxufe.core.dao.BaseDaoImpl" /> <!-- 定义上下文返回的消息的国际化 -->
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename"
value="classpath:org/springframework/seurity/messages_zh_CN" />
</bean> <!-- 未登录的切入点 -->
<bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.jsp"/>
</bean> </beans>

(详细的拦截过程将会在下一章中学习)。