aboutsummaryrefslogblamecommitdiff
path: root/src/main/java/com/juick/config/SecurityConfig.java
blob: c557ab4e6daa78dc48d4cf5db243752eebc7f876 (plain) (tree)
1
2
  
                                 













                                                                           
                         
 
                                    
                                 
                                            
                                     
                                                                    
                                                                
                                                     



                                                    

                                                            
                                                 
                                           
                                                      
                                                                                
                                                                                          
                                                                                             
                                                                        




                                                                                                                                           
                                                                 
                                                            
                                                                                    
                                                                                                     
                                                                                               
                                                                                         
                                                                                     
                                                                           


                                                                    
                           
                                              
                             
 
                                                                          


                                   
              
                             
           
                                    
                                            
                                      
                                                                  
 
         
                                             
                                                        
 
         
                                                       





                                                                                                  
                                                                     



                                                                   
 
           
                                                  
         
                                                             


                                                                    
                                                            









                                                             
                                                             


                                                                                    
                                             








                                                                                 
 




                                                                                        
                                                                              
                                                                                
                                                                                            
                                                                 
 


                                                                    
 




                                                                                   
                                 


                                             
 


                                                                            







                                                                      
         
                                          
                                                                      
                                                         
                                                                                            
                                                                                                        

                                                                        
                                                                                 
                                               
                                         
                                                     
                                                                         
                                                                                              

                                                                                  


                                                                                   
                                                                 
                                    
                                                                                  
                                                                                     
                                                 
                                                                                 
                                                                                  
                                                                                           

                                                                                
                                                                                 
                                                                                             
                            
     
 
         
                                                                                  



                                                                                     
                                                      

                                                                                
                                                                                 
                                                                                             
                            
 




                                                                          
 
         
                                          
                                                                      
                                                                                        
                                                                                               
                                           



                                                                                     

                                                                                   
                                                   
                                                            
                                                      
                                                         
                                                     
                                                      

                                                                             
                                                                                             
                            
     
 




                                                                                                     
 
/*
 * Copyright (C) 2008-2023, Juick
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.juick.config;

import com.juick.ActivityPubManager;
import com.juick.KeystoreManager;
import com.juick.service.ActivityPubService;
import com.juick.service.UserService;
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 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.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.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.inject.Inject;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.Collections;

import static org.springframework.security.config.Customizer.withDefaults;

/**
 * Created by aalexeev on 11/21/16.
 */
@EnableWebSecurity
@Configuration
public class SecurityConfig {
    @Inject
    private UserService userService;
    @Inject
    private KeystoreManager keystoreManager;
    @Inject
    private JdbcTemplate jdbcTemplate;
    private static final String COOKIE_NAME = "juick-remember-me";

    @Bean
    UserDetailsService userDetailsService() {
        return new JuickUserDetailsService(userService);
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();

        configuration.setAllowedOrigins(Collections.singletonList("*"));
        configuration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "OPTIONS", "DELETE"));
        configuration.setAllowedHeaders(Collections.singletonList("*"));

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/oauth/**", configuration);
        source.registerCorsConfiguration("/api/**", configuration);
        source.registerCorsConfiguration("/u/**", configuration);
        source.registerCorsConfiguration("/n/**", configuration);
        return source;
    }

    @Inject
    private ActivityPubManager activityPubManager;

    @Bean
    HashParamAuthenticationFilter apiAuthenticationFilter() {
        return new HashParamAuthenticationFilter(userService, null);
    }

    @Bean
    AuthenticationEntryPoint apiAuthenticationEntryPoint() {
        var entryPoint = new BasicAuthenticationEntryPoint();
        entryPoint.setRealmName("Juick");
        return entryPoint;
    }

    @Value("${auth_remember_me_key:secret}")
    private String rememberMeKey;
    @Value("${web_domain:localhost}")
    private String webDomain;

    @Bean
    HashParamAuthenticationFilter wwwAuthenticationFilter() {
        return new HashParamAuthenticationFilter(userService, hashCookieServices());
    }

    @Bean
    RememberMeServices hashCookieServices() {
        TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(
                rememberMeKey, userDetailsService());

        services.setCookieName(COOKIE_NAME);
        services.setCookieDomain(webDomain);
        services.setAlwaysRemember(true);
        services.setTokenValiditySeconds(6 * 30 * 24 * 3600);
        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)
                .oidc(Customizer.withDefaults());
        http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
                // Accept access tokens for User Info and/or Client Registration
                .oauth2ResourceServer(resourceServer -> resourceServer.jwt(withDefaults()));
        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(webDomain)
                .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()
                .authorizationEndpoint("/oauth/authorize")
                .tokenEndpoint("/oauth/token")
                .build();
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE + 1)
    SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
        http.securityMatcher("/api/**", "/u/**", "/n/**")
                .addFilterBefore(apiAuthenticationFilter(), BasicAuthenticationFilter.class)
                .addFilterBefore(new HTTPSignatureAuthenticationFilter(activityPubManager, userService),
                        BasicAuthenticationFilter.class)
                .authorizeHttpRequests(requests -> requests
                        .requestMatchers(HttpMethod.OPTIONS).permitAll()
                        .requestMatchers("/api/", "/api/messages", "/api/avatar",
                                "/v3/api-docs",
                                "/sw.js",
                                "/api/swagger-ui/**",
                                "/api/messages/discussions",
                                "/api/users", "/api/thread", "/api/tags",
                                "/api/tlgmbtwbhk", "/api/fbwbhk", "/api/_patreon", "/api/_vk",
                                "/api/skypebotendpoint", "/api/_fblogin",
                                "/api/_vklogin", "/api/_tglogin",
                                "/api/_google", "/api/_applelogin", "/api/signup",
                                "/api/inbox", "/api/events", "/api/u/", "/u/**",
                                "/n/**",
                                "/api/info/**", "/api/v1/apps", "/api/v1/instance",
                                "/api/v2/instance",
                                "/api/nodeinfo/2.0", "/oauth/**")
                        .permitAll()
                        .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(resourceServer -> resourceServer.jwt(withDefaults()))
                .sessionManagement(sessionManagement -> sessionManagement
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .exceptionHandling(exceptionHandling -> exceptionHandling
                        .authenticationEntryPoint(apiAuthenticationEntryPoint()))
                .csrf(AbstractHttpConfigurer::disable)
                .headers(headers -> headers.defaultsDisabled().cacheControl(withDefaults()));
        return http.build();
    }

    @Bean
    SecurityFilterChain h2ConsoleFilterChain(HttpSecurity http) throws Exception {
        http.securityMatcher("/h2-console/**")
                .authorizeHttpRequests(auth -> auth
                        .anyRequest().permitAll())
                .anonymous(anonymous -> anonymous.principal(JuickUser.ANONYMOUS_USER)
                        .authorities(JuickUser.ANONYMOUS_AUTHORITY))
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(sessionManagement -> sessionManagement
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .exceptionHandling(exceptionHandling -> exceptionHandling
                        .authenticationEntryPoint(apiAuthenticationEntryPoint()))
                .headers(headers -> headers.defaultsDisabled().cacheControl(withDefaults()));
        return http.build();
    }

    @Bean
    AuthenticationSuccessHandler successHandler() {
        var handler = new SavedRequestAwareAuthenticationSuccessHandler();
        handler.setUseReferer(true);
        return handler;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE + 2)
    SecurityFilterChain wwwChain(HttpSecurity http) throws Exception {
        http.addFilterBefore(wwwAuthenticationFilter(), BasicAuthenticationFilter.class)
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/settings", "/pm/**", "/**/bl", "/_twitter", "/post",
                                "/comment")
                        .authenticated()
                        .anyRequest().permitAll())
                .anonymous(anonymous -> anonymous.principal(JuickUser.ANONYMOUS_USER)
                        .authorities(JuickUser.ANONYMOUS_AUTHORITY))
                .cors(cors -> cors
                        .configurationSource(corsConfigurationSource()))
                .logout(logout -> logout
                        .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                        .invalidateHttpSession(true)
                        .clearAuthentication(true)
                        .logoutSuccessUrl("/login")
                        .deleteCookies("hash", COOKIE_NAME))
                .formLogin(form -> form.loginPage("/login")
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .successHandler(successHandler())
                        .failureUrl("/login?error=1")
                        .permitAll())
                .csrf(AbstractHttpConfigurer::disable)
                .rememberMe(rememberMe -> rememberMe
                        .rememberMeCookieDomain(webDomain).key(rememberMeKey)
                        .rememberMeServices(hashCookieServices()))
                .headers(headers -> headers.defaultsDisabled().cacheControl(withDefaults()));
        return http.build();
    }

    @Bean
    public SecurityFilterChain securityWebFilterChain(
            HttpSecurity http) throws Exception {
        return http.securityMatcher("/actuator/**")
                .authorizeHttpRequests(authorize -> authorize.anyRequest().hasRole("ADMIN")).build();
    }
}