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

news2024/12/24 3:17:30

目录

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项目,添加依赖

<!-- SpringMVC -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--Thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!--Spring Security-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- Mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- MyBatisPlus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.0</version>
</dependency>

<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

<!-- junit -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

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.Driver
    url: jdbc:mysql:///mysecurity?serverTimezone=UTC
    username: root
    password01: root

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

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>主页面</title>
</head>
<body>
    <h1>主页面</h1>
</body>
</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认证_内存认证 

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

// Security配置类
@Configuration
public class SecurityConfig {
    // 定义认证逻辑
    @Bean
    public 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;
   }
    //密码编码器,不解析密码
    @Bean
    public PasswordEncoder passwordEncoder()
   {
        return NoOpPasswordEncoder.getInstance();
   }
}

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

Spring Security认证_UserDetailsService 

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

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

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

 

 Spring Security认证_数据库认证

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

 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<Users> {}

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 {
    @Autowired
    private UsersMapper usersMapper;
    // 自定义认证逻辑
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1.构造查询条件
        QueryWrapper<Users> wrapper = new QueryWrapper<Users>().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 。

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

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

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

@SpringBootTest
public class PasswordEncoderTest {
    @Test
    public 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给我们提供了登录页面,但在实际项目中,更 多的是使用自己的登录页面。Spring Security也支持用户自定义登 录页面。用法如下:

1、编写登录页面

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

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    //Spring Security配置
    @Override
    protected 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();
   }
    @Override
    public 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值为令牌,令牌在服务端产生,如果携带的令牌 和服务端的令牌匹配成功,则正常访问。

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

Spring Security认证_会话管理 

 用户认证通过后,有时我们需要获取用户信息,比如在网站顶部显 示:欢迎您,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();
   }
}

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

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

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

public class MyLoginSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public 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"); //登录失败后跳转的路径

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

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

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

public class MyLoginFailureHandler implements AuthenticationFailureHandler {
    @Override
    public 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认证_退出登录 

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

1、清除认证状态

2、销毁HttpSession对象 

3、跳转到登录页面

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

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

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

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

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>主页面</title>
</head>
<body>
  <h1>主页面</h1>
  <a href="/logout">退出登录</a>
</body>
</html>

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());

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/51258.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

package-info.java

package-info.java 文件估计大家见过但是自己却很少去创建和使用它、因为对于一般应用来说可能真的太少见了。 它的作用主要是三个 描述包使用注解修饰包、达到修饰该包下的类声明包中使用的类和常量(这个比较少用) 描述包 package-info.java 文件 /*** 我是描述信息*/ pa…

如何用蓝牙实现无线定位(三)--本地定位显示

1. 被定位目标 本项目设计有两个定位装置&#xff0c;一个用于固定目标&#xff0c;一个用于可移动设备。在定位系统的帮助下&#xff0c;我们可以操作可移动设备向固定目标移动。假设这是一个救援场景的话&#xff0c;我们就可以把固定的目标看作等待救援的人或物&#xff0c;…

【使用 BERT 的问答系统】第 2 章 :用于自然语言处理的神经网络

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

大数据开发之词频统计传参打包成jar包发送到Hadoop运行并创建可执行文件方便运行

文章目录添加spark的jar包main传参调试打包成jar包发送到Hadoop运行使用脚本运行参考添加spark的jar包 点击Project Structure Global Libararies中 点击 选择java 然后选择spark文件里的jars下所有的jar包 然后点击ok即可。 main传参调试 首先给出词频统计代码 //包 imp…

OpenCV图像处理——光流估计

总目录 图像处理总目录←点击这里 二十二、光流估计 22.1、原理 光流 是空间运动物体在观测成像平面上的像素运动的“瞬时速度”&#xff0c;根据各个像素点的速度矢量特征&#xff0c;可以对图像进行动态分析&#xff0c;例如目标跟踪。 亮度恒定&#xff1a;同一点随着时…

HTML5期末考核大作业——学生网页设计作业源码HTML+CSS+JavaScript 中华美德6页面带音乐文化

&#x1f468;‍&#x1f393;静态网站的编写主要是用HTML DIVCSS JS等来完成页面的排版设计&#x1f469;‍&#x1f393;,常用的网页设计软件有Dreamweaver、EditPlus、HBuilderX、VScode 、Webstorm、Animate等等&#xff0c;用的最多的还是DW&#xff0c;当然不同软件写出的…

LeetCode HOT 100 —— 76 .最小覆盖子串

题目 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串"" 。 思路 **滑动窗口&#xff1a;**题目要求返回字符串s中包含字符串t的全部字符的最小窗口&#xff0c;即包含t的…

世界杯的那些二三事

文章目录 &#x1f525;关于世界杯 &#x1f525;关于2022卡塔尔世界杯 &#x1f525;我与世界杯 ⚽分享一颗足球 ⚽实现效果 &#x1f525;关于世界杯 大力神杯 国际足联世界杯&#xff08;FIFA World Cup&#xff09;&#xff0c;简称“世界杯”&#xff0c;是由全世界…

【自然语言处理(NLP)】基于Bi-DAF的机器阅读理解

【自然语言处理&#xff08;NLP&#xff09;】基于Bi-DAF的机器阅读理解 作者简介&#xff1a;在校大学生一枚&#xff0c;华为云享专家&#xff0c;阿里云专家博主&#xff0c;腾云先锋&#xff08;TDP&#xff09;成员&#xff0c;云曦智划项目总负责人&#xff0c;全国高等学…

java+jsp基于ssm汽车配件管理系统-计算机毕业设计

项目介绍 本汽车配件管理系统是针对目前网上车企的实际需求&#xff0c;从实际工作出发&#xff0c;对过去的汽车配件管理系统存在的问题进行分析&#xff0c;结合计算机系统的结构、概念、模型、原理、方法&#xff0c;在计算机各种优势的情况下&#xff0c;采用目前最流行的…

利用Python生成随机密码,灰常简单,小伙伴可以试试哟

知识点 文件读写 基础语法 字符串处理 字符拼接 Python合集视频 【整整800集】Python爬虫项目零基础入门合集&#xff0c;细狗都学会了&#xff0c;你还不会&#xff1f;代码解析 导入模块 import platform import string import random将string的几大字符串拼接在一起&…

Spring Boot实现任意位置的properties及yml文件内容配置与获取

〇、参考资料 1、Spring Boot 中文乱码问题解决方案汇总 https://blog.51cto.com/u_15236724/5372824 2、spring boot读取自定义配置properties文件★ https://www.yisu.com/zixun/366877.html 3、spring boot通过配置工厂类&#xff0c;实现读取指定位置的yml文件★ https://b…

TensorFlow之文本分类算法-5

1 前言 2 收集数据 3 探索数据 4 选择模型 5 准备数据 6 模型-构建训练评估 构建输出层 构建n-gram模型 根据前面章节的描述&#xff0c;n-gram模型是独立地处理分词&#xff0c;与原文中的单词顺序不相关。简单的多层神经感知&#xff08;逻辑回归&#xff09;、梯度推…

SCP命令在不同远程服务器之间发送文件(指定端口)

最近想把数据集放在另一个服务器上&#xff0c;但是如果先下载到本地然后再上传过去&#xff0c;则需要浪费好久时间。 特总结下如何快捷的通过命令完成不同远程服务器之间的文件传输&#xff0c;以及遇到的问题。 SCP命令 Linux scp 命令用于 Linux 之间复制文件和目录。1 s…

第 46 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(上海),签到题6题

文章目录E.Strange IntegersD.Strange FractionsG.Edge GroupsI.Steadily Growing SteamH.Life is a GameK.Circle of Life补题链接&#xff1a;https://codeforces.com/gym/103446 E.Strange Integers E. Strange Integers time limit per test1 second memory limit per te…

搭建Docker+SRS服务器实现推流拉流的效果

最初的一个想法&#xff0c;是针对当前的网络电视去的&#xff0c;很多网络电视买回家&#xff0c;还要充很多会员&#xff0c;甚至跌入连环坑。我想给妈妈买一台电视&#xff0c;想把我自己收集的电影电视剧做成一个影视库&#xff0c;通过搭建家庭影院服务器&#xff0c;然后…

基于PHP+MySQL保险理赔系统的设计与实现

随着我国经济的发展,车辆的数量也在不断的增加相对应的车辆保险理赔的数量也在不断的增加,但是目前市面上很多理赔要么就是通过手工管理的方式进行管理,要么就是管理软件太过于的专业,为了能够让大众都能够在线通过网络进行在线理赔,我开发了本系统。 本设计尝试用PHP开发一个保…

SpringCloud_第3章_微服务保护_Sentinel

SpringCloud_第3章_微服务保护 文章目录SpringCloud_第3章_微服务保护1.初识Sentinel1.1.雪崩问题及解决方案1.1.1.雪崩问题1.1.2.超时处理1.1.3.仓壁模式1.1.4.断路器1.1.5.限流1.1.6.总结1.2.服务保护技术对比1.3.Sentinel介绍和安装1.3.1.初识Sentinel1.3.2.安装Sentinel1.4…

vue2.x和vue3.x 环境相关配置

1.vue2.x配置多个环境 在根目录下创建多环境配置文件 例如&#xff1a; env.devlopment、env.prod、env.sit等&#xff0c;我的环境文件有以下几个: 分别配置各文件的参数 比如说uat环境和生产环境请求url是不同的 uat环境env.uat: # uat环境 NODE_ENV uat# uat环境请求…

java锁

java锁 乐观锁和悲观锁 悲观锁 悲观锁认为自己在使用数据的时候一定有别的线程来修改数据&#xff0c;因此在获取数据的时候会先加锁&#xff0c;确保数据不会被别的线程修改。 悲观锁的实现方式 synchronized关键字Lock的实现类都是悲观锁 适合写操作多的场景&#xff0c;…