依赖
只需要下面的这一个依赖,springboot 版本为 3.3
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId> </dependency>
|
授权服务AuthorizationServerConfig配置
spring 官方在快速开始里面给出了下面的默认最小配置Spring Authorization Server
AuthorizationServerConfig
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
| @Configuration public class AuthorizationServerConfig {
private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";
@Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI)) .oidc(oidc -> oidc .userInfoEndpoint(userInfo -> userInfo .userInfoResponseHandler(this::onAuthenticationSuccess) ) ); http .exceptionHandling((exceptions) -> exceptions .defaultAuthenticationEntryPointFor( new LoginUrlAuthenticationEntryPoint("/login"), new MediaTypeRequestMatcher(MediaType.TEXT_HTML) ) ) .oauth2ResourceServer((resourceServer) -> resourceServer .jwt(Customizer.withDefaults())) .cors(Customizer.withDefaults());
return http.build(); }
@Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient wordPress = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("kil-test") .clientName("TOY") .clientSecret("{noop}test") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .redirectUri("https://blog.122345.xyz") .postLogoutRedirectUri("https://blog.122345.xyz/") .scope("profile") .tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofDays(1L)).build()) .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) .build();
return new InMemoryRegisteredClientRepository(wordPress); }
@Bean public OAuth2TokenCustomizer<JwtEncodingContext> jwtTokenCustomizer() { return (context) -> { if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { context.getClaims().claims((claims) -> { Set<String> roles = AuthorityUtils.authorityListToSet(context.getPrincipal().getAuthorities()) .stream() .map(c -> c.replaceFirst("^ROLE_", "")) .collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet));
claims.put("roles", roles); }); } }; }
@Bean public JWKSource<SecurityContext> jwkSource() { KeyPair keyPair = generateRsaKey(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); RSAKey rsaKey = new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID(UUID.randomUUID().toString()) .build(); JWKSet jwkSet = new JWKSet(rsaKey); return new ImmutableJWKSet<>(jwkSet); }
private static KeyPair generateRsaKey() { KeyPair keyPair; try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); keyPair = keyPairGenerator.generateKeyPair(); } catch (Exception ex) { throw new IllegalStateException(ex); } return keyPair; }
@Bean public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); }
@Bean public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().build(); }
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpStatus.OK.value()); response.getWriter().write(JSON.toJSONString(request.getUserPrincipal())); response.getWriter().flush(); }
}
|
| 权限范围 |
声明 |
| openid |
sub |
| profile |
Name、family_name、given_name、middle_name、nickname、preferred_username、profile、 picture、website、gender、birthdate、zoneinfo、locale、updated_at |
| email |
email、email_verified |
| address |
address,是一个 JSON 对象、包含 formatted、street_address、locality、region、postal_code、country |
| phone |
phone_number、phone_number_verified |
DefaultSecurityConfig
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
| @EnableWebSecurity @Configuration(proxyBeanMethods = false) public class DefaultSecurityConfig {
@Bean @Order(2) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorize) -> authorize .requestMatchers(new AntPathRequestMatcher("/actuator/**"), new AntPathRequestMatcher("/oauth2/**"), new AntPathRequestMatcher("/login"), new AntPathRequestMatcher("/**/*.json"), new AntPathRequestMatcher("/**/*.html"), new AntPathRequestMatcher("/**/*.png")).permitAll() .anyRequest().authenticated() ) .cors(Customizer.withDefaults()) .csrf(AbstractHttpConfigurer::disable)
.formLogin(form -> form .loginPage("/login") .loginProcessingUrl("/login")) ;
return http.build(); }
@Bean public CorsConfigurationSource corsConfigurationSource() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.addAllowedOriginPattern("*"); config.setAllowCredentials(true); config.setMaxAge(3600L); source.registerCorsConfiguration("/**", config); return source; } }
|
持久化客户端
AuthorizationServerConfig类添加
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
| @Bean public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
return new JdbcRegisteredClientRepository(jdbcTemplate); }
@Bean public JdbcOAuth2AuthorizationService auth2AuthorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository); }
@Bean public JdbcOAuth2AuthorizationConsentService auth2AuthorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository); }
|
sql参考官方项目 spring-authorization-server
官方dome demo-authorizationserver
修改pom
需要额外引入spring security cas包原因是启动时(logging等级:org.springframework.security: trace)会报错:java.lang.ClassNotFoundException:org.springframework.security.cas.jackson2.CasJackson2Module错误。
1 2 3 4 5 6 7 8
|
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-cas</artifactId> </dependency>
|