使用RBAC权限模型结合Spring Security进行权限控制

数据库设计

使用RBAC模型至少需要创建五张表分别是

用户表 角色表 权限表 用户角色表 角色权限表

这里学习阶段对表的设计比较简单

  • 用户表主要是用户的账号密码等信息;
  • 角色表是角色名等信息;
  • 权限表是权限的url、名称、别名等信息;
  • 用户角色表是用户表与角色形成的多对多关系,一个用户可以对应多个角色,一个角色也可以是多名用户;
  • 角色权限表则是角色表与权限表对应的一对一关系,即一个角色对应一个权限

五张表环环相扣

1579061113169.png

进行相关的信息查询

通过数据库对用户进行相应的权限查询从而实现权限控制

使用Mapper层进行SQL语句查询操作,首先需要创建实体类,但是只需要创建用户类和权限类就行了

用户类 四个boolean类型和List类型的属性是实现UserDetails接口必须要实现的

  1. @Data
  2. @TableName("sys_user")
  3. public class User implements UserDetails {
  4. private Integer id;
  5. private String username;
  6. private String realname;
  7. private String password;
  8. /**
  9. * 创建时间
  10. */
  11. @JSONField(format = "yyyy-MM-dd hh:mm:ss")
  12. private Date createDate;
  13. /**
  14. * 最后登录时间
  15. */
  16. @JSONField(format = "yyyy-MM-dd hh:mm:ss")
  17. private Date lastLoginTime;
  18. @TableField(exist = false)
  19. private boolean enabled;
  20. @TableField(exist = false)
  21. private boolean accountNonExpired;
  22. @TableField(exist = false)
  23. private boolean accountNonLocked;
  24. @TableField(exist = false)
  25. private boolean credentialsNonExpired;
  26. /**
  27. * 用户所有权限
  28. */
  29. @TableField(exist = false)
  30. private List<GrantedAuthority> authorities;
  31. }

权限类

  1. @Data
  2. public class Permission {
  3. private Integer id;
  4. // 权限名称
  5. private String permName;
  6. // 权限标识
  7. private String permTag;
  8. // 请求url
  9. private String url;
  10. }

有了这两个实体类就可以实现相应的用户权限查询的操作了

Mapper层的代码,根据用户名查询用户的权限的sql语句有些复杂

  1. @Mapper
  2. public interface UserMapper {
  3. /**
  4. * 根据用户名查询用户的信息
  5. * @param username 用户名
  6. * @return List<Permission> 权限对象的list
  7. */
  8. @Select(" select * from sys_user where username = #{username}")
  9. User findByUsername(@Param("username") String username);
  10. /**
  11. * 根据用户名查询用户的权限
  12. * @param username 用户名
  13. * @return List<Permission> 权限list
  14. */
  15. @Select(" select permission.* from sys_user user"
  16. + " inner join sys_user_role user_role"
  17. + " on user.id = user_role.user_id inner join "
  18. + "sys_role_permission role_permission on user_role.role_id = role_permission.role_id "
  19. + " inner join sys_permission permission on role_permission.perm_id = permission.id where user.username = #{username};")
  20. List<Permission> findPermissionByUsername(@Param("username") String username);
  21. /**
  22. * 查询所有权限信息
  23. * @return List<Permission> 权限list
  24. */
  25. @Select(" select * from sys_permission ")
  26. List<Permission> findAllPermission();
  27. }

身份认证及配置类UserDetailsService

Spring Security进行用户身份认证需要实现UserDetailsService接口,然后要在配置类中传入

  1. @Service
  2. public class MyUserDetailsServiceImpl implements UserDetailsService {
  3. @Autowired
  4. private UserMapper userMapper;
  5. @Override
  6. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  7. // 根据用户名称查询数据库信息
  8. User user = userMapper.findByUsername(username);
  9. // 如果数据库中存在该用户名则进行以下操作
  10. if (user != null) {
  11. // 底层会根据数据库查询用户信息,判断密码是否正确
  12. List<Permission> list = userMapper.findPermissionByUsername(username);
  13. if (list != null && list.size() > 0) {
  14. //定义用户权限
  15. List<GrantedAuthority> authorities = new ArrayList<>();
  16. for (Permission permission : list) {
  17. authorities.add(new SimpleGrantedAuthority(permission.getPermTag()));
  18. }
  19. // 设置用户权限
  20. user.setAuthorities(authorities);
  21. }
  22. return user;
  23. } else {
  24. return null;
  25. }
  26. }
  27. }

SecurityConfig配置类的编写,需要注入userDetailsServiceuserMapper对象

  1. @Configuration
  2. @EnableWebSecurity
  3. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  4. @Autowired
  5. private MyAuthenticationHandler myAuthenticationHandler;
  6. @Autowired
  7. private MyUserDetailsServiceImpl userDetailsService;
  8. @Autowired
  9. private UserMapper userMapper;
  10. @Override
  11. //配置用户信息和权限
  12. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  13. //使用数据库动态添加
  14. auth.userDetailsService(userDetailsService).passwordEncoder(new PasswordEncoder() {
  15. @Override
  16. //对表单密码进行加密
  17. public String encode(CharSequence charSequence) {
  18. return MD5Util.encode((String) charSequence);
  19. }
  20. @Override
  21. //加密的密码和数据库中进行比对
  22. //s为数据库加密字段
  23. public boolean matches(CharSequence charSequence, String s) {
  24. return MD5Util.encode((String) charSequence).equals(s);
  25. }
  26. });
  27. }
  28. @Override
  29. //配置拦截请求资源
  30. protected void configure(HttpSecurity http) throws Exception {
  31. //如何权限控制 给每一条路径分配一个权限名称 账号只要管理该名称 就可以访问权限
  32. http.headers().frameOptions().disable();
  33. ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests = http.authorizeRequests();
  34. //读取数据库权限列表
  35. List<Permission> list = userMapper.findAllPermission();
  36. for (Permission permission : list) {
  37. //给URL分配权限
  38. authorizeRequests.antMatchers(permission.getUrl()).hasAnyAuthority(permission.getPermTag());
  39. }
  40. //设置登录页面 配置的权限管理范围是url含有order的
  41. authorizeRequests.antMatchers("/login").permitAll()
  42. .antMatchers("**order**")
  43. .fullyAuthenticated().and().formLogin().loginPage("/login")
  44. .successHandler(myAuthenticationHandler)
  45. .failureHandler(myAuthenticationHandler).and().csrf().disable();
  46. }
  47. }

这里还注入了一个myAuthenticationHandler对象 这个对象是Spring Security认证成功或失败时的操作处理

@Component
public class MyAuthenticationHandler implements AuthenticationFailureHandler, AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        System.out.println("登录失败");
        request.getSession().setAttribute("msg","登录失败");
        response.sendRedirect(request.getContextPath() + "/login");
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        System.out.println("登录成功");
        response.sendRedirect(request.getContextPath() + "/");
    }

}

分配权限注解使用

在配置类上使用@EnableGlobalMethodSecurity启用注解功能

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //...
}

给Url分配权限
PreAuthorize注解中写的是权限表达式,在这里的作用是用于showOrder权限的才能访问此接口

    @PreAuthorize("hasAnyAuthority('showOrder')")
    @GetMapping("/showOrder")
    public String showOrder() {
        return "查询订单";
    }

权限表达式可参考官方文档
https://docs.spring.io/spring-security/site/docs/5.3.4.RELEASE/reference/html5/#el-common-built-in