diff --git a/build.gradle.kts b/build.gradle.kts index fc5abdc..a3c5ea1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,7 +19,10 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter") + implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.springframework.boot:spring-boot-starter-web") testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.springframework.security:spring-security-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } diff --git a/src/main/java/org/javaspringcourse/config/SecurityConfig.java b/src/main/java/org/javaspringcourse/config/SecurityConfig.java new file mode 100644 index 0000000..d954b0c --- /dev/null +++ b/src/main/java/org/javaspringcourse/config/SecurityConfig.java @@ -0,0 +1,66 @@ +package org.javaspringcourse.config; + +import org.javaspringcourse.domain.UserRole; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + private static final String[] PUBLIC_URLS = { + "/public/**" + }; + private static final String[] ADMIN_URLS = { + "/admin/**" + }; + private static final String[] SUPPORT_URLS = { + "/support/**" + }; + + @Bean + public SecurityFilterChain configure(HttpSecurity http) throws Exception { + return http.csrf(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable) + .logout(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(c -> c + .requestMatchers(PUBLIC_URLS) + .permitAll() + .requestMatchers(ADMIN_URLS) + .hasAuthority(UserRole.ROLE_ADMIN.name()) + .requestMatchers(SUPPORT_URLS) + .hasAuthority(UserRole.ROLE_SUPPORT.name())) + .httpBasic(_ -> {}) + .build(); + } + + @Bean + public PasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) { + var admin = User.builder() + .username("admin") + .password(passwordEncoder.encode("maozedong")) + .authorities(UserRole.ROLE_ADMIN) + .build(); + + var support = User.builder() + .username("support") + .password(passwordEncoder.encode("password")) + .authorities(UserRole.ROLE_SUPPORT) + .build(); + + return new InMemoryUserDetailsManager(admin, support); + } +} diff --git a/src/main/java/org/javaspringcourse/controller/ApiController.java b/src/main/java/org/javaspringcourse/controller/ApiController.java new file mode 100644 index 0000000..e2fb87c --- /dev/null +++ b/src/main/java/org/javaspringcourse/controller/ApiController.java @@ -0,0 +1,31 @@ +package org.javaspringcourse.controller; + +import org.javaspringcourse.dto.UserInfo; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ApiController { + + @GetMapping("/public/api") + public String publicApi() { + return "Hello World"; + } + + @GetMapping("/admin/api") + public UserInfo getAdminInfo(@AuthenticationPrincipal UserDetails user) { + return new UserInfo(user.getUsername(), user.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .toList()); + } + + @GetMapping("/support/api") + public UserInfo getSupportUserInfo(@AuthenticationPrincipal UserDetails user) { + return new UserInfo(user.getUsername(), user.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .toList()); + } +} diff --git a/src/main/java/org/javaspringcourse/domain/UserRole.java b/src/main/java/org/javaspringcourse/domain/UserRole.java new file mode 100644 index 0000000..cd897f6 --- /dev/null +++ b/src/main/java/org/javaspringcourse/domain/UserRole.java @@ -0,0 +1,13 @@ +package org.javaspringcourse.domain; + +import org.springframework.security.core.GrantedAuthority; + +public enum UserRole implements GrantedAuthority { + ROLE_ADMIN, + ROLE_SUPPORT; + + @Override + public String getAuthority() { + return this.name(); + } +} diff --git a/src/main/java/org/javaspringcourse/dto/UserInfo.java b/src/main/java/org/javaspringcourse/dto/UserInfo.java new file mode 100644 index 0000000..796e868 --- /dev/null +++ b/src/main/java/org/javaspringcourse/dto/UserInfo.java @@ -0,0 +1,5 @@ +package org.javaspringcourse.dto; + +import java.util.List; + +public record UserInfo(String username, List roles) {} \ No newline at end of file