aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/juick/config/SecurityConfig.java
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2023-01-04 03:38:19 +0300
committerGravatar Vitaly Takmazov2023-01-04 05:46:16 +0300
commitc471503ede9aad91193ff6f93966196e6aff15d6 (patch)
tree8c70c8f58b140465be651cd019f26eadd476711f /src/main/java/com/juick/config/SecurityConfig.java
parent086d9a7625bfc5a386f5b1028d364fb546c2fa9d (diff)
OAuth authentication for Mastodon and ActivityPub C2S
Diffstat (limited to 'src/main/java/com/juick/config/SecurityConfig.java')
-rw-r--r--src/main/java/com/juick/config/SecurityConfig.java104
1 files changed, 84 insertions, 20 deletions
diff --git a/src/main/java/com/juick/config/SecurityConfig.java b/src/main/java/com/juick/config/SecurityConfig.java
index d2030a62..d60abe00 100644
--- a/src/main/java/com/juick/config/SecurityConfig.java
+++ b/src/main/java/com/juick/config/SecurityConfig.java
@@ -25,20 +25,37 @@ import com.juick.service.security.HTTPSignatureAuthenticationFilter;
import com.juick.service.security.HashParamAuthenticationFilter;
import com.juick.service.security.JuickUserDetailsService;
import com.juick.service.security.entities.JuickUser;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
+import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
-import org.springframework.security.web.authentication.RememberMeServices;
-import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
+import org.springframework.security.web.authentication.*;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@@ -48,8 +65,12 @@ import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import javax.inject.Inject;
+import java.io.IOException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.Collections;
+import java.util.UUID;
/**
* Created by aalexeev on 11/21/16.
@@ -61,7 +82,8 @@ public class SecurityConfig {
private UserService userService;
@Inject
private KeystoreManager keystoreManager;
-
+ @Inject
+ private JdbcTemplate jdbcTemplate;
private static final String COOKIE_NAME = "juick-remember-me";
@Bean
UserDetailsService userDetailsService() {
@@ -77,6 +99,7 @@ public class SecurityConfig {
configuration.setAllowedHeaders(Collections.singletonList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/oauth/**", configuration);
source.registerCorsConfiguration("/api/**", configuration);
source.registerCorsConfiguration("/u/**", configuration);
source.registerCorsConfiguration("/n/**", configuration);
@@ -124,14 +147,60 @@ public class SecurityConfig {
services.setUseSecureCookie(false); // TODO set true if https is supports
return services;
}
+ @Bean
+ @Order(Ordered.HIGHEST_PRECEDENCE)
+ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
+ throws Exception {
+ OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
+ http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
+ .authorizationServerSettings(AuthorizationServerSettings.builder()
+ .authorizationEndpoint("/oauth/authorize")
+ .tokenEndpoint("/oauth/token")
+ .build())
+ .oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
+ http
+ // Redirect to the login page when not authenticated from the
+ // authorization endpoint
+ .exceptionHandling((exceptions) -> exceptions
+ .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
+ )
+ // Accept access tokens for User Info and/or Client Registration
+ .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
+
+ return http.formLogin(Customizer.withDefaults()).build();
+ }
+ @Bean
+ public RegisteredClientRepository registeredClientRepository() {
+ return new JdbcRegisteredClientRepository(jdbcTemplate);
+ }
+
+ @Bean
+ public JWKSource<SecurityContext> jwkSource() {
+ RSAPublicKey publicKey = (RSAPublicKey) keystoreManager.getPublicKey();
+ RSAPrivateKey privateKey = (RSAPrivateKey) keystoreManager.getPrivateKey();
+ RSAKey rsaKey = new RSAKey.Builder(publicKey)
+ .privateKey(privateKey)
+ .keyID(UUID.randomUUID().toString())
+ .build();
+ JWKSet jwkSet = new JWKSet(rsaKey);
+ return new ImmutableJWKSet<>(jwkSet);
+ }
+ @Bean
+ public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
+ return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
+ }
+ @Bean
+ public AuthorizationServerSettings authorizationServerSettings() {
+ return AuthorizationServerSettings.builder().build();
+ }
@Bean
+ @Order(2)
SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
http.securityMatcher("/api/**")
.addFilterBefore(apiAuthenticationFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(new HTTPSignatureAuthenticationFilter(signatureManager, userService),
BasicAuthenticationFilter.class)
- .addFilterBefore(bearerTokenAuthenticationFilter(), BasicAuthenticationFilter.class)
.authorizeHttpRequests(requests -> requests
.requestMatchers(HttpMethod.OPTIONS).permitAll()
.requestMatchers("/api/", "/api/messages", "/api/avatar",
@@ -142,15 +211,16 @@ public class SecurityConfig {
"/api/_vklogin", "/api/_tglogin",
"/api/_google", "/api/_applelogin", "/api/signup",
"/api/inbox", "/api/events", "/api/u/",
- "/api/info/**",
- "/api/nodeinfo/2.0")
+ "/api/info/**", "/api/v1/apps", "/api/v1/instance",
+ "/api/nodeinfo/2.0", "/oauth/**")
.permitAll()
- .anyRequest().hasRole("USER"))
+ .anyRequest().hasAnyAuthority("SCOPE_write", "ROLE_USER"))
.anonymous(anonymous -> anonymous.principal(JuickUser.ANONYMOUS_USER)
.authorities(JuickUser.ANONYMOUS_AUTHORITY))
.httpBasic(httpBasic -> httpBasic
.authenticationEntryPoint(apiAuthenticationEntryPoint()))
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
+ .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement(sessionManagement -> sessionManagement
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(exceptionHandling -> exceptionHandling
@@ -159,14 +229,6 @@ public class SecurityConfig {
.headers().defaultsDisabled().cacheControl();
return http.build();
}
-
- @Bean
- AuthenticationSuccessHandler successHandler() {
- SimpleUrlAuthenticationSuccessHandler handler = new SimpleUrlAuthenticationSuccessHandler();
- handler.setUseReferer(true);
- return handler;
- }
-
@Bean
SecurityFilterChain h2ConsoFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/h2-console/**")
@@ -182,7 +244,12 @@ public class SecurityConfig {
.headers().defaultsDisabled().cacheControl();
return http.build();
}
-
+ @Bean
+ AuthenticationSuccessHandler successHandler() {
+ var handler = new SavedRequestAwareAuthenticationSuccessHandler();
+ handler.setUseReferer(true);
+ return handler;
+ }
@Bean
SecurityFilterChain wwwChain(HttpSecurity http) throws Exception {
http.addFilterBefore(wwwAuthenticationFilter(), BasicAuthenticationFilter.class)
@@ -197,9 +264,6 @@ public class SecurityConfig {
.authorities(JuickUser.ANONYMOUS_AUTHORITY))
.cors(cors -> cors
.configurationSource(corsConfigurationSource()))
- .sessionManagement(
- sessionManagement -> sessionManagement
- .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.logout(logout -> logout
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.invalidateHttpSession(true)