Spring Security认证_退出成功处理器
我们也可以自定义退出成功处理器,在退出后清理一些数据,写法 如下:
1、自定义退出成功处理器
public class MyLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("清除一些数据..."); response.sendRedirect("/login.html"); } }
2、配置退出成功处理器
// 退出登录配置 http.logout() .logoutUrl("/logout") // 退出登录路径 // .logoutSuccessUrl("/login.html") // 退出登录后跳转的路径 .clearAuthentication(true) //清除认证状态,默认为true .invalidateHttpSession(true) // 销毁HttpSession对象,默认为 true .logoutSuccessHandler(new MyLogoutSuccessHandler()); //自定义退出成功处 理器
Spring Security认证_Remember Me
Spring Security中Remember Me为“记住我”功能,即下次访问系统 时无需重新登录。当使用“记住我”功能登录后,Spring Security会 生成一个令牌,令牌一方面保存到数据库中,另一方面生成一个叫 remember-me 的Cookie保存到客户端。之后客户端访问项目时自动携 带令牌,不登录即可完成认证。
1、编写“记住我”配置类
@Configuration public class RememberMeConfig { @Autowired private DataSource dataSource; // 令牌Repository @Bean public PersistentTokenRepository getPersistentTokenRepository() { // 为Spring Security自带的令牌控制器设置数据源 JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl = new JdbcTokenRepositoryImpl(); jdbcTokenRepositoryImpl.setDataSource(dataSource); //自动建表,第一次启动时需要,第二次启动时注释掉 // jdbcTokenRepositoryImpl.setCreateTableOnStartup(true); return jdbcTokenRepositoryImpl; } }
2、修改Security配置类
// 记住我配置 http.rememberMe() .userDetailsService(userDetailsService)//登录逻辑交给哪个对象 .tokenRepository(repository) //持久层对象 .tokenValiditySeconds(30); //保存时间,单位:秒
3、在登录页面添加“记住我”复选框
<form class="form" action="/login" method="post"> <input type="text" placeholder="用户名" name="username"> <input type="password" placeholder="密码" name="password"> <input type="checkbox" name="remember-me" value="true"/>记住我</br> <button type="submit">登录</button> </form>
Spring Security授权_RBAC
授权即认证通过后,系统给用户赋予一定的权限,用户只能根据权 限访问系统中的某些资源。RBAC是业界普遍采用的授权方式,它有 两种解释:
Role-Based Access Control
基于角色的访问控制,即按角色进行授权。比如在企业管理系统 中,主体角色为总经理可以查询企业运营报表。逻辑为:
if(主体.hasRole("总经理角色")){ 查询运营报表 }
如果查询企业运营报表的角色变化为总经理和股东,此时就需要修 改判断逻辑代码:
if(主体.hasRole("总经理角色") || 主体.hasRole("股东角色")){ 查询运营报表 }
此时我们可以发现,当需要修改角色的权限时就需要修改授权的相 关代码,系统可扩展性差。
Resource-Based Access Control
基于资源的访问控制,即按资源(或权限)进行授权。比如在企业 管理系统中,用户必须 具有查询报表权限才可以查询企业运营报 表。逻辑为:
if(主体.hasPermission("查询报表权限")){ 查询运营报表 }
这样在系统设计时就已经定义好查询报表的权限标识,即使查询报 表所需要的角色变化为总经理和股东也不需要修改授权代码,系统 可扩展性强。该授权方式更加常用。
Spring Security授权_权限表设计
用户和权限的关系为多对多,即用户拥有多个权限,权限也属于多 个用户,所以建表方式如下:
这种方式需要指定用户有哪些权限,如:张三有查询工资的权限, 即在用户权限中间表中添加一条数据,分别记录张三和查询工资权 限ID。但在系统中权限数量可能非常庞大,如果一条一条添加维护 数据较为繁琐。所以我们通常的做法是再加一张角色表:
用户角色,角色权限都是多对多关系,即一个用户拥有多个角色, 一个角色属于多个用户;一个角色拥有多个权限,一个权限属于多 个角色。这种方式需要指定用户有哪些角色,而角色又有哪些权限。
如:张三拥有总经理的角色,而总经理拥有查询工资、查询报表的 权限,这样张三就拥有了查询工资、查询报表的权限。这样管理用 户时只需管理少量角色,而管理角色时也只需要管理少量权限即可。接下来我们创建五张表:
CREATE TABLE `users` (`uid` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`uid`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; INSERT INTO `users` VALUES (1, 'baizhan','$2a$10$Eqv9PRMl6bPt5BiwgPr2eucgyl.E.xLENt4b vfDvv7DyS5AVPT.U6', '13812345678'); CREATE TABLE `role` ( `rid` int(11) NOT NULL AUTO_INCREMENT, `roleName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `roleDesc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`rid`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; INSERT INTO `role` VALUES (1,'总经理','管理整个公司'); INSERT INTO `role` VALUES (2,'股东','参与公司决策'); INSERT INTO `role` VALUES (3,'财务','管理公司资产'); CREATE TABLE `permission` ( `pid` int(11) NOT NULL AUTO_INCREMENT, `permissionName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`pid`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; INSERT INTO `permission` VALUES (1,'查询报表', '/reportform/find'); INSERT INTO `permission` VALUES (2,'查询工资', '/salary/find'); INSERT INTO `permission` VALUES (3,'查询税务', '/tax/find'); CREATE TABLE `users_role` ( `uid` int(255) NOT NULL, `rid` int(11) NOT NULL, PRIMARY KEY (`uid`, `rid`) USING BTREE, INDEX `rid`(`rid`) USING BTREE, CONSTRAINT `users_role_ibfk_1` FOREIGN KEY(`uid`) REFERENCES `users` (`uid`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT `users_role_ibfk_2` FOREIGN KEY(`rid`) REFERENCES `role` (`rid`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; INSERT INTO `users_role` VALUES (1, 2); INSERT INTO `users_role` VALUES (1, 3); CREATE TABLE `role_permission` ( `rid` int(11) NOT NULL, `pid` int(11) NOT NULL, PRIMARY KEY (`rid`, `pid`) USING BTREE, INDEX `pid`(`pid`) USING BTREE, CONSTRAINT `role_permission_ibfk_1` FOREIGN KEY (`rid`) REFERENCES `role` (`rid`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT `role_permission_ibfk_2` FOREIGN KEY (`pid`) REFERENCES `permission` (`pid`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; INSERT INTO `role_permission` VALUES (1, 1); INSERT INTO `role_permission` VALUES (2, 1); INSERT INTO `role_permission` VALUES (1, 2); INSERT INTO `role_permission` VALUES (3, 2); INSERT INTO `role_permission` VALUES (1, 3); INSERT INTO `role_permission` VALUES (2, 3);
Spring Security授权_编写查询权限方法
在认证后进行授权需要根据用户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、编写UserMapper接口
// 根据用户名查询权限 List<Permission> findPermissionByUsername(String username);
3、在resources目录中编写UsersMapper的映射文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itbaizhan.myspringsecurity.mapper.UsersMapper"> <select id="findPermissionByUsername" parameterType="string" resultType="com.itbaizhan.myspringsecurity.domain.Permission"> SELECT DISTINCT permission.pid,permission.permissionName,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 username = #{username} </select> </mapper>
4、测试方法
@SpringBootTest public class UsersMapperTest { @Autowired private UsersMapper usersMapper; @Test public void testFindPermissionByUsername(){ List<Permission> baizhan = usersMapper.findPermissionByUsername("baizhan"); baizhan.forEach(System.out::println); } }
5、修改认证逻辑,认证成功后给用户授权
// 自定义认证逻辑 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 1.构造查询条件 QueryWrapper<Users> wrapper = new QueryWrapper<Users>().eq("username",username); // 2.查询用户 Users users = userMapper.selectOne(wrapper); if (users == null){ return null; } // 3.查询用户权限 List<Permission> permissions = userMapper.findPermissionByUsername(username); // 4.将自定义权限集合转为Security的权限类型集合 List<GrantedAuthority> grantedAuthorities = new ArrayList<>(); for (Permission permission : permissions) { grantedAuthorities.add(new SimpleGrantedAuthority(permission.getUrl())); } // 5.封装为UserDetails对象 UserDetails userDetails = User.withUsername(users.getUsername()) .password(users.getPassword()) .authorities(grantedAuthorities) .build(); // 6.返回封装好的UserDetails对象 return userDetails; }
Spring Security授权_配置类设置访问控制
在给用户授权后,我们就可以给系统中的资源设置访问控制,即拥 有什么权限才能访问什么资源。
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.authorizeRequests() .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、测试访问资源,由于没有权限被拦截访问时会抛出403异常
Spring Security授权_自定义访问控制逻辑
如果资源数量很多,一条条配置需要的权限效率较低。我们可以自 定义访问控制逻辑,即访问资源时判断用户是否具有名为该资源 URL的权限。
1、自定义访问控制逻辑
@Service public class MyAuthorizationService { // 自定义访问控制逻辑,返回值为是否可以访问资源 public boolean hasPermission(HttpServletRequest request,Authentication authentication){ // 获取会话中的登录用户 Object principal = authentication.getPrincipal(); if (principal instanceof UserDetails){ // 获取登录用户的权限 Collection<? extends GrantedAuthority> authorities = ((UserDetails)principal).getAuthorities(); // 获取请求的URL路径 String uri = request.getRequestURI(); // 将URL路径封装为权限对象 SimpleGrantedAuthority authority = new SimpleGrantedAuthority(uri); // 判断用户的权限集合是否包含请求的URL权限对象 return authorities.contains(authority); } return false; } }
2、在配置文件中使用自定义访问控制逻辑
// 权限拦截配置 http.authorizeRequests() .antMatchers("/login.html").permitAll() //表示任何权限都可以访问 // 任何请求都使用自定义访问控制逻辑 .anyRequest().access("@myAuthorizationService.hasPermission(request,authentication)" );
Spring Security授权_注解设置访问控制
除了配置类,在SpringSecurity中提供了一些访问控制的注解。这 些注解默认都是不可用的,需要开启后使用。
@Secured
该注解是基于角色的权限控制,要求UserDetails中的权限名必须以 ROLE_ 开头。
1、在配置类开启注解使用
@SpringBootApplication @MapperScan("com.itbaizhan.mysecurity.mapper") @EnableGlobalMethodSecurity(securedEnabled=true) public class MysecurityApplication { public static void main(String[] args) { SpringApplication.run(MysecurityApplication.class, args); } }
2、在控制器方法上添加注解
@Secured("ROLE_reportform") @GetMapping("/reportform/find") public String findReportForm() { return "查询报表"; }
@PreAuthorize
该注解可以在方法执行前判断用户是否具有权限
1、在配置类开启注解使用
@SpringBootApplication @MapperScan("com.itbaizhan.mysecurity.mapper") @EnableGlobalMethodSecurity(prePostEnabled = true) public class MysecurityApplication { public static void main(String[] args) { SpringApplication.run(MysecurityApplication.class, args); } }
2、在控制器方法上添加注解
@PreAuthorize("hasAnyAuthority('/reportform/find')") @GetMapping("/reportform/find") public String findReportForm() { return "查询报表"; }
Spring Security授权_在前端进行访问控制
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、这样面对不同权限的用户,前端可以显示不同的菜单
Spring Security授权_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());