使用RBAC权限模型结合Spring Security进行权限控制
数据库设计
使用RBAC模型至少需要创建五张表分别是
用户表 角色表 权限表 用户角色表 角色权限表
这里学习阶段对表的设计比较简单
- 用户表主要是用户的账号密码等信息;
- 角色表是角色名等信息;
- 权限表是权限的url、名称、别名等信息;
- 用户角色表是用户表与角色形成的多对多关系,一个用户可以对应多个角色,一个角色也可以是多名用户;
- 角色权限表则是角色表与权限表对应的一对一关系,即一个角色对应一个权限
五张表环环相扣
进行相关的信息查询
通过数据库对用户进行相应的权限查询从而实现权限控制
使用Mapper层进行SQL语句查询操作,首先需要创建实体类,但是只需要创建用户类和权限类就行了
用户类 四个boolean类型和List
@Data@TableName("sys_user")public class User implements UserDetails {private Integer id;private String username;private String realname;private String password;/*** 创建时间*/@JSONField(format = "yyyy-MM-dd hh:mm:ss")private Date createDate;/*** 最后登录时间*/@JSONField(format = "yyyy-MM-dd hh:mm:ss")private Date lastLoginTime;@TableField(exist = false)private boolean enabled;@TableField(exist = false)private boolean accountNonExpired;@TableField(exist = false)private boolean accountNonLocked;@TableField(exist = false)private boolean credentialsNonExpired;/*** 用户所有权限*/@TableField(exist = false)private List<GrantedAuthority> authorities;}
权限类
@Datapublic class Permission {private Integer id;// 权限名称private String permName;// 权限标识private String permTag;// 请求urlprivate String url;}
有了这两个实体类就可以实现相应的用户权限查询的操作了
Mapper层的代码,根据用户名查询用户的权限的sql语句有些复杂
@Mapperpublic interface UserMapper {/*** 根据用户名查询用户的信息* @param username 用户名* @return List<Permission> 权限对象的list*/@Select(" select * from sys_user where username = #{username}")User findByUsername(@Param("username") String username);/*** 根据用户名查询用户的权限* @param username 用户名* @return List<Permission> 权限list*/@Select(" select permission.* from sys_user user"+ " inner join sys_user_role user_role"+ " on user.id = user_role.user_id inner join "+ "sys_role_permission role_permission on user_role.role_id = role_permission.role_id "+ " inner join sys_permission permission on role_permission.perm_id = permission.id where user.username = #{username};")List<Permission> findPermissionByUsername(@Param("username") String username);/*** 查询所有权限信息* @return List<Permission> 权限list*/@Select(" select * from sys_permission ")List<Permission> findAllPermission();}
身份认证及配置类UserDetailsService
Spring Security进行用户身份认证需要实现UserDetailsService接口,然后要在配置类中传入
@Servicepublic class MyUserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 根据用户名称查询数据库信息User user = userMapper.findByUsername(username);// 如果数据库中存在该用户名则进行以下操作if (user != null) {// 底层会根据数据库查询用户信息,判断密码是否正确List<Permission> list = userMapper.findPermissionByUsername(username);if (list != null && list.size() > 0) {//定义用户权限List<GrantedAuthority> authorities = new ArrayList<>();for (Permission permission : list) {authorities.add(new SimpleGrantedAuthority(permission.getPermTag()));}// 设置用户权限user.setAuthorities(authorities);}return user;} else {return null;}}}
SecurityConfig配置类的编写,需要注入userDetailsService和userMapper对象
@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate MyAuthenticationHandler myAuthenticationHandler;@Autowiredprivate MyUserDetailsServiceImpl userDetailsService;@Autowiredprivate UserMapper userMapper;@Override//配置用户信息和权限protected void configure(AuthenticationManagerBuilder auth) throws Exception {//使用数据库动态添加auth.userDetailsService(userDetailsService).passwordEncoder(new PasswordEncoder() {@Override//对表单密码进行加密public String encode(CharSequence charSequence) {return MD5Util.encode((String) charSequence);}@Override//加密的密码和数据库中进行比对//s为数据库加密字段public boolean matches(CharSequence charSequence, String s) {return MD5Util.encode((String) charSequence).equals(s);}});}@Override//配置拦截请求资源protected void configure(HttpSecurity http) throws Exception {//如何权限控制 给每一条路径分配一个权限名称 账号只要管理该名称 就可以访问权限http.headers().frameOptions().disable();ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests = http.authorizeRequests();//读取数据库权限列表List<Permission> list = userMapper.findAllPermission();for (Permission permission : list) {//给URL分配权限authorizeRequests.antMatchers(permission.getUrl()).hasAnyAuthority(permission.getPermTag());}//设置登录页面 配置的权限管理范围是url含有order的authorizeRequests.antMatchers("/login").permitAll().antMatchers("**order**").fullyAuthenticated().and().formLogin().loginPage("/login").successHandler(myAuthenticationHandler).failureHandler(myAuthenticationHandler).and().csrf().disable();}}
这里还注入了一个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
