OAuth2.0系列之使用JWT令牌實踐教程(八)


@

OAuth2.0系列博客:

1、文章前言介紹

前面文章中我們學習了OAuth2的一些基本概念,對OAuth2有了基本的認識,也對OAuth2.0的令牌等進行數據庫存儲,對應博客:jdbc方式的數據存儲,然后如果不想存儲令牌可以實現?

IDEA中,Ctrl+Alt+B,可以看到TokenStore的實現,有如下幾種:
在這里插入圖片描述
ok,其實對於token存儲有如上方式,分別進行介紹:

  • InMemoryTokenStore,默認存儲,保存在內存
  • JdbcTokenStore,access_token存儲在數據庫
  • JwtTokenStore,JWT這種方式比較特殊,這是一種無狀態方式的存儲,不進行內存、數據庫存儲,只是JWT中攜帶全面的用戶信息,保存在jwt中攜帶過去校驗就可以
  • RedisTokenStore,將 access_token 存到 redis 中。
  • JwkTokenStore,將 access_token 保存到 JSON Web Key。

2、例子實驗驗證

實驗環境准備:

  • IntelliJ IDEA
  • Maven3.+版本
    新建SpringBoot Initializer項目,可以命名oauth2-jwt
    在這里插入圖片描述

在這里插入圖片描述
主要加入如下配置:

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 <!-- Spring Cloud Oauth2-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <!-- Spring Cloud Security-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

TokenStore:

   @Bean
    public TokenStore jwtTokenStore() {
        //基於jwt實現令牌(Access Token)
        return new JwtTokenStore(accessTokenConverter());
    }

JwtAccessTokenConverter :

 @Bean
    public JwtAccessTokenConverter accessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter(){
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                String grantType = authentication.getOAuth2Request().getGrantType();
                //授權碼和密碼模式才自定義token信息
                if(AUTHORIZATION_CODE.equals(grantType) || GRANT_TYPE_PASSWORD.equals(grantType)) {
                    String userName = authentication.getUserAuthentication().getName();
                    // 自定義一些token 信息
                    Map<String, Object> additionalInformation = new HashMap<String, Object>(16);
                    additionalInformation.put("user_name", userName);
                    additionalInformation = Collections.unmodifiableMap(additionalInformation);
                    ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
                }
                OAuth2AccessToken token = super.enhance(accessToken, authentication);
                return token;
            }
        };
        // 設置簽署key
        converter.setSigningKey("signingKey");
        return converter;
    }

配置accessTokenConverter

 @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(jwtTokenStore()).authenticationManager(authenticationManager)
                //自定義accessTokenConverter
                .accessTokenConverter(accessTokenConverter())
                //支持獲取token方式
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST,HttpMethod.PUT,HttpMethod.DELETE,HttpMethod.OPTIONS);
    }

總的配置類參考:

package com.example.springboot.oauth2.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * <pre>
 *  OAuth2.0配置類
 * </pre>
 *
 * <pre>
 * @author mazq
 * 修改記錄
 *    修改后版本:     修改人:  修改日期: 2020/06/17 11:44  修改內容:
 * </pre>
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {

    private static final String CLIENT_ID = "cms";
    private static final String SECRET_CHAR_SEQUENCE = "{noop}secret";
    private static final String SCOPE_READ = "read";
    private static final String SCOPE_WRITE = "write";
    private static final String TRUST = "trust";
    private static final String USER ="user";
    private static final String ALL = "all";
    private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 2*60;
    private static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 2*60;
    // 密碼模式授權模式
    private static final String GRANT_TYPE_PASSWORD = "password";
    //授權碼模式
    private static final String AUTHORIZATION_CODE = "authorization_code";
    //refresh token模式
    private static final String REFRESH_TOKEN = "refresh_token";
    //簡化授權模式
    private static final String IMPLICIT = "implicit";
    //指定哪些資源是需要授權驗證的
    private static final String RESOURCE_ID = "resource_id";

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                // 使用內存存儲
                .inMemory()
                //標記客戶端id
                .withClient(CLIENT_ID)
                //客戶端安全碼
                .secret(SECRET_CHAR_SEQUENCE)
                //為true 直接自動授權成功返回code
                .autoApprove(true)
                .redirectUris("http://127.0.0.1:8084/cms/login") //重定向uri
                //允許授權范圍
                .scopes(ALL)
                //token 時間秒
                .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
                //刷新token 時間 秒
                .refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS)
                //允許授權類型
                .authorizedGrantTypes(GRANT_TYPE_PASSWORD , AUTHORIZATION_CODE , REFRESH_TOKEN , IMPLICIT);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(jwtTokenStore()).authenticationManager(authenticationManager)
                //自定義accessTokenConverter
                .accessTokenConverter(accessTokenConverter())
                //支持獲取token方式
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST,HttpMethod.PUT,HttpMethod.DELETE,HttpMethod.OPTIONS);
    }

    /**
     * 認證服務器的安全配置
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                // 開啟/oauth/token_key驗證端口認證權限訪問
                .tokenKeyAccess("isAuthenticated()")
                //  開啟/oauth/check_token驗證端口認證權限訪問
                .checkTokenAccess("isAuthenticated()")
                //允許表單認證
                .allowFormAuthenticationForClients();
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter(){
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                String grantType = authentication.getOAuth2Request().getGrantType();
                //授權碼和密碼模式才自定義token信息
                if(AUTHORIZATION_CODE.equals(grantType) || GRANT_TYPE_PASSWORD.equals(grantType)) {
                    String userName = authentication.getUserAuthentication().getName();
                    // 自定義一些token 信息
                    Map<String, Object> additionalInformation = new HashMap<String, Object>(16);
                    additionalInformation.put("user_name", userName);
                    additionalInformation = Collections.unmodifiableMap(additionalInformation);
                    ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
                }
                OAuth2AccessToken token = super.enhance(accessToken, authentication);
                return token;
            }
        };
        // 設置簽署key
        converter.setSigningKey("signingKey");
        return converter;
    }

    @Bean
    public TokenStore jwtTokenStore() {
        //基於jwt實現令牌(Access Token)
        return new JwtTokenStore(accessTokenConverter());
    }

}

SpringSecurity配置類:

package com.example.springboot.oauth2.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * <pre>
 *      Spring Security配置類
 * </pre>
 *
 * <pre>
 * @author mazq
 * 修改記錄
 *    修改后版本:     修改人:  修改日期: 2020/06/15 10:39  修改內容:
 * </pre>
 */
@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

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


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {    //auth.inMemoryAuthentication()
        auth.inMemoryAuthentication()
                .withUser("nicky")
                .password("{noop}123")
                .roles("admin");
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        //解決靜態資源被攔截的問題
        web.ignoring().antMatchers("/asserts/**");
        web.ignoring().antMatchers("/favicon.ico");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http   // 配置登錄頁並允許訪問
                .formLogin().permitAll()
                // 配置Basic登錄
                //.and().httpBasic()
                // 配置登出頁面
                .and().logout().logoutUrl("/logout").logoutSuccessUrl("/")
                .and().authorizeRequests().antMatchers("/oauth/**", "/login/**", "/logout/**").permitAll()
                // 其余所有請求全部需要鑒權認證
                .anyRequest().authenticated()
                // 關閉跨域保護;
                .and().csrf().disable();
    }


}

3、功能簡單測試

訪問授權鏈接,在瀏覽器訪問就可以,授權碼模式response_type參數傳code:
http://localhost:8888/oauth/authorize?client_id=cms&client_secret=secret&response_type=code

因為沒登錄,所以會返回SpringSecurity的默認登錄頁面,具體代碼是 http .formLogin().permitAll();,如果要彈窗登錄的,可以配置http.httpBasic();,這種配置是沒有登錄頁面的,自定義登錄頁面可以這樣配置http.formLogin().loginPage("/login").permitAll()

如圖,輸入SpringSecurity配置的數據庫密碼
在這里插入圖片描述

登錄成功,返回redirect_uri,拿到授權碼

重定向回redirect_uri,http://localhost:8084/cms/login?code=???

配置一下請求頭的授權參數,用Basic Auth方式,username即client_id,password即client_secret
在這里插入圖片描述
拿到授權碼之后去獲取token,本教程使用授權碼方式

在這里插入圖片描述
JWT方式的token

{
    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTIzNzYwNjEsInVzZXJfbmFtZSI6Im5pY2t5IiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9hZG1pbiJdLCJqdGkiOiJiM2IwZGExNS1mMmQyLTRlN2MtYTUwNC1iMzg5YjkxMjM0MDMiLCJjbGllbnRfaWQiOiJjbXMiLCJzY29wZSI6WyJhbGwiXX0.TpIBd9Gtb4M7sC1MSQsxsn8mwnhAm59CUBZPU7jwdnE",
    "token_type":"bearer",
    "refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJuaWNreSIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiJiM2IwZGExNS1mMmQyLTRlN2MtYTUwNC1iMzg5YjkxMjM0MDMiLCJleHAiOjE1OTIzNzYwNjEsImF1dGhvcml0aWVzIjpbIlJPTEVfYWRtaW4iXSwianRpIjoiODVhYTlmMGYtNDliNS00NDg4LTk4MTQtNmM0MmZjMjZkYTc2IiwiY2xpZW50X2lkIjoiY21zIn0.TU8ZD_5AxRGbgbOWZSuWAxwWjMJ4HLHniA46M-dnChE",
    "expires_in":119,
    "scope":"all",
    "user_name":"nicky",
    "jti":"b3b0da15-f2d2-4e7c-a504-b389b9123403"
}

例子代碼下載:code download


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM