Spring Security
使用FilterSecurityInterceptor
过滤器来进行URL权限校验,实际使用流程大致如下:
FilterSecurityInterceptor
滤器到数据库加载系统资源权限列表UserDetailsService
加载当前用户的角色列表FilterSecurityInterceptor
对比系统资源权限列表和用户资源权限列表(在用户登录时添加到用户信息中)来判断用户是否有该url的访问权限。FilterSecurityInterceptor
自定义的配置项securityMetadataSource
:实现FilterInvocationSecurityMetadataSource
接口,在实现类中加载资源权限,并在filterSecurityInterceptor
中注入该实现类。accessDecisionManager
:通过实现AccessDecisionManager
接口自定义一个决策管理器,判断是否有访问权限。判断逻辑可以写在决策管理器的决策方法中,也可以通过投票器实现,除了框架提供的三种投票器还可以添加自定义投票器。自定义投票器通过实现AccessDecisionVoter
接口来实现。ExceptionTranslationFilter
自定义的配置项1、AuthenticationEntryPoint
用来解决匿名用户访问无权限资源时的异常
2、AccessDeineHandler
用来解决登陆认证过的用户访问无权限资源时的异常
demo代码如下:
1、自定义securityMetadataSource
从数据库加载动态配置的角色url资源权限
@Component
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private Map<RequestMatcher, Collection<ConfigAttribute>> allRoleSource = new HashMap<>();
public MyFilterInvocationSecurityMetadataSource(){
//模拟从数据库加载角色URL权限信息
Map<String,String> urlRoleMap = new HashMap<String,String>(){{
put("/open/**","ROLE_ANONYMOUS");
put("/home","ADMIN,USER");
put("/admin/**","ADMIN");
put("/user/**","ADMIN,USER");
}};
Map<RequestMatcher, Collection<ConfigAttribute>> loadRequestMap = new HashMap<>();
for(Map.Entry<String,String> entry:urlRoleMap.entrySet()){
loadRequestMap.put(new AntPathRequestMatcher(entry.getKey()),SecurityConfig.createList(entry.getValue().split(",")));
}
allRoleSource = loadRequestMap;
}
//返回当前URL允许访问的角色列表
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
FilterInvocation fi = (FilterInvocation) object;
HttpServletRequest request = fi.getRequest();
//String url = fi.getRequestUrl();
//String httpMethod = fi.getRequest().getMethod();
for(Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry:allRoleSource.entrySet()){
if(entry.getKey().matches(request)){
return entry.getValue();
}
}
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
2、自定义accessDecisionManager
替换默认的AffirmativeBased
,直接在决策管理器的决策方法中自己校验,不使用AccessDecisionVoter
。
public class MyAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while (iterator.hasNext()) {
if (authentication == null) {
throw new AccessDeniedException("当前访问没有权限");
}
ConfigAttribute configAttribute = iterator.next();
String needCode = configAttribute.getAttribute();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (StringUtils.equals(authority.getAuthority(), needCode)) {
return;
}
}
}
throw new AccessDeniedException("当前访问没有权限");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return false;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
3、自定义AuthenticationEntryPoint
替换默认的LoginUrlAuthenticationEntryPoint
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
redirectStrategy.sendRedirect(request, response, "/login");
}
}
4、定义 AccessDeniedHandler
替换默认的AccessDeniedHandlerImpl
public class MyAccessDeineHandler implements AccessDeniedHandler {
private String errorPage="error";
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
if (!response.isCommitted()) {
if (errorPage != null) {
// Put exception into request scope (perhaps of use to a view)
request.setAttribute(WebAttributes.ACCESS_DENIED_403,
accessDeniedException);
// Set the 403 status code.
response.setStatus(HttpStatus.FORBIDDEN.value());
// forward to error page.
RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
dispatcher.forward(request, response);
}
else {
response.sendError(HttpStatus.FORBIDDEN.value(),
HttpStatus.FORBIDDEN.getReasonPhrase());
}
}
}
}
3、同过HttpSecurity
来配置自定义的配置项
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login","/userlogin","/timeout").permitAll()
//.accessDecisionManager()
//.antMatchers("/user/**").hasRole("USER")
//其他地址的访问均需验证权限
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
fsi.setAccessDecisionManager(new MyAccessDecisionManager());
fsi.setSecurityMetadataSource(new MyFilterInvocationSecurityMetadataSource());
return fsi;
}
});
………………………………………………………………………………
http.exceptionHandling()
.authenticationEntryPoint(new MyAuthenticationEntryPoint())
.accessDeniedHandler(new MyAccessDeineHandler());
}