Spring Security配置使用介绍(三)暴力动态配置权限(修改源码不推荐)

前面介绍过spring security动态配置权限,使用的方法是增加过滤拦截对权限进行处理, 此篇介绍修改源码实现动态权限,部分代码基于上一篇进行修改,帮助理解spring security。

1、此方法修改源码,不需要另外增加拦截器,我们先在applicationContext-security.xml配置好已有权限,再动态调整其权限。

applicationContext-security.xml内容如下

<?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-4.0.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd">

	<http auto-config="true" use-expressions="true" access-denied-page="/access/denied.do">
		<!-- 允许访问 -->
		<intercept-url pattern="/css/**" access="permitAll" />
		<intercept-url pattern="/js/**" access="permitAll" />
		<intercept-url pattern="/access/**" access="permitAll" /> 
		
		<!-- 已有权限 -->
		<intercept-url pattern="/admin**" access="hasRole('test_role_admin')" />
		<intercept-url pattern="/normal**" access="hasRole('test_role_normal')" />
		<intercept-url pattern="/manage**" access="hasAnyRole('test_role_admin','test_role_normal')" />
		
		<!-- 表示其他均需授权访问url -->
		<intercept-url pattern="/**" access="isAuthenticated()" />
		
		<!-- 登录url及登录失败显示url -->
		<form-login login-page="/access/login.do"
			authentication-failure-url="/access/login.do"
			authentication-success-handler-ref="loginSuccessHandler"/>
		<logout success-handler-ref="logoutSuccessHandler" />
		
	</http>
	
	<beans:bean id="loginSuccessHandler" class="com.hode.security.handler.UserLoginSuccessHandler">
		<beans:property name="defaultTargetUrl" value="/manage.do" />
	</beans:bean>
	
	<beans:bean id="logoutSuccessHandler" class="com.hode.security.handler.UserLogoutSuccessHandler">
		<beans:property name="defaultTargetUrl" value="/access/login.do" />
	</beans:bean>
	
	<authentication-manager alias="authenticationManager">
		<authentication-provider user-service-ref="userDetailsService">
			<password-encoder hash="md5" ><salt-source system-wide="hode" /></password-encoder>
		</authentication-provider>
	</authentication-manager>
	
</beans:beans>

2、下载spring security源码,对类DefaultFilterInvocationSecurityMetadataSource进行修改,可以看到此类同样有个requestMap变量,为final私有变量, 将其final去掉并增加setter和getter方法,用于动态修改权限,修改后代码如下(仅列出变更处,详情可看demo,文章尾部附下载地址)

......
	private Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;
......
	public Map<RequestMatcher, Collection<ConfigAttribute>> getRequestMap() {
		return requestMap;
	}

	public void setRequestMap(
			Map<RequestMatcher, Collection<ConfigAttribute>> requestMap) {
		this.requestMap = requestMap;
	}
......	

3、新增一个SpringUtil.java工具类,用于取bean,

SpringUtil.java

package com.hode.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringUtil implements ApplicationContextAware {

	public static ApplicationContext context;

	public static ApplicationContext getApplicationContext() {
		return context;
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		SpringUtil.context = applicationContext;
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T getBean(String name) throws BeansException{
		return (T)context.getBean(name);
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T getBean(Class<?> clazz) throws BeansException{
		return (T)context.getBean(clazz);
	}

}

4、下载spring security源码,对ExpressionBasedFilterInvocationSecurityMetadataSource进行修改,将processMap方法改为公有,同时String expression下方增加判断,

修改后代码如下(仅列出变更处,详情可看demo,文章尾部附下载地址)

......
 public static LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> processMap
......
if(expression==null) //注意首次expression若有值,attribute已转为WebExpressionConfigAttribute类型对象
            	expression = entry.getValue().toArray(new ConfigAttribute[1])[0].toString();
......

5、重新调整AccessController.java中的reload方法,内容如下

AccessController.java

package com.hode.controller;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.hode.util.SpringUtil;

@Controller
@RequestMapping("access")
public class AccessController {

	@RequestMapping("login")
	public String login(HttpServletRequest request){
		return "login"; //返回login.jsp
	}
	
	@RequestMapping("denied")
	public String denied(HttpServletRequest request){
		return "denied";
	}
	
	//此处动态配置admin访问normal.do权限
	@RequestMapping("reload")
	@ResponseBody
	public String reload(){
		try{
			FilterSecurityInterceptor interceptor = SpringUtil.getBean(FilterSecurityInterceptor.class);
			DefaultWebSecurityExpressionHandler expressionHandler = SpringUtil.getBean(DefaultWebSecurityExpressionHandler.class);
			ExpressionBasedFilterInvocationSecurityMetadataSource metadataSource = (ExpressionBasedFilterInvocationSecurityMetadataSource) interceptor.obtainSecurityMetadataSource();
	
			LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = (LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>) metadataSource.getRequestMap();
	
			//修改权限
			RequestMatcher rm = new AntPathRequestMatcher("/normal**");
    		ConfigAttribute ca = new SecurityConfig("hasAnyRole('test_role_normal','test_role_admin')");
			Collection<ConfigAttribute> value = new ArrayList<ConfigAttribute>();
			value.add(ca);
			requestMap.put(rm, value);
			
	
			LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> result = ExpressionBasedFilterInvocationSecurityMetadataSource.processMap(requestMap, expressionHandler.getExpressionParser());
			metadataSource.setRequestMap(result);
		}catch(Exception e){
			e.printStackTrace();
		}
		return "reload success";
	}

}

6、启动JettyServer,分别使用以下url访问完成动态权限测试

打开登录页面 http://localhost/access/login.do
选中admin点击提交,此时admin用户登录成功;跳转到http://localhost/manage.do中,
根据权限配置admin用户有权访问http://localhost/admin.do,而无权限访问http://localhost/normal.do(访问时将显示禁止访问页面)

同样normal用户登录成功;跳转到http://localhost/manage.do中,
根据权限配置normal用户无权访问http://localhost/admin.do,而有权限访问http://localhost/normal.do

接着动态修改权限,使用admin登录成功,并访问http://localhost/access/reload.do将完成动态权限添加。此时admin用户已有权限访问http://localhost/normal.do

Demo代码下载

结束。


赞赏(Donation)
微信(Wechat Pay)

donation-wechatpay