SpringSecurity

springboot版本2.3.12RELEASE

一、环境搭建

  1. 引入jar包

<dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-security</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
           <version>2.2.2</version>
       </dependency>

       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-devtools</artifactId>
           <scope>runtime</scope>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <scope>runtime</scope>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
       <dependency>
           <groupId>org.springframework.security</groupId>
           <artifactId>spring-security-test</artifactId>
           <scope>test</scope>
       </dependency>
   </dependencies>
  1. 书写数据库配置

spring:
datasource:
  driver-class-name: com.mysql.cj.jdbc.Driver
  url: jdbc:mysql://localhost:3306/user_db?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf8&autoReconnect=true
  username: root
  password: 123456
  1. 创建security的配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class WebSecurity extends WebSecurityConfigurerAdapter {



   //采用springsecurity的用户权限设置
   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       super.configure(auth);
  }

   //配置密码加密的规则
   @Bean
   PasswordEncoder passwordEncoder(){
       return new BCryptPasswordEncoder();
  }


   //配置路径访问权限
   @Override
   protected void configure(HttpSecurity http) throws Exception {
       super.configure(http);
  }
}

二、基于内存的权限控制

  1. 修改security配置类(主要)


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;


@Configuration
public class WebSecurity extends WebSecurityConfigurerAdapter {
   //用户权限设置
   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.inMemoryAuthentication().withUser("zhangsan").password(passwordEncoder().encode("123")).authorities("p","p1");
  }

   //配置密码加密的规则
   @Bean
   PasswordEncoder passwordEncoder(){
       return new BCryptPasswordEncoder();
  }

   //配置路径访问权限
   @Override
   protected void configure(HttpSecurity http) throws Exception {
       http.csrf().disable()
              .authorizeRequests()
              .antMatchers("/r/r").hasAuthority("p")
              .antMatchers("/r/r1").hasAuthority("p1")
              .antMatchers("/r/p2").hasAuthority("p2")
              .anyRequest().permitAll()
              .and()
              .formLogin();
  }
}
  1. 创建测试controller

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/r")
public class TestController {

   @GetMapping("/r")
   public String r(){
       return "我是r 我需要p权限";
  }
   @GetMapping("/r1")
   public String r1(){
       return "我是r1 我需要p1权限";
  }
   @GetMapping("/r2")
   public String r2(){
       return "我是r2 我需要p2权限";
  }
}
  1. 启动测试

三、基于数据库的动态权限配置

  1. 创建数据库

CREATE DATABASE `user_db` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
use `user_db`;
#用户表
CREATE TABLE `t_user` (
`id` BIGINT(20) NOT NULL COMMENT '用户id',
`username` VARCHAR(64) NOT NULL,
`password` VARCHAR(64) NOT NULL,
`fullname` VARCHAR(255) NOT NULL COMMENT '用户姓名',
`mobile` VARCHAR(11) DEFAULT NULL COMMENT '手机号',
PRIMARY KEY (`id`) USING BTREE )
ENGINE=INNODB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

INSERT INTO t_user
VALUES
(1,'zhangsan','$2a$10$ZM/4QhxpLF7WCIdjBAcOOuMsDir2MQRVTLBuLEhbyO/XIJtN5d3E2','zhangsan','18888888888'),
(2,'lisi','$2a$10$qIw4453eKBfGdAuUzaVslO2brkJ5xsick9BT4oY9JMIe2LYgpmXuy','zhangsan','18888888888');
# 角色表
CREATE TABLE `t_role` (
`id` VARCHAR(32) NOT NULL,
`role_name` VARCHAR(255) DEFAULT NULL,
`description` VARCHAR(255) DEFAULT NULL,
`create_time` DATETIME DEFAULT NULL,
`update_time` DATETIME DEFAULT NULL,
`status` CHAR(1) NOT NULL, PRIMARY KEY (`id`),
UNIQUE KEY `unique_role_name` (`role_name`) )
ENGINE=INNODB DEFAULT CHARSET=utf8 ;

INSERT INTO `t_role`(`id`,`role_name`,`description`,`create_time`,`update_time`,`status`)
VALUES
('1','管理员',NULL,NULL,NULL,'');

-- 用户角色表
CREATE TABLE `t_user_role` (
`user_id` VARCHAR(32) NOT NULL,
`role_id` VARCHAR(32) NOT NULL,
`create_time` DATETIME DEFAULT NULL,
`creator` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`user_id`,`role_id`) )
ENGINE=INNODB DEFAULT CHARSET=utf8 ;

INSERT INTO `t_user_role`
(`user_id`,`role_id`,`create_time`,`creator`)
VALUES
('1','1',NULL,NULL);

-- 权限表

CREATE TABLE `t_permission` (
`id` VARCHAR(32) NOT NULL,
`code` VARCHAR(32) NOT NULL COMMENT '权限标识符',
`description` VARCHAR(64) DEFAULT NULL COMMENT '描述',
`url` VARCHAR(128) DEFAULT NULL COMMENT '请求地址',
PRIMARY KEY (`id`) )
ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `t_permission`
(`id`,`code`,`description`,`url`)
VALUES
('1','p1','测试资源 1','/r/r1'),
('2','p3','测试资源2','/r/r2');

-- 创建角色权限表

CREATE TABLE `t_role_permission`
( `role_id` VARCHAR(32) NOT NULL,
`permission_id` VARCHAR(32) NOT NULL,
PRIMARY KEY (`role_id`,`permission_id`) )
ENGINE=INNODB DEFAULT CHARSET=utf8 ;

INSERT INTO `t_role_permission`
(`role_id`,`permission_id`)
VALUES
('1','1'),
('1','2');
  1. 创建实体类

2.1创建UserPO继承UserDetail接口(重要)

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserPO implements UserDetails {
   private Long id;
   private String username;
   private String password;
   private String fullname;
   private String mobile;
   private List<GrantedAuthority> authorities=new ArrayList<>();

   @Override
   public Collection<? extends GrantedAuthority> getAuthorities() {
       return authorities;
  }

   @Override
   public boolean isAccountNonExpired() {
       return true;
  }

   @Override
   public boolean isAccountNonLocked() {
       return true;
  }

   @Override
   public boolean isCredentialsNonExpired() {
       return true;
  }

   @Override
   public boolean isEnabled() {
       return true;
  }
}

2.2创建权限实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PerssionPO implements Serializable {
   private String id;
   private String code;
   private String description;
   private String url;
}

2.3其余三张表的实体类(省略)

  1. 创建Mapper

3.1.创建UserMapper

import com.zhuoyue.po.PerssionPO;
import com.zhuoyue.po.UserPO;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface UserMapper {
   //根据用户名查询用户
   @Select("select * from t_user where username=#{userName}")
   UserPO findUserByUserName(String userName);
//根据用户名查询用户权限
   @Select("select e.* from t_user a join t_user_role b ON a.id=b.user_id join t_role c on b.role_id=c.id\n" +
           "join t_role_permission d on c.id=d.role_id join t_permission e on d.permission_id=e.id \n" +
           "where a.username=#{username}")
   List<PerssionPO> findPerssionByUserName(String username);
}

3.2.创建PerssionMapper

import com.zhuoyue.po.PerssionPO;
import org.apache.ibatis.annotations.Select;

public interface PerssionMapper {
//查询所有权限
   @Select("select * from t_permission")
   List<PerssionPO> findAll();
}

3.3.在启动类加上@MapperScan扫描所有mapper

  1. 创建Service(重要)

    4.1创建UserService继承UserDetailService

import org.springframework.security.core.userdetails.UserDetailsService;

public interface UserService extends UserDetailsService {

}

4.22.书写UserService的实现类

import com.zhuoyue.mapper.UserMapper;
import com.zhuoyue.po.PerssionPO;
import com.zhuoyue.po.UserPO;
import com.zhuoyue.service.UserService;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl implements UserService {
   @Resource
   private UserMapper userMapper;
   @Override
   public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
       //根据用户名查询用户信息
       UserPO userPO = userMapper.findUserByUserName(s);
       if (userPO==null)return null;
       //根据用户名查询用户的所有权限
       List<PerssionPO> perssion = userMapper.findPerssionByUserName(s);
       //转换权限
       List<GrantedAuthority> list = perssion.stream().map(perssionPO -> new SimpleGrantedAuthority(perssionPO.getCode())).collect(Collectors.toList());
       //设置用户的权限
       userPO.setAuthorities(list);
       return userPO;
  }
  1. 基于内存的问题分析

5.1.用户从内存里面取不合理

5.2配置的权限路径被写死

  1. 问题解决(修改Security配置)

    6.1用户从数据库里面去取

     @Resource
       protected UserService userService;
       //用户权限设置
       @Override
       protected void configure(AuthenticationManagerBuilder auth) throws Exception {
           //auth.inMemoryAuthentication().withUser("zhangsan").password(passwordEncoder().encode("123")).authorities("p","p1");
           auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
      }

    6.2权限从数据库里面取

        @Resource
       private PerssionMapper perssionMapper;
       //配置路径访问权限
       @Override
       protected void configure(HttpSecurity http) throws Exception {
           http.csrf().disable().formLogin();
           ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry requests = http.authorizeRequests();
           List<PerssionPO> all = perssionMapper.findAll();
           all.forEach(perssionPO -> {
               requests.antMatchers(perssionPO.getUrl()).hasAuthority(perssionPO.getCode());
          });
           requests.anyRequest().permitAll();
      }

    6.3最终结果

    import com.zhuoyue.mapper.PerssionMapper;
    import com.zhuoyue.po.PerssionPO;
    import com.zhuoyue.service.UserService;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;

    import javax.annotation.Resource;
    import java.util.List;

    @Configuration
    public class WebSecurity extends WebSecurityConfigurerAdapter {


       @Resource
       private PerssionMapper perssionMapper;
       @Resource
       protected UserService userService;
       //用户权限设置
       @Override
       protected void configure(AuthenticationManagerBuilder auth) throws Exception {
           //auth.inMemoryAuthentication().withUser("zhangsan").password(passwordEncoder().encode("123")).authorities("p","p1");
           auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
      }

       //配置密码加密的规则
       @Bean
       PasswordEncoder passwordEncoder(){
           return new BCryptPasswordEncoder();
      }

       //配置路径访问权限
       @Override
       protected void configure(HttpSecurity http) throws Exception {
           http.csrf().disable().formLogin();
           ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry requests = http.authorizeRequests();
           List<PerssionPO> all = perssionMapper.findAll();
           all.forEach(perssionPO -> {
               requests.antMatchers(perssionPO.getUrl()).hasAuthority(perssionPO.getCode());
          });
           requests.anyRequest().permitAll();
      }
    }

四、会话操作

  1. 获取用户信息(SecurityContextHolder)

/*** 获取当前登录用户名 * @return */
   private String getUsername() {
       Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
       if (!authentication.isAuthenticated()) {
           return null;
      }
       Object principal = authentication.getPrincipal();
       String username = null;
       if (principal instanceof org.springframework.security.core.userdetails.UserDetails) {
           UserDetails userDetails= (UserDetails) principal;
           System.out.println("userDetails = " + userDetails);//打印userDetails信息
           Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
           for(GrantedAuthority au:authorities){//遍历所有的权限
               System.out.println(au.getAuthority());
          }
           username = userDetails.getUsername();
      } else {
           username = principal.toString();
      }
       return username;
  }
  1. 设置过期时间(application.properties)

#设置session过期时间
server.servlet.session.timeout=60s