目录
Spring Security介绍
Spring Security认证_项目搭建
Spring Security认证_内存认证
Spring Security认证_UserDetailsService
Spring Security认证_数据库认证
Spring Security认证_PasswordEncoder
Spring Security认证_自定义登录页面
Spring Security认证_会话管理
Spring Security认证_认证成功后的处理方式
Spring Security认证_认证失败后的处理方式
Spring Security认证_退出登录
Spring Security认证_退出成功处理器
Spring Security认证_Remember Me
Spring Security授权_RBAC
Spring Security授权_权限表设计
Spring Security授权_编写查询权限方法
Spring Security授权_配置类设置访问控制
Spring Security授权_自定义访问控制逻辑
Spring Security授权_注解设置访问控制
Spring Security授权_在前端进行访问控制
Spring Security授权_403处理方案
Spring Security是Spring项目组提供的安全服务框架,核心功能包 括认证和授权。它为系统提供了声明式安全访问控制功能,减少了 为系统安全而编写大量重复代码的工作。
认证
认证即系统判断用户的身份是否合法,合法可继续访问,不合法则 拒绝访问。常见的用户身份认证方式有:用户名密码登录、二维码 登录、手机短信登录、脸部识别认证、指纹认证等方式。 认证是为了保护系统的隐私数据与资源,用户的身份合法才能访问 该系统的资源。
授权
授权即认证通过后,根据用户的权限来控制用户访问资源的过程, 拥有资源的访问权限则正常访问,没有权限则拒绝访问。 比如在一 些视频网站中,普通用户登录后只有观看免费视频的权限,而VIP用 户登录后,网站会给该用户提供观看VIP视频的权限。 认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐 私数据进行划分,控制不同的用户能够访问不同的资源。
举个例子:认证是公司大门识别你作为员工能进入公司,而授权则 是由于你作为公司会计可以进入财务室,查看账目,处理财务数据。
接下来我们先来搭建一个Spring Security项目
1、准备一个名为 mysecurity 的Mysql数据库
2、创建SpringBoot项目,添加依赖
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-security mysql mysql-connector-java runtime com.baomidou mybatis-plus-boot-starter 3.5.0 org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test 3、为SpringBoot项目编写配置文件
server:port: 80 #日志格式 logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n' # 数据源 spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql:///mysecurity?serverTimezone=UTCusername: rootpassword01: root
4、在 template 文件夹编写项目主页面 main.html
主页面 主页面
5、编写访问页面控制器
@Controller public class PageController {@RequestMapping("/{page}")public String showPage(@PathVariable String page){return page;} }
启动项目,访问项目主页面http://localhost/main,项目会自动 跳转到一个登录页面。这代表Spring Security已经开启了认证 功能,不登录无法访问所有资源,该页面就是Spring Security 自带的登录页面。 我们使用 user 作为用户名,控制台中的字符串作为密码登录,登 录成功后跳转到项目主页面。 在后续的课程中,我们会讲解在真实开发中,如何对登录页 面、登录逻辑等进行自定义配置。
在实际开发中,用户数量不会只有一个,且密码是自己设置的。所 以我们需要自定义配置用户信息。首先我们在内存中创建两个用 户,Spring Security会将登录页传来的用户名密码和内存中用户名 密码做匹配认证。
// Security配置类
@Configuration
public class SecurityConfig {// 定义认证逻辑@Beanpublic UserDetailsService userDetailsService(){// 1.使用内存数据进行认证InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();// 2.创建两个用户UserDetails user1 = User.withUsername("baizhan").password("123").authorities("admin").build();UserDetails user2 = User.withUsername("sxt").password("456").authorities("admin").build();// 3.将这两个用户添加到内存中manager.createUser(user1);manager.createUser(user2);return manager;}//密码编码器,不解析密码@Beanpublic PasswordEncoder passwordEncoder(){return NoOpPasswordEncoder.getInstance();}
}
此时进行认证测试,我们可以将登录页传来的用户名密码和内存中 用户名密码做匹配认证。
在实际项目中,认证逻辑是需要自定义控制的。将 UserDetailsService 接口 的实现类放入Spring容器即可自定义认证逻辑。 InMemoryUserDetailsManager 就是 UserDetailsService 接口的一个实现类,它将登 录页传来的用户名密码和内存中用户名密码做匹配认证。当然我们 也可以自定义 UserDetailsService 接口的实现类。
public interface UserDetailsService {UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
UserDetailsService 的实现类必须重写 loadUserByUsername 方法,该方法定义了 具体的认证逻辑,参数 username 是前端传来的用户名,我们需要根据 传来的用户名查询到该用户(一般是从数据库查询),并将查询到 的用户封装成一个UserDetails对象,该对象是Spring Security提供 的用户对象,包含用户名、密码、权限。Spring Security会根据 UserDetails对象中的密码和客户端提供密码进行比较。相同则认证 通过,不相同则认证失败。
接下来我们连接数据库进行认证:
1、准备数据库数据
CREATE TABLE `users` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(255),`password` varchar(255) ,`phone` varchar(255) ,PRIMARY KEY (`id`) ); INSERT INTO `users` VALUES (1, 'bazhn','bazhn', '13812345678'); INSERT INTO `users` VALUES (2, 'xt','xt', '13812345678');
2、编写用户实体类
@Data public class Users {private Integer id;private String username;private String password;private String phone; }
3、编写dao接口
public interface UsersMapper extends BaseMapper
{} 4、在SpringBoot启动类中添加 @MapperScan 注解,扫描Mapper文件夹
@SpringBootApplication @MapperScan("com.itbaizhan.myspringsecurity.mapper") public class MysecurityApplication {public static void main(String[] args){SpringApplication.run(MysecurityApplication.class, args);} }
5、创建 UserDetailsService 的实现类,编写自定义认证逻辑
@Service public class MyUserDetailsService implements UserDetailsService {@Autowiredprivate UsersMapper usersMapper;// 自定义认证逻辑@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1.构造查询条件QueryWrapper
wrapper = new QueryWrapper ().eq("username",username);// 2.查询用户Users users = usersMapper.selectOne(wrapper);// 3.封装为UserDetails对象UserDetails userDetails = User.withUsername(users.getUsername()).password(users.getPassword()).authorities("admin").build();// 4.返回封装好的UserDetails对象return userDetails;} } 6、测试连接数据库认证
在实际开发中,为了数据安全性,在数据库中存放密码时不会存放 原密码,而是会存放加密后的密码。而用户传入的参数是明文密 码。此时必须使用密码解析器才能将加密密码与明文密码做比对。 Spring Security中的密码解析器是 PasswordEncoder 。
Spring Security要求容器中必须有 PasswordEncoder 实例,之前使用的
NoOpPasswordEncoder 是 PasswordEncoder 的实现类,意思是不解析密码,使用 明文密码。
Spring Security官方推荐的密码解析器是 BCryptPasswordEncoder 。接下来 我们学习 BCryptPasswordEncoder 的使用。
@SpringBootTest public class PasswordEncoderTest {@Testpublic void testBCryptPasswordEncoder(){//创建解析器PasswordEncoder encoder = new BCryptPasswordEncoder();//密码加密String password = encoder.encode("baizhan");System.out.println("加密后:"+password);//密码校验/*** 参数1:明文密码* 参数2:加密密码* 返回值:是否校验成功*/boolean result = encoder.matches("baizhan","$2a$10$/MImcrpDO21HAP2amayhme8j2SM0YM50/WO8YBH.NC1hEGGSU9ByO");System.out.println(result);} }
在开发中,我们将 BCryptPasswordEncoder 的实例放入Spring容器即可,并 且在用户注册完成后,将密码加密再保存到数据库。
//密码编码器 @Bean public PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder(); }
虽然Spring Security给我们提供了登录页面,但在实际项目中,更 多的是使用自己的登录页面。Spring Security也支持用户自定义登 录页面。用法如下:
1、编写登录页面
2、在Spring Security配置类自定义登录页面
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter{//Spring Security配置@Overrideprotected void configure(HttpSecurity http) throws Exception {// 自定义表单登录http.formLogin().loginPage("/login.html") //自定义登录页面.usernameParameter("username")// 表单中的用户名项.passwordParameter("password")// 表单中的密码项.loginProcessingUrl("/login")// 登录路径,表单向该路径提交,提交后自动执行UserDetailsService的方法.successForwardUrl("/main")//登录成功后跳转的路径.failureForwardUrl("/fail");//登录失败后跳转的路径// 需要认证的资源http.authorizeRequests().antMatchers("/login.html").permitAll() //登录页不需要认证.anyRequest().authenticated();//其余所有请求都需要认证//关闭csrf防护http.csrf().disable();}@Overridepublic void configure(WebSecurity web) throws Exception {// 静态资源放行web.ignoring().antMatchers("/css/**");} }
CSRF防护: CSRF:跨站请求伪造,通过伪造用户请求访问受信任的站点 从而进行非法请求访问,是一种攻击手段。 Spring Security 为了防止CSRF攻击,默认开启了CSRF防护,这限制了除了 GET请求以外的大多数方法。我们要想正常使用Spring Security需要突破CSRF防护。
解决方法一:关闭CSRF防护:
http.csrf().disable();
解决方法二:突破CSRF防护:
CSRF为了保证不是其他第三方网站访问,要求访问时携带参 数名为_csrf值为令牌,令牌在服务端产生,如果携带的令牌 和服务端的令牌匹配成功,则正常访问。
用户认证通过后,有时我们需要获取用户信息,比如在网站顶部显 示:欢迎您,XXX。Spring Security将用户信息保存在会话中,并 提供会话管理,我们可以从 SecurityContext 对象中获取用户信息, SecurityContext 对象与当前线程进行绑定。
获取用户信息的写法如下:
@RestController public class MyController {// 获取当前登录用户名@RequestMapping("/users/username")public String getUsername(){// 1.获取会话对象SecurityContext context = SecurityContextHolder.getContext();// 2.获取认证对象Authentication authentication = context.getAuthentication();// 3.获取登录用户信息UserDetails userDetails = (UserDetails) authentication.getPrincipal();return userDetails.getUsername();} }
登录成功后,如果除了跳转页面还需要执行一些自定义代码时, 如:统计访问量,推送消息等操作时,可以自定义登录成功处理器。
1、自定义登录成功处理器
public class MyLoginSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {// 拿到登录用户的信息UserDetails userDetails = (UserDetails)authentication.getPrincipal();System.out.println("用户名:"+userDetails.getUsername());System.out.println("一些操作...");// 重定向到主页response.sendRedirect("/main");} }
2、配置登录成功处理器
http.formLogin() // 使用表单登录.loginPage("/login.html") // 自定义登录页面.usernameParameter("username") // 表单中的用户名项.passwordParameter("password") // 表单中的密码项.loginProcessingUrl("/login") // 登录路径,表单向该路径提交,提交后自动执行 UserDetailsService的方法//.successForwardUrl("/main") //登录成功后跳转的路径.successHandler(new MyLoginSuccessHandler()) //登录成功处理器.failureForwardUrl("/fail"); //登录失败后跳转的路径
登录失败后,如果除了跳转页面还需要执行一些自定义代码时, 如:统计失败次数,记录日志等,可以自定义登录失败处理器。
1、自定义登录失败处理器
public class MyLoginFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {System.out.println("记录失败日志...");response.sendRedirect("/fail");} }
2、配置登录失败处理器
http.formLogin() // 使用表单登录.loginPage("/login.html") // 自定义登录页面.usernameParameter("username") // 表单中的用户名项.passwordParameter("password") // 表单中的密码项.loginProcessingUrl("/login") // 登录路径,表单向该路径提交,提交后自动执行UserDetailsService的方法//.successForwardUrl("/main") //登录成功后跳转的路径.successHandler(new MyLoginSuccessHandler()) //登录成功处理器//.failureForwardUrl("/fail") //登录失败后跳转的路径.failureHandler(new MyLoginFailureHandler()); //登录失败处理器// 需要认证的资源 http.authorizeRequests().antMatchers("/login.html").permitAll() //登录页不需要认证.antMatchers("/fail").permitAll() //失败页不需要认证.anyRequest().authenticated(); //其余所有请求都需要认证
在系统中一般都有退出登录的操作。退出登录后,Spring Security 进行了以下操作:
1、清除认证状态
2、销毁HttpSession对象
3、跳转到登录页面
在Spring Security中,退出登录的写法如下:
1、配置退出登录的路径和退出后跳转的路径
// 退出登录配置 http.logout().logoutUrl("/logout") // 退出登录路径.logoutSuccessUrl("/login.html") // 退出登录后跳转的路径.clearAuthentication(true) //清除认证状态,默认为true.invalidateHttpSession(true); // 销毁HttpSession对象,默认为true
2、在网页中添加退出登录超链接
主页面 主页面
退出登录
我们也可以自定义退出成功处理器,在退出后清理一些数据,写法 如下:
1、自定义退出成功处理器
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic 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 的Cookie保存到客户端。之后客户端访问项目时自动携 带令牌,不登录即可完成认证。
1、编写“记住我”配置类
@Configuration public class RememberMeConfig {@Autowiredprivate DataSource dataSource;// 令牌Repository@Beanpublic 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、在登录页面添加“记住我”复选框
授权即认证通过后,系统给用户赋予一定的权限,用户只能根据权 限访问系统中的某些资源。RBAC是业界普遍采用的授权方式,它有 两种解释:
Role-Based Access Control
基于角色的访问控制,即按角色进行授权。比如在企业管理系统 中,主体角色为总经理可以查询企业运营报表。逻辑为:
if(主体.hasRole("总经理角色")){查询运营报表
}
如果查询企业运营报表的角色变化为总经理和股东,此时就需要修 改判断逻辑代码:
if(主体.hasRole("总经理角色") || 主体.hasRole("股东角色")){查询运营报表
}
此时我们可以发现,当需要修改角色的权限时就需要修改授权的相 关代码,系统可扩展性差。
Resource-Based Access Control
基于资源的访问控制,即按资源(或权限)进行授权。比如在企业 管理系统中,用户必须 具有查询报表权限才可以查询企业运营报 表。逻辑为:
if(主体.hasPermission("查询报表权限")){查询运营报表
}
这样在系统设计时就已经定义好查询报表的权限标识,即使查询报 表所需要的角色变化为总经理和股东也不需要修改授权代码,系统 可扩展性强。该授权方式更加常用。
用户和权限的关系为多对多,即用户拥有多个权限,权限也属于多 个用户,所以建表方式如下:
这种方式需要指定用户有哪些权限,如:张三有查询工资的权限, 即在用户权限中间表中添加一条数据,分别记录张三和查询工资权 限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 = 3CHARACTER 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);
在认证后进行授权需要根据用户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
findPermissionByUsername(String username); 3、在resources目录中编写UsersMapper的映射文件
4、测试方法
@SpringBootTest public class UsersMapperTest {@Autowiredprivate UsersMapper usersMapper;@Testpublic void testFindPermissionByUsername(){List
baizhan = usersMapper.findPermissionByUsername("baizhan");baizhan.forEach(System.out::println);} } 5、修改认证逻辑,认证成功后给用户授权
// 自定义认证逻辑 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1.构造查询条件QueryWrapper
wrapper = new QueryWrapper ().eq("username",username);// 2.查询用户Users users = userMapper.selectOne(wrapper);if (users == null){return null;}// 3.查询用户权限List permissions = userMapper.findPermissionByUsername(username);// 4.将自定义权限集合转为Security的权限类型集合List 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; }
在给用户授权后,我们就可以给系统中的资源设置访问控制,即拥 有什么权限才能访问什么资源。
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异常
如果资源数量很多,一条条配置需要的权限效率较低。我们可以自 定义访问控制逻辑,即访问资源时判断用户是否具有名为该资源 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)" );
除了配置类,在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 "查询报表"; }
SpringSecurity可以在一些视图技术中进行控制显示效果。例如 Thymeleaf中,只有登录用户拥有某些权限才会展示一些菜单。
1、在pom中引入Spring Security和Thymeleaf的整合依赖
org.thymeleaf.extras thymeleaf-extras-springsecurity5 2、在Thymeleaf中使用Security标签,控制前端的显示内容
主页面 主页面
退出登录3、这样面对不同权限的用户,前端可以显示不同的菜单
使用Spring Security时经常会看见403(无权限),这样的页面很 不友好,我们可以自定义403异常处理方案:
1、编写权限不足页面 noPermission.html
权限不足 您的权限不足,请联系管理员!
2、编写权限不足处理类
public class MyAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {response.sendRedirect("/noPermission.html");} }
3、在Spring Security配置文件中配置异常处理
//异常处理 http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());