Spring-Security源碼解析(權限)

前言

在讀這篇文章之前請先讀
https://blog.csdn.net/dtttyc/article/details/88950201

自定義登陸配置

上一篇文章講解的Security是如何使用的,但是寫的比較死,這次通過配置我們可以實現登陸可以利用表單也可以利用我們自己寫的頁面

  1. WebSecurityConfigurerAdapter中方法void configure(HttpSecurity http),登陸的時候不要把地址寫 http.httpBasic()或者表單
   http.formLogin()
                .loginPage("/authentication/require")


    @RequestMapping("/authentication/require")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    public SimpleSupport requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {

        //得到訪問的url
        SavedRequest savedRequest=requestCache.getRequest(request,response);
        if (savedRequest != null) {
            String redirectUrl = savedRequest.getRedirectUrl();
            //截取字符串後面爲HTML
            if (StringUtils.endsWithIgnoreCase(redirectUrl,"html")){
                redirectStrategy.sendRedirect(request,response,securityProperties.getBrowser().getLoginPage());
            }
        }
        return new SimpleSupport("訪問服務需要身份驗證");
    }
  1. 在application.properties
imooc.security.browser.loginPage = /demo-signIn.html
  1. 讀取配置信息
/**
 * @Author: judy
 * @Description: 讀取配置信息
 * @Date: Created in 10:58 2019/4/10
 */
@ConfigurationProperties(prefix = "imooc.security")
public class SecurityProperties {
    BrowserProperties browser= new BrowserProperties();

    public BrowserProperties getBrowser() {
        return browser;
    }

    public void setBrowser(BrowserProperties browser) {
        this.browser = browser;
    }
}

  1. 配置地址和設置默認的地址
/**
 * @Author: judy
 * @Description: 配置地址和設置默認的地址
 * @Date: Created in 10:59 2019/4/10
 */
public class BrowserProperties {
    private String loginPage="/imooc-signIn.html";

    public String getLoginPage() {
        return loginPage;
    }

    public void setLoginPage(String loginPage) {
        this.loginPage = loginPage;
    }
}

其實沒有太多的含金量, 主要就是抽象能力, 利用讀取配置和必要的條件做一些判斷和邏輯就可以

Security流程

下發的圖是關於Security的一個基本流程, 我設置了幾個斷點,走進去看看
#

UsernamePasswordAuthenticationFilter

登陸之後首先進入UsernamePasswordAuthenticationFilter,

核心代碼塊


            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);

劃重點

   UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

首先傳入我們的用戶名和密碼,然後設置權限爲false

 public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super((Collection)null);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);
    }

劃重點

//把用戶的權限信息set
 this.setDetails(request, authRequest);

AuthenticationManager和AuthenticationProvider

AuthenticationManager的作用是用來管理authenticationProvider,因爲Security爲我們了提供了很多方法, 假如我們是利用第三方登陸,根本不需要用戶名和祕密, 那麼這個時候authenticationProvider會遍歷它,也就是我們複合那種情況

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
     public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();
        Iterator var6 = this.getProviders().iterator();

        while(var6.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var6.next();
            //匹配使用的那種方法
            if (provider.supports(toTest)) {
                if (debug) {
                    logger.debug("Authentication attempt using " + provider.getClass().getName());
                }

                try {
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (AccountStatusException var11) {
                    this.prepareException(var11, authentication);
                    throw var11;
                } catch (InternalAuthenticationServiceException var12) {
                    this.prepareException(var12, authentication);
                    throw var12;
                } catch (AuthenticationException var13) {
                    lastException = var13;
                }
            }
        }

        if (result == null && this.parent != null) {
            try {
                result = this.parent.authenticate(authentication);
            } catch (ProviderNotFoundException var9) {
                ;
            } catch (AuthenticationException var10) {
                lastException = var10;
            }
        }

        if (result != null) {
            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials();
            }

            this.eventPublisher.publishAuthenticationSuccess(result);
            return result;
        } else {
            if (lastException == null) {
                lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
            }

            this.prepareException((AuthenticationException)lastException, authentication);
            throw lastException;
        }
    }
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {

 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
        String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("User '" + username + "' not found");
                if (this.hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }

                throw var6;
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }
         //check檢查
        try {
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            if (!cacheWasUsed) {
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        }
      //check檢查
        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

}

UserDetailsService

我們在實習Security的時候需要寫一個類來實現UserDetailsService,用來實現我們的業務邏輯,簡單來說可以實現用戶名和密碼

@Component
public class MyUserDetailsSservice implements UserDetailsService{
//    private Logger logger= (Logger) LoggerFactory.getLogger(getClass());
    @Autowired
    private pass passwordEncoder;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

//        logger.info("登陸用戶名:"+ username);
        System.out.println("登陸用戶名:"+ username);
        //這裏的true就是上面check用到的
        return new User(username,passwordEncoder.encode("123456"),
                true,true,true,true
                ,AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}
    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        UserDetails loadedUser;
       loadedUser = this.getUserDetailsService().loadUserByUsername(username);
       }

在這裏插入圖片描述

成功

等到都成功執行之後,會發現權限設置爲了true

   public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);
    }

總結

O(∩_∩)O,

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章