springsecurity03-springsecurity oauth2實現單點登錄之-資源服務器(Resource Service)

資源文件簡介

文章參考自 https://projects.spring.io/spring-security-oauth/docs/oauth2.html
一個資源服務(可以和授權服務在同一個應用中,當然也可以分離開成爲兩個不同的應用程序)提供一些受token令牌保護的資源,Spring OAuth提供者是通過Spring Security authentication filter 即驗證過濾器來實現的保護,你可以通過 @EnableResourceServer 註解到一個 @Configuration 配置類上,並且必須使用 ResourceServerConfigurer 這個配置對象來進行配置(可以選擇繼承自 ResourceServerConfigurerAdapter 然後覆寫其中的方法,參數就是這個對象的實例),下面是一些可以配置的屬性:

  • tokenServices:ResourceServerTokenServices 類的實例,用來實現令牌服務。
  • resourceId:這個資源服務的ID,這個屬性是可選的,但是推薦設置並在授權服務中進行驗證。
  • 其他的拓展屬性例如 tokenExtractor 令牌提取器用來提取請求中的令牌。
  • 請求匹配器,用來設置需要進行保護的資源路徑,默認的情況下是受保護資源服務的全部路徑。
  • 受保護資源的訪問規則,默認的規則是簡單的身份驗證(plain authenticated)。
  • 其他的自定義權限保護規則通過 HttpSecurity 來進行配置。

@EnableResourceServer 註解自動增加了一個類型爲 OAuth2AuthenticationProcessingFilter 的過濾器鏈,

在XML配置中,使用 標籤元素並指定id爲一個servlet過濾器就能夠手動增加一個標準的過濾器鏈。

ResourceServerTokenServices 是組成授權服務的另一半,如果你的授權服務和資源服務在同一個應用程序上的話,你可以使用 DefaultTokenServices ,這樣的話,你就不用考慮關於實現所有必要的接口的一致性問題,這通常是很困難的。如果你的資源服務器是分離開的,那麼你就必須要確保能夠有匹配授權服務提供的 ResourceServerTokenServices,它知道如何對令牌進行解碼。
在授權服務器上,你通常可以使用 DefaultTokenServices 並且選擇一些主要的表達式通過 TokenStore(後端存儲或者本地編碼)。
RemoteTokenServices 可以作爲一個替代,它將允許資源服務器通過HTTP請求來解碼令牌(也就是授權服務的 /oauth/check_token 端點)。如果你的資源服務沒有太大的訪問量的話,那麼使用RemoteTokenServices 將會很方便(所有受保護的資源請求都將請求一次授權服務用以檢驗token值),或者你可以通過緩存來保存每一個token驗證的結果。
使用授權服務的 /oauth/check_token 端點你需要將這個端點暴露出去,以便資源服務可以進行訪問,這在咱們授權服務配置中已經提到了,下面是一個例子:

@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
    oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')")
        .checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
}

在這個例子中,我們配置了 /oauth/check_token 和 /oauth/token_key 這兩個端點(受信任的資源服務能夠獲取到公有密匙,這是爲了驗證JWT令牌)。這兩個端點使用了HTTP Basic Authentication 即HTTP基本身份驗證,使用 client_credentials 授權模式可以做到這一點。

配置OAuth-Aware表達式處理器(OAuth-Aware Expression Handler):
你也許希望使用 Spring Security’s expression-based access control 來獲得一些優勢,一個表達式處理器會被註冊到默認的 @EnableResourceServer 配置中,這個表達式包含了 #oauth2.clientHasRole,#oauth2.clientHasAnyRole 以及 #oauth2.denyClient 所提供的方法來幫助你使用權限角色相關的功能(在 OAuth2SecurityExpressionMethods 中有完整的列表)。
在XML配置中你可以註冊一個 OAuth-Aware 表達式處理器即 元素標籤到 常規的 安全配置上。

資源文件實戰

使用之前的授權服務器: https://blog.csdn.net/liaomin416100569/article/details/88529127#_134

授權服務器改造

由於之前在授權服務器使用了jwtstore,使用資源服務器驗證時發現JwtHelper類的驗證token
有個SignatureVerifier發現如何不使用RSA(公私密鑰)verifier永遠是空,永遠是400 null錯誤

public static Jwt decodeAndVerify(String token, SignatureVerifier verifier) {
		Jwt jwt = decode(token);
		jwt.verifySignature(verifier);

		return jwt;
	}

生成公私密鑰(注意一定要添加–keyalg RSA 默認是DSA哦,JWT只支持RSA)

C:\Users\Administrator>keytool -genkeypair --keyalg RSA  -keystore c:/a.keystore -alias test1
輸入密鑰庫口令:
您的名字與姓氏是什麼?
  [Unknown]:  a
您的組織單位名稱是什麼?
  [Unknown]:  a
您的組織名稱是什麼?
  [Unknown]:  a
您所在的城市或區域名稱是什麼?
  [Unknown]:  a
您所在的省/市/自治區名稱是什麼?
  [Unknown]:  a
該單位的雙字母國家/地區代碼是什麼?
  [Unknown]:  a
CN=a, OU=a, O=a, L=a, ST=a, C=a是否正確?
  [否]:  y

輸入 <test1> 的密鑰口令
        (如果和密鑰庫口令相同, 按回車):

將生成的c:/a.keystore拷貝到授權項目的src/main/resources目錄
改造授權服務器配置方法

@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        super.configure(endpoints);



        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        //一定要設置對稱的SigningKey 用於驗證之後的token是否有效的
        jwtAccessTokenConverter.setSigningKey("123456");
        KeyStore keyStore = KeyStore.getInstance("JCEKS");
        keyStore.load(OAuth2AuthorizationServerConfig.class.getResourceAsStream("/a.keystore"),"123456".toCharArray());
        PublicKey publicKey = keyStore.getCertificate("test1").getPublicKey();
        PrivateKey privateKey = (PrivateKey)keyStore.getKey("test1", "123456".toCharArray());
        KeyPair kp=new KeyPair(publicKey,privateKey);
        jwtAccessTokenConverter.setKeyPair(kp);
        JwtTokenStore jwtTokenStore = new JwtTokenStore(jwtAccessTokenConverter);
        endpoints.pathMapping("/oauth/confirm_access ","/extenal/oauth/confirm_access")

                .tokenStore(jwtTokenStore)
                .tokenEnhancer(jwtAccessTokenConverter)

                //添加額外信息到token中,注意jwtAccessTokenConverter不能設置額外的tokenEnhancer否則無法生成jwt的token註釋掉
//                .tokenEnhancer(new TokenEnhancer() {
//                    @Override
//                    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
//                        DefaultOAuth2AccessToken doat= (DefaultOAuth2AccessToken) oAuth2AccessToken;
//                        Map<String, Object> additionalInfo = new HashMap<>();
//                        additionalInfo.put("myname", "jiaozi");
//                        doat.setAdditionalInformation(additionalInfo);
//                        return doat;
//                    }
//                })
        ;
    }

資源服務器改造

新建maven項目
添加依賴(和資源服務器一致)

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.8.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <version>1.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>nekohtml</groupId>
            <artifactId>nekohtml</artifactId>
            <version>1.9.6.2</version>
        </dependency>
    </dependencies>

添加資源服務器配置類OAuth2ResourceServerConfig

@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        super.configure(http);
    }

    public RestTemplate restTemplate() {
        //httpRequestFactory()
        RestTemplate restTemplate = new RestTemplate();
        List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters();
        for (HttpMessageConverter<?> converter : converters) {
            if (converter instanceof MappingJackson2HttpMessageConverter) {
                MappingJackson2HttpMessageConverter jsonConverter = (MappingJackson2HttpMessageConverter) converter;
                jsonConverter.setObjectMapper(new ObjectMapper());
                List<MediaType> supportedMediaTypes=new ArrayList<>();
                supportedMediaTypes.add(new MediaType("application", "json", MappingJackson2HttpMessageConverter.DEFAULT_CHARSET));
                supportedMediaTypes.add(new MediaType("text", "javascript", MappingJackson2HttpMessageConverter.DEFAULT_CHARSET));
                jsonConverter.setSupportedMediaTypes(supportedMediaTypes);
            }
        }
        return restTemplate;
    }
    /**
     因爲資源服務器和授權服務器分離所以使用RemoteTokenServices
   */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        super.configure(resources);
        RemoteTokenServices remoteTokenServices=new RemoteTokenServices();
        remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:8889/oauth/check_token");//指定授權服務器檢查token的地址
        remoteTokenServices.setClientId("client");
        remoteTokenServices.setClientSecret("secret");
        remoteTokenServices.setRestTemplate(restTemplate());
        remoteTokenServices.setAccessTokenConverter(new JwtAccessTokenConverter());
        resources.tokenServices(remoteTokenServices);
    }
}

配置application.properties

server.port=8887
spring.thymeleaf.cache=false
spring.thymeleaf.mode=LEGACYHTML5

添加啓動類

@SpringBootApplication
public class ResourceServerMain {
    public static void main(String[] args) {
        SpringApplication.run(ResourceServerMain.class);
    }
}

添加一個控制層的資源

@Controller
public class TestController {
    @ResponseBody
    @GetMapping("/test")
    public String test() {
        return "hello";
    }
}

開始測試(前面兩步過程參考授權服務器文章)

  1. 訪問認證服務器獲取授權碼
    localhost:8889/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com
    拷貝授權碼 假設是Ce6BxM
  2. 通過授權碼獲取token
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTI1OTk1MDEsInVzZXJfbmFtZSI6InRlc3QiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiNjQxNzA0MTEtYTQ3Ni00NzQ5LWIzZTItOTM2NzA0NmY5MjAzIiwiY2xpZW50X2lkIjoiY2xpZW50Iiwic2NvcGUiOlsiYWxsIl19.XERRnoc-NVXJ2H6wweDK2hl9Wf7gI48NHY0aMXh2g16Hctwv60-wn41FcQnOVuSxChbBe7oe5kaXQdq7SjbTMbjAD0VPu6B4X18IsTgJ5BP-tGruhWdtxcqCJ_Gg8HRkI-62F_RO7n-B1zkf-ZmvSPO3chvBL7xiH8S0lE0c1b5FXMMIFtoqvSPVPNAt9UDo6p4JiGWwKq9Podo1bH9FxOxoHrVBYb03IRn_ASjd0Vx0iQZVG-J6VTDDUfHcHPc1HwPgaK7aEoaE-9WucsgtXDaa-C69PcU-XHPwxfKDAYrgTIAJ1kux0DLuMjWavALho1BxlVeWImFe3b0WPhKooA","token_type":"bearer","expires_in":43199,"scope":"all","jti":"64170411-a476-4749-b3e2-9367046f9203"}
  1. 通過授權碼訪問/test
    注意訪問的過程是在訪問的請求頭 加上
    Authorization: Bearer 你的token

在這裏插入圖片描述
我圖裏面用的post請求

{"timestamp":"2019-03-14T09:40:59.035+0000","status":405,"error":"Method Not Allowed","message":"Request method 'POST' not supported","path":"/test"}

我的控制層是get請求使用get請求成功輸出hello
在這裏插入圖片描述
你也可以直接在瀏覽器輸入check_token來檢查token是否正確
在這裏插入圖片描述

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