|
@@ -0,0 +1,220 @@
|
|
|
+/*
|
|
|
+ * Copyright 2002-2018 the original author or authors.
|
|
|
+ *
|
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+ * you may not use this file except in compliance with the License.
|
|
|
+ * You may obtain a copy of the License at
|
|
|
+ *
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ *
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+ * See the License for the specific language governing permissions and
|
|
|
+ * limitations under the License.
|
|
|
+ */
|
|
|
+package sample;
|
|
|
+
|
|
|
+import java.math.BigInteger;
|
|
|
+import java.security.KeyFactory;
|
|
|
+import java.security.KeyPair;
|
|
|
+import java.security.Principal;
|
|
|
+import java.security.interfaces.RSAPublicKey;
|
|
|
+import java.security.spec.RSAPrivateKeySpec;
|
|
|
+import java.security.spec.RSAPublicKeySpec;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+import com.nimbusds.jose.jwk.JWKSet;
|
|
|
+import com.nimbusds.jose.jwk.RSAKey;
|
|
|
+
|
|
|
+import org.springframework.context.annotation.Bean;
|
|
|
+import org.springframework.context.annotation.Configuration;
|
|
|
+import org.springframework.security.authentication.AuthenticationManager;
|
|
|
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
|
|
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
|
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
|
|
+import org.springframework.security.core.Authentication;
|
|
|
+import org.springframework.security.core.authority.AuthorityUtils;
|
|
|
+import org.springframework.security.core.userdetails.User;
|
|
|
+import org.springframework.security.core.userdetails.UserDetailsService;
|
|
|
+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.provider.endpoint.FrameworkEndpoint;
|
|
|
+import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
|
|
|
+import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
|
|
|
+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 org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
|
|
+import org.springframework.web.bind.annotation.GetMapping;
|
|
|
+import org.springframework.web.bind.annotation.ResponseBody;
|
|
|
+
|
|
|
+/**
|
|
|
+ * An instance of Legacy Authorization Server (spring-security-oauth2) that uses a single,
|
|
|
+ * not-rotating key and exposes a JWK endpoint.
|
|
|
+ *
|
|
|
+ * See
|
|
|
+ * <a
|
|
|
+ * target="_blank"
|
|
|
+ * href="https://docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/">
|
|
|
+ * Spring Security OAuth Autoconfig's documentation</a> for additional detail
|
|
|
+ *
|
|
|
+ * @author Josh Cummings
|
|
|
+ * @since 5.1
|
|
|
+ */
|
|
|
+@EnableAuthorizationServer
|
|
|
+@Configuration
|
|
|
+public class JwkSetConfiguration extends AuthorizationServerConfigurerAdapter {
|
|
|
+
|
|
|
+ AuthenticationManager authenticationManager;
|
|
|
+ KeyPair keyPair;
|
|
|
+
|
|
|
+ public JwkSetConfiguration(
|
|
|
+ AuthenticationConfiguration authenticationConfiguration,
|
|
|
+ KeyPair keyPair) throws Exception {
|
|
|
+
|
|
|
+ this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
|
|
|
+ this.keyPair = keyPair;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void configure(ClientDetailsServiceConfigurer clients)
|
|
|
+ throws Exception {
|
|
|
+ // @formatter:off
|
|
|
+ clients.inMemory()
|
|
|
+ .withClient("reader")
|
|
|
+ .authorizedGrantTypes("password")
|
|
|
+ .secret("{noop}secret")
|
|
|
+ .scopes("message:read")
|
|
|
+ .accessTokenValiditySeconds(600_000_000)
|
|
|
+ .and()
|
|
|
+ .withClient("writer")
|
|
|
+ .authorizedGrantTypes("password")
|
|
|
+ .secret("{noop}secret")
|
|
|
+ .scopes("message:write")
|
|
|
+ .accessTokenValiditySeconds(600_000_000);
|
|
|
+ // @formatter:on
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
|
|
|
+ // @formatter:off
|
|
|
+ endpoints
|
|
|
+ .authenticationManager(this.authenticationManager)
|
|
|
+ .accessTokenConverter(accessTokenConverter())
|
|
|
+ .tokenStore(tokenStore());
|
|
|
+ // @formatter:on
|
|
|
+ }
|
|
|
+
|
|
|
+ @Bean
|
|
|
+ public TokenStore tokenStore() {
|
|
|
+ return new JwtTokenStore(accessTokenConverter());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Bean
|
|
|
+ public JwtAccessTokenConverter accessTokenConverter() {
|
|
|
+ JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
|
|
|
+ converter.setKeyPair(this.keyPair);
|
|
|
+
|
|
|
+ DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
|
|
|
+ accessTokenConverter.setUserTokenConverter(new SubjectAttributeUserTokenConverter());
|
|
|
+ converter.setAccessTokenConverter(accessTokenConverter);
|
|
|
+
|
|
|
+ return converter;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * For configuring the end users recognized by this Authorization Server
|
|
|
+ */
|
|
|
+@Configuration
|
|
|
+class UserConfig extends WebSecurityConfigurerAdapter {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void configure(HttpSecurity http) throws Exception {
|
|
|
+ http
|
|
|
+ .authorizeRequests()
|
|
|
+ .mvcMatchers("/.well-known/jwks.json").permitAll()
|
|
|
+ .anyRequest().authenticated();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Bean
|
|
|
+ @Override
|
|
|
+ public UserDetailsService userDetailsService() {
|
|
|
+ return new InMemoryUserDetailsManager(
|
|
|
+ User.withDefaultPasswordEncoder()
|
|
|
+ .username("user")
|
|
|
+ .password("password")
|
|
|
+ .roles("USER")
|
|
|
+ .build());
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Legacy Authorization Server (spring-security-oauth2) does not support any
|
|
|
+ * <a href target="_blank" href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a> endpoint.
|
|
|
+ *
|
|
|
+ * This class adds ad-hoc support in order to better support the other samples in the repo.
|
|
|
+ */
|
|
|
+@FrameworkEndpoint
|
|
|
+class JwkSetEndpoint {
|
|
|
+ KeyPair keyPair;
|
|
|
+
|
|
|
+ public JwkSetEndpoint(KeyPair keyPair) {
|
|
|
+ this.keyPair = keyPair;
|
|
|
+ }
|
|
|
+
|
|
|
+ @GetMapping("/.well-known/jwks.json")
|
|
|
+ @ResponseBody
|
|
|
+ public Map<String, Object> getKey(Principal principal) {
|
|
|
+ RSAPublicKey publicKey = (RSAPublicKey) this.keyPair.getPublic();
|
|
|
+ RSAKey key = new RSAKey.Builder(publicKey).build();
|
|
|
+ return new JWKSet(key).toJSONObject();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * An Authorization Server will more typically have a key rotation strategy, and the keys will not
|
|
|
+ * be hard-coded into the application code.
|
|
|
+ *
|
|
|
+ * For simplicity, though, this sample doesn't demonstrate key rotation.
|
|
|
+ */
|
|
|
+@Configuration
|
|
|
+class KeyConfig {
|
|
|
+ @Bean
|
|
|
+ KeyPair keyPair() {
|
|
|
+ try {
|
|
|
+ String privateExponent = "3851612021791312596791631935569878540203393691253311342052463788814433805390794604753109719790052408607029530149004451377846406736413270923596916756321977922303381344613407820854322190592787335193581632323728135479679928871596911841005827348430783250026013354350760878678723915119966019947072651782000702927096735228356171563532131162414366310012554312756036441054404004920678199077822575051043273088621405687950081861819700809912238863867947415641838115425624808671834312114785499017269379478439158796130804789241476050832773822038351367878951389438751088021113551495469440016698505614123035099067172660197922333993";
|
|
|
+ String modulus = "18044398961479537755088511127417480155072543594514852056908450877656126120801808993616738273349107491806340290040410660515399239279742407357192875363433659810851147557504389760192273458065587503508596714389889971758652047927503525007076910925306186421971180013159326306810174367375596043267660331677530921991343349336096643043840224352451615452251387611820750171352353189973315443889352557807329336576421211370350554195530374360110583327093711721857129170040527236951522127488980970085401773781530555922385755722534685479501240842392531455355164896023070459024737908929308707435474197069199421373363801477026083786683";
|
|
|
+ String exponent = "65537";
|
|
|
+
|
|
|
+ RSAPublicKeySpec publicSpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(exponent));
|
|
|
+ RSAPrivateKeySpec privateSpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent));
|
|
|
+ KeyFactory factory = KeyFactory.getInstance("RSA");
|
|
|
+ return new KeyPair(factory.generatePublic(publicSpec), factory.generatePrivate(privateSpec));
|
|
|
+ } catch ( Exception e ) {
|
|
|
+ throw new IllegalArgumentException(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Legacy Authorization Server does not support a custom name for the user parameter, so we'll need
|
|
|
+ * to extend the default. By default, it uses the attribute {@code user_name}, though it would be
|
|
|
+ * better to adhere to the {@code sub} property defined in the
|
|
|
+ * <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JWT Specification</a>.
|
|
|
+ */
|
|
|
+class SubjectAttributeUserTokenConverter extends DefaultUserAuthenticationConverter {
|
|
|
+ @Override
|
|
|
+ public Map<String, ?> convertUserAuthentication(Authentication authentication) {
|
|
|
+ Map<String, Object> response = new LinkedHashMap<String, Object>();
|
|
|
+ response.put("sub", authentication.getName());
|
|
|
+ if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
|
|
|
+ response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
|
|
|
+ }
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+}
|