5.security filter
@Service
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
private static final Logger LOG = LoggerFactory.getLogger(MyFilterSecurityInterceptor.class);
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
LOG.info(">>>>>>>>>>>>>>>>>MyFilterSecurityInterceptor-invoke");
//fi里面有一个被拦截的url
//里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public void destroy() {
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
}
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
private static final Logger LOG = LoggerFactory.getLogger(MyAccessDecisionManager.class);
//decide 方法是判定是否拥有权限的决策方法
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
LOG.info(">>>>>>>>MyAccessDecisionManager-decide");
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
String url, method;
AntPathRequestMatcher matcher;
for (GrantedAuthority ga : authentication.getAuthorities()) {
if (ga instanceof MyGrantedAuthority) {
MyGrantedAuthority urlGrantedAuthority = (MyGrantedAuthority) ga;
url = urlGrantedAuthority.getPermissionUrl();
method = urlGrantedAuthority.getMethod();
matcher = new AntPathRequestMatcher(url);
if (matcher.matches(request)) {
//当权限表权限的method为ALL时表示拥有此路径的所有请求方式权利。
if (method.equals(request.getMethod()) || "ALL".equals(method)) {
return;
}
}
} else if (ga.getAuthority().equals("ROLE_ANONYMOUS")) {//未登录只允许访问 login 页面
matcher = new AntPathRequestMatcher("/login");
if (matcher.matches(request)) {
return;
}
}
}
throw new AccessDeniedException("no right");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
public class MyGrantedAuthority implements GrantedAuthority {
private String url;
private String method;
public String getPermissionUrl() {
return url;
}
public void setPermissionUrl(String permissionUrl) {
this.url = permissionUrl;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public MyGrantedAuthority(String url, String method) {
this.url = url;
this.method = method;
}
@Override
public String getAuthority() {
return this.url + ";" + this.method;
}
}
@Service
public class MyInvocationSecurityMetadataSourceService implements
FilterInvocationSecurityMetadataSource {
//此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
//因为我不想每一次来了请求,都先要匹配一下权限表中的信息是不是包含此url,
// 我准备直接拦截,不管请求的url 是什么都直接拦截,然后在MyAccessDecisionManager的decide 方法中做拦截还是放行的决策。
//所以此方法的返回值不能返回 null 此处我就随便返回一下。
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
Collection<ConfigAttribute> co=new ArrayList<>();
co.add(new SecurityConfig("null"));
return co;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
- service
@Service
public class CustomUserService implements UserDetailsService { //自定义UserDetailsService 接口
@Autowired
UserDao userDao;
@Autowired
PermissionDao permissionDao;
public UserDetails loadUserByUsername(String username) {
SysUser user = userDao.findByUserName(username);
if (user != null) {
List<Permission> permissions = permissionDao.findByAdminUserId(user.getId());
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
for (Permission permission : permissions) {
if (permission != null && permission.getName() != null) {
GrantedAuthority grantedAuthority = new MyGrantedAuthority(permission.getUrl(), permission.getMethod());
grantedAuthorities.add(grantedAuthority);
}
}
return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
} else {
throw new UsernameNotFoundException("admin: " + username + " do not exist!");
}
}
}
附录
Spring Security提供了三种不同的安全注解:
- Spring Security自带的@Secured注解;
- JSR-250的@RolesAllowed注解;
- 表达式驱动的注解,包括@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter。
在Spring中,如果要启用基于注解的方法安全性,关键之处在于要在配置类上使用@EnableGlobalMethodSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,jsr250Enabled = true,prePostEnabled = true)
用法:往controller类添加一个方法
@GetMapping(value = "/admin")
@Secured("ROLE_ADMIN")
public String admin(){
return "admin";
}
总结
1 首先 该url会被AbstractSecurityInterceptor类的继承拦截器拦截
2 然后调用 FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限
3 再调用授权管理器AccessDecisionManager
3.1 通过spring的全局缓存SecurityContextHolder(将认证通过的用户放入到这个安全处理器中)获取用户的权限信息
3.2 获取被拦截的url和被拦截url所需的全部权限
3.3 根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。