关于权限
在一个系统中有很多功能例如:查看用户信息、修改用户信息、删除用户信息,而其中有些功能并不适宜让所有人都能够访问,例如修改用户信息和删除用户信息,这些功能需要被合理的使用,只能由一些人使用。
那么怎么识别谁能够使用这些功能,就是给每个人加上一个标识也就是角色
如果你是管理员,就可以进行修改和删除的操作
如果你是普通用户,就只能进行查看操作
有了对权限的认知,下面会讲解在数据库中如何实现权限
数据库表的建立
用户表
用户表就是存放用户的各种信息
大概为:
- id:作为方便查找用户信息的一种标识
- name:名字
- username:名字可能会与其他人重复,我们需要username来确认是否为要找的人
- password:其他人不能操作自己的空间,需要登录判断是否为本人
- phone:手机号 也是作为是否为本人的标识
- gender:性别
- enabled:是否为启用 如果进行了敏感操作,封禁账号
- last_login_time:上一次登录时间

角色表
我们首先思考角色需要什么,首先是名字用来区分,之后为备注对于角色的说明
字段:
- id:方便查找的标识
- name:角色的名称
- remark:备注

权限表
权限需要什么:
- id
- name: 名字用来区分哪个权限
- url:实际的权限:每个功能由url请求获取,所以需要存储url
- method:请求的方式
- service:服务名
- parent_id:父id,在一个菜单中可能拥有多个功能。在权限管理界面 查看权限时需要以树形结构显示

现在用户、角色、权限表的创建都已完成,那么这样就可以实现权限了吗?答案是还需要让它们关联起来
用户对应着角色而角色则对应着权限
首先创建用户角色表
用户角色表
用户对应的角色
字段:
- id
- role_id:角色的id
- user_id:用户的id

其次是角色权限表
角色权限表
角色拥有的权限
字段:
- id
- role_id:角色的id
- permission_id:权限的id

这样用户关联角色,角色关联权限,也让用户间接拥有了权限
如何实现
我们可以使用Spring整合SpringSecurity来实现权限控制

首先看流程
在SpringSecurity中它通过UserDetailsService来获取用户信息,之后角色信息也被加载封装为GrantedAuthority 代表赋予给用户的权限的接口。
因为我们用的是数据库中的数据
所以我们需要先实现UserDetails提供用户详细信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| public class AccountUser implements UserDetails { private static final long serialVersionUID = -1L; private Integer userId; private String password; private String username; private Collection<? extends GrantedAuthority> authorities; private boolean accountNonExpired; private boolean accountNonLocked; private boolean credentialsNonExpired; private boolean enabled; public AccountUser(Integer userId, String username, String password, Collection<? extends GrantedAuthority> authorities) { this(userId, username, password, true, true, true, true, authorities); }
public AccountUser(Integer userId, String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { Assert.isTrue(username != null && !"".equals(username) && password != null, "Cannot pass null or empty values to constructor"); this.userId = userId; this.username = username; this.password = password; this.enabled = enabled; this.accountNonExpired = accountNonExpired; this.credentialsNonExpired = credentialsNonExpired; this.accountNonLocked = accountNonLocked; this.authorities = authorities; }
@Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; }
public Integer getUserId() { return this.userId; }
@Override public String getPassword() { return this.password; }
@Override public String getUsername() { return this.username; }
@Override public boolean isAccountNonExpired() { return this.accountNonExpired; }
@Override public boolean isAccountNonLocked() { return this.accountNonLocked; }
@Override public boolean isCredentialsNonExpired() { return this.credentialsNonExpired; }
@Override public boolean isEnabled() { return this.enabled; }
}
|
再实现UserDetailsService来获取AccountUser对象,从而实现用户的认证和授权
- 创建AccountUserDetailsService实现UserDetailsService接口
1 2
| public class AccountUserDetailsService implements UserDetailsService { }
|
- 注入UserService,因为我们的数据是从数据库中获得。至于数据库的实体类根据上面的表自己创建
1 2 3 4 5
| private final UserService userService;
public AccountUserDetailsService(UserService userService) { this.userService = userService; }
|
UserService
此 Service 中创建了获取用户对应权限的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| @Service public class UserService extends ServiceImpl<UserMapper, User> { private final UserRoleService userRoleService; private final RolePermissionService rolePermissionService; private final PermissionService permissionService;
public UserService(UserRoleService userRoleService, RolePermissionService rolePermissionService, PermissionService permissionService) { this.userRoleService = userRoleService; this.rolePermissionService = rolePermissionService; this.permissionService = permissionService; }
public List<Permission> getPermissionByUsername(String username) { User user = super.getOne(Wrappers.<User>lambdaQuery().eq(User::getUsername, username), true); return this.getPermissionByUser(user); }
public List<Permission> getPermissionByUserId(Integer userId) { User user = super.getById(userId); return this.getPermissionByUser(user); } public List<Permission> getPermissionByUser(User user) { List<Permission> permissions = new ArrayList<>(); if (null != user) { List<UserRole> userRoles = userRoleService.list(Wrappers.<UserRole>lambdaQuery().eq(UserRole::getUserId, user.getId())); if (CollectionUtils.isNotEmpty(userRoles)) { List<Integer> roleIds = new ArrayList<>(); userRoles.forEach(userRole -> roleIds.add(userRole.getRoleId())); List<RolePermission> rolePermissions = rolePermissionService.list(Wrappers.<RolePermission>lambdaQuery().in(RolePermission::getRoleId, roleIds)); if (CollectionUtils.isNotEmpty(rolePermissions)) { List<Integer> permissionIds = new ArrayList<>(); rolePermissions.forEach(rolePermission -> permissionIds.add(rolePermission.getPermissionId())); permissions = permissionService.list(Wrappers.<Permission>lambdaQuery().in(Permission::getId, permissionIds)); } } } return permissions; } }
|
- 重写 loadUserByUsername(String username) 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userService.getOne(Wrappers.<User>lambdaQuery().eq(User::getUsername, username), true); if (user == null) { throw new UsernameNotFoundException("用户名或密码错误"); } return new AccountUser(user.getId(), user.getUsername(), user.getPassword(), getUserAuthority(user.getUsername())); }
public List<GrantedAuthority> getUserAuthority(String username) { List<Permission> permissions = userService.getPermissionByUsername(username); String authority = ""; if (CollectionUtils.isNotEmpty(permissions)) { List<String> urls = permissions.stream().map(Permission::getUrl).collect(Collectors.toList()); authority = StrUtil.join(",", urls); } return AuthorityUtils.commaSeparatedStringToAuthorityList(authority); }
|
创建SecurityConfig在其中注册AuthenticationProvider使用我们的来获取用户数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| @Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig{ private final AccountUserDetailsService accountUserDetailsService; private final JwtAuthenticationFilter jwtAuthenticationFilter; private final JwtLogoutSuccessHandler jwtLogoutSuccessHandler; private final JwtAccessDeniedHandler jwtAccessDeniedHandler; private final LoginSuccessHandler loginSuccessHandler; private final LoginFailureHandler loginFailureHandler; private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; public SecurityConfig(AccountUserDetailsService accountUserDetailsService, JwtAuthenticationFilter jwtAuthenticationFilter, JwtLogoutSuccessHandler jwtLogoutSuccessHandler, JwtAccessDeniedHandler jwtAccessDeniedHandler, LoginSuccessHandler loginSuccessHandler, LoginFailureHandler loginFailureHandler, JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint) { this.accountUserDetailsService = accountUserDetailsService; this.jwtAuthenticationFilter = jwtAuthenticationFilter; this.jwtLogoutSuccessHandler = jwtLogoutSuccessHandler; this.jwtAccessDeniedHandler = jwtAccessDeniedHandler; this.loginSuccessHandler = loginSuccessHandler; this.loginFailureHandler = loginFailureHandler; this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint; } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }
@Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(accountUserDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; }
@Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); }
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .formLogin(form -> form .successHandler(loginSuccessHandler).failureHandler(loginFailureHandler)) .logout(logout -> logout.logoutSuccessHandler(jwtLogoutSuccessHandler)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/user/login").permitAll() .anyRequest().authenticated()) .authenticationProvider(authenticationProvider()) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }
|
在需要进行权限控制的controller方法上添加权限控制注解 @PreAuthorize。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @RestController @RequestMapping(path = "/user", produces = "application/json;charset=utf-8") public class UserController { @Resource private JwtUtil jwtUtil;
private final AuthenticationProvider authenticationProvider;
public UserController(AuthenticationProvider authenticationProvider) { this.authenticationProvider = authenticationProvider; }
@PostMapping("/login") public ResultData login(@RequestBody @Validated UserLoginDTO userLoginDTO) { Authentication authenticate = authenticationProvider .authenticate(new UsernamePasswordAuthenticationToken(userLoginDTO.getUsername(), userLoginDTO.getPassword())); String token = jwtUtil.generateToken(userLoginDTO.getUsername()); Map<String, String> map = new HashMap<>(); map.put("token", token); return ResultData.success(map); }
@PreAuthorize("hasAuthority('/user/logout')") @GetMapping("/logout") public ResultData logout(HttpServletRequest request, HttpServletResponse response) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null) { new SecurityContextLogoutHandler().logout(request, response, auth); } return ResultData.success(); } }
|
关于**@PreAuthorize:value** (可省略)属性是 Spring-EL 表达式类型的字符串。其值可参考类 SecurityExpressionRoot,这是Spring Security 框架封装的专门针对 Spring Security 框架本身的 Spring—EL表达式解析类。
分别介绍一下其中值选项:
- hasRole,对应 public final boolean hasRole(String role) 方法,含义为必须含有某角色(ROLE_开头),如有多个的话,必须同时具有这些角色,才可访问对应资源。
- hasAnyRole,对应 public final boolean hasAnyRole(String… roles) 方法,含义为只要具有某一角色(多个角色的话,具有任意一个即可),即可访问对应资源。
- hasAuthority,对应 public final boolean hasAuthority(String authority) 方法,含义同 hasRole,不同点在于这是权限,而不是角色,区别就在于角色往往带有前缀(如默认的ROLE_),而权限只有标识。
- hasAnyAuthority,对应 public final boolean hasAnyAuthority(String… authorities) 方法,含义同 hasAnyRole,不同点在于这是权限,而不是角色,区别就在于角色往往带有前缀(如默认的ROLE_),而权限只有标识。
- permitAll,对应 public final boolean permitAll() 方法,含义为允许所有人(可无任何权限)访问。
- denyAll,对应 public final boolean denyAll() 方法,含义为不允许任何(即使有最大权限)访问。
- isAnonymous,对应 public final boolean isAnonymous() 方法,含义为可匿名(不登录)访问。
- isAuthenticated,对应 public final boolean isAuthenticated() 方法,含义为身份证认证后访问。
- isRememberMe,对应 public final boolean isRememberMe() 方法,含义为记住我用户操作访问。
- isFullyAuthenticated,对应 public final boolean isFullyAuthenticated() 方法,含义为非匿名且非记住我用户允许访问。
最终效果:
admin用户拥有退出权限


可以看到admin有登出权限,成功登出
normal用户没有 登出权限


可以看到normal没有权限登出,操作失败返回不允许访问
动态权限管理
我们第一个实现权限管理的方法是直接在接口/Service层方法上添加注解,这样做的好处是实现简单,但是有一个问题就是权限硬编码,每一个方法需要什么权限都是在代码中配置好的,后期如果想通过管理页面修改是不可能的,要修改某一个方法所需要的权限只能改代码
所以我们使用动态权限管理解决
将请求和权限的关系通过数据库来描述,每一个请求需要什么权限都在数据库中配置好,当请求到达的时候,动态查询,然后判断权限是否满足,这样做的好处是比较灵活,将来需要修改接口和权限之间的关系时,可以通过管理页面点几下,问题就解决了,不用修改代码。
一个url可以被多个role访问,只要user中有一个role即可访问该url
- 首先创建一个类存储url和role对应的关系
1 2 3 4 5 6 7
| @Data @AllArgsConstructor public class PermissionWithRoleVO { private String url;
private List<Role> roles; }
|
- 创建Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Service public class PermissionWithRoleService { @Resource private PermissionService permissionService; @Resource private RolePermissionService rolePermissionService; @Resource private RoleService roleService;
public List<PermissionWithRoleVO> getPermissionWithRole(){ List<PermissionWithRoleVO> permissionWithRoleVOS = new ArrayList<>(); List<Permission> permissions = permissionService.list(); permissions.forEach(permission -> { List<Integer> roleIds = rolePermissionService .list(Wrappers.<RolePermission>lambdaQuery().eq(RolePermission::getPermissionId, permission.getId())) .stream().map(RolePermission::getRoleId).toList(); List<Role> roles = roleService.list(Wrappers.<Role>lambdaQuery().in(Role::getId, roleIds)); permissionWithRoleVOS.add(new PermissionWithRoleVO(permission.getUrl(),roles)); }); return permissionWithRoleVOS; } }
|
在SecurityConfig中的**securityFilterChain()**方法配置动态权限管理
动态权限管理在.anyRequest().access()中配置。
此配置进行了如下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| .authorizeHttpRequests(auth -> auth .requestMatchers(URL_WHITELIST).permitAll() .anyRequest() .access((authentication, object) -> { if (authentication.get() instanceof AnonymousAuthenticationToken) { return new AuthorizationDecision(false); }
String requestURI = object.getRequest().getRequestURI(); for (PermissionWithRoleVO p : permissionWithRoleService.getPermissionWithRole()) { if (new AntPathMatcher().match(p.getUrl(), requestURI)) { List<Role> roles = p.getRoles(); for (GrantedAuthority authority : authentication.get().getAuthorities()) { for (Role role : roles) { if (authority.getAuthority().equals(role.getName())) { return new AuthorizationDecision(true); } } } } } return new AuthorizationDecision(false); }) )
|
因为我们前面实现用户获取的权限信息是urls,所以我们需要将其改为获取角色
UserService 获取用户拥有的权限方法改为获取用户拥有的角色,之前的方法名为getPermissionByUser()
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public List<Role> getRoleByUser(User user) { List<Role> roles = new ArrayList<>(); if (null != user) { List<UserRole> userRoles = userRoleService.list(Wrappers.<UserRole>lambdaQuery().eq(UserRole::getUserId, user.getId())); if (CollectionUtils.isNotEmpty(userRoles)) { List<Integer> roleIds = new ArrayList<>(); userRoles.forEach(userRole -> roleIds.add(userRole.getRoleId())); roles = roleService.list(Wrappers.<Role>lambdaQuery().in(Role::getId, roleIds)); } } return roles; }
|
AccountUserDetailsService
1 2 3 4 5 6 7 8 9 10 11 12
| public List<GrantedAuthority> getUserAuthority(String username) { List<Role> roles = userService.getPermissionByUsername(username); String authority = ""; if (CollectionUtils.isNotEmpty(roles)) { List<String> names = roles.stream().map(Role::getName).collect(Collectors.toList()); authority = StrUtil.join(",", names); } return AuthorityUtils.commaSeparatedStringToAuthorityList(authority); }
|
这样我们的动态权限管理就完成了,效果跟用注解一样。