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
|
* @author zihluwang
|
||||||
*/
|
*/
|
||||||
@Repository
|
@Repository
|
||||||
public interface FirearmRepository extends JpaRepository<Firearm, Long> {
|
public interface FirearmRepository extends BaseRepository<Firearm, Long> {
|
||||||
|
|
||||||
Page<Firearm> findAllByType(FirearmType type, Pageable pageable);
|
Page<Firearm> findAllByType(FirearmType type, Pageable pageable);
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import java.util.Optional;
|
|||||||
* @author zihluwang
|
* @author zihluwang
|
||||||
*/
|
*/
|
||||||
@Repository
|
@Repository
|
||||||
public interface ModificationRepository extends JpaRepository<Modification, Long> {
|
public interface ModificationRepository extends BaseRepository<Modification, Long> {
|
||||||
|
|
||||||
@EntityGraph(attributePaths = {"firearm"})
|
@EntityGraph(attributePaths = {"firearm"})
|
||||||
Page<Modification> findAllBy(Pageable pageable);
|
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.UserCredential;
|
||||||
import com.onixbyte.deltaforceguide.domain.entity.UserCredentialId;
|
import com.onixbyte.deltaforceguide.domain.entity.UserCredentialId;
|
||||||
import org.springframework.data.jpa.repository.Modifying;
|
import org.springframework.data.jpa.repository.*;
|
||||||
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.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@@ -18,7 +15,9 @@ import java.util.Optional;
|
|||||||
* @author zihluwang
|
* @author zihluwang
|
||||||
*/
|
*/
|
||||||
@Repository
|
@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.
|
* Find all credentials belonging to a given user.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import java.util.Optional;
|
|||||||
* @author zihluwang
|
* @author zihluwang
|
||||||
*/
|
*/
|
||||||
@Repository
|
@Repository
|
||||||
public interface UserRepository extends JpaRepository<User, Long> {
|
public interface UserRepository extends BaseRepository<User, Long> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@EntityGraph(attributePaths = {"credentials"})
|
@EntityGraph(attributePaths = {"credentials"})
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import java.util.List;
|
|||||||
* @author zihluwang
|
* @author zihluwang
|
||||||
*/
|
*/
|
||||||
@Repository
|
@Repository
|
||||||
public interface UserRoleRepository extends JpaRepository<UserRole, UserRoleId> {
|
public interface UserRoleRepository extends BaseRepository<UserRole, UserRoleId> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all roles assigned to a given user.
|
* 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) {
|
public void setDetails(User user) {
|
||||||
this.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;
|
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.InternalServerErrorException;
|
||||||
import com.onixbyte.deltaforceguide.exeption.UnauthorisedException;
|
import com.onixbyte.deltaforceguide.exeption.UnauthorisedException;
|
||||||
import com.onixbyte.deltaforceguide.manager.UserManager;
|
import com.onixbyte.deltaforceguide.manager.UserManager;
|
||||||
import com.onixbyte.deltaforceguide.repository.UserCredentialRepository;
|
import com.onixbyte.deltaforceguide.repository.UserCredentialRepository;
|
||||||
|
import com.onixbyte.deltaforceguide.repository.UserRoleRepository;
|
||||||
import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication;
|
import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication;
|
||||||
import com.onixbyte.deltaforceguide.shared.CredentialProvider;
|
import com.onixbyte.deltaforceguide.shared.CredentialProvider;
|
||||||
|
import com.onixbyte.deltaforceguide.shared.Role;
|
||||||
|
import jakarta.persistence.criteria.Predicate;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.Example;
|
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authentication provider that validates username/password credentials against stored BCrypt hashes.
|
* 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 UserManager userManager;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
private final UserCredentialRepository userCredentialRepository;
|
private final UserCredentialRepository userCredentialRepository;
|
||||||
|
private final UserRoleRepository userRoleRepository;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public UsernamePasswordAuthenticationProvider(
|
public UsernamePasswordAuthenticationProvider(
|
||||||
UserManager userManager,
|
UserManager userManager,
|
||||||
PasswordEncoder passwordEncoder,
|
PasswordEncoder passwordEncoder,
|
||||||
UserCredentialRepository userCredentialRepository
|
UserCredentialRepository userCredentialRepository,
|
||||||
) {
|
UserRoleRepository userRoleRepository) {
|
||||||
this.userManager = userManager;
|
this.userManager = userManager;
|
||||||
this.passwordEncoder = passwordEncoder;
|
this.passwordEncoder = passwordEncoder;
|
||||||
this.userCredentialRepository = userCredentialRepository;
|
this.userCredentialRepository = userCredentialRepository;
|
||||||
|
this.userRoleRepository = userRoleRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,12 +70,13 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
|
|||||||
|
|
||||||
var user = userContainer.get();
|
var user = userContainer.get();
|
||||||
|
|
||||||
var userCredentialExample = new UserCredential();
|
|
||||||
userCredentialExample.setUserId(user.getId());
|
|
||||||
userCredentialExample.setProvider(CredentialProvider.LOCAL);
|
|
||||||
|
|
||||||
// get userContainer credentials from database
|
// 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("您还没有配置密码,请联系管理员处理。"));
|
.orElseThrow(() -> new UnauthorisedException("您还没有配置密码,请联系管理员处理。"));
|
||||||
|
|
||||||
// validate password
|
// validate password
|
||||||
@@ -76,12 +85,21 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
|
|||||||
throw new UnauthorisedException("用户名或密码错误。");
|
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
|
// erase credentials
|
||||||
usernamePasswordAuthentication.eraseCredentials();
|
usernamePasswordAuthentication.eraseCredentials();
|
||||||
|
|
||||||
// set values
|
// set values
|
||||||
usernamePasswordAuthentication.setAuthenticated(true);
|
usernamePasswordAuthentication.setAuthenticated(true);
|
||||||
usernamePasswordAuthentication.setDetails(user);
|
usernamePasswordAuthentication.setDetails(user);
|
||||||
|
usernamePasswordAuthentication.setAuthorities(List.of(new SimpleGrantedAuthority(role.getRole())));
|
||||||
|
|
||||||
return usernamePasswordAuthentication;
|
return usernamePasswordAuthentication;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ package com.onixbyte.deltaforceguide.shared;
|
|||||||
*/
|
*/
|
||||||
public class Role {
|
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_USER = "ROLE_USER";
|
||||||
public static final String ROLE_ADMIN = "ROLE_ADMIN";
|
public static final String ROLE_ADMIN = "ROLE_ADMIN";
|
||||||
public static final String ROLE_SUPER_USER = "ROLE_SUPER_USER";
|
public static final String ROLE_SUPER_USER = "ROLE_SUPER_USER";
|
||||||
|
|||||||
Reference in new issue
Block a user