Spring Security总结

news2024/11/15 19:56:10

目录

介绍

项目搭建

Security认证 

        UserDetailsService

        内存认证

        数据库认证

        PasswordEncoder密码解析器

        自定义登录页面

        退出登录

        CSRF防护

        Remember me

Security授权

        RBAC

        权限表设计

        查询访问权限

        配置类设置访问权限

        自定义访问控制逻辑

注解设置访问权限

        @Secured

        @PreAuthorize

前端进行访问控制 

403处理方案


 

 

介绍

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

认证

认证即系统判断用户的身份是否合法,合法可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录、二维码登录、手机短信登录、脸部识别认证、指纹认证等方式。

认证是为了保护系统的隐私数据与资源,用户的身份合法才能访问该系统的资源。

授权

授权即认证通过后,根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。 比如在一些视频网站中,普通用户登录后只有观看免费视频的权限,而VIP用户登录后,网站会给该用户提供观看VIP视频的权限。

认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐私数据进行划分,控制不同的用户能够访问不同的资源。

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

项目搭建

接下来我们先来搭建一个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、配置数据源、端口

#数据源
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql:///mysecurity?serverTimezone=UTC
    username: root
    password: root

#端口
server:
  port: 8080

#日志格式
logging:
  pattern:
    console: '%d{HH:mm:ss.SSS} %clr(%-5level) ---  [%-15thread] %cyan(%-50logger{50}):%msg%n'

4、在template文件夹编写main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
</head>
<body>
    <h1>主页面</h1>
</body>
</html>

5、编写访问页面控制器

@Controller
public class MyController {
    @RequestMapping("/{page}")
    public String getPage(@PathVariable String page){
        return page;
    }
}

6、启动springboot,访问localhost:8080/main,网页自动跳转到一个登录页面,这就是security默认提供的登录页面,用户名为user,密码为控制台输出的一串字符串 

输入过后即可进入main.html

 

 

Security认证 

        UserDetailsService

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

public interface UserDetailsService{

  UserDetailsloadUserByUsername(String username) throws UsernameNotFoundException;

}

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

 

 简单说就是:

1根据用户名查询用户,将查询结果封装为UserDetails对象

2Spring Security根据UserDetailspassword和客户端表单上传的password比对

 

        内存认证

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

编写security配置类,定义内存认证和密码解析器

@Configuration
public class SecurityConfig {
    //定义认证逻辑
    @Bean
    public UserDetailsService userDetailsService(){
        //1、使用内存数据进行认证
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        //2、创建两个用户
        UserDetails u1 = User.withUsername("cm").password("123").authorities("admin").build();
        UserDetails u2 = User.withUsername("sxt").password("456").authorities("admin").build();
        //3、将两个用户添加到内存中
        manager.createUser(u1);
        manager.createUser(u2);
        return manager;
    }

    //密码编译器,不解析密码
    @Bean
    public PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }
}

启动springboot登录时,就可以使用自己添加的用户名和密码了

 

 

 

        数据库认证

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

1、准备数据库

CREATE TABLE `users` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `username` varchar(255),
 `password` varchar(255) ,
 `phone` varchar(255) ,
 PRIMARY KEY (`id`)
);

 

 

2、编写用户实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Users {
    private Integer id;
    private String username;
    private String password;
    private String phone;
}

3、编写dao接口

public interface UsersMapper extends BaseMapper<User> {
}

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

@SpringBootApplication
@MapperScan
public class SecurityBlogApplication {

    public static void main(String[] args) {
        SpringApplication.run(SecurityBlogApplication.class, args);
    }

}

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

@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UsersMapper usersMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1、构建查询条件
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("username",username);
        //2、查询用户
        Users users = usersMapper.selectOne(queryWrapper);
        //3、封装为UserDetails对象
        UserDetails userDetails = User.withUsername(users.getUsername())
                .password(users.getPassword())
                .authorities("admin")
                .build();
        return userDetails;
    }
}

6、注释之前写过的认证逻辑,因为只能使用一种认证逻辑 

7、启动SpringBoot, 访问localhost:8080/main,跳转到登录页面,输入数据库中对应用户名和密码即可登录 

 

        PasswordEncoder密码解析器

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

Spring Security要求容器中必须有PasswordEncoder实例,之前使用的NoOpPasswordEncoderPasswordEncoder的实现类,意思是不解析密码,使用明文密码。

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

 1、在junit中测试密码加密

@SpringBootTest
public class TestBCryptPasswordEncoder {
    @Test
    //测试BCryptPasswordEncoder
    public void t1(){
        //创建解析器
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

        //密码加密
        String password = encoder.encode("baizhan");
        System.out.println("加密后:"+password);

        //密码校验
        /**
         * 参数1:明文密码
         * 参数2:加密密码
         * 返回值:是否校验成功
         */
        boolean result = encoder.matches("baizhan","$2a$10$Kv7P7Hz/8fKsMH6LLL6uk.E2bxheW/M1jIwwTVQE6kwPK9IThpXne");
        System.out.println(result);
    }

}

2、将密码解析器设置为BCryptPasswordEncoder

@Configuration
public class SecurityConfig {

    //密码编译器,不解析密码
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

 3、将数据库中的明文密码改为刚刚测试输出的密文密码

 4、启动springBoot,访问localhost:8080/main,进入登录页面,分别输入用户名baizhan,sxt,和密码baizhan,sxt

 

 

当时输入sxt时就会报错误,这是因为sxt在数据库中的密码还是明文保存的,而BCryptPasswordEncoder会对所有密码进行加密,然后用加密的密码和数据库中的密码进行比对,所以登录报错

 

        自定义登录页面

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

  1. 编写登录页面
  2. 在Spring Security配置类自定义登录页面(需要继承WebSecurityConfigurerAdapter)
@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/**");
   }
}

 3、启动SpringBoot,访问localhost:8080/main,进入自定义登录页面,输入用户名和密码即可进入主界面

 

 

 

        退出登录

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

  • 清除认证状态
  • 销毁HttpSession对象
  • 跳转到登录页面

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

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

//退出登录配置
        http.logout()
                .logoutUrl("/logout")//退出登录路径
                .logoutSuccessUrl("/main")//退出成功访问路径
                .clearAuthentication(true)//清除认证状态,默认为true
                .invalidateHttpSession(true);//销毁HttpSession对象,默认为true

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

<!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>
    <a href="/logout">退出登录</a>
</body>
</html>

 

 

 

        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>

 

        Remember me

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

1、编写RememberMeConfig配置类(固定写法)


@Configuration
public class RememberMeConfig {
    @Autowired
    private DataSource dataSource;

    //令牌Repository
    @Bean
    public PersistentTokenRepository getPersistentTokenRepository(){
        // 为Spring Security自带的令牌控制器设置数据源
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //自动建表,第一次启动时需要,第二次启动时注释掉
        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
}

2、修改security配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    @Autowired
    private MyUserDetailsService userDetailsService;
    @Autowired
    private PersistentTokenRepository repository;

    //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.logout()
                .logoutUrl("/logout")//退出登录路径
                .logoutSuccessUrl("/main")//退出成功访问路径
                .clearAuthentication(true)//清除认证状态,默认为true
                .invalidateHttpSession(true);//销毁HttpSession对象,默认为true




        //权限拦截配置
        http.authorizeRequests()
                .antMatchers("/login.html").permitAll() //登录页不需要认证
                .anyRequest().authenticated(); //其余所有请求都需要认证


        //Remember Me
        http.rememberMe()
                .userDetailsService(userDetailsService)//登录逻辑交给哪个对象
                .tokenRepository(repository) //持久层对象
                .tokenValiditySeconds(30); //保存时间,单位:秒

        //关闭csrf防护
//        http.csrf().disable();
    }

3、在登录页面中添加"记住我"复选框

<!doctype html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录</title>
    <link href="/css/styles.css" rel="stylesheet" >
</head>
<body>
<div class="htmleaf-container">
    <div class="wrapper">
        <div class="container">
            <h1>Welcome</h1>
            <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">
                <input type="checkbox" name="remember-me" value="true"/>记住我<br/>
                <button type="submit" id="login-button">登录</button>
            </form>
        </div>
        <ul class="bg-bubbles">
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
        </ul>
    </div>
</div>
</body>
</html>

重点:

为何要修改 name 属性修改为 remember-me,而不是别的什么名字呢?其实,原因并不复杂,简单点来说,这是Spring Security 框架默认的 记住我 复选框的名称。

 

 

Security授权

        RBAC

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

1Role-Based Access Control

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

if(主体.hasRole("总经理角色")){

    查询运营报表

}

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

if(主体.hasRole总经理角色") ||主体.hasRole("股东角色")){("

    查询运营报表

}

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

 

2Resource-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 = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `users` VALUES (1, 'baizhan', '$2a$10$Eqv9PRMl6bPt5BiwgPr2eucgyl.E.xLENt4bvfDvv7DyS5AVPT.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);

 

        查询访问权限

1、编写pojo类

// 不要命名为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接口

@Repository
public interface UsersMapper extends BaseMapper<Users> {
    List<Permission> findPermissionByUsername(String username);
}

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

<?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.itbaizhan.security_blog.mapper.UsersMapper">
    <select id="findPermissionByUsername" parameterType="string" resultType="com.itbaizhan.security_blog.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);
    }
}

 

        配置类设置访问权限

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、修改认证逻辑MyUserDetailsService类,认证成功后给用户授权 

// 自定义认证逻辑
@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;
}

 3、修改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(); //表示任何请求都需要认证后才能访问

 4、测试访问资源,由于没有权限被拦截访问时会抛出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的整合依赖

<!--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、这样面对不同权限的用户,前端可以显示不同的菜单

 

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、编写权限不足处理类(需要实现AccessDeniedHandler接口)

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/83462.html

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

相关文章

29.前端笔记-HTML-Html5的新特性

目录1、HtML5新增的语义化标签2、HTML5新增的多媒体标签&#xff08;1&#xff09;音频标签&#xff1a;< audio>audio的常见属性&#xff08;2&#xff09;视频标签&#xff1a;< video>video常见属性3、新增表单元素input的类型type4、新增表单属性1、HtML5新增的…

R语言中的生存分析Survival analysis晚期肺癌患者4例

第1部分&#xff1a;生存分析简介 最近我们被客户要求撰写关于生存分析的研究报告&#xff0c;包括一些图形和统计输出。本演示文稿将介绍生存分析 &#xff0c;参考&#xff1a; Clark, T., Bradburn, M., Love, S., & Altman, D. (2003). Survival analysis part I: Ba…

06 数学软件与建模---拟合

一、知识储备 1.曲线拟合问题的提法 已知一组&#xff08;二维&#xff09;数据&#xff0c;即平面上 n个点&#xff08;xi,yi) i1,…,n, 寻求一个函数&#xff08;曲线&#xff09;yf(x), 使 f(x) 在某种准则下与所有数据点最为接近&#xff0c;即曲线拟合得最好&#xff0…

前后端分离项目-Springboot 【后端框架搭建,SSM】

1.创建新项目 点击File->New->Project 选择Spring Initializr 填写信息 修改Group修改Artifact修改管理类型Maven&#xff08;带有文件目录&#xff09;修改Java version 选择依赖 这里只需要选择Web->Spring Web即可 创建的文件目录如下 2.创建文件目录 controlle…

【深入浅出Spring原理及实战】「原理分析专题」从零开始教你SpringEL表达式使用和功能分析讲解指南(上篇)

Spring EL表达式语言,这种语言jsp中学到的el,但是在整个spring之中其表达式语言要更加的复杂,而且支持度更加的广泛,最重要的是他可以进行方法的调用,对象的实例化,集合操作等等,但是唯一的难点就是:代码太复杂了,表达式太复杂了。深刻领会,spring中针对于字符串的改进,程序员使…

基于jsp+mysql+ssm医院出车管理与绩效分配系统-计算机毕业设计

项目介绍 本毕业设计主要实现集人性化、高效率、便捷等优点于一身的出车管理系统与绩效分配系统&#xff0c;完成系统用户管理、车辆信息管理、调度员信息管理、驾驶员信息管理、救护员信息管理、科室人员管理、院领导信息管理、调度信息管理、出车统计等功能模块。系统通过浏…

Spring Cloud微服务之loadbalancer负载平衡

Spring Cloud微服务之loadbalancer负载平衡 小学时候&#xff0c;曾经做过这样的数学题。 说有一个水池子&#xff0c;上面有一个排水管&#xff0c;下面有一个进水管&#xff0c;开一个进水管&#xff0c;6个小时灌满水池&#xff0c;开一个排水管&#xff0c;10个小时放光整…

给博客网站添加loading加载中动画代码

只需添加几行代码&#xff0c;就可以加强浏览者的体验,不在等待页面加载时感到枯燥&#xff0c;从而关闭网页&#xff0c;很多网站都会制作一个“网页正在加载中”的提示效果或显示加载进程&#xff0c;加载完成后提示消失&#xff0c;大部分都应用在网站的首页。当然网上很多教…

02时间复杂度与空间复杂度

开始系统学习算法啦&#xff01;为后面力扣和蓝桥杯的刷题做准备&#xff01;这个专栏将记录自己学习算法是的笔记&#xff0c;包括概念&#xff0c;算法运行过程&#xff0c;以及代码实现&#xff0c;希望能给大家带来帮助&#xff0c;感兴趣的小伙伴欢迎评论区留言或者私信博…

如何将多个视频剪辑到一起?如何把两段视频合成一段

如何将多个视频剪辑到一起&#xff1f;那么如何拼接视频&#xff1f;接下来小编就带大家一起了解下有关视频拼接的几款工具推荐。 工具一&#xff1a;TS视频拼合工具 TS视频合并工具是一款轻便简单的视频合并工具&#xff0c;主要是用于合并TS视频文件的工具&#xff0c;当用…

【分享】本地js文件替换源网页js文件的方法

本文所有教程及源码、软件仅为技术研究。不涉及计算机信息系统功能的删除、修改、增加、干扰,更不会影响计算机信息系统的正常运行。不得将代码用于非法用途,如侵立删!本地js文件替换源网页js文件的方法 环境 win10Fiddlerchrome方法一:Fiddler抓包替换js文件 自动回复器–…

HashMap源码分析以及面试题

目录 5、HashMap源码分析 5.1、初始化容量 5.2、负载因子是多少&#xff1f; 5.3、负载因子可以大于或小于0.75吗&#xff1f; 5.4、扩容长度为多少&#xff1f; 5.5、下标是怎么计算的&#xff1f; 5.6、hash冲突&#xff0c;是怎么解决的&#xff1f; 5.7、什么时候…

[附源码]JAVA毕业设计英语课程学习网站(系统+LW)

[附源码]JAVA毕业设计英语课程学习网站&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术…

安装 NVSwitch GPU 服务器的 cuda 驱动版本、nvidia-docker 指南

一&#xff0c;安装 Cuda 驱动 可参考笔者之前写过的文章&#xff1a; 升级 GPU 服务器 cuda 驱动版本指南 如果出现如下报错&#xff0c;则需安装 gcc、kernel-devel&#xff0c;请参考下面第二步安装 gcc、kernel-devel。 二&#xff0c;安装 gcc、kernel-devel 1&#…

基于狮群算法优化的lssvm回归预测-附代码

基于狮群算法优化的lssvm回归预测 - 附代码 文章目录基于狮群算法优化的lssvm回归预测 - 附代码1.数据集2.lssvm模型3.基于狮群算法优化的LSSVM4.测试结果5.Matlab代码摘要&#xff1a;为了提高最小二乘支持向量机&#xff08;lssvm&#xff09;的回归预测准确率&#xff0c;对…

[附源码]计算机毕业设计高校车辆管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis MavenVue等等组成&#xff0c;B/S模式…

地表最强:免费文字转语音工具

0、前言 我们在刷短视频的时候&#xff0c;经常会听到一些AI合成声音&#xff0c;它们有各种音色、语调&#xff0c;甚至不同的情绪&#xff0c;听起来与人声无异&#xff0c;其实这些大都是利用微软Azure的文字转语音技术来实现的。 虽然国内也有很多配音工具&#xff0c;但…

【优化算法】粒子群优化算法

粒子群优化算法粒子群优化算法简介粒子群优化算法原理粒子群优化算法的数学描述粒子群优化算法框架PySwarms&#xff1a;Python中粒子群优化的研究工具包PySwarms快速使用示例&#xff1a;编写自己的优化循环相关资料粒子群优化算法简介 粒子群优化算法(Particle Swarm Optimi…

如何安然度过行业大萧条,听听10年测试老鸟的分析

国内的互联网行业发展较快&#xff0c;所以造成了技术研发类员工工作强度比较大&#xff0c;同时技术的快速更新又需要员工不断的学习新的技术。因此淘汰率也比较高&#xff0c;超过35岁的基层研发类员工&#xff0c;往往因为家庭原因、身体原因&#xff0c;比较难以跟得上工作…

Huawei Compute Architecture for Neural Networks - CANN

Huawei Compute Architecture for Neural Networks - CANN1. Technical support (技术支持) https://support.huawei.com/enterprise/en/index.html https://support.huawei.com/enterprise/zh/index.html Server - Intelligent Computing -> Ascend Computing (昇腾计算)…