AuthorizationServerConfiguration.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /*
  2. * Copyright 2002-2019 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * https://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package sample;
  17. import java.math.BigInteger;
  18. import java.security.KeyFactory;
  19. import java.security.KeyPair;
  20. import java.security.interfaces.RSAPublicKey;
  21. import java.security.spec.RSAPrivateKeySpec;
  22. import java.security.spec.RSAPublicKeySpec;
  23. import java.util.HashMap;
  24. import java.util.LinkedHashMap;
  25. import java.util.Map;
  26. import java.util.stream.Collectors;
  27. import com.nimbusds.jose.jwk.JWKSet;
  28. import com.nimbusds.jose.jwk.RSAKey;
  29. import org.springframework.beans.factory.annotation.Value;
  30. import org.springframework.context.annotation.Bean;
  31. import org.springframework.context.annotation.Configuration;
  32. import org.springframework.security.authentication.AuthenticationManager;
  33. import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
  34. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  35. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  36. import org.springframework.security.core.Authentication;
  37. import org.springframework.security.core.authority.AuthorityUtils;
  38. import org.springframework.security.core.userdetails.User;
  39. import org.springframework.security.core.userdetails.UserDetailsService;
  40. import org.springframework.security.oauth2.common.OAuth2AccessToken;
  41. import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
  42. import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
  43. import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
  44. import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
  45. import org.springframework.security.oauth2.provider.OAuth2Authentication;
  46. import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpoint;
  47. import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
  48. import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
  49. import org.springframework.security.oauth2.provider.token.TokenStore;
  50. import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
  51. import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
  52. import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
  53. import org.springframework.security.provisioning.InMemoryUserDetailsManager;
  54. import org.springframework.web.bind.annotation.GetMapping;
  55. import org.springframework.web.bind.annotation.PostMapping;
  56. import org.springframework.web.bind.annotation.RequestParam;
  57. import org.springframework.web.bind.annotation.ResponseBody;
  58. /**
  59. * An instance of Legacy Authorization Server (spring-security-oauth2) that uses a single,
  60. * not-rotating key and exposes a JWK endpoint.
  61. *
  62. * See
  63. * <a
  64. * target="_blank"
  65. * href="https://docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/">
  66. * Spring Security OAuth Autoconfig's documentation</a> for additional detail
  67. *
  68. * @author Josh Cummings
  69. * @since 5.1
  70. */
  71. @EnableAuthorizationServer
  72. @Configuration
  73. public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
  74. AuthenticationManager authenticationManager;
  75. KeyPair keyPair;
  76. boolean jwtEnabled;
  77. public AuthorizationServerConfiguration(
  78. AuthenticationConfiguration authenticationConfiguration,
  79. KeyPair keyPair,
  80. @Value("${security.oauth2.authorizationserver.jwt.enabled:true}") boolean jwtEnabled) throws Exception {
  81. this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
  82. this.keyPair = keyPair;
  83. this.jwtEnabled = jwtEnabled;
  84. }
  85. @Override
  86. public void configure(ClientDetailsServiceConfigurer clients)
  87. throws Exception {
  88. // @formatter:off
  89. clients.inMemory()
  90. .withClient("reader")
  91. .authorizedGrantTypes("password")
  92. .secret("{noop}secret")
  93. .scopes("message:read")
  94. .accessTokenValiditySeconds(600_000_000)
  95. .and()
  96. .withClient("writer")
  97. .authorizedGrantTypes("password")
  98. .secret("{noop}secret")
  99. .scopes("message:write")
  100. .accessTokenValiditySeconds(600_000_000)
  101. .and()
  102. .withClient("noscopes")
  103. .authorizedGrantTypes("password")
  104. .secret("{noop}secret")
  105. .scopes("none")
  106. .accessTokenValiditySeconds(600_000_000);
  107. // @formatter:on
  108. }
  109. @Override
  110. public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
  111. // @formatter:off
  112. endpoints
  113. .authenticationManager(this.authenticationManager)
  114. .tokenStore(tokenStore());
  115. if (this.jwtEnabled) {
  116. endpoints
  117. .accessTokenConverter(accessTokenConverter());
  118. }
  119. // @formatter:on
  120. }
  121. @Bean
  122. public TokenStore tokenStore() {
  123. if (this.jwtEnabled) {
  124. return new JwtTokenStore(accessTokenConverter());
  125. } else {
  126. return new InMemoryTokenStore();
  127. }
  128. }
  129. @Bean
  130. public JwtAccessTokenConverter accessTokenConverter() {
  131. JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
  132. converter.setKeyPair(this.keyPair);
  133. DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
  134. accessTokenConverter.setUserTokenConverter(new SubjectAttributeUserTokenConverter());
  135. converter.setAccessTokenConverter(accessTokenConverter);
  136. return converter;
  137. }
  138. }
  139. /**
  140. * For configuring the end users recognized by this Authorization Server
  141. */
  142. @Configuration
  143. class UserConfig extends WebSecurityConfigurerAdapter {
  144. @Override
  145. protected void configure(HttpSecurity http) throws Exception {
  146. http
  147. .authorizeRequests()
  148. .mvcMatchers("/.well-known/jwks.json").permitAll()
  149. .anyRequest().authenticated()
  150. .and()
  151. .httpBasic()
  152. .and()
  153. .csrf().ignoringRequestMatchers(request -> "/introspect".equals(request.getRequestURI()));
  154. }
  155. @Bean
  156. @Override
  157. public UserDetailsService userDetailsService() {
  158. return new InMemoryUserDetailsManager(
  159. User.withDefaultPasswordEncoder()
  160. .username("subject")
  161. .password("password")
  162. .roles("USER")
  163. .build());
  164. }
  165. }
  166. /**
  167. * Legacy Authorization Server (spring-security-oauth2) does not support any
  168. * Token Introspection endpoint.
  169. *
  170. * This class adds ad-hoc support in order to better support the other samples in the repo.
  171. */
  172. @FrameworkEndpoint
  173. class IntrospectEndpoint {
  174. TokenStore tokenStore;
  175. public IntrospectEndpoint(TokenStore tokenStore) {
  176. this.tokenStore = tokenStore;
  177. }
  178. @PostMapping("/introspect")
  179. @ResponseBody
  180. public Map<String, Object> introspect(@RequestParam("token") String token) {
  181. OAuth2AccessToken accessToken = this.tokenStore.readAccessToken(token);
  182. Map<String, Object> attributes = new HashMap<>();
  183. if (accessToken == null || accessToken.isExpired()) {
  184. attributes.put("active", false);
  185. return attributes;
  186. }
  187. OAuth2Authentication authentication = this.tokenStore.readAuthentication(token);
  188. attributes.put("active", true);
  189. attributes.put("exp", accessToken.getExpiration().getTime());
  190. attributes.put("scope", accessToken.getScope().stream().collect(Collectors.joining(" ")));
  191. attributes.put("sub", authentication.getName());
  192. return attributes;
  193. }
  194. }
  195. /**
  196. * Legacy Authorization Server (spring-security-oauth2) does not support any
  197. * <a href target="_blank" href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a> endpoint.
  198. *
  199. * This class adds ad-hoc support in order to better support the other samples in the repo.
  200. */
  201. @FrameworkEndpoint
  202. class JwkSetEndpoint {
  203. KeyPair keyPair;
  204. public JwkSetEndpoint(KeyPair keyPair) {
  205. this.keyPair = keyPair;
  206. }
  207. @GetMapping("/.well-known/jwks.json")
  208. @ResponseBody
  209. public Map<String, Object> getKey() {
  210. RSAPublicKey publicKey = (RSAPublicKey) this.keyPair.getPublic();
  211. RSAKey key = new RSAKey.Builder(publicKey).build();
  212. return new JWKSet(key).toJSONObject();
  213. }
  214. }
  215. /**
  216. * An Authorization Server will more typically have a key rotation strategy, and the keys will not
  217. * be hard-coded into the application code.
  218. *
  219. * For simplicity, though, this sample doesn't demonstrate key rotation.
  220. */
  221. @Configuration
  222. class KeyConfig {
  223. @Bean
  224. KeyPair keyPair() {
  225. try {
  226. String privateExponent = "3851612021791312596791631935569878540203393691253311342052463788814433805390794604753109719790052408607029530149004451377846406736413270923596916756321977922303381344613407820854322190592787335193581632323728135479679928871596911841005827348430783250026013354350760878678723915119966019947072651782000702927096735228356171563532131162414366310012554312756036441054404004920678199077822575051043273088621405687950081861819700809912238863867947415641838115425624808671834312114785499017269379478439158796130804789241476050832773822038351367878951389438751088021113551495469440016698505614123035099067172660197922333993";
  227. String modulus = "18044398961479537755088511127417480155072543594514852056908450877656126120801808993616738273349107491806340290040410660515399239279742407357192875363433659810851147557504389760192273458065587503508596714389889971758652047927503525007076910925306186421971180013159326306810174367375596043267660331677530921991343349336096643043840224352451615452251387611820750171352353189973315443889352557807329336576421211370350554195530374360110583327093711721857129170040527236951522127488980970085401773781530555922385755722534685479501240842392531455355164896023070459024737908929308707435474197069199421373363801477026083786683";
  228. String exponent = "65537";
  229. RSAPublicKeySpec publicSpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(exponent));
  230. RSAPrivateKeySpec privateSpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent));
  231. KeyFactory factory = KeyFactory.getInstance("RSA");
  232. return new KeyPair(factory.generatePublic(publicSpec), factory.generatePrivate(privateSpec));
  233. } catch ( Exception e ) {
  234. throw new IllegalArgumentException(e);
  235. }
  236. }
  237. }
  238. /**
  239. * Legacy Authorization Server does not support a custom name for the user parameter, so we'll need
  240. * to extend the default. By default, it uses the attribute {@code user_name}, though it would be
  241. * better to adhere to the {@code sub} property defined in the
  242. * <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JWT Specification</a>.
  243. */
  244. class SubjectAttributeUserTokenConverter extends DefaultUserAuthenticationConverter {
  245. @Override
  246. public Map<String, ?> convertUserAuthentication(Authentication authentication) {
  247. Map<String, Object> response = new LinkedHashMap<>();
  248. response.put("sub", authentication.getName());
  249. if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
  250. response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
  251. }
  252. return response;
  253. }
  254. }