-
Notifications
You must be signed in to change notification settings - Fork 0
feature/security-config #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
92983e4
Add spring security dependency in pom file
gvaguirres 9388942
Add package webauthn with LoginController, SignupController, UserEnti…
gvaguirres 80a2bd2
First fix after coderabbit review
gvaguirres 37e30d1
Add SecurityConfig
gvaguirres 3496add
Refactor: comment out AppUser class to transition towards UserEntity
gvaguirres a61248a
Update UserEntity mapping to align with Flyway migrations
gvaguirres 696d457
Implement cleanup V2 to remove redundant code and optimize imports an…
gvaguirres c0fe4b0
Security: lock UserDetailsService password to prevent form-login bypass
gvaguirres ba182c8
Make mvnw executable and fix credential_id size
gvaguirres File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
src/main/java/backendlab/team4you/config/SecurityConfig.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| package backendlab.team4you.config; | ||
|
|
||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.jdbc.core.JdbcOperations; | ||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
| import org.springframework.security.core.userdetails.User; | ||
| import org.springframework.security.core.userdetails.UserDetailsService; | ||
| import org.springframework.security.web.SecurityFilterChain; | ||
| import org.springframework.security.web.webauthn.management.JdbcPublicKeyCredentialUserEntityRepository; | ||
| import org.springframework.security.web.webauthn.management.JdbcUserCredentialRepository; | ||
| import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository; | ||
| import org.springframework.security.web.webauthn.management.UserCredentialRepository; | ||
|
|
||
| @Configuration | ||
| public class SecurityConfig { | ||
|
|
||
| @Bean | ||
| SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | ||
|
|
||
| return http | ||
| .authorizeHttpRequests( | ||
| authorizeHttp -> authorizeHttp | ||
| // Public endpoints | ||
| .requestMatchers( "/login", "/signup").permitAll() | ||
| .anyRequest().authenticated() | ||
|
|
||
| // Add elevated permissions | ||
|
|
||
| ) | ||
| .webAuthn( passkeys -> passkeys | ||
| .rpId("localhost") //identity of the website | ||
| .allowedOrigins("http://localhost:8080") | ||
| .rpName("Passkey team4you") | ||
| ) | ||
| .formLogin(form -> form.loginPage("/login")) | ||
| .logout(logout -> logout.logoutSuccessUrl("/").permitAll()) | ||
| .build(); | ||
| } | ||
|
|
||
| //todo: add jte called add-passkey but in thymelife | ||
|
|
||
| @Bean | ||
| PublicKeyCredentialUserEntityRepository jdbcPublicKeyCredentialRepository(JdbcOperations jdbc) { | ||
| return new JdbcPublicKeyCredentialUserEntityRepository(jdbc); | ||
| } | ||
|
|
||
| @Bean | ||
| UserCredentialRepository userCredentialRepository(JdbcOperations jdbc) { | ||
| return new JdbcUserCredentialRepository(jdbc); | ||
| } | ||
|
|
||
| @Bean | ||
| public UserDetailsService userDetailsService(){ | ||
| return username -> User.builder() | ||
| .username(username) | ||
| .password("{noop}!LOCKED!") // Non-empty impossible-to-match password | ||
| .roles("USER") | ||
| .accountLocked(true) // Prevent password-based login | ||
| .build(); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,57 +1,57 @@ | ||
| package backendlab.team4you.user; | ||
|
|
||
| import jakarta.persistence.*; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| @Entity | ||
| @Table(name = "app_user") | ||
| public class AppUser { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @Column(nullable = false, unique = true) | ||
| private String email; | ||
|
|
||
| @Column(name = "password_hash", nullable = false) | ||
| private String passwordHash; | ||
|
|
||
| @Column(name = "created_at", nullable = false) | ||
| private LocalDateTime createdAt; | ||
|
|
||
| public AppUser() { | ||
| } | ||
|
|
||
| public Long getId() { | ||
| return id; | ||
| } | ||
|
|
||
| public String getEmail() { | ||
| return email; | ||
| } | ||
|
|
||
| public String getPasswordHash() { | ||
| return passwordHash; | ||
| } | ||
|
|
||
| public LocalDateTime getCreatedAt() { | ||
| return createdAt; | ||
| } | ||
|
|
||
| public void setEmail(String email) { | ||
| this.email = email; | ||
| } | ||
|
|
||
| public void setPasswordHash(String passwordHash) { | ||
| this.passwordHash = passwordHash; | ||
| } | ||
|
|
||
| @PrePersist | ||
| void onCreate() { | ||
| if (this.createdAt == null) { | ||
| this.createdAt = LocalDateTime.now(); | ||
| } | ||
| } | ||
| } | ||
| //package backendlab.team4you.user; | ||
| // | ||
| //import jakarta.persistence.*; | ||
| // | ||
| //import java.time.LocalDateTime; | ||
| // | ||
| //@Entity | ||
| //@Table(name = "app_user") | ||
| //public class AppUser { | ||
| // | ||
| // @Id | ||
| // @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| // private Long id; | ||
| // | ||
| // @Column(nullable = false, unique = true) | ||
| // private String email; | ||
| // | ||
| // @Column(name = "password_hash", nullable = false) | ||
| // private String passwordHash; | ||
| // | ||
| // @Column(name = "created_at", nullable = false) | ||
| // private LocalDateTime createdAt; | ||
| // | ||
| // public AppUser() { | ||
| // } | ||
| // | ||
| // public Long getId() { | ||
| // return id; | ||
| // } | ||
| // | ||
| // public String getEmail() { | ||
| // return email; | ||
| // } | ||
| // | ||
| // public String getPasswordHash() { | ||
| // return passwordHash; | ||
| // } | ||
| // | ||
| // public LocalDateTime getCreatedAt() { | ||
| // return createdAt; | ||
| // } | ||
| // | ||
| // public void setEmail(String email) { | ||
| // this.email = email; | ||
| // } | ||
| // | ||
| // public void setPasswordHash(String passwordHash) { | ||
| // this.passwordHash = passwordHash; | ||
| // } | ||
| // | ||
| // @PrePersist | ||
| // void onCreate() { | ||
| // if (this.createdAt == null) { | ||
| // this.createdAt = LocalDateTime.now(); | ||
| // } | ||
| // } | ||
| //} |
15 changes: 15 additions & 0 deletions
15
src/main/java/backendlab/team4you/webauthn/LoginController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package backendlab.team4you.webauthn; | ||
|
|
||
| import org.springframework.security.web.csrf.CsrfToken; | ||
| import org.springframework.stereotype.Controller; | ||
| import org.springframework.ui.Model; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
|
|
||
| @Controller | ||
| public class LoginController { | ||
| @GetMapping("/login") | ||
| public String login(CsrfToken token, Model model){ | ||
| model.addAttribute("csrfToken", token.getToken()); | ||
| return "login"; | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
98 changes: 98 additions & 0 deletions
98
src/main/java/backendlab/team4you/webauthn/SignupController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| package backendlab.team4you.webauthn; | ||
|
|
||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
| import org.springframework.security.core.Authentication; | ||
| import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
| import org.springframework.security.core.context.SecurityContext; | ||
| import org.springframework.security.core.context.SecurityContextHolder; | ||
| import org.springframework.security.web.context.HttpSessionSecurityContextRepository; | ||
| import org.springframework.security.web.context.SecurityContextRepository; | ||
| import org.springframework.security.web.webauthn.api.Bytes; | ||
| import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository; | ||
| import org.springframework.stereotype.Controller; | ||
| import org.springframework.ui.Model; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.ResponseBody; | ||
| import org.springframework.web.server.ResponseStatusException; | ||
|
|
||
| import java.security.SecureRandom; | ||
| import java.util.List; | ||
|
|
||
| @Controller | ||
| public class SignupController { | ||
|
|
||
| private final PublicKeyCredentialUserEntityRepository users; | ||
| private final SecureRandom random = new SecureRandom(); | ||
|
|
||
| public SignupController(PublicKeyCredentialUserEntityRepository users) { | ||
| this.users = users; | ||
| } | ||
|
|
||
| @GetMapping("/signup") | ||
| String signup(org.springframework.security.web.csrf.CsrfToken token, Model model) { | ||
| model.addAttribute("csrfToken", token.getToken()); | ||
| return "signup"; | ||
| } | ||
|
|
||
| @PostMapping("/signup") | ||
| @ResponseBody | ||
| public void signup(@RequestBody SignupRequest req, HttpServletRequest request, HttpServletResponse response) { | ||
|
|
||
| if (req.username == null || req.username.isBlank()) { | ||
| throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Username is required"); | ||
| } | ||
|
|
||
| if (users.findByUsername(req.username) != null) { | ||
| throw new ResponseStatusException(HttpStatus.CONFLICT, "Username already exists"); | ||
| } | ||
|
|
||
| byte[] idBytes = new byte[32]; | ||
| random.nextBytes(idBytes); | ||
|
|
||
| UserEntity userEntity = new UserEntity( | ||
| new Bytes(idBytes), | ||
| req.username, | ||
| req.displayName | ||
| ); | ||
|
|
||
| users.save(userEntity); | ||
|
|
||
| Authentication auth = new UsernamePasswordAuthenticationToken( | ||
| userEntity.getName(), null, List.of(new SimpleGrantedAuthority("ROLE_USER"))); | ||
|
|
||
| SecurityContext context = SecurityContextHolder.createEmptyContext(); | ||
| context.setAuthentication(auth); | ||
|
|
||
| SecurityContextRepository repo = new HttpSessionSecurityContextRepository(); | ||
| repo.saveContext(context, request, response); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| public static class SignupRequest { | ||
| private String username; | ||
| private String displayName; | ||
|
|
||
| public SignupRequest() { | ||
| } | ||
|
|
||
| public String getUsername() { | ||
| return username; | ||
| } | ||
|
|
||
| public void setUsername(String username) { | ||
| this.username = username; | ||
| } | ||
|
|
||
| public String getDisplayName() { | ||
| return displayName; | ||
| } | ||
|
|
||
| public void setDisplayName(String displayName) { | ||
| this.displayName = displayName; | ||
| } | ||
| } | ||
| } | ||
80 changes: 80 additions & 0 deletions
80
src/main/java/backendlab/team4you/webauthn/UserEntity.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| package backendlab.team4you.webauthn; | ||
|
|
||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Table; | ||
| import org.jspecify.annotations.Nullable; | ||
| import org.springframework.security.web.webauthn.api.Bytes; | ||
| import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| @Entity | ||
| @Table(name = "app_user") | ||
| public class UserEntity implements PublicKeyCredentialUserEntity { | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| @Id | ||
| @Column(name = "id", length = 255) | ||
| private String id; | ||
|
|
||
| @Column(unique = true, nullable = false) | ||
| private String name; | ||
|
|
||
| private String displayName; | ||
|
|
||
| @Column(name = "password_hash", nullable = true) | ||
| private String passwordHash; | ||
|
|
||
| @Column(name = "created_at", nullable = false) | ||
| private LocalDateTime createdAt; | ||
|
|
||
| public UserEntity() { | ||
| } | ||
|
|
||
| public UserEntity(Bytes id, String name, String displayName) { | ||
| this.id = id != null ? id.toBase64UrlString() : null; | ||
| this.name = name; | ||
| this.displayName = displayName; | ||
| this.createdAt = LocalDateTime.now(); | ||
| } | ||
|
|
||
| @Override | ||
| public Bytes getId() { | ||
| return id != null ? Bytes.fromBase64(id) : null; | ||
| } | ||
|
|
||
| public void setId(Bytes id) { | ||
| this.id = id != null ? id.toBase64UrlString() : null; | ||
| } | ||
|
|
||
| @Override | ||
| public String getName() { | ||
| return name; | ||
| } | ||
|
|
||
| @Override | ||
| public @Nullable String getDisplayName() { | ||
| return displayName; | ||
| } | ||
|
|
||
| public void setDisplayName(String displayName) { | ||
| this.displayName = displayName; | ||
| } | ||
|
|
||
| public LocalDateTime getCreatedAt() { | ||
| return createdAt; | ||
| } | ||
|
|
||
| public void setCreatedAt(LocalDateTime createdAt) { | ||
| this.createdAt = createdAt; | ||
| } | ||
|
|
||
| public String getPasswordHash() { | ||
| return passwordHash; | ||
| } | ||
|
|
||
| public void setPasswordHash(String passwordHash) { | ||
| this.passwordHash = passwordHash; | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.