Spring Security-全面详解(学习总结---从入门到深化)

时间:2024-10-14 19:08:46

目录

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 Security是Spring项目组提供的安全服务框架,核心功能包 括认证和授权。它为系统提供了声明式安全访问控制功能,减少了 为系统安全而编写大量重复代码的工作。

认证 

认证即系统判断用户的身份是否合法,合法可继续访问,不合法则 拒绝访问。常见的用户身份认证方式有:用户名密码登录、二维码 登录、手机短信登录、脸部识别认证、指纹认证等方式。 认证是为了保护系统的隐私数据与资源,用户的身份合法才能访问 该系统的资源。

授权

授权即认证通过后,根据用户的权限来控制用户访问资源的过程, 拥有资源的访问权限则正常访问,没有权限则拒绝访问。 比如在一 些视频网站中,普通用户登录后只有观看免费视频的权限,而VIP用 户登录后,网站会给该用户提供观看VIP视频的权限。 认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐 私数据进行划分,控制不同的用户能够访问不同的资源。

举个例子:认证是公司大门识别你作为员工能进入公司,而授权则 是由于你作为公司会计可以进入财务室,查看账目,处理财务数据。

Spring Security认证_项目搭建 

接下来我们先来搭建一个Spring Security项目

1、准备一个名为 mysecurity 的Mysql数据库

2、创建SpringBoot项目,添加依赖

  1. <!-- SpringMVC -->
  2. <dependency>
  3. <groupId></groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <!--Thymeleaf-->
  7. <dependency>
  8. <groupId></groupId>
  9. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  10. </dependency>
  11. <!--Spring Security-->
  12. <dependency>
  13. <groupId></groupId>
  14. <artifactId>spring-boot-starter-security</artifactId>
  15. </dependency>
  16. <!-- Mysql驱动 -->
  17. <dependency>
  18. <groupId>mysql</groupId>
  19. <artifactId>mysql-connector-java</artifactId>
  20. <scope>runtime</scope>
  21. </dependency>
  22. <!-- MyBatisPlus -->
  23. <dependency>
  24. <groupId></groupId>
  25. <artifactId>mybatis-plus-boot-starter</artifactId>
  26. <version>3.5.0</version>
  27. </dependency>
  28. <!-- lombok -->
  29. <dependency>
  30. <groupId></groupId>
  31. <artifactId>lombok</artifactId>
  32. <optional>true</optional>
  33. </dependency>
  34. <!-- junit -->
  35. <dependency>
  36. <groupId></groupId>
  37. <artifactId>spring-boot-starter-test</artifactId>
  38. <scope>test</scope>
  39. </dependency>

3、为SpringBoot项目编写配置文件

  1. server:
  2. port: 80
  3. #日志格式
  4. logging:
  5. pattern:
  6. console: '%d{HH:mm:} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
  7. # 数据源
  8. spring:
  9. datasource:
  10. driver-class-name:
  11. url: jdbc:mysql:///mysecurity?serverTimezone=UTC
  12. username: root
  13. password01: root

4、在 template 文件夹编写项目主页面

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>主页面</title>
  6. </head>
  7. <body>
  8. <h1>主页面</h1>
  9. </body>
  10. </html>

5、编写访问页面控制器

  1. @Controller
  2. public class PageController {
  3. @RequestMapping("/{page}")
  4. public String showPage(@PathVariable String page){
  5. return page;
  6. }
  7. }

启动项目,访问项目主页面http://localhost/main,项目会自动 跳转到一个登录页面。这代表Spring Security已经开启了认证 功能,不登录无法访问所有资源,该页面就是Spring Security 自带的登录页面。 我们使用 user 作为用户名,控制台中的字符串作为密码登录,登 录成功后跳转到项目主页面。 在后续的课程中,我们会讲解在真实开发中,如何对登录页 面、登录逻辑等进行自定义配置。

Spring Security认证_内存认证 

 在实际开发中,用户数量不会只有一个,且密码是自己设置的。所 以我们需要自定义配置用户信息。首先我们在内存中创建两个用 户,Spring Security会将登录页传来的用户名密码和内存中用户名 密码做匹配认证。

  1. // Security配置类
  2. @Configuration
  3. public class SecurityConfig {
  4. // 定义认证逻辑
  5. @Bean
  6. public UserDetailsService userDetailsService(){
  7. // 1.使用内存数据进行认证
  8. InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
  9. // 2.创建两个用户
  10. UserDetails user1 = ("baizhan").password("123").authorities("admin").build();
  11. UserDetails user2 = ("sxt").password("456").authorities("admin").build();
  12. // 3.将这两个用户添加到内存中
  13. (user1);
  14. (user2);
  15. return manager;
  16. }
  17. //密码编码器,不解析密码
  18. @Bean
  19. public PasswordEncoder passwordEncoder()
  20. {
  21. return ();
  22. }
  23. }

此时进行认证测试,我们可以将登录页传来的用户名密码和内存中 用户名密码做匹配认证。

Spring Security认证_UserDetailsService 

 在实际项目中,认证逻辑是需要自定义控制的。将 UserDetailsService 接口 的实现类放入Spring容器即可自定义认证逻辑。 InMemoryUserDetailsManager 就是 UserDetailsService 接口的一个实现类,它将登 录页传来的用户名密码和内存中用户名密码做匹配认证。当然我们 也可以自定义 UserDetailsService 接口的实现类。

  1. public interface UserDetailsService {
  2. UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
  3. }

UserDetailsService 的实现类必须重写 loadUserByUsername 方法,该方法定义了 具体的认证逻辑,参数 username 是前端传来的用户名,我们需要根据 传来的用户名查询到该用户(一般是从数据库查询),并将查询到 的用户封装成一个UserDetails对象,该对象是Spring Security提供 的用户对象,包含用户名、密码、权限。Spring Security会根据 UserDetails对象中的密码和客户端提供密码进行比较。相同则认证 通过,不相同则认证失败。

 

 Spring Security认证_数据库认证

接下来我们连接数据库进行认证:

 1、准备数据库数据

  1. CREATE TABLE `users` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT,
  3. `username` varchar(255),
  4. `password` varchar(255) ,
  5. `phone` varchar(255) ,
  6. PRIMARY KEY (`id`)
  7. );
  8. INSERT INTO `users` VALUES (1, 'bazhn','bazhn', '13812345678');
  9. INSERT INTO `users` VALUES (2, 'xt','xt', '13812345678');

2、编写用户实体类

  1. @Data
  2. public class Users {
  3. private Integer id;
  4. private String username;
  5. private String password;
  6. private String phone;
  7. }

3、编写dao接口

public interface UsersMapper extends BaseMapper<Users> {}

4、在SpringBoot启动类中添加 @MapperScan 注解,扫描Mapper文件夹

  1. @SpringBootApplication
  2. @MapperScan("")
  3. public class MysecurityApplication {
  4. public static void main(String[] args)
  5. {
  6. SpringApplication.run(, args);
  7. }
  8. }

5、创建 UserDetailsService 的实现类,编写自定义认证逻辑

  1. @Service
  2. public class MyUserDetailsService
  3. implements UserDetailsService {
  4. @Autowired
  5. private UsersMapper usersMapper;
  6. // 自定义认证逻辑
  7. @Override
  8. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  9. // 1.构造查询条件
  10. QueryWrapper<Users> wrapper = new QueryWrapper<Users>().eq("username",username);
  11. // 2.查询用户
  12. Users users = (wrapper);
  13. // 3.封装为UserDetails对象
  14. UserDetails userDetails = User
  15. .withUsername(())
  16. .password(())
  17. .authorities("admin")
  18. .build();
  19. // 4.返回封装好的UserDetails对象
  20. return userDetails;
  21. }
  22. }

6、测试连接数据库认证

 Spring Security认证_PasswordEncoder

在实际开发中,为了数据安全性,在数据库中存放密码时不会存放 原密码,而是会存放加密后的密码。而用户传入的参数是明文密 码。此时必须使用密码解析器才能将加密密码与明文密码做比对。 Spring Security中的密码解析器是 PasswordEncoder 。

Spring Security要求容器中必须有 PasswordEncoder 实例,之前使用的

NoOpPasswordEncoder 是 PasswordEncoder 的实现类,意思是不解析密码,使用 明文密码。

Spring Security官方推荐的密码解析器是 BCryptPasswordEncoder 。接下来 我们学习 BCryptPasswordEncoder 的使用。

  1. @SpringBootTest
  2. public class PasswordEncoderTest {
  3. @Test
  4. public void testBCryptPasswordEncoder(){
  5. //创建解析器
  6. PasswordEncoder encoder = new BCryptPasswordEncoder();
  7. //密码加密
  8. String password = ("baizhan");
  9. ("加密后:"+password);
  10. //密码校验
  11. /**
  12. * 参数1:明文密码
  13. * 参数2:加密密码
  14. * 返回值:是否校验成功
  15. */
  16. boolean result = ("baizhan","$2a$10$/MImcrpDO21HAP2amayhme8j2SM0YM50/WO8YBH.NC1hEGGSU9ByO");
  17. (result);
  18. }
  19. }

在开发中,我们将 BCryptPasswordEncoder 的实例放入Spring容器即可,并 且在用户注册完成后,将密码加密再保存到数据库。

  1. //密码编码器
  2. @Bean
  3. public PasswordEncoder passwordEncoder() {
  4. return new BCryptPasswordEncoder();
  5. }

Spring Security认证_自定义登录页面 

 

 虽然Spring Security给我们提供了登录页面,但在实际项目中,更 多的是使用自己的登录页面。Spring Security也支持用户自定义登 录页面。用法如下:

1、编写登录页面

2、在Spring Security配置类自定义登录页面

  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter{
  3. //Spring Security配置
  4. @Override
  5. protected void configure(HttpSecurity http) throws Exception {
  6. // 自定义表单登录
  7. ()
  8. .loginPage("/") //自定义登录页面
  9. .usernameParameter("username")// 表单中的用户名项
  10. .passwordParameter("password")// 表单中的密码项
  11. .loginProcessingUrl("/login")
  12. // 登录路径,表单向该路径提交,提交后自动执行UserDetailsService的方法
  13. .successForwardUrl("/main")//登录成功后跳转的路径
  14. .failureForwardUrl("/fail");//登录失败后跳转的路径
  15. // 需要认证的资源
  16. ().antMatchers("/").permitAll()
  17. //登录页不需要认证
  18. .anyRequest().authenticated();
  19. //其余所有请求都需要认证
  20. //关闭csrf防护
  21. ().disable();
  22. }
  23. @Override
  24. public void configure(WebSecurity web) throws Exception {
  25. // 静态资源放行
  26. ().antMatchers("/css/**");
  27. }
  28. }

CSRF防护: CSRF:跨站请求伪造,通过伪造用户请求访问受信任的站点 从而进行非法请求访问,是一种攻击手段。 Spring Security 为了防止CSRF攻击,默认开启了CSRF防护,这限制了除了 GET请求以外的大多数方法。我们要想正常使用Spring Security需要突破CSRF防护。

解决方法一:关闭CSRF防护:

http.csrf().disable();

解决方法二:突破CSRF防护:

CSRF为了保证不是其他第三方网站访问,要求访问时携带参 数名为_csrf值为令牌,令牌在服务端产生,如果携带的令牌 和服务端的令牌匹配成功,则正常访问。

  1. <form class="form" action="/login" method="post">
  2. <!-- 在表单中添加令牌隐藏域 -->
  3. <input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>
  4. <input type="text" placeholder="用户名" name="username">
  5. <input type="password" placeholder="密码" name="password">
  6. <button type="submit">登录</button>
  7. </form>

Spring Security认证_会话管理 

 用户认证通过后,有时我们需要获取用户信息,比如在网站顶部显 示:欢迎您,XXX。Spring Security将用户信息保存在会话中,并 提供会话管理,我们可以从 SecurityContext 对象中获取用户信息, SecurityContext 对象与当前线程进行绑定。

获取用户信息的写法如下:

  1. @RestController
  2. public class MyController {
  3. // 获取当前登录用户名
  4. @RequestMapping("/users/username")
  5. public String getUsername(){
  6. // 1.获取会话对象
  7. SecurityContext context = ();
  8. // 2.获取认证对象
  9. Authentication authentication = ();
  10. // 3.获取登录用户信息
  11. UserDetails userDetails = (UserDetails) ();
  12. return ();
  13. }
  14. }

 Spring Security认证_认证成功后的处理方式

登录成功后,如果除了跳转页面还需要执行一些自定义代码时, 如:统计访问量,推送消息等操作时,可以自定义登录成功处理器。

1、自定义登录成功处理器

  1. public class MyLoginSuccessHandler implements AuthenticationSuccessHandler {
  2. @Override
  3. public void onAuthenticationSuccess(HttpServletRequest
  4. request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
  5. // 拿到登录用户的信息
  6. UserDetails userDetails = (UserDetails)();
  7. ("用户名:"+());
  8. ("一些操作...");
  9. // 重定向到主页
  10. ("/main");
  11. }
  12. }

2、配置登录成功处理器

  1. () // 使用表单登录
  2. .loginPage("/") // 自定义登录页面
  3. .usernameParameter("username") // 表单中的用户名项
  4. .passwordParameter("password") // 表单中的密码项
  5. .loginProcessingUrl("/login") // 登录路径,表单向该路径提交,提交后自动执行 UserDetailsService的方法
  6. //.successForwardUrl("/main") //登录成功后跳转的路径
  7. .successHandler(new MyLoginSuccessHandler()) //登录成功处理器
  8. .failureForwardUrl("/fail"); //登录失败后跳转的路径

 Spring Security认证_认证失败后的处理方式

登录失败后,如果除了跳转页面还需要执行一些自定义代码时, 如:统计失败次数,记录日志等,可以自定义登录失败处理器。

1、自定义登录失败处理器

  1. public class MyLoginFailureHandler implements AuthenticationFailureHandler {
  2. @Override
  3. public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
  4. AuthenticationException exception) throws IOException, ServletException {
  5. ("记录失败日志...");
  6. ("/fail");
  7. }
  8. }

2、配置登录失败处理器

  1. () // 使用表单登录
  2. .loginPage("/") // 自定义登录页面
  3. .usernameParameter("username") // 表单中的用户名项
  4. .passwordParameter("password") // 表单中的密码项
  5. .loginProcessingUrl("/login") // 登录路径,表单向该路径提交,提交后自动执行UserDetailsService的方法
  6. //.successForwardUrl("/main") //登录成功后跳转的路径
  7. .successHandler(new MyLoginSuccessHandler()) //登录成功处理器
  8. //.failureForwardUrl("/fail") //登录失败后跳转的路径
  9. .failureHandler(new MyLoginFailureHandler()); //登录失败处理器
  10. // 需要认证的资源
  11. ()
  12. .antMatchers("/").permitAll() //登录页不需要认证
  13. .antMatchers("/fail").permitAll() //失败页不需要认证
  14. .anyRequest().authenticated(); //其余所有请求都需要认证

Spring Security认证_退出登录 

 在系统中一般都有退出登录的操作。退出登录后,Spring Security 进行了以下操作:

1、清除认证状态

2、销毁HttpSession对象 

3、跳转到登录页面

在Spring Security中,退出登录的写法如下:

 1、配置退出登录的路径和退出后跳转的路径

  1. // 退出登录配置
  2. ()
  3. .logoutUrl("/logout") // 退出登录路径
  4. .logoutSuccessUrl("/") // 退出登录后跳转的路径
  5. .clearAuthentication(true) //清除认证状态,默认为true
  6. .invalidateHttpSession(true); // 销毁HttpSession对象,默认为true

2、在网页中添加退出登录超链接

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>主页面</title>
  6. </head>
  7. <body>
  8. <h1>主页面</h1>
  9. <a href="/logout">退出登录</a>
  10. </body>
  11. </html>

Spring Security认证_退出成功处理器 

我们也可以自定义退出成功处理器,在退出后清理一些数据,写法 如下:

1、自定义退出成功处理器

  1. public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
  2. @Override
  3. public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
  4. Authentication authentication) throws IOException, ServletException {
  5. ("清除一些数据...");
  6. ("/");
  7. }
  8. }

2、配置退出成功处理器

  1. // 退出登录配置
  2. ()
  3. .logoutUrl("/logout") // 退出登录路径
  4. // .logoutSuccessUrl("/") // 退出登录后跳转的路径
  5. .clearAuthentication(true) //清除认证状态,默认为true
  6. .invalidateHttpSession(true) // 销毁HttpSession对象,默认为 true
  7. .logoutSuccessHandler(new MyLogoutSuccessHandler()); //自定义退出成功处
  8. 理器

 Spring Security认证_Remember Me

Spring Security中Remember Me为“记住我”功能,即下次访问系统 时无需重新登录。当使用“记住我”功能登录后,Spring Security会 生成一个令牌,令牌一方面保存到数据库中,另一方面生成一个叫 remember-me 的Cookie保存到客户端。之后客户端访问项目时自动携 带令牌,不登录即可完成认证。

 1、编写“记住我”配置类

  1. @Configuration
  2. public class RememberMeConfig {
  3. @Autowired
  4. private DataSource dataSource;
  5. // 令牌Repository
  6. @Bean
  7. public PersistentTokenRepository getPersistentTokenRepository() {
  8. // 为Spring Security自带的令牌控制器设置数据源
  9. JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl = new JdbcTokenRepositoryImpl();
  10. jdbcTokenRepositoryImpl.setDataSource(dataSource);
  11. //自动建表,第一次启动时需要,第二次启动时注释掉
  12. // (true);
  13. return jdbcTokenRepositoryImpl;
  14. }
  15. }

2、修改Security配置类

  1. // 记住我配置
  2. ()
  3. .userDetailsService(userDetailsService)//登录逻辑交给哪个对象
  4. .tokenRepository(repository) //持久层对象
  5. .tokenValiditySeconds(30); //保存时间,单位:秒

3、在登录页面添加“记住我”复选框

  1. <form class="form" action="/login" method="post">
  2. <input type="text" placeholder="用户名" name="username">
  3. <input type="password" placeholder="密码" name="password">
  4. <input type="checkbox" name="remember-me" value="true"/>记住我</br>
  5. <button type="submit">登录</button>
  6. </form>

Spring Security授权_RBAC 

 授权即认证通过后,系统给用户赋予一定的权限,用户只能根据权 限访问系统中的某些资源。RBAC是业界普遍采用的授权方式,它有 两种解释:

Role-Based Access Control

基于角色的访问控制,即按角色进行授权。比如在企业管理系统 中,主体角色为总经理可以查询企业运营报表。逻辑为:

  1. if(主体.hasRole("总经理角色")){
  2. 查询运营报表
  3. }

如果查询企业运营报表的角色变化为总经理和股东,此时就需要修 改判断逻辑代码:

  1. if(主体.hasRole("总经理角色") || 主体.hasRole("股东角色")){
  2. 查询运营报表
  3. }

此时我们可以发现,当需要修改角色的权限时就需要修改授权的相 关代码,系统可扩展性差。

Resource-Based Access Control

基于资源的访问控制,即按资源(或权限)进行授权。比如在企业 管理系统中,用户必须 具有查询报表权限才可以查询企业运营报 表。逻辑为:

  1. if(主体.hasPermission("查询报表权限")){
  2. 查询运营报表
  3. }

这样在系统设计时就已经定义好查询报表的权限标识,即使查询报 表所需要的角色变化为总经理和股东也不需要修改授权代码,系统 可扩展性强。该授权方式更加常用。

Spring Security授权_权限表设计

 用户和权限的关系为多对多,即用户拥有多个权限,权限也属于多 个用户,所以建表方式如下:

 这种方式需要指定用户有哪些权限,如:张三有查询工资的权限, 即在用户权限中间表中添加一条数据,分别记录张三和查询工资权 限ID。但在系统中权限数量可能非常庞大,如果一条一条添加维护 数据较为繁琐。所以我们通常的做法是再加一张角色表:

 用户角色,角色权限都是多对多关系,即一个用户拥有多个角色, 一个角色属于多个用户;一个角色拥有多个权限,一个权限属于多 个角色。这种方式需要指定用户有哪些角色,而角色又有哪些权限。

如:张三拥有总经理的角色,而总经理拥有查询工资、查询报表的 权限,这样张三就拥有了查询工资、查询报表的权限。这样管理用 户时只需管理少量角色,而管理角色时也只需要管理少量权限即可。接下来我们创建五张表:

  1. CREATE TABLE `users` (`uid` int(11) NOT NULL AUTO_INCREMENT,
  2. `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  3. `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  4. `phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  5. PRIMARY KEY (`uid`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3
  6. CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
  7. INSERT INTO `users` VALUES (1, 'baizhan','$2a$10$.xLENt4b
  8. vfDvv7DyS5AVPT.U6', '13812345678');
  9. CREATE TABLE `role` (
  10. `rid` int(11) NOT NULL AUTO_INCREMENT,
  11. `roleName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  12. `roleDesc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  13. PRIMARY KEY (`rid`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4
  14. CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
  15. INSERT INTO `role` VALUES (1,'总经理','管理整个公司');
  16. INSERT INTO `role` VALUES (2,'股东','参与公司决策');
  17. INSERT INTO `role` VALUES (3,'财务','管理公司资产');
  18. CREATE TABLE `permission` (
  19. `pid` int(11) NOT NULL AUTO_INCREMENT,
  20. `permissionName` varchar(255) CHARACTER
  21. SET utf8 COLLATE utf8_general_ci NULL
  22. DEFAULT NULL,
  23. `url` varchar(255) CHARACTER SET utf8
  24. COLLATE utf8_general_ci NULL DEFAULT NULL,
  25. PRIMARY KEY (`pid`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4
  26. CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
  27. INSERT INTO `permission` VALUES (1,'查询报表', '/reportform/find');
  28. INSERT INTO `permission` VALUES (2,'查询工资', '/salary/find');
  29. INSERT INTO `permission` VALUES (3,'查询税务', '/tax/find');
  30. CREATE TABLE `users_role` (
  31. `uid` int(255) NOT NULL,
  32. `rid` int(11) NOT NULL,
  33. PRIMARY KEY (`uid`, `rid`) USING BTREE,
  34. INDEX `rid`(`rid`) USING BTREE,
  35. CONSTRAINT `users_role_ibfk_1` FOREIGN KEY(`uid`) REFERENCES `users` (`uid`) ON DELETE
  36. RESTRICT ON UPDATE RESTRICT,
  37. CONSTRAINT `users_role_ibfk_2` FOREIGN KEY(`rid`) REFERENCES `role` (`rid`) ON DELETE
  38. RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8
  39. COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
  40. INSERT INTO `users_role` VALUES (1, 2);
  41. INSERT INTO `users_role` VALUES (1, 3);
  42. CREATE TABLE `role_permission` (
  43. `rid` int(11) NOT NULL,
  44. `pid` int(11) NOT NULL,
  45. PRIMARY KEY (`rid`, `pid`) USING BTREE,
  46. INDEX `pid`(`pid`) USING BTREE,
  47. CONSTRAINT `role_permission_ibfk_1` FOREIGN KEY (`rid`) REFERENCES `role`
  48. (`rid`) ON DELETE RESTRICT ON UPDATE
  49. RESTRICT,
  50. CONSTRAINT `role_permission_ibfk_2`
  51. FOREIGN KEY (`pid`) REFERENCES `permission`
  52. (`pid`) ON DELETE RESTRICT ON UPDATE
  53. RESTRICT
  54. ) ENGINE = InnoDB CHARACTER SET = utf8
  55. COLLATE = utf8_general_ci ROW_FORMAT =
  56. Dynamic;
  57. INSERT INTO `role_permission` VALUES (1, 1);
  58. INSERT INTO `role_permission` VALUES (2, 1);
  59. INSERT INTO `role_permission` VALUES (1, 2);
  60. INSERT INTO `role_permission` VALUES (3, 2);
  61. INSERT INTO `role_permission` VALUES (1, 3);
  62. INSERT INTO `role_permission` VALUES (2, 3);

Spring Security授权_编写查询权限方法 

 

 在认证后进行授权需要根据用户id查询到用户的权限,写法如下:

1、编写用户、角色、权限实体类

  1. // 不要命名为User,避免和Spring Security提供的User混淆
  2. @Data
  3. public class Users {
  4. private Integer uid;
  5. private String username;
  6. private String password;
  7. private String phone;
  8. }
  9. // 角色
  10. @Data
  11. public class Role {
  12. private String rid;
  13. private String roleName;
  14. private String roleDesc;
  15. }
  16. // 权限
  17. @Data
  18. public class Permission {
  19. private String pid;
  20. private String permissionName;
  21. private String url;
  22. }

2、编写UserMapper接口

  1. // 根据用户名查询权限
  2. List<Permission> findPermissionByUsername(String username);

3、在resources目录中编写UsersMapper的映射文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-////DTD Mapper3.0//EN"
  4. "/dtd/">
  5. <mapper namespace="">
  6. <select id="findPermissionByUsername" parameterType="string" resultType="">
  7. SELECT DISTINCT ,, FROM
  8. users
  9. LEFT JOIN users_role on = users_role.uid
  10. LEFT JOIN role on users_role.rid =
  11. LEFT JOIN role_permission on = role_permission.rid
  12. LEFT JOIN permission on role_permission.pid =
  13. where username = #{username}
  14. </select>
  15. </mapper>

4、测试方法

  1. @SpringBootTest
  2. public class UsersMapperTest {
  3. @Autowired
  4. private UsersMapper usersMapper;
  5. @Test
  6. public void testFindPermissionByUsername(){
  7. List<Permission> baizhan = usersMapper.findPermissionByUsername("baizhan");
  8. baizhan.forEach(System.out::println);
  9. }
  10. }

5、修改认证逻辑,认证成功后给用户授权

  1. // 自定义认证逻辑
  2. @Override
  3. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  4. // 1.构造查询条件
  5. QueryWrapper<Users> wrapper = new QueryWrapper<Users>().eq("username",username);
  6. // 2.查询用户
  7. Users users = (wrapper);
  8. if (users == null){
  9. return null;
  10. }
  11. // 3.查询用户权限
  12. List<Permission> permissions = (username);
  13. // 4.将自定义权限集合转为Security的权限类型集合
  14. List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
  15. for (Permission permission : permissions) {
  16. grantedAuthorities.add(new SimpleGrantedAuthority(()));
  17. }
  18. // 5.封装为UserDetails对象
  19. UserDetails userDetails = (())
  20. .password(())
  21. .authorities(grantedAuthorities)
  22. .build();
  23. // 6.返回封装好的UserDetails对象
  24. return userDetails;
  25. }

Spring Security授权_配置类设置访问控制 

 在给用户授权后,我们就可以给系统中的资源设置访问控制,即拥 有什么权限才能访问什么资源。

1、编写控制器类,添加控制器方法资源

  1. @RestController
  2. public class MyController {
  3. @GetMapping("/reportform/find")
  4. public String findReportForm() {
  5. return "查询报表";
  6. }
  7. @GetMapping("/salary/find")
  8. public String findSalary() {
  9. return "查询工资";
  10. }
  11. @GetMapping("/staff/find")
  12. public String findStaff() {
  13. return "查询员工";
  14. }
  15. }

2、修改Security配置类

  1. // 权限拦截配置
  2. http.authorizeRequests()
  3. .antMatchers("/").permitAll() //表示任何权限都可以访问
  4. .antMatchers("/reportform/find").hasAnyAuthority("/reportform/find") // 给资源配置需要的权限
  5. .antMatchers("/salary/find").hasAnyAuthority("/salary/find")
  6. .antMatchers("/staff/find").hasAnyAuthority("/staff/find")
  7. .anyRequest().authenticated(); //表示任何请求都需要认证后才能访问

3、测试访问资源,由于没有权限被拦截访问时会抛出403异常

Spring Security授权_自定义访问控制逻辑 

如果资源数量很多,一条条配置需要的权限效率较低。我们可以自 定义访问控制逻辑,即访问资源时判断用户是否具有名为该资源 URL的权限。

1、自定义访问控制逻辑

  1. @Service
  2. public class MyAuthorizationService {
  3. // 自定义访问控制逻辑,返回值为是否可以访问资源
  4. public boolean hasPermission(HttpServletRequest request,Authentication authentication){
  5. // 获取会话中的登录用户
  6. Object principal = ();
  7. if (principal instanceof UserDetails){
  8. // 获取登录用户的权限
  9. Collection<? extends GrantedAuthority> authorities = ((UserDetails)principal).getAuthorities();
  10. // 获取请求的URL路径
  11. String uri = ();
  12. // 将URL路径封装为权限对象
  13. SimpleGrantedAuthority authority = new SimpleGrantedAuthority(uri);
  14. // 判断用户的权限集合是否包含请求的URL权限对象
  15. return authorities.contains(authority);
  16. }
  17. return false;
  18. }
  19. }

2、在配置文件中使用自定义访问控制逻辑

  1. // 权限拦截配置
  2. ()
  3. .antMatchers("/").permitAll() //表示任何权限都可以访问
  4. // 任何请求都使用自定义访问控制逻辑
  5. .anyRequest().access("@(request,authentication)"
  6. );

Spring Security授权_注解设置访问控制 

 除了配置类,在SpringSecurity中提供了一些访问控制的注解。这 些注解默认都是不可用的,需要开启后使用。

@Secured

该注解是基于角色的权限控制,要求UserDetails中的权限名必须以 ROLE_ 开头。

1、在配置类开启注解使用

  1. @SpringBootApplication
  2. @MapperScan("")
  3. @EnableGlobalMethodSecurity(securedEnabled=true)
  4. public class MysecurityApplication {
  5. public static void main(String[] args)
  6. {
  7. SpringApplication.run(, args);
  8. }
  9. }

2、在控制器方法上添加注解

  1. @Secured("ROLE_reportform")
  2. @GetMapping("/reportform/find")
  3. public String findReportForm() {
  4. return "查询报表";
  5. }

@PreAuthorize 

该注解可以在方法执行前判断用户是否具有权限

1、在配置类开启注解使用

  1. @SpringBootApplication
  2. @MapperScan("")
  3. @EnableGlobalMethodSecurity(prePostEnabled = true)
  4. public class MysecurityApplication {
  5. public static void main(String[] args)
  6. {
  7. SpringApplication.run(, args);
  8. }
  9. }

2、在控制器方法上添加注解

  1. @PreAuthorize("hasAnyAuthority('/reportform/find')")
  2. @GetMapping("/reportform/find")
  3. public String findReportForm() {
  4. return "查询报表";
  5. }

Spring Security授权_在前端进行访问控制 

 SpringSecurity可以在一些视图技术中进行控制显示效果。例如 Thymeleaf中,只有登录用户拥有某些权限才会展示一些菜单。

1、在pom中引入Spring Security和Thymeleaf的整合依赖

  1. <!--Spring Security整合Thymeleaf-->
  2. <dependency>
  3. <groupId></groupId>
  4. <artifactId>thymeleaf-extras-springsecurity5</artifactId>
  5. </dependency>

2、在Thymeleaf中使用Security标签,控制前端的显示内容

  1. <!DOCTYPE html>
  2. <html xmlns:th=""
  3. xmlns:sec="/thymeleaf-extras-springsecurity5">
  4. <head>
  5. <meta charset="UTF-8">
  6. <title>主页面</title>
  7. </head>
  8. <body>
  9. <h1>主页面</h1>
  10. <ul>
  11. <li sec:authorize="hasAnyAuthority('/reportform/find')">
  12. <a href="/reportform/find">查询报表</a></li>
  13. <li sec:authorize="hasAnyAuthority('/salary/find')">
  14. <a href="/salary/find">查询工资</a></li>
  15. <li sec:authorize="hasAnyAuthority('/staff/find')">
  16. <a href="/staff/find">查询员工</a>
  17. </li>
  18. </ul>
  19. <a href="/logout">退出登录</a>
  20. </body>
  21. </html>

3、这样面对不同权限的用户,前端可以显示不同的菜单

Spring Security授权_403处理方案 

使用Spring Security时经常会看见403(无权限),这样的页面很 不友好,我们可以自定义403异常处理方案: 

1、编写权限不足页面

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>权限不足</title>
  6. </head>
  7. <body>
  8. <h1>您的权限不足,请联系管理员!</h1>
  9. </body>
  10. </html>

2、编写权限不足处理类

  1. public class MyAccessDeniedHandler implements AccessDeniedHandler {
  2. @Override
  3. public void handle(HttpServletRequest request, HttpServletResponse response,
  4. AccessDeniedException accessDeniedException) throws IOException, ServletException {
  5. ("/");
  6. }
  7. }

3、在Spring Security配置文件中配置异常处理

  1. //异常处理
  2. http.exceptionHandling().
  3. accessDeniedHandler(new MyAccessDeniedHandler());