Spring cloud oauth2 研究--授權方式分析

密碼模式授權相對來說比較簡單,只使用到了申請token的接口也就是/oauth/token的接口,授權碼模式相對復雜一點,以下為介紹:

1.增加AuthorizationServer配置

為了讓數(shù)據(jù)更直觀,將授權碼模式的一些存儲方式改為jdbc的

/// ### AuthorizationServerConfiguration.java

    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
    // 授權碼模式code存儲方式,默認內(nèi)存存儲
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    
    @Bean
    public ApprovalStore approvalStore() {
    // 授權允許存儲方式,默認內(nèi)存存儲
        return new JdbcApprovalStore(dataSource);
    }
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                // 授權允許存儲方式
                .approvalStore(approvalStore())
                // 授權碼模式code存儲方式
                .authorizationCodeServices(authorizationCodeServices())
                // token存儲方式
                .tokenStore(tokenStore())
                // 使用jwt增強
                .tokenEnhancer(jwtAccessTokenConverter())
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                .authenticationManager(authenticationManager);

        // 配置tokenServices參數(shù)
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        endpoints.tokenServices(tokenServices);
    }

2.增加webSecurity的配置,由于需要訪問服務下的登陸與允許授權的頁面,需要配置permitall

@Configuration
@EnableWebSecurity
@Order(10)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Autowired
    private UserServiceDetail userServiceDetail;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .parentAuthenticationManager(authenticationManagerBean())
                .userDetailsService(userServiceDetail)
                .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http    
        // 登陸位置會有rememberMe選項
                .rememberMe()
                .and()
                .authorizeRequests()
         // 允許/login訪問地址
                .anyRequest().authenticated()        
                .and()
                .formLogin().permitAll()
                .and()
                .logout()
                .logoutSuccessUrl("/login")
                .invalidateHttpSession(true)
                .permitAll()
                .and()
                .csrf().disable();
    }
}

3.數(shù)據(jù)庫oauth_client_details增加一條記錄

image.png

密碼是123456的bcrypt方式加密

4.開始測試

  • 瀏覽器輸入地址

http://localhost:4001/oauth/authorize?response_type=code&client_id=code&redirect_uri=http://baidu.com&state=123

response_type=code: 返回的是授權碼
client_id=code: 與數(shù)據(jù)庫的clientId對應
redirect_uri=http:/baidu.com: 與數(shù)據(jù)庫web_redirect_uri對應,授權碼code獲取成功重定向的地址
state=123 任意的字符串

  • 跳轉到login頁面


    image.png

輸入賬號密碼賬號,重定向到redirect_uri,并且攜帶code: https://www.baidu.com/?code=vsf7NQ&state=123

  • 查看數(shù)據(jù)庫

oauth_approvals 增加一條數(shù)據(jù)
oauth_code 增加一條數(shù)據(jù),code=vsf7NQ

  • 通過得到的code,訪問/oauth/token進行申請token,由于之前設置了允許方法GET,POST所以這兩種請求方法都可以申請到token

http://localhost:4001/oauth/token?client_id=code&grant_type=authorization_code&redirect_uri=http://baidu.com&code=vsf7NQ&client_secret=123456

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTcyOTk0NjEsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiMDUyNDdiMGItZjJiZS00MWZhLTlmODItOWRhZjU2NDZjNWI0IiwiY2xpZW50X2lkIjoiY29kZSIsInNjb3BlIjpbImFsbCJdfQ.-KDyRZyp-dWlFOk7XVnRg4EpDKzabspkzlKcr0M3tu4",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiIwNTI0N2IwYi1mMmJlLTQxZmEtOWY4Mi05ZGFmNTY0NmM1YjQiLCJleHAiOjE1NTczMDMwNjEsImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6IjZhMWNiMmFkLWMxODEtNDcyZi1hYjIzLWY5ZjQ1MTQwMDJhMiIsImNsaWVudF9pZCI6ImNvZGUifQ.aG72owlXCaqdOTVw2NM5fALKMkWsW1Dce2zIvz_lqu4",
    "expires_in": 2248,
    "scope": "all",
    "jti": "05247b0b-f2be-41fa-9f82-9daf5646c5b4"
}
  • 再次查看oauth_code表,發(fā)現(xiàn)code=vsf7NQ的記錄被刪除了,說明授權碼模式code只能被使用一次。

接下來通過源碼來解析一下整個過程

攔截鏈

image.png

依次執(zhí)行順序為:

  1. WebAsyncManagerIntegrationFilter

    沒做什么相關的操作

  2. SecurityContextPersistenceFilter

    創(chuàng)建安全上下文,請求結束時,清空,同時獲取到用戶,以及認證信息

  3. HeaderWriterFilter

    忽略

  4. LogoutFilter

    判斷是否是登出操作;如果是,執(zhí)行登出操作

  5. UsernamePasswordAuthenticationFilter

    判斷哪些請求需要鑒權,哪些不需要,如果是登陸的接口,就會通過authenticationManager去進行校驗,然后根據(jù)檢驗結果執(zhí)行不同的操作

  6. DefaultLoginPageGeneratingFilter

    如果是登陸,登陸錯誤,登陸成功的請求,直接返回到頁面

boolean loginError = isErrorPage(request);
boolean logoutSuccess = isLogoutSuccess(request);
if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
    String loginPageHtml = generateLoginPageHtml(request, loginError,
                    logoutSuccess);
    response.setContentType("text/html;charset=UTF-8");
    response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
    response.getWriter().write(loginPageHtml);

    return;
}
  1. RequestCacheAwareFilter

    判斷使用緩存的請求

  2. SecurityContextHolderAwareRequestFilter

    忽略

  3. RememberMeAuthenticationFilter

    RememberMe的校驗

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
                    response);
            if (rememberMeAuth != null) {
                // .... 從cookie里面獲取登陸狀態(tài),成功直接結束請求,返回給用戶
            }
            chain.doFilter(request, response);
        }
        // .....
    }

  1. AnonymousAuthenticationFilter

如果SecurityContextHolder中沒有當前請求用戶授權信息,創(chuàng)建一個匿名用戶放到全局域SecurityContextHolder中

  1. SessionManagementFilter

判斷session中是否已經(jīng)存在授權信息,首次請求只會獲取到上一個filter存放的匿名信息

  1. ExceptionTranslationFilter

這個fitler會全局處理下游拋出的異常,如果拋出的異常是AccessDeniedException,且是匿名用戶的話會跳到指定的登陸頁面

  1. FilterSecurityInterceptor

如果沒拋出異常,會將登陸的用戶鑒權信息存儲到SecurityContextHolder,供一次訪問使用

匿名用戶


image.png
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容