最近一朋友做了一个小程序,看着还挺好玩的,于是乎就帮朋友捎带推广了下,走过路过的小伙伴们,你心中是否也是有一个愿望,赶紧来这里和大家一起分享和实现吧~ 来,来,来,我在这里等你~
【点击????????????直接进入~】
说明
(1)JDK版本:1.8
(2)Spring Boot 2.0.6
(3)Spring Security 5.0.9
(4)Spring Data JPA 2.0.11.RELEASE
(5)hibernate5.2.17.Final
(6)MySQLDriver 5.1.47
(7)MySQL 8.0.12
需求缘起
在上一篇我们通过自定义AccessDesionManager实现了动态权限控制,本节将通过自定义Filter进行实现动态权限,理解了上一篇文章的话,对于这篇文章就没有什么难点了。代码基于《基于URL动态权限:准备工作》在往下编码。
编码思路
对于URL动态权限配置,主要解决如下几个问题:
(1)基于URL的用户权限信息保存在哪里:需要定义一张权限表,保存权限信息,然后角色和权限有一个关联关系(准备工作已经完成)。
(2)怎么加载用户的权限信息:仍然是通过loadUserByUsername进行加载,用户的权限信息这块的编码不变(在之前已经编码)。
(3)URL对应的权限配置:这个主要是通过FilterInvocationSecurityMetadataSource进行配置。
(4)如何决定某一个用户是否有权限访问某个URL : 自定义AccessDecisionManager类的decide方法,决定某一个用户是否有权限访问某个URL。
(5)使用自定义的Filter进行拦截处理请求。
一、基于URL动态权限配置
接下来我们看下具体的编码步骤,我们的代码在基本工作之后进行编码,所以权限的实体类,初始化数据就不重复说明了。
1.1 加载权限信息
继承FilterInvocationSecurityMetadataSource重写getAttributes的方法进行通过uri获取权限配置信息:
package com.kfit.config;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import com.kfit.permission.service.PermissionService;
@Component
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{
@Autowired
private PermissionService permissionService;
/**
* 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法。
* object-->FilterInvocation
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
System.out.println("MyFilterInvocationSecurityMetadataSource.getAttributes()");
Map<String, Collection<ConfigAttribute>> map = permissionService.getPermissionMap();
FilterInvocation filterInvocation = (FilterInvocation) object;
System.out.println(filterInvocation.getFullRequestUrl());
if (isMatcherAllowedRequest(filterInvocation)) return null ; //return null 表示允许访问,不做拦截
HttpServletRequest request = filterInvocation.getHttpRequest();
String resUrl;
//URL规则匹配.
AntPathRequestMatcher matcher;
for(Iterator<String> it = map.keySet().iterator();it.hasNext();) {
resUrl = it.next();
matcher = new AntPathRequestMatcher(resUrl);
if(matcher.matches(request)) {
System.out.println(map.get(resUrl));
return map.get(resUrl);
}
}
//SecurityConfig.createList("ROLE_USER");
//方式一:没有匹配到,直接是白名单了.不登录也是可以访问的。
//return null;
//方式二:配有匹配到,需要指定相应的角色:
return SecurityConfig.createList("ROLE_admin");
}
/**
* 判断当前请求是否在允许请求的范围内
* @param fi 当前请求
* @return 是否在范围中
*/
private boolean isMatcherAllowedRequest(FilterInvocation fi){
return allowedRequest().stream().map(AntPathRequestMatcher::new)
.filter(requestMatcher -> requestMatcher.matches(fi.getHttpRequest()))
.toArray().length > 0;
}
/**
* 参考:
* https://blog.csdn.net/pujiaolin/article/details/73928491
* @return 定义允许请求的列表
*/
private List<String> allowedRequest(){
return Arrays.asList("/login","/css/**","/fonts/**","/js/**","/scss/**","/img/**");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
1.2 权限校验
继承AccessDecisionManager重写decide方法进行编码:
package com.kfit.config;
import java.util.Collection;
import java.util.Iterator;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
@Component
public class MyAccessDecisionManager implements AccessDecisionManager{
/**
* 方法是判定是否拥有权限的决策方法,
* (1)authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
* (2)object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
* (3)configAttributes 为MyFilterInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
System.out.println("MyAccessDecisionManager.decide()");
if(configAttributes == null || configAttributes.size()==0) {
throw new AccessDeniedException("permission denied");
}
ConfigAttribute cfa;
String needRole;
//遍历基于URL获取的权限信息和用户自身的角色信息进行对比.
for(Iterator<ConfigAttribute> it=configAttributes.iterator();it.hasNext();) {
cfa = it.next();
needRole = cfa.getAttribute();
System.out.println("decide,needRole:"+needRole+",authentication="+authentication);
//authentication 为CustomUserDetailService中添加的权限信息.
for(GrantedAuthority grantedAuthority:authentication.getAuthorities()) {
if(needRole.equals(grantedAuthority.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("permission denied");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
1.3 自定义Filter
自定义一个Filter继承AbstractSecurityInterceptor,在这个方法里主要注入我们上面编写的两个类:
(1)安全信息数据源:MyFilterInvocationSecurityMetadataSource
(2)访问决策管理器:MyAccessDecisionManager
具体代码如下:
package com.kfit.config;
import java.util.Collection;
import java.util.Iterator;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
@Component
public class MyAccessDecisionManager implements AccessDecisionManager{
/**
* 方法是判定是否拥有权限的决策方法,
* (1)authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
* (2)object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
* (3)configAttributes 为MyFilterInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
System.out.println("MyAccessDecisionManager.decide()");
if(configAttributes == null || configAttributes.size()==0) {
throw new AccessDeniedException("permission denied");
}
ConfigAttribute cfa;
String needRole;
//遍历基于URL获取的权限信息和用户自身的角色信息进行对比.
for(Iterator<ConfigAttribute> it=configAttributes.iterator();it.hasNext();) {
cfa = it.next();
needRole = cfa.getAttribute();
System.out.println("decide,needRole:"+needRole+",authentication="+authentication);
//authentication 为CustomUserDetailService中添加的权限信息.
for(GrantedAuthority grantedAuthority:authentication.getAuthorities()) {
if(needRole.equals(grantedAuthority.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("permission denied");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
到这里就可以测试下效果了,和之前的结果应该是一样的。