Spring Security 自定义 登陆 权限验证

项目简介

基于Spring Cloud 的项目,Spring Cloud是在Spring Boot上搭建的所以按照Spring Boot的方式来写

Spring Security 配置

继承 WebSecurityConfigurerAdapter ,重写configure(HttpSecurity http)配置相关权限以及重写拦截器

     http.authorizeRequests()
        .antMatchers("/auth/**").permitAll()
        .anyRequest().authenticated().and()
        //证书 认证 自动登陆
        .addFilterBefore(authTokenFilter, UsernamePasswordAuthenticationFilter.class)
        //登陆以及权限控制Filter
        ......
    ;

自定义UsernamePasswordAuthenticationFilter

自定义 UsernamePasswordAuthenticationFilter 实现自动登陆
创建Authentication 模拟登陆

Authentication authentication = new UsernamePasswordAuthenticationToken(auth, token);
SecurityContextHolder.getContext().setAuthentication(authentication);;

自定义FilterSecurityInterceptor

Spring Security 是通过这个过滤器来实现 Http资源安全过滤的。

获取资源权限

FilterSecurityInterceptor继承自 AbstractSecurityInterceptor ,源码中的其中beforeInvocation方法的一段代码是:

Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
                .getAttributes(object);

这个方法是来获取资源权限 ,可以重写SecurityMetadataSource obtainSecurityMetadataSource(){}方法来实现,传入一个FilterInvocation对象,返回一个Collection<ConfigAttribute>对象。
这个对象中可以获取到request, response等内置对象,可以通过一下代码来匹配

RequestMatcher requestMatcher = new AntPathRequestMatcher("/manager/**");
if(requestMatcher.matches(request)){
    return RESOURCE            
}

ConfigAttribute 可以通过new SecurityConfig((String)input) 来创建

编写认证提供者

重写 AuthenticationManager 实现,用户登陆可以放这里面

Authentication authenticate(Authentication authentication)
            throws AuthenticationException;

用来生成Authentication, 原始的够用的话直接注入设置就好。

用户是否有获取资源权限

AbstructSecurityIntercepter 中的一下方法来判断用户权限是否可以拥有该资源

this.accessDecisionManager.decide(authenticated, object, attributes);

为了达到自定义控制的目的,我们需要实现AccessDecisionManager接口,来重写这个方法,如果判断不通过 decide方法可以抛出AccessDeniedException,来阻止用户访问

/**
 * 判断用户是否有访问资源权限
 * @param authentication    用户Auth
 * @param object FilterInvocation对象
 * @param configAttributes  资源所需权限
 * @throws AccessDeniedException  无权限Exception
 * @throws InsufficientAuthenticationException
 */
public void decide(Authentication authentication, Object object,
                       Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
                if(access){
                    //允许通过
                    return;
                }
                //不允许角色访问
                throw new AccessDeniedException("NO ALLOW");
            }

JAVA 源码片

WebSecurityConfig

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private AuthTokenFilter authTokenFilter;   
    @Autowired
    private ApiPermissionSecurityFilter securityFilter;
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/auth/**").permitAll()
            .anyRequest().authenticated().and()
            //证书 认证 自动登陆
            .addFilterBefore(authTokenFilter, UsernamePasswordAuthenticationFilter.class)
            //登陆以及权限控制Filter
            .addFilterBefore(securityFilter, FilterSecurityInterceptor.class)
            .csrf().disable()
            //基于Token 不需要Session
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        ;
    }
}

AuthTokenFilter (自定义UsernamePasswordAuthenticationFilter)

@Component
public class AuthTokenFilter extends OncePerRequestFilter {
    @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            String auth = request.getHeader("Authorization");
            //用户登陆,暂不设置权限
            Token token = new Token(auth, null);
            Authentication authentication = new UsernamePasswordAuthenticationToken(auth, token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            filterChain.doFilter(request, response);
        }
}

ApiPermissionSecurityFilter

@Component
public class ApiPermissionSecurityFilter extends AbstractSecurityInterceptor implements Filter {
    @Autowired
    private ApiInvocationSecurityMetadataSourceService apiInvocationSecurityMetadataSourceService;
    @Autowired
    private ApiAccessDecisionManager apiAccessDecisionManager;
    @Autowired
    private AuthenticationManager authenticationManager;

    @PostConstruct
    public void init(){
        super.setAuthenticationManager(authenticationManager);
        super.setAccessDecisionManager(apiAccessDecisionManager);
    }
    
    public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException{
        FilterInvocation fi = new FilterInvocation( request, response, chain );
        invoke(fi);
    }

    public Class<? extends Object> getSecureObjectClass(){
        return FilterInvocation.class;
    }

    public void invoke( FilterInvocation fi ) throws IOException, ServletException{
        InterceptorStatusToken  token = super.beforeInvocation(fi);
        try{
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        }finally{
            super.afterInvocation(token, null);
        }
    }


    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource(){
        return this.apiInvocationSecurityMetadataSourceService;
    }
    public void destroy(){
    }
    public void init( FilterConfig filterconfig ) throws ServletException{
    }
}

ApiInvocationSecurityMetadataSourceService

/**
 * 资源-权限控制对象
 * Created by liang on 2017/3/17.
 */
@Component
public class ApiInvocationSecurityMetadataSourceService implements
        FilterInvocationSecurityMetadataSource {
    //缓存 英文名-权限
    private static LoadingCache<String, Collection<ConfigAttribute>> permitMap = null;
    //缓存 英文名-ODCINFO信息对象
    private static LoadingCache<String, OdcInfo> odcInfoMap = null;
    @PostConstruct
    private void init() {
        //资源启动时初始化 资源和角色权限
        //缓存 英文名-权限 初始化
        //缓存 英文名-ODCINFO
    }
    
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        FilterInvocation filterInvocation = (FilterInvocation) object;
        //TODO 干你想干事情,下面是获取路径所具有的资源
        return permitMap.get(getHttpRequest().getRequestURI());
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return new ArrayList<ConfigAttribute>();
    }

    @Override
    public boolean supports(Class<?> aClass) {
        //很重要,不然不起作用
        return true;
    }
}

ApiAccessDecisionManager

@Component
public class ApiAccessDecisionManager implements AccessDecisionManager {
    /**
     * 判断用户是否有访问资源权限
     * @param authentication    用户Auth
     * @param object FilterInvocation对象
     * @param configAttributes  资源所需权限
     * @throws AccessDeniedException  无权限Exception
     */
    public void decide(Authentication authentication, Object object,
               Collection<ConfigAttribute> configAttributes)
               throws AccessDeniedException {
        if(access){
            //允许通过
            return;
        }
        //不允许角色访问
        throw new AccessDeniedException("NO ALLOW");
    }
    
    public boolean supports( ConfigAttribute attribute ){
            return true;
    }

    public boolean supports(Class<?> clazz){
        return true;
    }
}

作者:libertinus
链接:https://www.jianshu.com/p/6b8fb59b614b
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

Leave a Comment