3.3 编写查询权限方法
在认证后进行授权需要根据用户id查询到用户的权限,写法如下:
1、编写用户、角色、权限实体类
// 不要命名为User,避免和Spring Security提供的User混淆 @Data public class Users { private Integer uid; private String username; private String password; private String phone; } // 角色 @Data public class Role { private String rid; private String roleName; private String roleDesc; } // 权限 @Data public class Permission { private String pid; private String permissionName; private String url; }
2、编写UsersMapper接口
// 根据用户名查询权限 List<Permission> findPermissionByUsername(String username);
3、编写UsersMapper.xml文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zj.mapper.UsersMapper"> <select id="findPermissionByUsername" parameterType="string" resultType="com.zj.pojo.Permission"> select DISTINCT permission.permissionName,permission.pid,permission.url from users LEFT JOIN users_role on users.uid = users_role.uid LEFT JOIN role on users_role.rid = role.rid LEFT JOIN role_permission on role.rid =role_permission.rid LEFT JOIN permission on role_permission.pid = permission.pid where users.username = #{username} </select> </mapper>
4、测试方法
package com.zj.security; import com.zj.mapper.UsersMapper; import com.zj.pojo.Permission; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import javax.annotation.Resource; import java.util.List; @SpringBootTest public class UsersMapperTest { @Resource private UsersMapper usersMapper; @Test public void testFindPermissionByUsername(){ List<Permission> permissions = usersMapper.findPermissionByUsername("张三"); for (Permission permission : permissions) { System.out.println(permission); } } }
Permission(pid=1, permissionName=查询报表, url=/reportform/find) Permission(pid=3, permissionName=查询税务, url=/tax/find) Permission(pid=2, permissionName=查询工资, url=/salary/find)
5、修改MyUserDetailsService中对用户权限的处理。
package com.zj.service; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.zj.mapper.UsersMapper; import com.zj.pojo.Permission; import com.zj.pojo.Users; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; @Service public class MyUserDetailsService implements UserDetailsService { @Resource private UsersMapper usersMapper; //自定义认正逻辑 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //1.构造查询条件 QueryWrapper<Users> query = new QueryWrapper<Users>(); query.eq("username", username); //2.查询用户 Users users = new Users(); Users users1 = users.selectOne(query); if (users1 == null) { return null; } //3.查询用户权限 List<Permission> permissions = usersMapper.findPermissionByUsername(users1.getUsername()); //4.将自定义权限集合转为security自带的权限集合 List<GrantedAuthority> authorities = new ArrayList<>(); for (Permission permission : permissions) { /*SimpleGrantedAuthority为GrantedAuthority接口的实现类*/ authorities.add(new SimpleGrantedAuthority(permission.getUrl())); } //5.将查询结果封装为UserDetails对象返回 UserDetails userDetails = User.withUsername(users1.getUsername()) .password(users1.getPassword()).authorities(authorities).build(); return userDetails; } }
3.4 配置类设置访问控制
在给用户授权后,我们就可以给系统中的资源设置访问控制,即拥有什么权限才能访问什么资源。
1、编写控制器类,添加控制器方法资源
@RestController public class MyController { @GetMapping("/reportform/find") public String findReportForm() { return "查询报表"; } @GetMapping("/salary/find") public String findSalary() { return "查询工资"; } @GetMapping("/staff/find") public String findStaff() { return "查询员工"; } }
2、修改Security配置类
//需要认证的资源 http.authorizeHttpRequests() .antMatchers("/login.html").permitAll()//登录页不需要认证 .antMatchers("/reportform/find").hasAnyAuthority("/reportform/find") .antMatchers("/salary/find").hasAnyAuthority("/salary/find") .antMatchers("/staff/find").hasAnyAuthority("/staff/find") .anyRequest().authenticated(); //其他请求都需要认证
3、登录并测试
搞定!!!
3.5 自定义访问控制逻辑
如果资源数量很多,一条条配置需要的权限效率较低。我们可以自定义访问控制逻辑,即访问资源时判断用户是否具有名为该资源URL的权限。
1、自定义访问逻辑
package com.zj.service; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.util.Collection; @Service public class MyAuthorizationService { // 自定义访问控制逻辑,返回值为是否可以访问资源 public boolean hasPermission(HttpServletRequest request, Authentication authentication){ //1.获取认证的用户 UserDetails userDetails = (UserDetails) authentication.getPrincipal(); //2.获取登录用户的权限 Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities(); //3.获取请求的url String url = request.getRequestURI(); //4.将url封装为权限对象 SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(url); //5.判断用户权限集合中是否包含请求的url权限对象 return authorities.contains(simpleGrantedAuthority); } }
2、配置类中使用自定义访问逻辑
// 权限拦截配置 http.authorizeRequests() .antMatchers("/login.html").permitAll() // 表示任何权限都可以访问 // 任何请求都使用自定义访问控制逻辑 .anyRequest().access("@myAuthorizationService.hasPermission(request,authentication)");
3.6 @Secured设置访问控制
除了配置类,在SpringSecurity中提供了一些访问控制的注解。这些注解默认都是不可用的,需要开启后使用。
该注解是基于角色的权限控制,要求UserDetails中的权限名必须以ROLE_
开头,当然数据库中的权限也是要以ROLE_开头。
1、在启动类开启注解使用
@SpringBootApplication @MapperScan("com.zj.mapper") @EnableGlobalMethodSecurity(securedEnabled=true) public class SecurityApplication { public static void main(String[] args) { SpringApplication.run(SecurityApplication.class, args); } }
2、在控制器方法上添加注解
@Secured("ROLE_reportform") @GetMapping("/reportform/find") public String findReportForm() { return "查询报表"; }
3.7 @PreAuthorize设置访问控制
该注解可以在方法执行前判断用户是否具有权限
1、在启动类开启注解使用
@SpringBootApplication @MapperScan("com.zj.mapper") @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityApplication { public static void main(String[] args) { SpringApplication.run(SecurityApplication.class, args); } }
2、在控制器方法上添加注解
@PreAuthorize("hasAnyAuthority('/reportform/find')") @GetMapping("/reportform/find") public String findReportForm() { return "查询报表"; }
3、取消配置类中对权限的访问配置
// // 权限拦截配置 // http.authorizeRequests() // .antMatchers("/login.html").permitAll() // 表示任何权限都可以访问 // // 任何请求都使用自定义访问控制逻辑 // .anyRequest().access("@myAuthorizationService.hasPermission(request,authentication)");
3.8 前端进行访问控制
SpringSecurity可以在一些视图技术中进行控制显示效果。例如Thymeleaf中,只有登录用户拥有某些权限才会展示一些菜单。
1、在pom中引入Spring Security和Thymeleaf的整合依赖(在创建项目选择起步依赖的时候默认就添加上了)
<!--Spring Security整合Thymeleaf--> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency>
2、在Thymeleaf中使用Security标签,控制前端的显示内容
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"> <head> <meta charset="UTF-8"> <title>主页面</title> </head> <body> <h1>主页面</h1> <ul> <li sec:authorize="hasAnyAuthority('/reportform/find')"><a href="/reportform/find">查询报表</a></li> <li sec:authorize="hasAnyAuthority('/salary/find')"><a href="/salary/find">查询工资</a></li> <li sec:authorize="hasAnyAuthority('/staff/find')"><a href="/staff/find">查询员工</a></li> </ul> <a href="/logout">退出登录</a> </body> </html>
这样面对不同权限的用户,前端可以显示不同的菜单。
3.9 403处理方案
使用Spring Security时经常会看见403(无权限),这样的页面很不友好,我们可以自定义403异常处理方案:
1、编写权限不足页面noPermission.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>权限不足</title> </head> <body> <h1>您的权限不足,请联系管理员!</h1> </body> </html>
2、编写权限不足处理类
public class MyAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { response.sendRedirect("/noPermission.html"); } }
3、在Spring Security配置文件中配置异常处理
//异常处理 http.exceptionHandling(). accessDeniedHandler(new MyAccessDeniedHandler());