本文为博主学习笔记
首先写以下配置类,常规操作,不多说了。
@Autowired
UserService userService;
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
和之前不同的是,这次的动态配置权限新增了两个配置类,一个MyFilter一个MyAccessDecisionManager类。
MyFilter类的作用是根据访问的路径与数据库中的路径进行匹配,如果匹配上,则返回访问该路径需要的所有角色。在这里,MyFilter这个类继承了FilterInvocationSecurityMetadataSource类,分析一下这个类,作用是SecurityMetadataSource实现的标记接口,这些实现旨在执行以FilterInvocations为键的查找。而这个类的父类是SecurityMetadataSource 类中有三个方法,分别是
//访问适用于给定安全对象的配置信息。
Collection<ConfigAttribute> getAttributes(Object var1) throws IllegalArgumentException;
//如果可用,则返回实现类定义的所有配置信息。
Collection<ConfigAttribute> getAllConfigAttributes();
//指示SecurityMetadataSource实现是否能够为指示的安全对象类型提供ConfigAttributes。
boolean supports(Class<?> var1);
MyFIlter过滤器:
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
//这里需要强转称FilterInvocation的原因是因为要获取请求的url。FilterInvocation中有对应的方法
String requestUrl = ((FilterInvocation) o).getRequestUrl();
List<Menu> allMenu = menuService.getAllMenus();
for (Menu menu : allMenu) {
if (pathMatcher.match(menu.getPattern(),requestUrl)) {
List<Role> roles = menu.getRoles();
String[] rolesStr = new String[roles.size()];
for (int i = 0; i < roles.size(); i++) {
rolesStr[i] = roles.get(i).getName();
}
//传递的是需要的角色数组
return SecurityConfig.createList(rolesStr);
}
}
return SecurityConfig.createList("ROLE_login");
}
返回值这里需要注意,我们可以在IDEA编辑器里按住command点进去看一下。
该类继承了父类ConfigAttribute,而getAttributes的返回值类型正好相符。
在SecurityConfig中的createList源码为
public static List<ConfigAttribute> createList(String... attributeNames) {
Assert.notNull(attributeNames, "You must supply an array of attribute names");
List<ConfigAttribute> attributes = new ArrayList(attributeNames.length);
String[] var2 = attributeNames;
int var3 = attributeNames.length;
for(int var4 = 0; var4 < var3; ++var4) {
String attribute = var2[var4];
attributes.add(new SecurityConfig(attribute.trim()));
}
return attributes;
}
我们只需要把用户拥有的角色返回就行了。
接下来配置一个认证管理器。
MyAccessDecisionManager类实现了AccessDecisionManager接口,该接口中共有三个方法。
//为传递的参数解决访问控制决策。
void decide(Authentication var1, Object var2, Collection<ConfigAttribute> var3) throws AccessDeniedException, InsufficientAuthenticationException;
boolean supports(ConfigAttribute var1);
boolean supports(Class<?> var1);
在MyAccessDecisionManager类。方法的第一个参数作用是在AuthenticationManager.authenticate(Authentication)方法处理请求后,代表认证请求或已认证主体的令牌。
一旦对请求进行了身份验证,身份验证通常将通过使用的身份验证机制存储在由SecurityContextHolder管理的线程本地SecurityContext中。 通过创建Authentication实例并使用以下代码,无需使用Spring Security的身份验证机制之一即可实现显式身份验证也就是说存储着用户当前的登录信息。方法的第三个参数的作用是与被调用的受保护对象关联的配置属性,也就是说,该参数中存储着用户需要哪些角色
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
//ConfigAttribute的作用是存储与安全系统相关的配置属性。
for (ConfigAttribute attribute : collection) {
//判断用户角色是否与ROLE_login匹配
if ("ROLE_login".equals(attribute.getAttribute())) {
if (authentication instanceof AnonymousAuthenticationToken) {
throw new AccessDeniedException("非法请求!");
} else {
return;
}
}
//我现在具备的角色
//getAuthorities方法返回的是授权给委托人的权限;如果令牌尚未通过验证,则为空集合。 永不为空。
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(attribute.getAttribute())){
return;
}
}
}
throw new AccessDeniedException("非法请求!");
}
在配置类中重写config方法
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
//设置认证决策器
o.setAccessDecisionManager(myAccessDecisionManager);
o.setSecurityMetadataSource(myFilter);
return o;
}
})
.and()
.formLogin()
.permitAll()
.and()
.csrf()
.disable();
}