feat(auth): add role-based authorization
- Load the user's role during authentication and expose it as a granted authority on the authenticated token - Add @RequiresAdmin and @RequiresSuperUser method-level annotations for role-gated endpoints - Introduce a shared BaseRepository with specification support - Replace Example-based credential queries with JPA specifications
This commit is contained in:
11 files changed
+78
-18
No files matched your search
@@ -0,0 +1,7 @@
|
||||
package com.onixbyte.deltaforceguide.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
public interface BaseRepository<T, K> extends JpaRepository<T, K>, JpaSpecificationExecutor<T> {
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import java.util.List;
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Repository
|
||||
public interface FirearmRepository extends JpaRepository<Firearm, Long> {
|
||||
public interface FirearmRepository extends BaseRepository<Firearm, Long> {
|
||||
|
||||
Page<Firearm> findAllByType(FirearmType type, Pageable pageable);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.util.Optional;
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Repository
|
||||
public interface ModificationRepository extends JpaRepository<Modification, Long> {
|
||||
public interface ModificationRepository extends BaseRepository<Modification, Long> {
|
||||
|
||||
@EntityGraph(attributePaths = {"firearm"})
|
||||
Page<Modification> findAllBy(Pageable pageable);
|
||||
|
||||
@@ -2,10 +2,7 @@ package com.onixbyte.deltaforceguide.repository;
|
||||
|
||||
import com.onixbyte.deltaforceguide.domain.entity.UserCredential;
|
||||
import com.onixbyte.deltaforceguide.domain.entity.UserCredentialId;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.jpa.repository.*;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@@ -18,7 +15,9 @@ import java.util.Optional;
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Repository
|
||||
public interface UserCredentialRepository extends JpaRepository<UserCredential, UserCredentialId> {
|
||||
public interface UserCredentialRepository extends
|
||||
JpaRepository<UserCredential, UserCredentialId>,
|
||||
JpaSpecificationExecutor<UserCredential> {
|
||||
|
||||
/**
|
||||
* Find all credentials belonging to a given user.
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.Optional;
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
public interface UserRepository extends BaseRepository<User, Long> {
|
||||
|
||||
@Override
|
||||
@EntityGraph(attributePaths = {"credentials"})
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.List;
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Repository
|
||||
public interface UserRoleRepository extends JpaRepository<UserRole, UserRoleId> {
|
||||
public interface UserRoleRepository extends BaseRepository<UserRole, UserRoleId> {
|
||||
|
||||
/**
|
||||
* Find all roles assigned to a given user.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.onixbyte.deltaforceguide.security.annotation;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("isAuthenticated() && hasAnyRole('ROLE_ADMIN', 'ROLE_SUPER_USER')")
|
||||
public @interface RequiresAdmin {
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.onixbyte.deltaforceguide.security.annotation;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("isAuthenticated() && hasRole('ROLE_SUPER_USER')")
|
||||
public @interface RequiresSuperUser {
|
||||
}
|
||||
+4
@@ -93,5 +93,9 @@ public class UsernamePasswordAuthentication implements Authentication, Credentia
|
||||
public void setDetails(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
|
||||
this.authorities = new ArrayList<>(authorities);
|
||||
}
|
||||
}
|
||||
|
||||
+27
-9
@@ -1,22 +1,28 @@
|
||||
package com.onixbyte.deltaforceguide.security.provider;
|
||||
|
||||
import com.onixbyte.deltaforceguide.domain.entity.UserCredential;
|
||||
import com.onixbyte.deltaforceguide.domain.entity.UserRole;
|
||||
import com.onixbyte.deltaforceguide.exeption.InternalServerErrorException;
|
||||
import com.onixbyte.deltaforceguide.exeption.UnauthorisedException;
|
||||
import com.onixbyte.deltaforceguide.manager.UserManager;
|
||||
import com.onixbyte.deltaforceguide.repository.UserCredentialRepository;
|
||||
import com.onixbyte.deltaforceguide.repository.UserRoleRepository;
|
||||
import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication;
|
||||
import com.onixbyte.deltaforceguide.shared.CredentialProvider;
|
||||
import com.onixbyte.deltaforceguide.shared.Role;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Authentication provider that validates username/password credentials against stored BCrypt hashes.
|
||||
*
|
||||
@@ -29,16 +35,18 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
|
||||
private final UserManager userManager;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final UserCredentialRepository userCredentialRepository;
|
||||
private final UserRoleRepository userRoleRepository;
|
||||
|
||||
@Autowired
|
||||
public UsernamePasswordAuthenticationProvider(
|
||||
UserManager userManager,
|
||||
PasswordEncoder passwordEncoder,
|
||||
UserCredentialRepository userCredentialRepository
|
||||
) {
|
||||
UserCredentialRepository userCredentialRepository,
|
||||
UserRoleRepository userRoleRepository) {
|
||||
this.userManager = userManager;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.userCredentialRepository = userCredentialRepository;
|
||||
this.userRoleRepository = userRoleRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,12 +70,13 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
|
||||
|
||||
var user = userContainer.get();
|
||||
|
||||
var userCredentialExample = new UserCredential();
|
||||
userCredentialExample.setUserId(user.getId());
|
||||
userCredentialExample.setProvider(CredentialProvider.LOCAL);
|
||||
|
||||
// get userContainer credentials from database
|
||||
var userCredentials = userCredentialRepository.findOne(Example.of(userCredentialExample))
|
||||
var userCredentials = userCredentialRepository.findOne((root, query, cb) -> {
|
||||
var predicates = new ArrayList<Predicate>();
|
||||
predicates.add(cb.equal(root.get("provider"), CredentialProvider.LOCAL));
|
||||
predicates.add(cb.equal(root.get("userId"), user.getId()));
|
||||
return cb.and(predicates.toArray(Predicate[]::new));
|
||||
})
|
||||
.orElseThrow(() -> new UnauthorisedException("您还没有配置密码,请联系管理员处理。"));
|
||||
|
||||
// validate password
|
||||
@@ -76,12 +85,21 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
|
||||
throw new UnauthorisedException("用户名或密码错误。");
|
||||
}
|
||||
|
||||
var role = userRoleRepository.findOne((root, query, cb) -> cb.equal(root.get("userId"), user.getId()))
|
||||
.orElseGet(() -> {
|
||||
var ur = new UserRole();
|
||||
ur.setUserId(user.getId());
|
||||
ur.setRole(Role.ROLE_USER);
|
||||
return ur;
|
||||
});
|
||||
|
||||
// erase credentials
|
||||
usernamePasswordAuthentication.eraseCredentials();
|
||||
|
||||
// set values
|
||||
usernamePasswordAuthentication.setAuthenticated(true);
|
||||
usernamePasswordAuthentication.setDetails(user);
|
||||
usernamePasswordAuthentication.setAuthorities(List.of(new SimpleGrantedAuthority(role.getRole())));
|
||||
|
||||
return usernamePasswordAuthentication;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ package com.onixbyte.deltaforceguide.shared;
|
||||
*/
|
||||
public class Role {
|
||||
|
||||
public static final String ROLE_USER_VALUE = "USER";
|
||||
public static final String ROLE_ADMIN_VALUE = "ADMIN";
|
||||
public static final String ROLE_SUPER_USER_VALUE = "SUPER_USER";
|
||||
|
||||
public static final String ROLE_USER = "ROLE_USER";
|
||||
public static final String ROLE_ADMIN = "ROLE_ADMIN";
|
||||
public static final String ROLE_SUPER_USER = "ROLE_SUPER_USER";
|
||||
|
||||
Reference in new issue
Block a user