In the previous blog post, we discussed how to configure a simple OAuth2 authentication. However, our implementation has a major flaw in it: we are using an in-memory token store.
In-Memory token stores should be used only during development or whether your application has a single server, as you can’t easily share them between nodes and, in case of a server restart, you will lose all access tokens in it.
Spring-security-oauth2 already has built-in support for JDBC and JWT. However, if you need to save your tokens somewhere else, you have to create your own spring security token store. Unfortunately, implementing such a thing is not a trivial task, and I hope the following recipe will save you a couple hours of work.
Let’s start by creating the two entities responsible for storing your access and refresh token, and their respective repositories:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
가져오기 lombok.데이터; 가져오기 org.springframework.데이터.주석.Id; 가져오기 org.springframework.데이터.카우치베이스.core.mapping.문서; 가져오기 org.springframework.security.oauth2.common.OAuth2AccessToken; 가져오기 org.springframework.security.oauth2.제공자.OAuth2Authentication; @Document @Data 공공의 클래스 CouchbaseAccessToken { @Id 사적인 문자열 아이디; 사적인 문자열 tokenId; 사적인 OAuth2AccessToken 토큰; 사적인 문자열 authenticationId; 사적인 문자열 사용자 이름; 사적인 문자열 clientId; 사적인 문자열 인증; 사적인 문자열 refreshToken; 공공의 OAuth2Authentication getAuthentication() { 반환 SerializableObjectConverter.deserialize(인증); } 공공의 무효 setAuthentication(OAuth2Authentication 인증) { 이것.인증 = SerializableObjectConverter.serialize(인증); } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@N1qlPrimaryIndexed @ViewIndexed(designDoc = “couchbaseAccessToken”) 공공의 인터페이스 CouchbaseAccessTokenRepository 확장합니다 CouchbasePagingAndSortingRepository<CouchbaseAccessToken, 문자열< { 목록<CouchbaseAccessToken> findByClientId(문자열 clientId); 목록<CouchbaseAccessToken> findByClientIdAndUsername(문자열 clientId, 문자열 사용자 이름); Optional<CouchbaseAccessToken> findByTokenId(문자열 tokenId); Optional<CouchbaseAccessToken> findByRefreshToken(문자열 refreshToken); Optional<CouchbaseAccessToken> findByAuthenticationId(문자열 authenticationId); } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
가져오기 lombok.데이터; 가져오기 org.springframework.데이터.주석.Id; 가져오기 org.springframework.데이터.카우치베이스.core.mapping.문서; 가져오기 org.springframework.security.oauth2.common.OAuth2RefreshToken; 가져오기 org.springframework.security.oauth2.제공자.OAuth2Authentication; @Document @Data 공공의 클래스 CouchbaseRefreshToken { @Id 사적인 문자열 아이디; 사적인 문자열 tokenId; 사적인 OAuth2RefreshToken 토큰; 사적인 문자열 인증; 공공의 OAuth2Authentication getAuthentication() { 반환 SerializableObjectConverter.deserialize(인증); } 공공의 무효 setAuthentication(OAuth2Authentication 인증) { 이것.인증 = SerializableObjectConverter.serialize(인증); } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
가져오기 org.springframework.데이터.카우치베이스.core.질의.N1qlPrimaryIndexed; 가져오기 org.springframework.데이터.카우치베이스.core.질의.인덱싱된 뷰; 가져오기 org.springframework.데이터.카우치베이스.repository.CouchbasePagingAndSortingRepository; 가져오기 java.util.목록; 가져오기 java.util.Optional; @N1qlPrimaryIndexed @ViewIndexed(designDoc = “couchbaseAccessToken”) 공공의 인터페이스 CouchbaseAccessTokenRepository 확장합니다 CouchbasePagingAndSortingRepository<CouchbaseAccessToken, 문자열< { 목록<CouchbaseAccessToken> findByClientId(문자열 clientId); 목록<CouchbaseAccessToken> findByClientIdAndUsername(문자열 clientId, 문자열 사용자 이름); Optional<CouchbaseAccessToken> findByTokenId(문자열 tokenId); Optional<CouchbaseAccessToken> findByRefreshToken(문자열 refreshToken); Optional<CouchbaseAccessToken> findByAuthenticationId(문자열 authenticationId); } |
Note that OAuth2Authentication is an interface, so I have no option other than serializing the object to store it in the database. Here is the class responsible for serializing/deserializing it:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
공공의 클래스 SerializableObjectConverter { 공공의 정적 문자열 serialize(OAuth2Authentication object) { 시도하다 { byte[] bytes = SerializationUtils.serialize(object); 반환 베이스64.encodeBase64String(bytes); } catch(Exception e) { e.스택 추적 출력(); 던지다 e; } } 공공의 정적 OAuth2Authentication deserialize(문자열 encodedObject) { 시도하다 { byte[] bytes = 베이스64.decodeBase64(encodedObject); 반환 (OAuth2Authentication) SerializationUtils.deserialize(bytes); } catch(Exception e) { e.스택 추적 출력(); 던지다 e; } } |
Now, we can finally create our custom spring oauth2 token store. To do that, all we need is to implement the long list of methods of the org.springframework.security.oauth2.provider.token.TokenStore:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
가져오기 org.springframework.security.oauth2.common.OAuth2AccessToken; 가져오기 org.springframework.security.oauth2.common.OAuth2RefreshToken; 가져오기 org.springframework.security.oauth2.제공자.OAuth2Authentication; 가져오기 org.springframework.security.oauth2.제공자.토큰.AuthenticationKeyGenerator; 가져오기 org.springframework.security.oauth2.제공자.토큰.DefaultAuthenticationKeyGenerator; 가져오기 org.springframework.security.oauth2.제공자.토큰.TokenStore; 가져오기 java.io.UnsupportedEncodingException; 가져오기 java.math.BigInteger; 가져오기 java.security.MessageDigest; 가져오기 java.security.NoSuchAlgorithmException; 가져오기 java.util.*; 공공의 클래스 CouchbaseTokenStore 구현한다 TokenStore { 사적인 CouchbaseAccessTokenRepository cbAccessTokenRepository; 사적인 CouchbaseRefreshTokenRepository cbRefreshTokenRepository; 공공의 CouchbaseTokenStore(CouchbaseAccessTokenRepository cbAccessTokenRepository, CouchbaseRefreshTokenRepository cbRefreshTokenRepository){ 이것.cbAccessTokenRepository = cbAccessTokenRepository; 이것.cbRefreshTokenRepository = cbRefreshTokenRepository; } 사적인 AuthenticationKeyGenerator authenticationKeyGenerator = 새로운 DefaultAuthenticationKeyGenerator(); @Override 공공의 OAuth2Authentication readAuthentication(OAuth2AccessToken accessToken) { 반환 readAuthentication(accessToken.getValue()); } @Override 공공의 OAuth2Authentication readAuthentication(문자열 토큰) { Optional<CouchbaseAccessToken> accessToken = cbAccessTokenRepository.findByTokenId(extractTokenKey(토큰)); 만약 (accessToken.isPresent()) { 반환 accessToken.얻다().getAuthentication(); } 반환 null; } @Override 공공의 무효 storeAccessToken(OAuth2AccessToken accessToken, OAuth2Authentication 인증) { 문자열 refreshToken = null; 만약 (accessToken.getRefreshToken() != null) { refreshToken = accessToken.getRefreshToken().getValue(); } 만약 (readAccessToken(accessToken.getValue()) != null) { 이것.removeAccessToken(accessToken); } CouchbaseAccessToken cat = 새로운 CouchbaseAccessToken(); cat.setId(UUID.randomUUID().문자열로변환()+UUID.randomUUID().문자열로변환()); cat.setTokenId(extractTokenKey(accessToken.getValue())); cat.토큰설정(accessToken); cat.setAuthenticationId(authenticationKeyGenerator.extractKey(인증)); cat.setUsername(인증.isClientOnly() ? null : 인증.getName()); cat.setClientId(인증.getOAuth2Request().getClientId()); cat.setAuthentication(인증); cat.setRefreshToken(extractTokenKey(refreshToken)); cbAccessTokenRepository.저장(cat); } @Override 공공의 OAuth2AccessToken readAccessToken(문자열 tokenValue) { Optional<CouchbaseAccessToken> accessToken = cbAccessTokenRepository.findByTokenId(extractTokenKey(tokenValue)); 만약 (accessToken.isPresent()) { 반환 accessToken.얻다().getToken(); } 반환 null; } @Override 공공의 무효 removeAccessToken(OAuth2AccessToken oAuth2AccessToken) { Optional<CouchbaseAccessToken> accessToken = cbAccessTokenRepository.findByTokenId(extractTokenKey(oAuth2AccessToken.getValue())); 만약 (accessToken.isPresent()) { cbAccessTokenRepository.삭제(accessToken.얻다()); } } @Override 공공의 무효 storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication 인증) { CouchbaseRefreshToken crt = 새로운 CouchbaseRefreshToken(); crt.setId(UUID.randomUUID().문자열로변환()+UUID.randomUUID().문자열로변환()); crt.setTokenId(extractTokenKey(refreshToken.getValue())); crt.토큰설정(refreshToken); crt.setAuthentication(인증); cbRefreshTokenRepository.저장(crt); } @Override 공공의 OAuth2RefreshToken readRefreshToken(문자열 tokenValue) { Optional<CouchbaseRefreshToken> refreshToken = cbRefreshTokenRepository.findByTokenId(extractTokenKey(tokenValue)); 반환 refreshToken.isPresent()? refreshToken.얻다().getToken() :null; } @Override 공공의 OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken refreshToken) { Optional<CouchbaseRefreshToken> rtk = cbRefreshTokenRepository.findByTokenId(extractTokenKey(refreshToken.getValue())); 반환 rtk.isPresent()? rtk.얻다().getAuthentication() :null; } @Override 공공의 무효 removeRefreshToken(OAuth2RefreshToken refreshToken) { Optional<CouchbaseRefreshToken> rtk = cbRefreshTokenRepository.findByTokenId(extractTokenKey(refreshToken.getValue())); 만약 (rtk.isPresent()) { cbRefreshTokenRepository.삭제(rtk.얻다()); } } @Override 공공의 무효 removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { Optional<CouchbaseAccessToken> 토큰 = cbAccessTokenRepository.findByRefreshToken(extractTokenKey(refreshToken.getValue())); 만약(토큰.isPresent()){ cbAccessTokenRepository.삭제(토큰.얻다()); } } @Override 공공의 OAuth2AccessToken getAccessToken(OAuth2Authentication 인증) { OAuth2AccessToken accessToken = null; 문자열 authenticationId = authenticationKeyGenerator.extractKey(인증); Optional<CouchbaseAccessToken> 토큰 = cbAccessTokenRepository.findByAuthenticationId(authenticationId); 만약(토큰.isPresent()) { accessToken = 토큰.얻다().getToken(); 만약(accessToken != null && !authenticationId.equals(이것.authenticationKeyGenerator.extractKey(이것.readAuthentication(accessToken)))) { 이것.removeAccessToken(accessToken); 이것.storeAccessToken(accessToken, 인증); } } 반환 accessToken; } @Override 공공의 수집<OAuth2AccessToken> findTokensByClientIdAndUserName(문자열 clientId, 문자열 userName) { 수집<OAuth2AccessToken> tokens = 새로운 ArrayList<OAuth2AccessToken>(); 목록<CouchbaseAccessToken> 결과 = cbAccessTokenRepository.findByClientIdAndUsername(clientId, userName); 결과.forEach(e-> tokens.추가(e.getToken())); 반환 tokens; } @Override 공공의 수집<OAuth2AccessToken> findTokensByClientId(문자열 clientId) { 수집<OAuth2AccessToken> tokens = 새로운 ArrayList<OAuth2AccessToken>(); 목록<CouchbaseAccessToken> 결과 = cbAccessTokenRepository.findByClientId(clientId); 결과.forEach(e-> tokens.추가(e.getToken())); 반환 tokens; } 사적인 문자열 extractTokenKey(문자열 가치) { 만약(가치 == null) { 반환 null; } 그 외 { MessageDigest digest; 시도하다 { digest = MessageDigest.getInstance(“MD5”); } catch (NoSuchAlgorithmException var5) { 던지다 새로운 IllegalStateException(“MD5 algorithm not available. Fatal (should be in the JDK).”); } 시도하다 { byte[] e = digest.digest(가치.getBytes(“UTF-8”)); 반환 문자열.format(“%032x”, 새로운 물체[]{새로운 BigInteger(1, e)}); } catch (UnsupportedEncodingException var4) { 던지다 새로운 IllegalStateException(“UTF-8 encoding not available. Fatal (should be in the JDK).”); } } } } |
Finally, we can slightly change our SecurityConfig class, which we have created in the previous article. It will return now an instance of CouchbaseTokenStore ~ 대신에 InMemoryTokenStore:
|
1 2 3 4 5 6 7 8 9 10 11 |
@Autowired 사적인 CouchbaseAccessTokenRepository couchbaseAccessTokenRepository; @Autowired 사적인 CouchbaseRefreshTokenRepository couchbaseRefreshTokenRepository; @Bean 공공의 TokenStore tokenStore() { 반환 새로운 CouchbaseTokenStore(couchbaseAccessTokenRepository, couchbaseRefreshTokenRepository); } |
Here is the complete version of the SecurityConfig class:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
@Configuration @EnableWebMvc 공공의 클래스 SecurityConfig 확장합니다 WebSecurityConfigurerAdapter { @Autowired 사적인 CustomUserDetailsService customUserDetailsService; @Autowired 사적인 CouchbaseAccessTokenRepository couchbaseAccessTokenRepository; @Autowired 사적인 CouchbaseRefreshTokenRepository couchbaseRefreshTokenRepository; @Autowired 공공의 무효 globalUserDetails(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserDetailsService) .passwordEncoder(encoder()); } @Override 공공의 무효 configure( WebSecurity 웹 ) throws Exception { 웹.ignoring().antMatchers( HttpMethod.OPTIONS, “/**” ); } @Override protected 무효 configure(HttpSecurity http) throws Exception { http .csrf().비활성화() .authorizeRequests() .antMatchers(“/oauth/token”).permitAll() .antMatchers(“/api-docs/**”).permitAll() .anyRequest().authenticated() .그리고().anonymous().비활성화(); } @Bean 공공의 TokenStore tokenStore() { 반환 새로운 CouchbaseTokenStore(couchbaseAccessTokenRepository, couchbaseRefreshTokenRepository); } @Bean 공공의 PasswordEncoder encoder(){ 반환 NoOpPasswordEncoder.getInstance(); } @Bean 공공의 FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource 원천 = 새로운 UrlBasedCorsConfigurationSource(); CorsConfiguration 설정 = 새로운 CorsConfiguration(); 설정.setAllowCredentials(참인); 설정.addAllowedOrigin(“*”); 설정.addAllowedHeader(“*”); 설정.addAllowedMethod(“*”); 원천.registerCorsConfiguration(“/**”, 설정); FilterRegistrationBean bean = 새로운 FilterRegistrationBean(새로운 CorsFilter(원천)); bean.setOrder(0); 반환 bean; } @Bean @Override 공공의 AuthenticationManager authenticationManagerBean() throws Exception { 반환 super.authenticationManagerBean(); } } |
Well Done! That is all we had to do.
Your access token will look like the following in your database:
|
1 2 |
선택 * 에서 test where _class = ‘com.bc.quicktask.standalone.model.CouchbaseAccessToken’ |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
[ { “YOUR_BUCKET_NAME”: { “_class”: “com.bc.quicktask.standalone.model.CouchbaseAccessToken”, “authentication”: “rO0ABXNyAEFvcmcuc3ByaW5nZnJhbWV3b3JrLnNlY3VyaXR5Lm9hdXRoMi5wcm92aWRlci5PQXV0aDJBdXRoZW50aWNhdGlvbr1ACwIWYlITAgACTAANc3RvcmVkUmVxdWVzdHQAPExvcmcvc3ByaW5nZnJhbWV3b3JrL3NlY3VyaXR5L29hdXRoMi9wcm92aWRlci9PQXV0aDJSZXF1ZXN0O0wAEnVzZXJBdXRoZW50aWNhdGlvbnQAMkxvcmcvc3ByaW5nZnJhbWV3b3JrL3NlY3VyaXR5L2NvcmUvQXV0aGVudGljYXRpb247eHIAR29yZy5zcHJpbmdmcmFtZXdvcmsuc2VjdXJpdHkuYXV0aGVudGljYXRpb24uQWJzdHJhY3RBdXRoZW50aWNhdGlvblRva2Vu06oofm5HZA4CAANaAA1hdXRoZW50aWNhdGVkTAALYXV0aG9yaXRpZXN0ABZMamF2YS91dGlsL0NvbGxlY3Rpb247TAAHZGV0YWlsc3QAEkxqYXZhL2xhbmcvT2JqZWN0O3hwAHNyACZqYXZhLnV0aWwuQ29sbGVjdGlvbnMkVW5tb2RpZmlhYmxlTGlzdPwPJTG17I4QAgABTAAEbGlzdHQAEExqYXZhL3V0aWwvTGlzdDt4cgAsamF2YS51dGlsLkNvbGxlY3Rpb25zJFVubW9kaWZpYWJsZUNvbGxlY3Rpb24ZQgCAy173HgIAAUwAAWNxAH4ABHhwc3IAE2phdmEudXRpbC5BcnJheUxpc3R4gdIdmcdhnQMAAUkABHNpemV4cAAAAAB3BAAAAAB4cQB+AAxwc3IAOm9yZy5zcHJpbmdmcmFtZXdvcmsuc2VjdXJpdHkub2F1dGgyLnByb3ZpZGVyLk9BdXRoMlJlcXVlc3QAAAAAAAAAAQIAB1oACGFwcHJvdmVkTAALYXV0aG9yaXRpZXNxAH4ABEwACmV4dGVuc2lvbnN0AA9MamF2YS91dGlsL01hcDtMAAtyZWRpcmVjdFVyaXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wAB3JlZnJlc2h0ADtMb3JnL3NwcmluZ2ZyYW1ld29yay9zZWN1cml0eS9vYXV0aDIvcHJvdmlkZXIvVG9rZW5SZXF1ZXN0O0wAC3Jlc291cmNlSWRzdAAPTGphdmEvdXRpbC9TZXQ7TAANcmVzcG9uc2VUeXBlc3EAfgAReHIAOG9yZy5zcHJpbmdmcmFtZXdvcmsuc2VjdXJpdHkub2F1dGgyLnByb3ZpZGVyLkJhc2VSZXF1ZXN0Nih6PqNxab0CAANMAAhjbGllbnRJZHEAfgAPTAARcmVxdWVzdFBhcmFtZXRlcnNxAH4ADkwABXNjb3BlcQB+ABF4cHQACG15Y2xpZW50c3IAJWphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVNYXDxpaj+dPUHQgIAAUwAAW1xAH4ADnhwc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAABncIAAAACAAAAAN0AApncmFudF90eXBldAAIcGFzc3dvcmR0AAljbGllbnRfaWR0AAhteWNsaWVudHQACHVzZXJuYW1ldAAGbXl1c2VyeHNyACVqYXZhLnV0aWwuQ29sbGVjdGlvbnMkVW5tb2RpZmlhYmxlU2V0gB2S0Y+bgFUCAAB4cQB+AAlzcgAXamF2YS51dGlsLkxpbmtlZEhhc2hTZXTYbNdald0qHgIAAHhyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAABA/QAAAAAAAA3QABXRydXN0dAAEcmVhZHQABXdyaXRleAFzcQB+ACJ3DAAAABA/QAAAAAAAAHhzcQB+ABc/QAAAAAAAAHcIAAAAEAAAAAB4cHBzcQB+ACJ3DAAAABA/QAAAAAAAAHhzcQB+ACJ3DAAAABA/QAAAAAAAAHhzcgBPb3JnLnNwcmluZ2ZyYW1ld29yay5zZWN1cml0eS5hdXRoZW50aWNhdGlvbi5Vc2VybmFtZVBhc3N3b3JkQXV0aGVudGljYXRpb25Ub2tlbgAAAAAAAAH0AgACTAALY3JlZGVudGlhbHNxAH4ABUwACXByaW5jaXBhbHEAfgAFeHEAfgADAXNyAB9qYXZhLnV0aWwuQ29sbGVjdGlvbnMkRW1wdHlMaXN0ergXtDynnt4CAAB4cHNyABdqYXZhLnV0aWwuTGlua2VkSGFzaE1hcDTATlwQbMD7AgABWgALYWNjZXNzT3JkZXJ4cQB+ABc/QAAAAAAABncIAAAACAAAAAR0AA1jbGllbnRfc2VjcmV0dAAIbXlzZWNyZXRxAH4AGXEAfgAacQB+ABtxAH4AHHEAfgAdcQB+AB54AHBzcgAyY29tLmJjLnF1aWNrdGFzay5zdGFuZGFsb25lLm1vZGVsLkN1c3RvbVVzZXJEZXRhaWz9dbY7wdosOwIAAkwABmdyb3Vwc3EAfgAITAAEdXNlcnQAKExjb20vYmMvcXVpY2t0YXNrL3N0YW5kYWxvbmUvbW9kZWwvVXNlcjt4cHNxAH4ACwAAAAB3BAAAAAB4c3IAJmNvbS5iYy5xdWlja3Rhc2suc3RhbmRhbG9uZS5tb2RlbC5Vc2VyWvIkR494dqQCAAdMAAljb21wYW55SWRxAH4AD0wADWV4dGVybmFsTG9naW5xAH4AD0wAAmlkcQB+AA9MAAlpc0VuYWJsZWR0ABNMamF2YS9sYW5nL0Jvb2xlYW47TAAJaXNWaXNpYmxlcQB+ADhMAAhwYXNzd29yZHEAfgAPTAAIdXNlcm5hbWVxAH4AD3hwdAAKY29tcGFueS0tMXQABm15dXNlcnQACXVzZXJJZC0tMXNyABFqYXZhLmxhbmcuQm9vbGVhbs0gcoDVnPruAgABWgAFdmFsdWV4cAFxAH4APnQACHBhc3N3b3JkdAAGbXl1c2Vy”, “authenticationId”: “202d6940ebd428bbe2098530c8de3958”, “clientId”: “myclient”, “refreshToken”: “7613ffc6480ae83beb8f0988ef9ecfcf”, “token”: { “_class”: “org.springframework.security.oauth2.common.DefaultOAuth2AccessToken”, “additionalInformation”: {}, “expiration”: 1537420023432, “refreshToken”: { “_class”: “org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken”, “expiration”: 1537420023421, “value”: “c44db735-403e-49d6-8a22-cfe0e29f21d3” }, “scope”: [ “trust”, “read”, “write” ], “tokenType”: “bearer”, “value”: “7759804c-e1c6-4d63-9520-8737f5b46dbf” }, “tokenId”: “12b6bd9de380e1dfab348f4c15abb805”, “username”: “myuser” } } ] |
I have used caelwinner’s project as a reference, here is my special thanks to him.
If you have any questions, feel free to tweet me at @deniswsrosa
작가
1개의 응답
-
Hi Denis,
First of all, thank you for the very brief but concise tutorial. Although it is over a year, it is well explained, and I must acknowledge that you are an excellent teacher.
Please, I have one question and a request to make. Is it possible to add JWT to this implementation OAuth2, and if yes,can you please provide a guide?
Waiting for a reply.
Thank you so much.

댓글 남기기
댓글을 달기 위해서는 로그인해야합니다.