栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Security方法注解权限访问控制及原理剖析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Security方法注解权限访问控制及原理剖析

spring security官方文档:https://docs.spring.io/spring-security/site/docs/5.1.4.RELEASE/reference/html5/
哦哦,原来spring的文档都是 docs.spring.io/项目名/ 这个项目名跟直接点击项目后出现在地址栏是一样的

使用案例

引入依赖


    4.0.0

    com.zzhua
    demo-security-anno
    1.0-SNAPSHOT

    war

    
        
            javax.servlet
            javax.servlet-api
            3.1.0
            provided
        

        
            javax.servlet
            jsp-api
            2.0
            provided
        

        
            org.springframework
            spring-web
            5.0.2.RELEASE
        
        
            org.springframework
            spring-webmvc
            5.0.2.RELEASE
        

		
		
            com.fasterxml.jackson.core
            jackson-databind
            2.9.0
        

        
            com.fasterxml.jackson.core
            jackson-core
            2.9.0
        

        
            com.fasterxml.jackson.core
            jackson-databind
            2.9.0
        

        
        
            org.springframework.security
            spring-security-web
            5.1.4.RELEASE
        

        
            org.springframework.security
            spring-security-config
            5.1.4.RELEASE
        

        
            org.springframework
            spring-jdbc
            5.1.4.RELEASE
        

        
            mysql
            mysql-connector-java
            5.1.47
        

        
            org.projectlombok
            lombok
            1.18.16
        

    

    
        demo-spring-security
        
            
                
                    org.apache.tomcat.maven
                    tomcat7-maven-plugin
                    2.2
                    
                        
                        8080
                        
                        UTF-8
                        
                        /
                    
                
                
                    org.apache.maven.plugins
                    maven-compiler-plugin
                    
                        1.8
                        1.8
                    
                
                
            
        
    




webapp文件下 web.xml



    
        contextClass
        org.springframework.web.context.support.AnnotationConfigWebApplicationContext
    
    
    
        org.springframework.web.context.ContextLoaderListener
    
    
    
        contextConfigLocation
        com.zzhua.config.AppConfig
    

    
    
        springMvc
        com.zzhua.config.CustomizeDispatcherServlet
        
            contextConfigLocation
            com.zzhua.config.MyWebConfig
        
        1
    
    
        springMvc
        /
    

    
    
        springSecurityFilterChain
        org.springframework.web.filter.DelegatingFilterProxy
    
    
        springSecurityFilterChain
        /*
    



index.jsp
<%--
  Created by IntelliJ IDEA.
  User: zzhua195
  Date: 2022/3/27
  Time: 16:24
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    Title


    欢迎来到index.jsp



配置 AppConfig
@Configuration
@ComponentScan("com.zzhua")
public class AppConfig {

}
CustomizeDispatcherServlet
public class CustomizeDispatcherServlet extends DispatcherServlet {
    public Class getContextClass() {
        return AnnotationConfigWebApplicationContext.class;
    }
}

MyWebConfig
@Configuration
@EnableWebMvc
@EnableGlobalMethodSecurity(prePostEnabled = true) // 因为要控制controller中的方法访问,所以此注解要加到子容器中
@ComponentScan(basePackages = "com.zzhua.controller",
                excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,
                classes = Service.class)})
public class MyWebConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        // 开启静态资源访问
        configurer.enable();
    }

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/view/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

}
MySecurityConfig
@Configuration
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
    	// 这个相当于是父类提供的一个方便暴露认证管理器的方法,它里面引用的认证管理器构建者正是要置入ProviderManager中的。
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated();
        http.formLogin().successHandler((request, response, authentication) -> {
            
            // 登录成功之后,写个消息
            response.setContentType("application/json;charset=utf8");
            response.getWriter().write("登录成功");
        });
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("zhangsan")
                .password(passwordEncoder().encode("123"))
                .authorities("r1","ROLE_admin") 
				 // .role("admin") 
				 // 踩坑: 在security中角色其实就是一种权限(authority),
				 // 只不过是在authority的字符串前面加了一个前缀“ROLE_”
				 // (见:SecurityexpressionRoot#hasAnyRole、User#roles(String... roles)),
				 // 如果打开这个注释,将会覆盖掉上面配置的权限,因为它们配置的是同一个嘛
                .and()
                .withUser("lisi")
                .password(passwordEncoder().encode("456"))
                .authorities("r2","ROLE_guest")
        ;
    }

    @Bean
    public PasswordEncoder passwordEncoder() { // 密码匹配器
        return new BCryptPasswordEncoder();
    }

    @Bean
    public Permissionevaluator permissionevaluator() { // 用于支持hasPermission表达式,
                                                       // 框架默认使用的是DenyAllPermissionevaluator(即默认全部拒绝访问)
        return new Permissionevaluator(){
            @Override
            public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
                System.out.println(authentication);
                System.out.println(targetDomainObject);
                System.out.println(permission);
                // 这里可以拿到hasPermission表达式的参数,
                // 可以访问到所拦截到的执行方法的参数,并且可以带上权限字符
                // 这里可以自定义逻辑,返回false标识拒绝访问
                return false;
            }

            @Override
            public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
                System.out.println(authentication);
                System.out.println(targetId);
                System.out.println(targetType);
                System.out.println(permission);
                // 这里可以拿到hasPermission表达式的参数,
                // 可以访问到所拦截到的执行方法的参数,并且可以带上权限字符
                // 这里可以自定义逻辑,返回false标识拒绝访问
                return false;
            }
        };
    }

}

controller IndexController
@Controller
public class IndexController {

    @RequestMapping("index")
    public String index() {
        return "index";
    }

}
RController
@RestController
@RequestMapping
public class RController {

    @RequestMapping()
    @PreAuthorize("denyAll()") // 全部拒绝访问
    public String denyAll() {
        return "denyAll";
    }

    @RequestMapping("permitAll")
    @PreAuthorize("permitAll()") // 全都允许访问
    public String permitAll() {
        return "permitAll";
    }

    @GetMapping("r1")
    @PreAuthorize("hasAuthority('r1')") // 必须具有r1的authority
    public String r1() {
        return " r1";
    }

    @GetMapping("r2")
    @PreAuthorize("hasAuthority('r2')") // 必须具有r2的authority
    public String r2() {
        return " r2";
    }


    @RequestMapping("admin")
    @PreAuthorize("hasRole('admin')") // 必须具有ROLE_admin的authority
    public String admin() {
        return "admin";
    }

    @RequestMapping("guest")
    @PreAuthorize("hasRole('guest')")// 必须具有ROLE_guest的authority
    public String guest() {
        return "guest";
    }

    @RequestMapping("isAnonymous") // 必须是匿名访问
    @PreAuthorize("isAnonymous()")
    public String isAnonymous() {
        return "isAnonymous";
    }

    @RequestMapping("isRememberMe")
    @PreAuthorize("isRememberMe()") // 必须是通过记住我才能访问
    public String isRememberMe() {
        return "isRememberMe";
    }

    @RequestMapping("isAuthenticated")
    @PreAuthorize("isAuthenticated()") // 必须认证通过才能访问(包括记住我)
    public String isAuthenticated() {
        return "isAuthenticated";
    }

    @RequestMapping("combineLogic")
    @PreAuthorize("isRememberMe() or isAuthenticated()") // 必须是通过记住我或者是认证通过才能访问(可以使用逻辑符)
    public String combineLogic() {
        return "combineLogic";
    }

    @RequestMapping("isFullyAuthenticated")
    @PreAuthorize("isFullyAuthenticated()") // 必须是通过登录认证(不包括记住我)
    public String isFullyAuthenticated() {
        return "isFullyAuthenticated";
    }

    @RequestMapping("useMethodArg")
    @PreAuthorize("#uname == principal.username") // 传的参数必须是登录身份的username属性(这里可以写表达式噢)
    public String useMethodArg(@P("uname") String username) {
        return "userMethodArg";
    }

    @RequestMapping("hasPermission1")
    @PreAuthorize("hasPermission(#contact,'admin')") // 使用了自定义的Permissionevaluator来实现,#contact可以用来引用方法中的参数
    public String hasPermission1(Contact contact) {
        return "hasPermission1";
    }

    @RequestMapping("hasPermission1-1")
    @PreAuthorize("hasPermission(#contact,#age)") // 使用了自定义的Permissionevaluator来实现,#contact可以用来引用方法中的参数
    public String hasPermission11(Contact contact,Integer age) {
        return "hasPermission1-1";
    }

    @RequestMapping("hasPermission2") // 使用了自定义的Permissionevaluator来实现
    @PreAuthorize("hasPermission(25,'com.zzhua.entity.Contact','read')")
    public String hasPermission2(Contact contact) {
        return "hasPermission2";
    }

    @RequestMapping("postAuthorize")
    @PostAuthorize("returnObject == 'postAuthorize'") // 方法执行后,再判断的权限校验,returnObject用于引用返回的结果
    public String postAuthorize(Integer flag) {
        return flag != null ? "postAuthorize" : "";
    }

    @RequestMapping("postAuthorize2")
    @PostAuthorize("hasPermission(returnObject,#flag)") //  使用了自定义的Permissionevaluator,方法执行后,再判断的权限校验,returnObject用于引用返回的结果,#flag引用方法参数
        return flag != null ? "postAuthorize" : "";
    }
    
    @RequestMapping("postFilter")
    @PostFilter("filterObject.equals('1') || filterObject.equals('4')") // 对返回的结果挨个过滤,返回false的将会被丢弃
    public List postFilter() {
        ArrayList list = new ArrayList<>();
        Collections.addAll(list, "1", "2", "3", "4");
        return list;
    }

    @RequestMapping("postFilter2")
    @PostFilter("hasPermission(filterObject, 'read')") // 对返回的结果挨个过滤,返回false的将会被丢弃
    public List postFilter2() {
        ArrayList list = new ArrayList<>();
        Collections.addAll(list, "1", "2", "3", "4");
        return list;
    }

    @RequestMapping("preFilter")
    @PreFilter(filterTarget="ids", value="filterObject%2==0") // 对传入的参数集合中的元素挨个过滤,返回false的将会被丢弃
    public List preFilter(@RequestParam(value = "ids",required = false) List ids,
                                   @RequestParam(value = "nameList", required = false) List nameList) {
        return ids;
    }

    @RequestMapping("preFilter2")
    @PreFilter(filterTarget="ids", value="hasPermission(filterObject,'admin')") // 对传入的参数集合中的元素挨个过滤,返回false的将会被丢弃
    public List preFilter2(@RequestParam(value = "ids",required = false) List ids,
                                    @RequestParam(value = "nameList", required = false) List nameList) {
        return ids;
    }


}
Contact
@Data
public class Contact {
    String name;
}
原理剖析 概述

首先,security框架使用注解实现对方法的权限访问控制,本质上是基于Spring Aop代理的方式实现的,也就是说加上这些注解的bean将会被切面切到,切到之后,使用springEL解析表达式,处理过程中security依然使用它的那一套访问决策管理器啥的,进行投票,如果没有权限,将会抛出拒绝访问异常,这和FilterSecurityInterceptor的处理逻辑是类似的。但是我们应该清楚,请求要达到加上注解的方法,那肯定是需要经过了过滤器的,所有的过滤器都要放行才能到达方法。所以可以把方法注解当做一种更加细粒度的控制方式。

在看源码之前,必须至少对Spring IOC容器、Spring Aop过程熟悉,熟悉之后,只要找到关键的组件的配置位置和配置方式,那么整个配置流程和执行过程就都清楚了。

找组件

那么找哪些组件呢

我们知道:切面 = 切点 + 增强

切点负责找到要切入的方法,即哪些bean应该要被代理

增强负责目标方法执行时,想要插入自定义的执行的逻辑

切面则是把切点和增强结合起来,以配合Spring Aop框架。

@EnableGlobalMethodSecurity
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@documented
@import({ GlobalMethodSecuritySelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableGlobalMethodSecurity {

	boolean prePostEnabled() default false;

	boolean securedEnabled() default false;

	boolean jsr250Enabled() default false;

	boolean proxyTargetClass() default false;

	AdviceMode mode() default AdviceMode.PROXY;

	int order() default Ordered.LOWEST_PRECEDENCE;
}

使用@import机制,导入了GlobalMethodSecuritySelector这个选择器

GlobalMethodSecuritySelector
final class GlobalMethodSecuritySelector implements importSelector {

	public final String[] selectimports(Annotationmetadata importingClassmetadata) {
		Class annoType = EnableGlobalMethodSecurity.class;
		Map annotationAttributes = importingClassmetadata
				.getAnnotationAttributes(annoType.getName(), false);
		AnnotationAttributes attributes = AnnotationAttributes
				.fromMap(annotationAttributes);
		Assert.notNull(attributes, () -> String.format(
				"@%s is not present on importing class '%s' as expected",
				annoType.getSimpleName(), importingClassmetadata.getClassName()));

		// TODO would be nice if could use BeanClassLoaderAware (does not work)
		Class importingClass = ClassUtils
				.resolveClassName(importingClassmetadata.getClassName(),
						ClassUtils.getDefaultClassLoader());
		boolean skipMethodSecurityConfiguration = GlobalMethodSecurityConfiguration.class
				.isAssignableFrom(importingClass);

		AdviceMode mode = attributes.getEnum("mode");
		boolean isProxy = AdviceMode.PROXY == mode;
		String autoProxyClassName = isProxy ? AutoProxyRegistrar.class
				.getName() : GlobalMethodSecurityAspectJAutoProxyRegistrar.class
				.getName();

		boolean jsr250Enabled = attributes.getBoolean("jsr250Enabled");

		List classNames = new ArrayList<>(4);
		if (isProxy) {
			classNames.add(MethodSecuritymetadataSourceAdvisorRegistrar.class.getName());
		}

		classNames.add(autoProxyClassName);

		if (!skipMethodSecurityConfiguration) {
			classNames.add(GlobalMethodSecurityConfiguration.class.getName());
		}

		if (jsr250Enabled) {
			classNames.add(Jsr250metadataSourceConfiguration.class.getName());
		}

		return classNames.toArray(new String[0]);
	}
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/782280.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号