SpringSecurity

目录

引入依赖

编写请求对应接口

引入security依赖

启动类注解

编写Security配置类

配置密码加密方式

项目运行

权限颗粒度细化

[动态权限分配 - RBAC权限模型](#动态权限分配 - RBAC权限模型)

RBAC概述

数据库建模

修改项目动态权限

添加项目依赖

配置数据库

编写实体类

获取登录人权限


引入依赖

复制代码
       
XML 复制代码
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
​
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
​
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

编写请求对应接口

java 复制代码
@RestController
@RequestMapping("/order")
public class OrderController {
    @GetMapping("/create")
    public String create() {
        return "访问了新增接口";
    }
​
    @GetMapping("/delete")
    public String delete() {
        return "访问了删除接口";
    }
​
    @GetMapping("/update")
    public String update() {
        return "访问了修改接口";
    }
​
    @GetMapping("/select")
    public String select() {
        return "访问了查找接口";
    }
}

引入security依赖

XML 复制代码
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
</dependency>

启动类注解

java 复制代码
@SpringBootApplication
@EnableWebSecurity
public class SecurityApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(SecurityApplication.class, args);
    }
​
}

编写Security配置类

spring security 中提供了专门的配置类WebSecurityConfigurerAdapter,这里需要研究其中的两个方法。

java 复制代码
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // 用于配置认证信息。通过这个方法,你可以定义用户的认证方式,例如内存认证、数据库认证、LDAP 认证等
    }
​
    @Override
    protected void configure(HttpSecurity http) throws Exception {
      // configure(HttpSecurity http):用于配置拦截规则、认证方式、授权方式等。通过这个方法,你可以定义哪些 URL 需要保护,哪些             // URL 是允许匿名访问的,以及如何进行认证和授权。
       
    }
}
java 复制代码
/**
 * 配置用户签名服务 主要是user-details机制
 *
 * @param auth 签名管理器构造器,用于构建用户具体权限控制
 * @throws Exception
 */
protected void configure(AuthenticationManagerBuilder auth) throws Exception;
​
/**
 * 用来配置拦截保护的请求
 *
 * @param http
 * @throws Exception
 */
protected void configure(HttpSecurity http) throws Exception;

针对这2个方法进行重写,完成自定义配置。

java 复制代码
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
​
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("admin").password("123456").authorities("/");
    }
​
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().formLogin();
    }
}

配置密码加密方式

新版本的SpringSecurity密码支持多种加密方式,但是不推荐使用不加密的密码使用,我们在项目中使用方便,可以改为之前的不加密的方式。

java 复制代码
@Bean
PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
    // return new BCryptPasswordEncoder();
}

项目运行

在项目运行之后,访问对应端口,出现了登录的页面,可以使用自己配置的用户名和密码进行登录,登录成功后,则能够访问对应的接口。

但是,我们想要的是对每个接口可以有权限的控制,所以可以把颗粒度继续细分一下。

权限颗粒度细化

  • 权限配置策略

    • 账号whitecamellia_admin能够访问所有的接口

    • 账号whitecamellia_create能够访问新增接口

    • 账号whitecamellia_delete能够访问删除接口

    • 账号whitecamellia_update能够访问更新接口

    • 账号whitecamellia_select能够访问查找接口

  • 修改相关配置类

    java 复制代码
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    ​
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication().withUser("whitecamellia_admin").password("123456").authorities("create", "delete", "update", "select");
            auth.inMemoryAuthentication().withUser("whitecamellia_create").password("123456").authorities("create");
            auth.inMemoryAuthentication().withUser("whitecamellia_delete").password("123456").authorities("delete");
            auth.inMemoryAuthentication().withUser("whitecamellia_update").password("123456").authorities("update");
            auth.inMemoryAuthentication().withUser("whitecamellia_select").password("123456").authorities("select");
        }
    ​
        @Override
        protected void configure(HttpSecurity http) throws Exception {
              http.authorizeRequests()
                    .antMatchers("/order/create").hasAnyAuthority("create")
                    .antMatchers("/order/delete").hasAnyAuthority("delete")
                    .antMatchers("/order/update").hasAnyAuthority("update")
                    .antMatchers("/order/select").hasAnyAuthority("select")
                    .antMatchers("/**").fullyAuthenticated().and().formLogin(); 
        }
    ​
        @Bean
        PasswordEncoder passwordEncoder() {
            return NoOpPasswordEncoder.getInstance();
        }
    }

动态权限分配 - RBAC权限模型

RBAC概述

RBAC(Role-Based Access Control )基于角色的访问控制。

在20世纪90年代期间,大量的专家学者和专门研究单位对RBAC的概念进行了深入研究,先后提出了许多类型的RBAC模型,其中以美国George Mason大学信息安全技术实验室(LIST)提出的RBAC96模型最具有系统性,得到普遍的公认。

RBAC支持公认的安全原则:最小特权原则、责任分离原则和数据抽象原则。

  • 最小特权原则得到支持,是因为在RBAC模型中可以通过限制分配给角色权限的多少和大小来实现,分配给与某用户对应的角色的权限只要不超过该用户完成其任务的需要就可以了。

  • 责任分离原则的实现,是因为在RBAC模型中可以通过在完成敏感任务过程中分配两个责任上互相约束的两个角色来实现,例如在清查账目时,只需要设置财务管理员和会计两个角色参加就可以了。

  • 数据抽象是借助于抽象许可权这样的概念实现的,如在账目管理活动中,可以使用信用、借方等抽象许可权,而不是使用操作系统提供的读、写、执行等具体的许可权。但RBAC并不强迫实现这些原则,安全管理员可以允许配置RBAC模型使它不支持这些原则。因此,RBAC支持数据抽象的程度与RBAC模型的实现细节有关。

数据库建模

  • 扩展RBAC用户角色权限设计方案

    RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成"用户-角色-权限"的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。(如下图)

  • 根据模型创建系统数据库权限模型

    依照RBAC权限模型,给自己的系统创建权限模型。

修改项目动态权限

添加项目依赖
XML 复制代码
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
​
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
配置数据库
XML 复制代码
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///security
spring.datasource.username=root
spring.datasource.password=123456
编写实体类

获取登录人权限

java 复制代码
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
     private SysUserService userService;
   
     @Override
     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       // auth.userDetailsService需要接受一个参数userService,这是一个接口,
       // 以及 passwordEncoder ,passwordEncoder 我们下面有一个获取的方法,可以直接使用
       // userService 需要我们自己写userService,让其继承UserDetailsService,userService实现类需要重写
       // 抽象方法 loadUserByUsername
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
     }
    
     @Override
      protected void configure(HttpSecurity http) throws Exception {
     ......
     }
​
     @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
​
}

service.SysUserService

java 复制代码
public interface SysUserService extends UserDetailsService {
}

Service.impl.SysUserServiceImpl

java 复制代码
@Service
public class SysUserServiceImpl implements UserService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      
        // 该方法的作用就是根据用户名查找出当前用户的所有权限信息,组装成UserDetails 返回
        // 但是要如何查找某个用户的权限呢?这就需要我们再写方法去查询数据库了findByAccount(account)
        return null;
    }
}

SysUserService

java 复制代码
public interface SysUserService extends UserDetailsService {
     UserAuthDTO findAuthsByUserAccount(String account);
}

UserAuthDTO

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserAuthDTO {
        private String userAccount;
        private String userPassword;
        private String authCodes;
        private List<String> authList;
​
        public void authCodesTranceToList () {
             this.authList = Arrays.stream(this.authCodes.split(",")).collect(Collectors.toList());
        }
}
​

SysUserServiceImpl

java 复制代码
@Service
public class SysUserServiceImpl implements UserService {
    @Autowired
    private SysUserMapper userMapper;
​
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
​
        // 该方法的作用就是根据用户名查找出当前用户的所有权限信息,组装成UserDetails 返回
        // 但是要如何查找某个用户的权限信息呢?
​
        UserAuthDTO userAuthDTO = findAuthsByUserAccount(username);
        userAuthDTO.authCodesTranceToList();
        UserDetailDTO userDetailDTO = new UserDetailDTO();
        userDetailDTO.setUsername(userAuthDTO.getUserAccount());
        userDetailDTO.setPassword(userAuthDTO.getUserPassword());
        userAuthDTO.getAuthList().forEach(item->userDetailDTO.getGrantedAuthorityList().add(new SimpleGrantedAuthority(item)));
        return userDetailDTO;
    }
    @Override
    public UserAuthDTO findAuthsByUserAccount(String account) {
​
        return userMapper.findAuthsByUserAccount( account);
    }
    
}

SysUserMapper

java 复制代码
@Mapper
public interface SysUserMapper {
    UserAuthDTO findAuthsByUserAccount(String account);
}
​

数据库查询sql

sql 复制代码
SELECT
    su.user_account,
    su.user_password,
    sa.auth_code 
FROM
    sys_user su
    LEFT JOIN sys_user_role sur ON sur.is_deleted = 0 
    AND sur.user_id = su.user_id
    LEFT JOIN sys_role_auth sra ON sra.is_deleted = 0 
    AND sra.role_id = sur.role_id
    LEFT JOIN sys_auth sa ON sa.is_deleted = 0 
    AND sa.auth_id = sra.auth_id 
WHERE
    su.is_deleted = 0
​

系统中,每个用户是一条数据,所以我们需要将权限分成逗号分隔 的形式。所以要对sql进行再优化,同时替换成传入username查询的形式

sql 复制代码
   SELECT
            su.user_account,
            su.user_password,
--  将分组的结果按照逗号形式分隔开
            GROUP_CONCAT( sa.auth_code ) AS auth_codes
        FROM
            sys_user su
                LEFT JOIN sys_user_role sur ON sur.is_deleted = 0
                AND sur.user_id = su.user_id
                LEFT JOIN sys_role_auth sra ON sra.is_deleted = 0
                AND sra.role_id = sur.role_id
                LEFT JOIN sys_auth sa ON sa.is_deleted = 0
                AND sa.auth_id = sra.auth_id
        WHERE
            su.is_deleted = 0
          AND su.user_account = #{account}
        GROUP BY
            user_account,
            user_password

UserMapper.xml

XML 复制代码
<?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.whitecamellia.security.mapper.UserMapper">
  
    <select id="findAuthsByUserAccount" resultType="com.whitecamellia.security.dto.UserAuthDTO">
        SELECT
            su.user_account,
            su.user_password,
--  将分组的结果按照逗号形式分隔开
            GROUP_CONCAT( sa.auth_code ) AS auth_codes
        FROM
            sys_user su
                LEFT JOIN sys_user_role sur ON sur.is_deleted = 0
                AND sur.user_id = su.user_id
                LEFT JOIN sys_role_auth sra ON sra.is_deleted = 0
                AND sra.role_id = sur.role_id
                LEFT JOIN sys_auth sa ON sa.is_deleted = 0
                AND sa.auth_id = sra.auth_id
        WHERE
            su.is_deleted = 0
          AND su.user_account = #{account}
        GROUP BY
            user_account,
            user_password
    </select>
</mapper>

loadUserByUsername(String username) 返回的是一个UserDetails,所以我们还需要造一个UserDetailDTO

java 复制代码
@Data
public class UserDetailDTO implements UserDetails {
​
    private List<GrantedAuthority> grantedAuthorityList;
    private String username;
    private String password;
    private Boolean accountNonExpired;
    private Boolean accountNonLocked;
    private Boolean credentialsNonExpired;
    private Boolean enabled;
​
    public UserDetailDTO () {
        this.grantedAuthorityList = new ArrayList<>();
        this.accountNonExpired = true;
        this.accountNonLocked = true;
        this.credentialsNonExpired = true;
        this.enabled = true;
    }
​
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return grantedAuthorityList;
    }
​
    @Override
    public String getPassword() {
        return password;
    }
​
    @Override
    public String getUsername() {
        return username;
    }
​
    @Override
    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }
​
    @Override
    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }
​
    @Override
    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }
​
    @Override
    public boolean isEnabled() {
        return enabled;
    }
}

修改WebSecurityConfig

java 复制代码
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
​
    @Autowired
    private SysAuthService authService;
​
    @Autowired
    private SysUserService userService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
​
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }
    
    .......
}

authcode也得是数据库中查询而来,所以继续修改WebSecurityConfig

复制代码
java 复制代码
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
​
    @Autowired
    private SysAuthService authService;
​
    @Autowired
    private SysUserService userService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
​
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }
​
    @Override
    protected void configure(HttpSecurity http) throws Exception {
​
        List<SysAuth> list =  authService.findAll() ;
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
​
        list.forEach(sysAuth -> registry.antMatchers(sysAuth.getAuthUrls()).hasAnyAuthority(sysAuth.getAuthCode()));
        registry.antMatchers("/**").fullyAuthenticated().and().formLogin();
    }
​
    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

SysAuthService

java 复制代码
public interface SysAuthService {
    List<SysAuth> findAll();
​
}

SysAuthServiceImpl

java 复制代码
@Service
public class SysAuthServiceImpl implements SysAuthService {
​
    @Autowired
    private SysAuthMapper sysAuthMapper;
​
    @Override
    public List<SysAuth> findAll() {
        return sysAuthMapper.findAll();
    }
}
复制代码

SysAuthMapper

java 复制代码
@Mapper
public interface SysAuthMapper {
    List<SysAuth> findAll();
}
SysAuthMapper.xml

<?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.whitecamellia.security.mapper.SysAuthMapper">
    <select id="findAll" resultType="com.whitecamellia.security.entity.SysAuth">
        select  * from  sys_auth where is_deleted = 0
    </select>
</mapper>
相关推荐
Chen-Edward20 小时前
有了Spring为什么还有要Spring Boot?
java·spring boot·spring
云创智城-yuncitys20 小时前
SpringCloud 架构在智慧交通路侧停车系统中的实践:从技术落地到城市级服务升级
spring·spring cloud·架构·智慧城市·停车系统·充电系统源码
陈小桔21 小时前
idea中重新加载所有maven项目失败,但maven compile成功
java·maven
小学鸡!21 小时前
Spring Boot实现日志链路追踪
java·spring boot·后端
xiaogg367821 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
逆光的July21 小时前
Hikari连接池
java
微风粼粼21 小时前
eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
java·ide·eclipse
番茄Salad21 小时前
Spring Boot临时解决循环依赖注入问题
java·spring boot·spring cloud
天若有情6731 天前
Spring MVC文件上传与下载全面详解:从原理到实战
java·spring·mvc·springmvc·javaee·multipart
祈祷苍天赐我java之术1 天前
Redis 数据类型与使用场景
java·开发语言·前端·redis·分布式·spring·bootstrap