123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- /*
- * Copyright 2002-2019 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
- *
- * https://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.interfaces.RSAPublicKey;
- import java.security.spec.RSAPrivateKeySpec;
- import java.security.spec.RSAPublicKeySpec;
- import java.util.HashMap;
- import java.util.LinkedHashMap;
- import java.util.Map;
- import java.util.stream.Collectors;
- import com.nimbusds.jose.jwk.JWKSet;
- import com.nimbusds.jose.jwk.RSAKey;
- import org.springframework.beans.factory.annotation.Value;
- 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.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.provider.OAuth2Authentication;
- 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.InMemoryTokenStore;
- 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.PostMapping;
- import org.springframework.web.bind.annotation.RequestParam;
- 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 AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
- AuthenticationManager authenticationManager;
- KeyPair keyPair;
- boolean jwtEnabled;
- public AuthorizationServerConfiguration(
- AuthenticationConfiguration authenticationConfiguration,
- KeyPair keyPair,
- @Value("${security.oauth2.authorizationserver.jwt.enabled:true}") boolean jwtEnabled) throws Exception {
- this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
- this.keyPair = keyPair;
- this.jwtEnabled = jwtEnabled;
- }
- @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)
- .and()
- .withClient("noscopes")
- .authorizedGrantTypes("password")
- .secret("{noop}secret")
- .scopes("none")
- .accessTokenValiditySeconds(600_000_000);
- // @formatter:on
- }
- @Override
- public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
- // @formatter:off
- endpoints
- .authenticationManager(this.authenticationManager)
- .tokenStore(tokenStore());
- if (this.jwtEnabled) {
- endpoints
- .accessTokenConverter(accessTokenConverter());
- }
- // @formatter:on
- }
- @Bean
- public TokenStore tokenStore() {
- if (this.jwtEnabled) {
- return new JwtTokenStore(accessTokenConverter());
- } else {
- return new InMemoryTokenStore();
- }
- }
- @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()
- .and()
- .httpBasic()
- .and()
- .csrf().ignoringRequestMatchers(request -> "/introspect".equals(request.getRequestURI()));
- }
- @Bean
- @Override
- public UserDetailsService userDetailsService() {
- return new InMemoryUserDetailsManager(
- User.withDefaultPasswordEncoder()
- .username("subject")
- .password("password")
- .roles("USER")
- .build());
- }
- }
- /**
- * Legacy Authorization Server (spring-security-oauth2) does not support any
- * Token Introspection endpoint.
- *
- * This class adds ad-hoc support in order to better support the other samples in the repo.
- */
- @FrameworkEndpoint
- class IntrospectEndpoint {
- TokenStore tokenStore;
- public IntrospectEndpoint(TokenStore tokenStore) {
- this.tokenStore = tokenStore;
- }
- @PostMapping("/introspect")
- @ResponseBody
- public Map<String, Object> introspect(@RequestParam("token") String token) {
- OAuth2AccessToken accessToken = this.tokenStore.readAccessToken(token);
- Map<String, Object> attributes = new HashMap<>();
- if (accessToken == null || accessToken.isExpired()) {
- attributes.put("active", false);
- return attributes;
- }
- OAuth2Authentication authentication = this.tokenStore.readAuthentication(token);
- attributes.put("active", true);
- attributes.put("exp", accessToken.getExpiration().getTime());
- attributes.put("scope", accessToken.getScope().stream().collect(Collectors.joining(" ")));
- attributes.put("sub", authentication.getName());
- return attributes;
- }
- }
- /**
- * 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() {
- 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<>();
- response.put("sub", authentication.getName());
- if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
- response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
- }
- return response;
- }
- }
|