Spring security4.x浅析

一、概述

最近使用Spring security进行细粒度的动态权限管理,就再花了一些时间梳理spring security的过程。其实spring security主要分为两个部分:(1)认证,Authentication。(2)授权。说实话,如果你看过源码的话,你就会知道spring security的认证授权都是通过一系列的过滤器来实现的。
官方介绍:https://spring.io/guides/topicals/spring-security-architecture/

二、认证

先来说一下认证流程。

认证拦截AbstractAuthenticationProcessingFilter,如UsernamePasswordAuthenticationFilter等都继承于它(当然我们可以自己继承实现自己的操作如验证码、短信等的验证)。
实现认证过程即调用attemptAuthentication实现认证。
先看AbstractAuthenticationProcessingFilter的doFilter。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        //1.先判断是否需要进行认证
        if (!requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
            return;
        }
        Authentication authResult;
        try {
            //2.调用实现类进行认证
            authResult = attemptAuthentication(request, response);
            if (authResult == null) {
                return;
            }
            sessionStrategy.onAuthentication(authResult, request, response);
        }
        catch (InternalAuthenticationServiceException failed) {
            logger.error(
                    "An internal error occurred while trying to authenticate the user.",
                    failed);
            unsuccessfulAuthentication(request, response, failed);

            return;
        }
        catch (AuthenticationException failed) {
            //3.认证失败回调failureHandler
            unsuccessfulAuthentication(request, response, failed);

            return;
        }
        // Authentication success
        if (continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }
        //4.认证成功
        successfulAuthentication(request, response, chain, authResult);
    }

认证的基本流程如下:

2.1.先判断当前url是否需要进行认证。是则进行下一步,否则执行下一个过滤器。这里的url配置就是我们在设置webSecurity的时候设置的。例如 http.authorizeRequests().antMatchers(“/hello”).permitAll()等。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/hello").permitAll()
         .anyRequest().authenticated()
    }

}
2.2调用实现类的attemptAuthentication进行认证

以UsernamePasswordAuthenticationFilter为例,对用户名和密码进行认证。

public Authentication attemptAuthentication(HttpServletRequest request,
            HttpServletResponse response) throws AuthenticationException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
        String username = obtainUsername(request);
        String password = obtainPassword(request);

        if (username == null) {
            username = "";
        }
        if (password == null) {
            password = "";
        }
        username = username.trim();
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username, password);
        setDetails(request, authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);
    }

this.getAuthenticationManager().authenticate(authRequest);会调用具体的AuthenticationProvider进行认证(当然我们也可以添加实现自己的Provider)。
ProviderManager的authenticate

public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();

        for (AuthenticationProvider provider : getProviders()) {
            if (!provider.supports(toTest)) {
                continue;
            }

            if (debug) {
                logger.debug("Authentication attempt using "
                        + provider.getClass().getName());
            }

            try {
                //调用具体的provider进行认证
                result = provider.authenticate(authentication);
                if (result != null) {
                    copyDetails(authentication, result);
                    break;
                }
            }
            catch (AccountStatusException e) {
                prepareException(e, authentication);
                // SEC-546: Avoid polling additional providers if auth failure is due to
                // invalid account status
                throw e;
            }
            catch (InternalAuthenticationServiceException e) {
                prepareException(e, authentication);
                throw e;
            }
            catch (AuthenticationException e) {
                lastException = e;
            }
        }

2.3认证失败

认证失败调用unsuccessfulAuthentication(request, response, failed);

protected void unsuccessfulAuthentication(HttpServletRequest request,
            HttpServletResponse response, AuthenticationException failed)
            throws IOException, ServletException {
        SecurityContextHolder.clearContext();
        rememberMeServices.loginFail(request, response);
        failureHandler.onAuthenticationFailure(request, response, failed);
    }

我们这里看到有个failureHandler,那么我们可以自定义failureHandler,实现认证失败返回自定义的JSON。是不是很方便

2.4认证成功

认证成功 successfulAuthentication

protected void successfulAuthentication(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException {


        SecurityContextHolder.getContext().setAuthentication(authResult);
        rememberMeServices.loginSuccess(request, response, authResult);
        // Fire event
        if (this.eventPublisher != null) {
            eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                    authResult, this.getClass()));
        }
        successHandler.onAuthenticationSuccess(request, response, authResult);
    }

认证成功主要内容为:把认证信息存入SecurityContextHolder;通知认证成功给用户。同样我们可以自定义successHandler,返回JSON返回XML,
怎么自定义呢,在WebSecurityConfig里面,例如:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
        .anyRequest().authenticated()
                .and()
                .addFilterBefore(gogUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)

    }
@Bean
    public GogUsernamePasswordAuthenticationFilter gogUsernamePasswordAuthenticationFilter() throws Exception {
        GogUsernamePasswordAuthenticationFilter myFilter = new GogUsernamePasswordAuthenticationFilter();
        myFilter.setAuthenticationManager(authenticationManagerBean());
        myFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
        myFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
        return myFilter;
    }

到这里整改认证过程就执行完了,后面就是授权的问题。

三、授权

授权的过程采用决策器投票的形式,有一票通过则通过,否则不通过。首选看决策器AccessDecisionManager的子类AffirmativeBased,整个决策过程在decide方法里,decide方法调用决策器进行表决。AccessDecisionVoter常用的如RoleVoter,WebExpressionVoter等,另外我们当然可以自定义自己的Voter。

public void decide(Authentication authentication, Object object,
            Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
        int deny = 0;

        for (AccessDecisionVoter voter : getDecisionVoters()) {
            int result = voter.vote(authentication, object, configAttributes);

            if (logger.isDebugEnabled()) {
                logger.debug("Voter: " + voter + ", returned: " + result);
            }

            switch (result) {
            case AccessDecisionVoter.ACCESS_GRANTED:
                return;

            case AccessDecisionVoter.ACCESS_DENIED:
                deny++;

                break;

            default:
                break;
            }
        }

        if (deny > 0) {
            throw new AccessDeniedException(messages.getMessage(
                    "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
        }

        // To get this far, every AccessDecisionVoter abstained
        checkAllowIfAllAbstainDecisions();
    }

例如自定义决策器:

    @Bean
    public AccessDecisionManager accessDecisionManager(){
        RoleVoter roleVoter = new RoleVoter();//角色决策器
        AuthenticatedVoter authenticatedVoter = new AuthenticatedVoter();//认证决策器
        UrlMatchVoter urlMatchVoter = new UrlMatchVoter();
        List<AccessDecisionVoter<? extends Object>> list = new ArrayList<>();
        list.add(roleVoter);
        list.add(authenticatedVoter);
        list.add(urlMatchVoter);
        AccessDecisionManager accessDecisionManager = new AffirmativeBased(list);
        return accessDecisionManager;
    }

结束

到认证授权的过程就基本结束了,重要的还是自己DEBUG跟踪代码,一步一步来实现。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值