当使用 @PreAuthorize
注解结合自定义权限校验类实现基于方法级别的权限控制时,以下是一个完整的示例:
首先,在 SecurityService 类中添加权限校验方法 hasPermission
:
public boolean hasPermission(int userId, int departmentId) {
User user = userDao.getUserById(userId);
if (user != null) {
return user.getDepartmentId() == departmentId && isDepartmentManager(userId, departmentId);
}
return false;
}
private boolean isDepartmentManager(int userId, int departmentId) {
// 根据 userId 和 departmentId 判断该用户是否为部门经理
// 返回 true 或 false
}
然后,在授权方法上添加 @PreAuthorize
注解:
@PreAuthorize("@securityService.hasPermission(#userId, #departmentId)")
public void grantPermission(int userId, int departmentId) {
User user = userDao.getUserById(userId);
if (user != null && user.getDepartmentId() == departmentId) {
// 进行授权操作
}
}
接下来,配置 Spring Security,以启用注解的权限控制。在 Spring Security 配置类中添加如下配置:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 配置其他 Spring Security 相关的配置
@Bean
public SecurityService securityService(UserDao userDao) {
return new SecurityService(userDao);
}
}
最后,使用 @Autowired
注解将 SecurityService 注入到其他类中,并调用授权方法:
@Autowired
private SecurityService securityService;
@Autowired
private UserDao userDao;
public void someMethod(int userId, int departmentId) {
if (securityService.hasPermission(userId, departmentId)) {
User user = userDao.getUserById(userId);
if (user != null && user.getDepartmentId() == departmentId) {
securityService.grantPermission(userId, departmentId);
}
} else {
// 没有权限进行授权
}
}
这样,通过 @PreAuthorize
注解和自定义的权限校验类,实现了基于方法级别的权限控制。在调用 grantPermission
方法之前,会自动进行权限判断,只有具有权限的用户才能执行授权操作。
接下来, 我们将定义一个基于Spring Security的角色和菜单权限控制的案例:
- 创建菜单实体类
Menu
:
public class Menu {
private int id;
private String name;
private String url;
// 其他属性
// 省略构造函数、getter和setter方法
}
- 创建角色实体类
Role
:
public class Role {
private int id;
private String name;
private List<Menu> menus;
// 省略构造函数、getter和setter方法
}
- 创建用户实体类
User
:
public class User {
private int id;
private String username;
private String password;
private List<Role> roles;
// 省略构造函数、getter和setter方法
}
- 创建用于查询用户、角色和菜单信息的相关接口:
public interface UserDao {
User getUserByUsername(String username);
// 其他数据库操作方法
}
public interface RoleDao {
Role getRoleById(int roleId);
// 其他数据库操作方法
}
public interface MenuDao {
List<Menu> getAllMenus();
// 其他数据库操作方法
}
- 创建一个实现Spring Security的
UserDetailsService
接口的类CustomUserDetailsService
:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.getUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("Invalid username or password.");
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
getAuthorities(user.getRoles()));
}
private List<GrantedAuthority> getAuthorities(List<Role> roles) {
List<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
for (Menu menu : role.getMenus()) {
authorities.add(new SimpleGrantedAuthority(menu.getUrl()));
}
}
return authorities;
}
}
在上述代码中,CustomUserDetailsService
类实现了 UserDetailsService
接口,并根据用户名从数据库中加载用户信息。同时,根据用户的角色和菜单信息分配相应的权限。
- 创建一个基于角色的权限控制注解
@HasRole
:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole(@authentication, #role)")
public @interface HasRole {
String value();
}
- 在控制器层(Controller)中使用
@HasRole
来实现基于角色的权限控制:
@Controller
public class MyController {
@GetMapping("/admin")
@HasRole("ADMIN")
public String adminPage() {
return "admin";
}
@GetMapping("/user")
@HasRole("USER")
public String userPage() {
return "user";
}
// 其他方法
}
使用 @HasRole
注解来标记方法,通过传入指定的角色名称实现对应角色的权限控制。
除此之外,还需要在 Spring Security 的配置类中进行相关配置:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin").hasRole("ADMIN")
.antMatchers("/user").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.logout()
.permitAll();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
在上述代码中,我们通过 configure(HttpSecurity http)
方法配置了针对 /admin
和 /user
路径的访问权限,只有具有指定角色的用户才能访问相应的页面。同时,我们还配置了基于表单的登录和登出功能。
在上面的代码中,我们实现了基于角色的权限控制和登录认证,但还没有包含菜单权限控制。接下来我们将添加菜单权限控制的功能。
- 在
CustomUserDetailsService
中,我们需要从数据库加载所有菜单信息:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Autowired
private MenuDao menuDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.getUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("Invalid username or password.");
}
List<Menu> menus = menuDao.getAllMenus();
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
getAuthorities(user.getRoles(), menus));
}
private List<GrantedAuthority> getAuthorities(List<Role> roles, List<Menu> allMenus) {
List<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
for (Menu menu : role.getMenus()) {
authorities.add(new SimpleGrantedAuthority(menu.getUrl()));
}
}
for (Menu menu : allMenus) {
if (!hasAuthority(authorities, menu.getUrl())) {
authorities.add(new SimpleGrantedAuthority(menu.getUrl()));
}
}
return authorities;
}
private boolean hasAuthority(List<GrantedAuthority> authorities, String authority) {
for (GrantedAuthority grantedAuthority : authorities) {
if (authority.equals(grantedAuthority.getAuthority())) {
return true;
}
}
return false;
}
}
在上述代码中,我们在 getAuthorities
方法中将所有菜单的 url
作为权限添加到用户的权限列表中。通过检查用户的权限列表,如果不包含菜单的 url
,则将其添加到用户的权限列表中。
- 在控制器层的方法上添加菜单权限控制的注解
@HasMenuPermission
:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAuthority(#menuUrl)")
public @interface HasMenuPermission {
String value();
}
- 在控制器层中使用
@HasMenuPermission
注解来实现菜单权限控制:
@Controller
public class MyController {
@GetMapping("/admin")
@HasRole("ADMIN")
public String adminPage() {
return "admin";
}
@GetMapping("/user")
@HasRole("USER")
public String userPage() {
return "user";
}
@GetMapping("/menu1")
@HasMenuPermission("/menu1")
public String menu1Page() {
return "menu1";
}
@GetMapping("/menu2")
@HasMenuPermission("/menu2")
public String menu2Page() {
return "menu2";
}
// 其他方法
}
使用 @HasMenuPermission
注解标记方法,并传入相应菜单的 url
,实现菜单级别的权限控制。
通过以上步骤,我们实现了基于角色和菜单的权限控制和登录认证,@HasRole
注解用于角色的权限控制,@HasMenuPermission
注解用于菜单的权限控制。
请注意,以上示例中的内容是一个简化的示例,实际项目中可能涉及到更复杂的业务逻辑和数据库操作。这个示例提供了一个基本框架,你可以根据实际需求进行扩展和优化。