spring security url动态授权
????????
官网:https://docs.spring.io/spring-security/reference/servlet/appendix/faq.html#appendix-faq-dynamic-url-metadata
????????????????????????
????????????????????????????????????????
引入jar包
??????????????
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
...
</dependencies>
...
</project>
???????
????????????
????????????????????????????????????????
动态数据源
??????????????
FilterInvocationSecurityMetadataSource:获取数据源
public interface FilterInvocationSecurityMetadataSource extends SecurityMetadataSource {
}
???????????
SecurityMetadataSource
public interface SecurityMetadataSource extends AopInfrastructureBean {
Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException;
//获取object对应的权限
Collection<ConfigAttribute> getAllConfigAttributes();
boolean supports(Class<?> clazz);
}
????????????
ConfigAttribute:权限属性
public interface ConfigAttribute extends Serializable {
String getAttribute();
}
?????????????
?????????????
?SecurityConfig:attrib可用来设置权限值(admin、user等)
public class SecurityConfig implements ConfigAttribute {
private final String attrib;
public SecurityConfig(String config) {
Assert.hasText(config, "You must provide a configuration attribute");
this.attrib = config;
}
public boolean equals(Object obj) {
if (obj instanceof ConfigAttribute) {
ConfigAttribute attr = (ConfigAttribute)obj;
return this.attrib.equals(attr.getAttribute());
} else {
return false;
}
}
public String getAttribute() {
return this.attrib;
}
public int hashCode() {
return this.attrib.hashCode();
}
public String toString() {
return this.attrib;
}
public static List<ConfigAttribute> createListFromCommaDelimitedString(String access) {
return createList(StringUtils.commaDelimitedListToStringArray(access));
}
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;
}
}
????????????????
?????????????????????
????????????????????????????????????????
权限拦截器
????????????
AbstractSecurityInterceptor
public abstract class AbstractSecurityInterceptor implements InitializingBean, ApplicationEventPublisherAware, MessageSourceAware {
protected final Log logger = LogFactory.getLog(this.getClass());
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private ApplicationEventPublisher eventPublisher;
private AccessDecisionManager accessDecisionManager;
private AfterInvocationManager afterInvocationManager;
private AuthenticationManager authenticationManager = new AbstractSecurityInterceptor.NoOpAuthenticationManager();
private RunAsManager runAsManager = new NullRunAsManager();
private boolean alwaysReauthenticate = false;
private boolean rejectPublicInvocations = false;
private boolean validateConfigAttributes = true;
private boolean publishAuthorizationSuccess = false;
public AbstractSecurityInterceptor() {
}
public void afterPropertiesSet() {
public void setRunAsManager(RunAsManager runAsManager) {
public void setMessageSource(MessageSource messageSource) {
public void setAlwaysReauthenticate(boolean alwaysReauthenticate) {
public void setAuthenticationManager(AuthenticationManager newManager) {
public void setRejectPublicInvocations(boolean rejectPublicInvocations) {
public void setValidateConfigAttributes(boolean validateConfigAttributes) {
public void setPublishAuthorizationSuccess(boolean publishAuthorizationSuccess) {
public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) {
public void setAfterInvocationManager(AfterInvocationManager afterInvocationManager) {
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
public boolean isAlwaysReauthenticate() {
public boolean isRejectPublicInvocations() {
public boolean isValidateConfigAttributes() {
public AccessDecisionManager getAccessDecisionManager() {
public AfterInvocationManager getAfterInvocationManager() {
public AuthenticationManager getAuthenticationManager() {
public RunAsManager getRunAsManager() {
public abstract Class<?> getSecureObjectClass();
public abstract SecurityMetadataSource obtainSecurityMetadataSource();
protected void finallyInvocation(InterceptorStatusToken token) {
protected InterceptorStatusToken beforeInvocation(Object object) {
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
private Authentication authenticateIfRequired() {
private void publishEvent(ApplicationEvent event) {
private void validateAttributeDefs(Collection<ConfigAttribute> attributeDefs) {
private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes, Authentication authenticated) {
private void credentialsNotFound(String reason, Object secureObject, Collection<ConfigAttribute> configAttribs) {
*********
静态内部类:NoOpAuthenticationManager
private static class NoOpAuthenticationManager implements AuthenticationManager {
private NoOpAuthenticationManager() {
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
throw new AuthenticationServiceException("Cannot authenticate " + authentication);
}
}
}
?????????????
???????????
FilterSecurityInterceptor
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
private FilterInvocationSecurityMetadataSource securityMetadataSource;
private boolean observeOncePerRequest = true;
public FilterSecurityInterceptor() {
}
public void init(FilterConfig arg0) {
}
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
this.invoke(new FilterInvocation(request, response, chain));
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
this.securityMetadataSource = newSource;
}
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
if (this.isApplied(filterInvocation) && this.observeOncePerRequest) {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} else {
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, (Object)null);
}
}
private boolean isApplied(FilterInvocation filterInvocation) {
return filterInvocation.getRequest() != null && filterInvocation.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null;
}
public boolean isObserveOncePerRequest() {
return this.observeOncePerRequest;
}
public void setObserveOncePerRequest(boolean observeOncePerRequest) {
this.observeOncePerRequest = observeOncePerRequest;
}
}
???????????
??????????????????????????
????????????????????????????????????????
认证管理器
????????????
AccessDecisionManager
public interface AccessDecisionManager {
void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
}
?????????????
AbstractAccessDecisionManager
public abstract class AbstractAccessDecisionManager implements AccessDecisionManager, InitializingBean, MessageSourceAware {
protected final Log logger = LogFactory.getLog(this.getClass());
private List<AccessDecisionVoter<?>> decisionVoters;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private boolean allowIfAllAbstainDecisions = false;
public boolean supports(ConfigAttribute attribute) {
Iterator var2 = this.decisionVoters.iterator();
AccessDecisionVoter voter;
do {
if (!var2.hasNext()) {
return false;
}
voter = (AccessDecisionVoter)var2.next();
} while(!voter.supports(attribute));
return true;
}
public boolean supports(Class<?> clazz) {
Iterator var2 = this.decisionVoters.iterator();
AccessDecisionVoter voter;
do {
if (!var2.hasNext()) {
return true;
}
voter = (AccessDecisionVoter)var2.next();
} while(voter.supports(clazz));
return false;
}
public void setMessageSource(MessageSource messageSource) {
public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) {
public List<AccessDecisionVoter<?>> getDecisionVoters() {
public boolean isAllowIfAllAbstainDecisions() {
public String toString() {
public void afterPropertiesSet() {
protected final void checkAllowIfAllAbstainDecisions() {
protected AbstractAccessDecisionManager(List<AccessDecisionVoter<?>> decisionVoters) {
?????????????
? ? ? ? ? ? ? ??
AffirmativedBased
public class AffirmativeBased extends AbstractAccessDecisionManager {
public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {
super(decisionVoters);
}
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0;
Iterator var5 = this.getDecisionVoters().iterator();
while(var5.hasNext()) {
AccessDecisionVoter voter = (AccessDecisionVoter)var5.next();
int result = voter.vote(authentication, object, configAttributes);
switch(result) {
case -1:
++deny;
break;
case 1:
return;
}
}
if (deny > 0) {
throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
} else {
this.checkAllowIfAllAbstainDecisions();
}
}
}
?????????????
AccessDecisionVoter
public interface AccessDecisionVoter<S> {
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
}
?????????????
??????????
?RoleVoter:根据权限判断是否让请求通过
public class RoleVoter implements AccessDecisionVoter<Object> {
private String rolePrefix = "ROLE_";
public RoleVoter() {
}
public String getRolePrefix() {
return this.rolePrefix;
}
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
public boolean supports(ConfigAttribute attribute) {
return attribute.getAttribute() != null && attribute.getAttribute().startsWith(this.getRolePrefix());
}
public boolean supports(Class<?> clazz) {
return true;
}
public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
if (authentication == null) {
return -1;
} else {
int result = 0;
Collection<? extends GrantedAuthority> authorities = this.extractAuthorities(authentication);
Iterator var6 = attributes.iterator();
while(true) {
ConfigAttribute attribute;
do {
if (!var6.hasNext()) {
return result;
}
attribute = (ConfigAttribute)var6.next();
} while(!this.supports(attribute));
result = -1;
Iterator var8 = authorities.iterator();
while(var8.hasNext()) {
GrantedAuthority authority = (GrantedAuthority)var8.next();
if (attribute.getAttribute().equals(authority.getAuthority())) {
return 1;
}
}
}
}
}
Collection<? extends GrantedAuthority> extractAuthorities(Authentication authentication) {
return authentication.getAuthorities();
}
}
?????????
?????????????
????????????????????????????????????????
使用示例
????????????
????????????????????????????
?????????
AuthorityDto
@Data
public class AuthorityDto {
private String path;
private List<String> authorities;
}
??????????
SecurityDataSourceUtil
public class SecurityDataSourceUtil {
public static Map<String, Collection<ConfigAttribute>> requestAuthoritiesMap = new HashMap<>();
public static void setAuthorities(String path, Collection<ConfigAttribute> configAttributes){
requestAuthoritiesMap.put(path, configAttributes);
}
public static Collection<ConfigAttribute> getAuthorities(String path){
return requestAuthoritiesMap.get(path);
}
}
???????????
CustomFilterSecurityMetadataSource
@Component
public class CustomFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
Set<ConfigAttribute> configAttributes = new HashSet<>();
for (Collection<ConfigAttribute> item : SecurityDataSourceUtil.requestAuthoritiesMap.values()){
configAttributes.addAll(item);
}
return configAttributes;
}
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
HttpServletRequest request = ((FilterInvocation)object).getRequest();
for (Map.Entry<String, Collection<ConfigAttribute>> entry: SecurityDataSourceUtil.requestAuthoritiesMap.entrySet()){
if (new AntPathRequestMatcher(entry.getKey()).matches(request)) {
return entry.getValue();
}
}
return null;
}
}
?????????
CustomUrlInterceptor
@Data
@Component
@EqualsAndHashCode(callSuper = true)
public class CustomUrlInterceptor extends AbstractSecurityInterceptor implements Filter, InitializingBean {
private static final String FILTER_APPLIED = "__spring_security_custom_filterSecurityInterceptor_filterApplied";
private boolean observeOncePerRequest = true;
@Resource
private FilterInvocationSecurityMetadataSource securityMetadataSource;
public CustomUrlInterceptor() {
}
@Override
public void afterPropertiesSet() {
List<AccessDecisionVoter<?>> accessDecisionVoters = new ArrayList<>();
accessDecisionVoters.add(new RoleVoter());
AccessDecisionManager accessDecisionManager = new AffirmativeBased(accessDecisionVoters);
this.setAccessDecisionManager(accessDecisionManager);
}
public void init(FilterConfig arg0) {
}
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
this.invoke(new FilterInvocation(request, response, chain));
}
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
if (this.isApplied(filterInvocation) && this.observeOncePerRequest) {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} else {
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
private boolean isApplied(FilterInvocation filterInvocation) {
return filterInvocation.getRequest() != null && filterInvocation.getRequest().getAttribute(FILTER_APPLIED) != null;
}
public boolean isObserveOncePerRequest() {
return this.observeOncePerRequest;
}
public void setObserveOncePerRequest(boolean observeOncePerRequest) {
this.observeOncePerRequest = observeOncePerRequest;
}
}
?????????????
WebSecurity
@Configuration
public class WebSecurity {
@Bean
public PasswordEncoder initPasswordEncoder(){
return new Pbkdf2PasswordEncoder();
}
}
???????????
WebSecurityConfig
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Resource
private PasswordEncoder passwordEncoder;
@Resource
private CustomUrlInterceptor customUrlInterceptor;
@Bean
public SecurityFilterChain initSecurityFilterChain(HttpSecurity http) throws Exception{
http.addFilterAfter(customUrlInterceptor, FilterSecurityInterceptor.class);
http.csrf().disable();
return http.formLogin().and().authorizeRequests()
.antMatchers("/authority").permitAll()
.antMatchers("/hello").hasRole("admin")
.anyRequest().authenticated()
.and().build();
}
@Bean
public UserDetailsService initUserDetailsService(){
UserDetails userDetails = User.withUsername("gtlx").password(passwordEncoder.encode("123456")).roles("admin").build();
return new InMemoryUserDetailsManager(userDetails);
}
}
???????????
HelloController
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "hello";
}
@RequestMapping("/hello2")
public String hello2(){
return "hello2";
}
}
????????????
AuthorityController
@RestController
public class AuthorityController {
@RequestMapping("/authority")
public String authority(@RequestBody AuthorityDto authorityDto){
List<ConfigAttribute> configAttributes = new ArrayList<>();
for (String s: authorityDto.getAuthorities()){
SecurityConfig securityConfig = new SecurityConfig("ROLE_"+s);
configAttributes.add(securityConfig);
}
SecurityDataSourceUtil.setAuthorities(authorityDto.getPath(), configAttributes);
return "success";
}
}
??????????
??????????????????
????????????????????????????????????????
使用测试
????????????
localhost:8080/hello,输入密码
????????????
????????????
????????????
localhost:8080/hello2
????????????
????????????
localhost:8080/authority,路径/hello2添加权限认证
????????????
????????????
localhost:8080/hello2,当前用户没有user权限,禁止访问
????????????
? ? ? ? ? ??
localhost:8080/authority,修改路径权限为admin
????????????
????????????
localhost:8080/hello2,当前用户有admin权限,可正常访问
????????????
?????????????????
??????????????????????????????
|