0
点赞
收藏
分享

微信扫一扫

13.Spring security权限管理

ZMXQQ233 2022-09-12 阅读 375

文章目录

权限管理

13.1什么是权限管理

13.2Spring security权限管理策略

13.3核心概念

13.3.1角色与权限

public class Role implements GrantedAuthority {
	private String name;
	private List<SimpleGrantedAuthority> allowedOperations = new ArrayList<>();

	@Override
	public String getAuthority() {
		return name;
	}
	
	// 省略getter/setter
}
public class User implements UserDetails {
	private List<Role> roles = new ArrayList<>();

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		List<SimpleGrantedAuthority> authorities= new ArrayList<>();
	
		for (Role role : roles) {
			authorities.addAll(role.getAllowedOperations());
		}
		
		return authorities.stream().distinct().collect(Collectors.toList());
	}

	// 省略getter/setter
}

13.3.2角色继承

public interface RoleHierarchy {
	/**
	 * 该方法返回用户真正可触达的权限。例如,假设用户定义了ROLE_ADMIN继承自ROLE_USER,ROLE_USER继承自ROLE_GUEST,
	 * 现在当前用户角色是ROLE_ADMIN,但是它实际可访问的资源也包含ROLE_USER和ROLE_GUEST能访问的资源。该方法就是根据
	 * 当前用户所具有的角色,从角色层级映射中解析出用户真正可触达的权限。
	 */
	Collection<? extends GrantedAuthority> getReachableGrantedAuthorities(
			Collection<? extends GrantedAuthority> authorities);

}
@Bean
RoleHierarchy roleHierarchy() {
	RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
	// ROLE_A继承自ROLE_B,ROLE_B继承自ROLE_C,ROLE_C继承自ROLE_D
	hierarchy.setHierarchy("ROLE_A > ROLE_B > ROLE_C > ROLE_D");
	return hierarchy;
}
ROLE_A -> ROLE_B
ROLE_B -> ROLE_C
ROLE_C -> ROLE_D
ROLE_A -> [ROLE_B, ROLE_C, ROLE_D]
ROLE_B -> [ROLE_C, ROLE_D]
ROLE_C -> ROLE_D

13.3.3两种处理器

在这里插入图片描述

13.3.4前置处理器

投票器
public interface AccessDecisionVoter<S> {
	// 表示投票通过
	int ACCESS_GRANTED = 1;
	// 表示弃权
	int ACCESS_ABSTAIN = 0;
	// 表示拒绝
	int ACCESS_DENIED = -1;
	// 用来判断是否支持处理ConfigAttribute对象
	boolean supports(ConfigAttribute attribute);
	// 用来判断是否支持处理受保护的安全对象
	boolean supports(Class<?> clazz);
	/**
	 * 具体的投票方法,根据用户所具有的权限以及当前请求需要的权限进行投票。
	 * @param authentication 进行调用的调用者,可以提取出来当前用户所具备的权限
	 * @param object 受保护的安全对象,如果受保护的是URL地址,则object就是一个FilterInvocation对象;如果受保护的是一个
	 * 方法,则object就是一个MethodInvocation对象
	 * @param attributes 访问受保护对象所需要的权限
	 * @return 定义的三个常量之一
	 */
	int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
}

在这里插入图片描述

决策器
public interface AccessDecisionManager {
	// 核心的决策方法,在这个方法中判断是否允许当前URL或者方法的调用,如果不允许,则抛出AccessDeniedException异常
	void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
			throws AccessDeniedException, InsufficientAuthenticationException;
	// 用来判断是否支持处理ConfigAttribute对象
	boolean supports(ConfigAttribute attribute);
	// 用来判断是否支持当前安全对象
	boolean supports(Class<?> clazz);
}

在这里插入图片描述

13.3.5后置处理器

// 和AccessDecisionManager高度相似
public interface AfterInvocationManager {
	/**
	 * 主要的区别在于decide方法的参数和返回值。当后置处理器执行时,被权限保护的方法以及执行完毕,后置处理器主要是对执行的结果
	 * 进行过滤,所以decide方法中有一个returnedObject参数,这就是目标方法的执行结果,decide方法的返回值就是对returnedObject
	 * 对象进行过滤/鉴权后的结果
	 */
	Object decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes,
			Object returnedObject) throws AccessDeniedException;

	boolean supports(ConfigAttribute attribute);

	boolean supports(Class<?> clazz);
}

在这里插入图片描述

13.3.6权限元数据

ConfigAttribute
public interface ConfigAttribute extends Serializable {
	String getAttribute();
}

在这里插入图片描述

SecurityMetadataSource
public interface SecurityMetadataSource extends AopInfrastructureBean {
	/**
	 * 根据传入的安全对象参数返回其所需要的权限。如果受保护的对象是一个URL地址,那么传入的参数object就是一个FilterInvocation
	 * 对象;如果受保护的是一个方法,那么object就是一个MethodInvocation对象。
	 */
	Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException;
	// 返回所有的角色/权限,以便验证是否支持。不过这个方法不是必须的,也可以直接返回null
	Collection<ConfigAttribute> getAllConfigAttributes();
	// 返回当前的SecurityMetadataSource是否支持受保护的对象如FilterInvocation或者MethodInvocation
	boolean supports(Class<?> clazz);
}

在这里插入图片描述

private final Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;
http.autorizeRequests()
	// 访问/admin/**格式的URL地址需要admin角色
	.antMatchers("/admin/**").hasRole("admin")
	// 访问/user/**格式的URL地址需要user角色
	.antMatchers("/user/**").access("hasRole('user')")
	// 其余地址认证后即可访问
	.anyRequest().access("isAuthenticated()")

13.3.7权限表达式

配置类名称

作用

hasRole(String role)

当前用户是否具备指定角色

hasAnyRole(String... roles)

当前用户是否具备指定角色中的任意一个

hasAuthority(String authority)

当前用户是否具备指定的权限

hasAnyAuthority(String... authorities)

当前用户是否具备指定权限中的任意一个

principal

代表当前登录主体Principal

authentication

这个是从SecurityContext中获取到的Authentication对象

permitAll

允许所有的请求/调用

denyAll

拒绝所有的请求/调用

isAnonymouse()

当前用户是否是一个匿名用户

isRememberMe()

当前用户是否是通过remember-me自动登录

isAuthenticated()

当前用户是否已经认证成功

isFullyAuthenticated()

当前用户是否既不是匿名用户又不是通过remember-me自动登录的

hasPermission(Object target, Object permission)

当前用户是否具备指定目标的指定权限

hasPermission(Object targetId, String targetType, Object permission)

当前用户是否具备指定目标的指定权限

hasIpAddress(String ipAddress)

当前请求IP地址是否为指定IP

public interface SecurityExpressionOperations {
	Authentication getAuthentication();

	boolean hasAuthority(String authority);

	boolean hasAnyAuthority(String... authorities);

	boolean hasRole(String role);

	boolean hasAnyRole(String... roles);

	boolean permitAll();

	boolean denyAll();

	boolean isAnonymous();

	boolean isAuthenticated();

	boolean isRememberMe();

	boolean isFullyAuthenticated();

	boolean hasPermission(Object target, Object permission);

	boolean hasPermission(Object targetId, String targetType, Object permission);
}

在这里插入图片描述

SecurityExpressionRoot
WebSecurityExpressionRoot
MethodSecurityExpressionOperations
MethodSecurityExpressionRoot

13.4基于URL地址的权限管理

13.4.1基本用法

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 定义用户以及相应的角色和权限。
     * 对于复杂的权限管理系统,用户和角色关联,角色和权限关联,权限和资源关联;对于简单的权限管理系统,
     * 用户和权限关联,权限和资源关联。无论是哪种,用户都不会和角色以及权限同时直接关联。反应到代码上
     * 就是roles方法和authorities方法不能同时调用,如果同时调用,后者会覆盖前者(可以自行查看源码,
     * 最终都会调用authorities(Collection<? extends GrantedAuthority> authorities)方法)。
     * 需要注意的是,spring security会自动给用户的角色加上ROLE_前缀。
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                // javaboy具有ADMIN角色
                .withUser("javaboy").password("{noop}123").roles("ADMIN")
                .and()
                // zhangsan具有USER角色
                .withUser("zhangsan").password("{noop}123").roles("USER")
                .and()
                // itboyhub具有READ_INFO权限
                .withUser("itboyhub").password("{noop}123").authorities("READ_INFO");
    }

    /**
     * 配置拦截规则
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 用户必须具备ADMIN角色才可以访问/admin/**格式的地址
                .antMatchers("/admin/**").hasRole("ADMIN")
                // 用户必须具备USER和ADMIN任意一个角色,才可以访问/user/**格式的地址
                .antMatchers("/user/**").access("hasAnyRole('USER', 'ADMIN')")
                // 用户必须具备READ_INFO权限,才可以访问/getInfo接口
                .antMatchers("/getInfo").hasAuthority("READ_INFO")
                // 剩余的请求只要是认证后的用户就可以访问
                .anyRequest().access("isAuthenticated()")
                .and()
                .formLogin()
                .and()
                .csrf().disable();
    }
}
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello";
    }

    @GetMapping("/admin/hello")
    public String admin() {
        return "Hello admin";
    }

    @GetMapping("/user/hello")
    public String user() {
        return "Hello user";
    }

    @GetMapping("/getInfo")
    public String getInfo() {
        return "GetInfo";
    }
}

13.4.2角色继承

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 如果需要配置角色继承,则只需要提供一个RoleHierarchy的实例即可
     */
    @Bean
    RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
        hierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
        return hierarchy;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 省略
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                // ROLE_ADMIN继承自ROLE_USER,因此可以直接访问/user/**格式的地址
                .antMatchers("/user/**").access("hasRole('USER')")
                .antMatchers("/getInfo").hasAuthority("READ_INFO")
                .anyRequest().access("isAuthenticated()")
                .and()
                .formLogin()
                .and()
                .csrf().disable();
    }
}

13.4.3自定义表达式

@RestController
public class HelloController {
    /**
     * 第一个接口:参数userId必须是偶数方可请求成功。
     */
    @GetMapping("/hello/{userId}")
    public String hello(@PathVariable Integer userId) {
        return "Hello " + userId;
    }

    /**
     * 第二个接口:参数username必须是javaboy方可请求成功,同时这两个接口必须认证后才能访问。
     */
    @GetMapping("/hi")
    public String hello2User(String username) {
        return "Hello " + username;
    }
}

/**
 * 自定义PermissionExpression类并注册到spring容器中,然后定义相应的方法。
 */
@Component
public class PermissionExpression {
    public boolean checkId(Authentication authentication, Integer userId) {
        if (authentication.isAuthenticated()) {
            return userId % 2 == 0;
        }

        return false;
    }

    public boolean check(HttpServletRequest request) {
        return "javaboy".equals(request.getParameter("username"));
    }
}

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 省略
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 省略其他
                // 在access方法中,可以通过@符号引用一个bean并调用其中的方法。在checkId方法调用时,
                // #userId表示前面的userId参数
                .antMatchers("/hello/{userId}").access("@permissionExpression.checkId(authentication, #userId)")
                // 需要同时满足isAuthenticated和check方法都为true,该请求才会通过
                .antMatchers("/hi").access("isAuthenticated() and @permissionExpression.check(request)")
                // 省略其他
    }
}

13.4.4原理剖析

AbstractSecurityInterceptor
protected InterceptorStatusToken beforeInvocation(Object object) {
	if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
		throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName()
				+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
				+ getSecureObjectClass());
	}

	// 首先调用obtainSecurityMetadataSource方法获取SecurityMetadataSource对象,然后调用其getAttributes方法获取
	// 受保护对象所需要的权限
	Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
	 
	// 如果获取到的权限值为空
	if (CollectionUtils.isEmpty(attributes)) {
		// 则发布PublicInvocationEvent事件,该事件在调用公共安全对象(没有定义ConfigAttributes的对象)时生成
		publishEvent(new PublicInvocationEvent(object));
		// 此时直接返回null即可
		return null; // no further work post-invocation
	}
	
	// 查看当前用户的认证信息是否存在
	if (SecurityContextHolder.getContext().getAuthentication() == null) {
		credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
				"An Authentication object was not found in the SecurityContext"), object, attributes);
	}
	
	// 检查当前用户是否已经登录
	Authentication authenticated = authenticateIfRequired();
	// 尝试授权
	attemptAuthorization(object, attributes, authenticated);

	if (this.publishAuthorizationSuccess) {
		publishEvent(new AuthorizedEvent(object, attributes, authenticated));
	}

	// 临时替换用户身份,不过默认情况下,runAsManager的实例是NullRunAsManager,即不做任何替换,所以runAs为null
	Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
	
	if (runAs != null) {
		// 如果runAs不为空,则将SecurityContext中保存的用户信息修改为替换的用户对象,然后返回一个InterceptorStatusToken。
		// InterceptorStatusToken对象中保存了当前用户的SecurityContext对象,假如进行了临时用户替换,在替换完成后,最终
		// 还是要恢复成当前用户身份的
		SecurityContext origCtx = SecurityContextHolder.getContext();
		SecurityContext newCtx = SecurityContextHolder.createEmptyContext();
		newCtx.setAuthentication(runAs);
		SecurityContextHolder.setContext(newCtx);
		// need to revert to token.Authenticated post-invocation
		return new InterceptorStatusToken(origCtx, true, attributes, object);
	}

	// 如果runAs为空,则直接创建一个InterceptorStatusToken对象返回即可
	return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
}

private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes,
		Authentication authenticated) {
	try {
		// 核心功能:进行决策,该方法中会调用投票器进行投票,如果该方法执行抛出异常,则说明权限不足
		this.accessDecisionManager.decide(authenticated, object, attributes);
	}
	catch (AccessDeniedException ex) {
		publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex));
		throw ex;
	}
}
/**
 * 如果临时替换了用户身份,那么最终要将用户身份恢复,finallyInvocation方法所做的事情就是恢复用户身份。这里的参数token就是
 * beforeInvocation方法的返回值,用户原始的身份信息都保存在token中,从token中取出用户身份信息,并设置到SecurityContextHolder
 * 中去即可。
 */
protected void finallyInvocation(InterceptorStatusToken token) {
	if (token != null && token.isContextHolderRefreshRequired()) {
		SecurityContextHolder.setContext(token.getSecurityContext());
	}
}
/**
 * 该方法接收两个参数,第一个参数token就是beforeInvocation方法的返回值,第二个参数returnedObject则是受保护对象的返回值,
 * afterInvocation方法的核心工作就是调用afterInvocationManager.decide方法对returnedObject进行过滤,然后将过滤后的
 * 结果返回。
 */
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
	if (token == null) {
		// public object
		return returnedObject;
	}
	finallyInvocation(token); // continue to clean in this method for passivity
	if (this.afterInvocationManager != null) {
		// Attempt after invocation handling
		try {
			returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(),
					token.getSecureObject(), token.getAttributes(), returnedObject);
		}
		catch (AccessDeniedException ex) {
			publishEvent(new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(),
					token.getSecurityContext().getAuthentication(), ex));
			throw ex;
		}
	}
	return returnedObject;
}
FilterSecurityInterceptor
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	// 构建受保护对象FilterInvocation,然后调用invoke方法
	invoke(new FilterInvocation(request, response, chain));
}

public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
	// 判断当前过滤器是否已经执行过,如果是,则继续执行剩下的过滤器
	if (isApplied(filterInvocation) && this.observeOncePerRequest) {
		// filter already applied to this request and user wants us to observe
		// once-per-request handling, so don't re-do security checking
		filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
		return;
	}
	
	// first time this request being called, so perform security checking
	if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
		filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
	}
	
	// 调用父类的beforeInvocation方法进行权限校验
	InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
	
	try {
		// 校验通过后,继续执行剩余的过滤器
		filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
	}
	finally {
		// 调用父类的finallyInvocation方法
		super.finallyInvocation(token);
	}
	
	// 最后调用父类的afterInvocation方法,可以看到,前置处理器和后置处理器都是在invoke方法中触发的
	super.afterInvocation(token, null);
}
AbstractInterceptUrlConfigurer
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .antMatchers("/user/**").access("hasAnyRole('USER', 'ADMIN')")
			// ...
}
@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .and()
                .formLogin()
                .and()
                .csrf().disable();
    }
@Override
protected void configure(HttpSecurity http) throws Exception {
    ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class);
    
    // 开发者手动创建一个UrlAuthorizationConfigurer对象出来,并调用其getRegistry方法去开启URL路径和权限之间映射关系的配置。
    // 由于UrlAuthorizationConfigurer中使用的投票器是RoleVoter和AuthenticatedVoter,所以这里的角色需要自带ROLE_前缀
    // (因为RoleVoter的supports方法中会判断角色是否带有ROLE_前缀)
    http.apply(new UrlAuthorizationConfigurer<>(applicationContext))
            .getRegistry()
            .mvcMatchers("/admin/**").access("ROLE_ADMIN")
            .mvcMatchers("/user/**").access("ROLE_USER");
    http.formLogin()
            .and()
            .csrf().disable();
}

13.4.5动态管理权限规则

13.4.5.1数据库设计

在这里插入图片描述

CREATE TABLE `menu` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pattern` varchar(128) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `menu` (`id`, `pattern`)
VALUES
	(1,'/admin/**'),
	(2,'/user/**'),
	(3,'/guest/**');

CREATE TABLE `menu_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `mid` int(11) DEFAULT NULL,
  `rid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `mid` (`mid`),
  KEY `rid` (`rid`),
  CONSTRAINT `menu_role_ibfk_1` FOREIGN KEY (`mid`) REFERENCES `menu` (`id`),
  CONSTRAINT `menu_role_ibfk_2` FOREIGN KEY (`rid`) REFERENCES `role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `menu_role` (`id`, `mid`, `rid`)
VALUES
	(1,1,1),
	(2,2,2),
	(3,3,3),
	(4,3,2);

CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) DEFAULT NULL,
  `nameZh` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `role` (`id`, `name`, `nameZh`)
VALUES
	(1,'ROLE_ADMIN','系统管理员'),
	(2,'ROLE_USER','普通用户'),
	(3,'ROLE_GUEST','游客');

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `enabled` tinyint(1) DEFAULT NULL,
  `locked` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `user` (`id`, `username`, `password`, `enabled`, `locked`)
VALUES
	(1,'admin','{noop}123',1,0),
	(2,'user','{noop}123',1,0),
	(3,'javaboy','{noop}123',1,0);

CREATE TABLE `user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) DEFAULT NULL,
  `rid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `uid` (`uid`),
  KEY `rid` (`rid`),
  CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user` (`id`),
  CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`rid`) REFERENCES `role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `user_role` (`id`, `uid`, `rid`)
VALUES
	(1,1,1),
	(2,1,2),
	(3,2,2),
	(4,3,3);
13.4.5.2实战
项目创建
创建实体类
public class Role {
    private Integer id;
    private String name;
    private String nameZh;
    // 省略getter/setter
}

public class Menu {
    private Integer id;
    private String pattern;
    private List<Role> roles;
	// 省略getter/setter
}

public class User implements UserDetails {
    private Integer id;
    private String password;
    private String username;
    private boolean enabled;
    private boolean locked;
    private List<Role> roles;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles.stream().map(r -> new SimpleGrantedAuthority(r.getName())).collect(Collectors.toList());
    }

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

    @Override
    public boolean isAccountNonLocked() {
        return !locked;
    }

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

    @Override
    public boolean isEnabled() {
        return enabled;
    }
	// 省略其他getter/setter
}
创建service
@Service
public class UserService implements UserDetailsService {
    @Autowired
    UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        user.setRoles(userMapper.getUserRoleByUid(user.getId()));
        return user;
    }
}

@Mapper
public interface UserMapper {
    List<Role> getUserRoleByUid(Integer uid);

    User loadUserByUsername(String username);
}


<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fuxiao.part13_4_5.mapper.UserMapper">

    <select id="loadUserByUsername" resultType="com.fuxiao.part13_4_5.bean.User">
        select * from user where username=#{username};
    </select>

    <select id="getUserRoleByUid" resultType="com.fuxiao.part13_4_5.bean.Role">
        select r.* from role r,user_role ur where ur.uid=#{uid} and ur.rid=r.id
    </select>
</mapper>


@Service
public class MenuService {
    @Autowired
    MenuMapper menuMapper;

    public List<Menu> getAllMenu() {
        return menuMapper.getAllMenu();
    }
}

@Mapper
public interface MenuMapper {
    List<Menu> getAllMenu();
}


<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fuxiao.part13_4_5.mapper.MenuMapper">

    <resultMap id="MenuResultMap" type="com.fuxiao.part13_4_5.bean.Menu">
        <id property="id" column="id"/>
        <result property="pattern" column="pattern"></result>
        <collection property="roles" ofType="com.fuxiao.part13_4_5.bean.Role">
            <id column="rid" property="id"/>
            <result column="rname" property="name"/>
            <result column="rnameZh" property="nameZh"/>
        </collection>
    </resultMap>

    <select id="getAllMenu" resultMap="MenuResultMap">
        select m.*,r.id as rid,r.name as rname,r.nameZh as rnameZh from menu m left join menu_role mr on m.`id`=mr.`mid` left join role r on r.`id`=mr.`rid`
    </select>

</mapper>
配置spring security
/**
 * SecurityMetadataSource接口负责提供受保护对象所需要的权限。由于该案例中,受保护对象所需要的权限保存在数据库中,所以可以通过自定义类继承自
 * FilterInvocationSecurityMetadataSource,并重写getAttributes方法来提供受保护对象所需要的权限。
 */
@Component
public class CustomSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Autowired
    MenuService menuService;

    AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * 在基于URL地址的权限控制中,受保护对象就是FilterInvocation。
     * @param object 受保护对象
     * @return 受保护对象所需要的权限
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        // 从受保护对象FilterInvocation中提取出当前请求的URI地址,例如/admin/hello
        String requestURI = ((FilterInvocation) object).getRequest().getRequestURI();
        // 查询所有菜单数据(每条数据中都包含访问该条记录所需要的权限)
        List<Menu> allMenu = menuService.getAllMenu();

        // 遍历菜单数据,如果当前请求的URL地址和菜单中某一条记录的pattern属性匹配上了(例如/admin/hello匹配上/admin/**)
        // 那么就可以获取当前请求所需要的权限;如果没有匹配上,则返回null。需要注意的是,如果AbstractSecurityInterceptor
        // 中的rejectPublicInvocations属性为false(默认值)时,则表示当getAttributes返回null时,允许访问受保护对象
        for (Menu menu : allMenu) {
            if (antPathMatcher.match(menu.getPattern(), requestURI)) {
                String[] roles = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
                return SecurityConfig.createList(roles);
            }
        }

        return null;
    }

    /**
     * 方便在项目启动阶段做校验,如果不需要校验,则直接返回null即可。
     * @return 所有的权限属性
     */
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    /**
     * 表示当前对象支持处理的受保护对象是FilterInvocation。
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    CustomSecurityMetadataSource customSecurityMetadataSource;
    @Autowired
    UserService userService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class);
        http.apply(new UrlAuthorizationConfigurer<>(applicationContext))
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        // 使用配置好的CustomSecurityMetadataSource来代替默认的SecurityMetadataSource对象
                        object.setSecurityMetadataSource(customSecurityMetadataSource);
                        // 将rejectPublicInvocations设置为true,表示当getAttributes返回null时,不允许访问受保护对象
                        object.setRejectPublicInvocations(true);
                        return object;
                    }
                });
        http.formLogin()
                .and()
                .csrf().disable();
    }
}
测试
@RestController
public class HelloController {
    @GetMapping("/admin/hello")
    public String admin() {
        return "Hello admin";
    }

    @GetMapping("/user/hello")
    public String user() {
        return "Hello user";
    }

    @GetMapping("/guest/hello")
    public String guest() {
        return "Hello guest";
    }

	/**
     * 由于rejectPublicInvocations设置为true,因此,只有具备该接口权限的用户才能访问
     */
    @GetMapping("/hello")
    public String hello() {
        return "Hello";
    }
}

13.5基于方法的权限管理

13.5.1注解介绍

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}

13.5.2基本用法

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
}
public class User {
    private Integer id;
    private String username;
    // 省略构造器、toString和getter/setter
}
@PreAuthorize
@Service
public class HelloService {
	// 执行该方法必须具备ADMIN角色才可以访问
    @PreAuthorize("hasRole('ADMIN')")
    public String preAuthorizeTest01() {
        return "Hello";
    }

    // 访问者名称必须是javaboy,而且还需要同事具备ADMIN角色才可以访问
    @PreAuthorize("hasRole('ADMIN') and authentication.name == 'javaboy'")
    public String preAuthorizeTest02() {
        return "Hello";
    }

    // 通过#引用方法参数,并对其进行校验,表示请求者的用户名必须等于方法参数name的值,方法才可以被执行
    @PreAuthorize("authentication.name == #name")
    public String preAuthorizeTest03(String name) {
        return "Hello: " + name;
    }
}

@SpringBootTest
class BasedOnMethodApplicationTests {
	@Autowired
    HelloService helloService;

    @Test
    // 通过该注解设定当前执行的用户角色是ADMIN
    @WithMockUser(roles = "ADMIN")
    void preAuthorizeTest01() {
        String hello = helloService.preAuthorizeTest01();
        Assertions.assertNotNull(hello);
        Assertions.assertEquals("Hello", hello);
    }

    @Test
    @WithMockUser(roles = "ADMIN", username = "javaboy")
    void preAuthorizeTest02() {
        String hello = helloService.preAuthorizeTest02();
        Assertions.assertNotNull(hello);
        Assertions.assertEquals("Hello", hello);
    }

    @Test
    @WithMockUser(username = "javaboy")
    void preAuthorizeTest03() {
        String hello = helloService.preAuthorizeTest03("javaboy");
        Assertions.assertNotNull(hello);
        Assertions.assertEquals("Hello: javaboy", hello);
    }
}
@PreFilter
@Service
public class HelloService {
	/**
     * PreFilter主要是对方法的请求参数进行过滤,它里边包含了一个内置对象filterObject,表示具体元素。
     * 如果方法只有一个参数,则内置的filterObject对象就代表该参数;如果有多个参数,则需要通过filterTarget
     * 来指定filterObject到底代表哪个对象。
     * 表示只保留id为奇数的user对象。
     */
    @PreFilter(filterTarget = "users", value = "filterObject.id % 2 != 0")
    public void preFilterTest(List<User> users) {
        System.out.println("users = " + users);
    }
}


@Test
@WithMockUser(username = "javaboy")
void preFilterTest() {
    List<User> users = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        users.add(new User(i, "javaboy: " + i));
    }
    helloService.preFilterTest(users);
}
@PostAuthorize
@Service
public class HelloService {
	/**
     * PostAuthorize是在目标方法执行之后进行权限校验,其实这个主要是在ACL权限模型中会用到,目标方法执行完毕后,
     * 通过该注解去校验目标方法的返回值是否满足相应的权限要求。从技术角度来讲,该注解中也可以使用权限表达式,但是
     * 在实际开发中权限表达式一般都是结合PreAuthorize注解一起使用的。PostAuthorize包含一个内置对象returnObject,
     * 表示方法的返回值,开发中可以对返回值进行校验。
     */
    @PostAuthorize("returnObject.id == 1")
    public User postAuthorizeTest(Integer id) {
        return new User(id, "javaboy");
    }
}


@Test
@WithMockUser(username = "javaboy")
void postAuthorizeTest() {
    User user = helloService.postAuthorizeTest(1);
    Assertions.assertNotNull(user);
    Assertions.assertEquals(1, user.getId());
    Assertions.assertEquals("javaboy", user.getUsername());
}
@PostFilter
@Service
public class HelloService {
	/**
     * PostFilter注解在目标方法执行之后,对目标方法的返回结果进行过滤,该注解中包含了一个内置对象filterObject,
     * 表示目标方法返回的集合/数组中的具体元素。
     */
    @PostFilter("filterObject.id % 2 == 0")
    public List<User> postFilterTest() {
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            users.add(new User(i, "javaboy: " + i));
        }
        return users;
    }
}


@Test
@WithMockUser(roles = "ADMIN")
void postFilterTest() {
    List<User> all = helloService.postFilterTest();
    Assertions.assertNotNull(all);
    Assertions.assertEquals(5, all.size());
    Assertions.assertEquals(2, all.get(1).getId());
}
@Secured
@Service
public class HelloService {
	// 该注解不支持权限表达式,只能做一些简单的权限描述
    @Secured({"ROLE_ADMIN", "ROLE_USER"})
    public User securedTest(String username) {
        return new User(99, username);
    }
}


@Test
@WithMockUser(roles = "ADMIN")
void securedTest() {
    User user = helloService.securedTest("javaboy");
    Assertions.assertNotNull(user);
    Assertions.assertEquals(99, user.getId());
    Assertions.assertEquals("javaboy", user.getUsername());
}
@DenyAll
@Service
public class HelloService {
	// JSR-250:拒绝所有访问
    @DenyAll
    public void denyAllTest() {
    }
}


@Test
@WithMockUser(username = "javaboy")
void denyAllTest() {
    helloService.denyAllTest();
}
@PermitAll
@Service
public class HelloService {
	// JSR-250:允许所有访问
    @PermitAll
    public void permitAllTest() {
    }
}


@Test
@WithMockUser(username = "javaboy")
void permitAllTest() {
    helloService.permitAllTest();
}
@RolesAllowed
@Service
public class HelloService {
	/**
     * JSR-250:可以添加在方法上或类上,当添加在类上时,表示该注解对类中的所有方法生效;如果类上和方法上都有该注解,
     * 并且起冲突,则以方法上的注解为准。
     */
    @RolesAllowed({"ADMIN", "USER"})
    public String rolesAllowedTest() {
        return "RolesAllowed";
    }
}


@Test
@WithMockUser(roles = "ADMIN")
void rolesAllowedTest() {
    String s = helloService.rolesAllowedTest();
    Assertions.assertNotNull(s);
    Assertions.assertEquals("RolesAllowed", s);
}

13.5.3原理剖析

MethodSecurityInterceptor
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    // 调用父类的beforeInvocation方法进行权限校验
    InterceptorStatusToken token = super.beforeInvocation(mi);
    Object result;

    try {
        // 校验通过后,调用mi.proceed()方法继续执行目标方法
        result = mi.proceed();
    }
    finally {
        // 在finally块中调用finallyInvocation方法完成一些清理工作
        super.finallyInvocation(token);
    }

    // 最后调用父类的afterInvocation方法进行请求结果的过滤
    return super.afterInvocation(token, result);
}
EnableGlobalMethodSecurity
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 引入了一个配置GlobalMethodSecuritySelector,该类的作用主要是用来导入外部配置类
@Import({ GlobalMethodSecuritySelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableGlobalMethodSecurity {
    // 省略其他
}


final class GlobalMethodSecuritySelector implements ImportSelector {
    // importingClassMetadata中保存了@EnableGlobalMethodSecurity注解的元数据,包括各个属性的值、注解是加在哪个配置类上等
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		Class<EnableGlobalMethodSecurity> annoType = EnableGlobalMethodSecurity.class;
		Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(annoType.getName(),
				false);
		AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationAttributes);
		// 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<String> 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]);
	}

}
class MethodSecurityMetadataSourceAdvisorRegistrar implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 首先定义BeanDefinitionBuilder
		BeanDefinitionBuilder advisor = BeanDefinitionBuilder
				.rootBeanDefinition(MethodSecurityMetadataSourceAdvisor.class);
        // 然后给目标对象MethodSecurityMetadataSourceAdvisor的构造方法设置参数
		advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        // 要引用的MethodInterceptor对象名
		advisor.addConstructorArgValue("methodSecurityInterceptor");
        // 要引用的MethodSecurityMetadataSource对象名
		advisor.addConstructorArgReference("methodSecurityMetadataSource");
        // 和第二个一样,只不过一个是引用,一个是字符串
		advisor.addConstructorArgValue("methodSecurityMetadataSource");
		MultiValueMap<String, Object> attributes = importingClassMetadata
				.getAllAnnotationAttributes(EnableGlobalMethodSecurity.class.getName());
		Integer order = (Integer) attributes.getFirst("order");
		if (order != null) {
			advisor.addPropertyValue("order", order);
		}
        // 所有属性都配置好之后,将其注册到spring容器中
		registry.registerBeanDefinition("metaDataSourceAdvisor", advisor.getBeanDefinition());
	}

}
/**
 * 继承自AbstractPointcutAdvisor,主要定义了AOP的pointcut和advice,poincut也就是切点,可以简单理解为方法的拦截规则,
 * 即哪些方法需要拦截,哪些方法不需要拦截;advice也就是增强/通知,就是将方法拦截下来之后要增强的功能
 */
public class MethodSecurityMetadataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {

	private transient MethodSecurityMetadataSource attributeSource;

	private transient MethodInterceptor interceptor;

	// 这里的pointcut对象就是内部类MethodSecurityMetadataSourcePointcut,在它的matches方法中,定义了具体的拦截规则
	private final Pointcut pointcut = new MethodSecurityMetadataSourcePointcut();

	private BeanFactory beanFactory;

	private final String adviceBeanName;

	private final String metadataSourceBeanName;

	private transient volatile Object adviceMonitor = new Object();

    // 构造方法所需的三个参数就是MethodSecurityMetadataSourceAdvisorRegistrar类中提供的三个参数
	public MethodSecurityMetadataSourceAdvisor(String adviceBeanName, MethodSecurityMetadataSource attributeSource,
			String attributeSourceBeanName) {
		this.adviceBeanName = adviceBeanName;
		this.attributeSource = attributeSource;
		this.metadataSourceBeanName = attributeSourceBeanName;
	}

	@Override
	public Pointcut getPointcut() {
		return this.pointcut;
	}

	/**
	 * Advice由getAdvice方法返回,在该方法内部,就是去spring容器中查找一个名为
	 * methodSecurityInterceptor的MethodInterceptor对象。
	 */
	@Override
	public Advice getAdvice() {
		synchronized (this.adviceMonitor) {
			if (this.interceptor == null) {
				this.interceptor = this.beanFactory.getBean(this.adviceBeanName, MethodInterceptor.class);
			}
			return this.interceptor;
		}
	}

	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}

	private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
		ois.defaultReadObject();
		this.adviceMonitor = new Object();
		this.attributeSource = this.beanFactory.getBean(this.metadataSourceBeanName,
				MethodSecurityMetadataSource.class);
	}

	class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
		@Override
		public boolean matches(Method m, Class<?> targetClass) {
			MethodSecurityMetadataSource source = MethodSecurityMetadataSourceAdvisor.this.attributeSource;
			// 通过attributeSource.getAttributes方法去查看目标方法上有没有相应的权限注解,
			// 如果有,则返回true,目标方法就被拦截下来;如果没有,则返回false,目标方法就
			// 不会被拦截,这里的attributeSource实际上就是MethodSecurityMetadataSource对象,
			// 也就是提供权限元数据的类
			return !CollectionUtils.isEmpty(source.getAttributes(m, targetClass));
		}

	}
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public MethodSecurityMetadataSource methodSecurityMetadataSource() {
	// 创建List集合,用来保存所有的MethodSecurityMetadataSource对象
	List<MethodSecurityMetadataSource> sources = new ArrayList<>();
	ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
			getExpressionHandler());
	// 然后调用customMethodSecurityMetadataSource方法去获取自定义的MethodSecurityMetadataSource,
	// 默认情况下该方法返回null,如果想买有需要,开发者可以重写该方法来提供自定义的MethodSecurityMetadataSource对象
	MethodSecurityMetadataSource customMethodSecurityMetadataSource = customMethodSecurityMetadataSource();

	if (customMethodSecurityMetadataSource != null) {
		sources.add(customMethodSecurityMetadataSource);
	}

	// 接下来就是根据注解中配置的属性值,来向sources集合中添加相应的MethodSecurityMetadataSource对象
	boolean hasCustom = customMethodSecurityMetadataSource != null;
	boolean isPrePostEnabled = prePostEnabled();
	boolean isSecuredEnabled = securedEnabled();
	boolean isJsr250Enabled = jsr250Enabled();

	// 如果@EnableGlobalMethodSecurity注解配置了prePostEnabled=true,
	// 则加入PrePostAnnotationSecurityMetadataSource对象来解析相应的注解
	if (isPrePostEnabled) {
		sources.add(new PrePostAnnotationSecurityMetadataSource(attributeFactory));
	}

	// 如果@EnableGlobalMethodSecurity注解配置了securedEnabled=true,
	// 则加入SecuredAnnotationSecurityMetadataSource对象来解析相应的注解
	if (isSecuredEnabled) {
		sources.add(new SecuredAnnotationSecurityMetadataSource());
	}

	// 如果@EnableGlobalMethodSecurity注解配置了jsr250Enabled=true,
	// 则加入Jsr250MethodSecurityMetadataSource对象来解析相应的注解
	if (isJsr250Enabled) {
		GrantedAuthorityDefaults grantedAuthorityDefaults = getSingleBeanOrNull(GrantedAuthorityDefaults.class);
		Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource = this.context
				.getBean(Jsr250MethodSecurityMetadataSource.class);
		if (grantedAuthorityDefaults != null) {
			jsr250MethodSecurityMetadataSource.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
		}
		sources.add(jsr250MethodSecurityMetadataSource);
	}

	// 最后构建一个代理对象DelegatingMethodSecurityMetadataSource返回即可
	return new DelegatingMethodSecurityMetadataSource(sources);
}
@Bean
public MethodInterceptor methodSecurityInterceptor(MethodSecurityMetadataSource methodSecurityMetadataSource) {
	// 查看代理的方式,默认使用spring自带的AOP,所以使用MethodSecurityInterceptor来创建对应的MethodInterceptor实例
	this.methodSecurityInterceptor = isAspectJ() ? new AspectJMethodSecurityInterceptor()
			: new MethodSecurityInterceptor();
	// 然后给methodSecurityInterceptor设置AccessDecisionManager决策管理器
	this.methodSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
	// 接下来给methodSecurityInterceptor配置后置处理器
	this.methodSecurityInterceptor.setAfterInvocationManager(afterInvocationManager());
	// 最后再把前面创建好的MethodSecurityMetadataSource对象配置给methodSecurityInterceptor
	this.methodSecurityInterceptor.setSecurityMetadataSource(methodSecurityMetadataSource);
	RunAsManager runAsManager = runAsManager();
	if (runAsManager != null) {
		this.methodSecurityInterceptor.setRunAsManager(runAsManager);
	}
	return this.methodSecurityInterceptor;
}

protected AccessDecisionManager accessDecisionManager() {
	// 默认的决策管理器是AffirmativeBased
	List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
	// 根据@EnableGlobalMethodSecurity注解的配置,配置不同的投票器
	if (prePostEnabled()) {
		ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
		expressionAdvice.setExpressionHandler(getExpressionHandler());
		decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice));
	}
	if (jsr250Enabled()) {
		decisionVoters.add(new Jsr250Voter());
	}
	RoleVoter roleVoter = new RoleVoter();
	GrantedAuthorityDefaults grantedAuthorityDefaults = getSingleBeanOrNull(GrantedAuthorityDefaults.class);
	if (grantedAuthorityDefaults != null) {
		roleVoter.setRolePrefix(grantedAuthorityDefaults.getRolePrefix());
	}
	decisionVoters.add(roleVoter);
	decisionVoters.add(new AuthenticatedVoter());
	return new AffirmativeBased(decisionVoters);
}

protected AfterInvocationManager afterInvocationManager() {
	// 如果@EnableGlobalMethodSecurity注解配置了prePostEnabled=true,则添加一个后置处理器
	// PostInvocationAdviceProvider,该类用来处理@PostAuthorize和@PostFilter两个注解
	if (prePostEnabled()) {
		AfterInvocationProviderManager invocationProviderManager = new AfterInvocationProviderManager();
		ExpressionBasedPostInvocationAdvice postAdvice = new ExpressionBasedPostInvocationAdvice(
				getExpressionHandler());
		PostInvocationAdviceProvider postInvocationAdviceProvider = new PostInvocationAdviceProvider(postAdvice);
		List<AfterInvocationProvider> afterInvocationProviders = new ArrayList<>();
		afterInvocationProviders.add(postInvocationAdviceProvider);
		invocationProviderManager.setProviders(afterInvocationProviders);
		return invocationProviderManager;
	}
	return null;
}

最后

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

小编已加密:aHR0cHM6Ly9kb2NzLnFxLmNvbS9kb2MvRFVrVm9aSGxQZUVsTlkwUnc==出于安全原因,我们把网站通过base64编码了,大家可以通过base64解码把网址获取下来。

举报

相关推荐

0 条评论