SpringSecurity

目录

引入依赖

编写请求对应接口

引入security依赖

启动类注解

编写Security配置类

配置密码加密方式

项目运行

权限颗粒度细化

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

RBAC概述

数据库建模

修改项目动态权限

添加项目依赖

配置数据库

编写实体类

获取登录人权限


引入依赖

       
 <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>

编写请求对应接口

@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依赖

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

启动类注解

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

编写Security配置类

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

@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 是允许匿名访问的,以及如何进行认证和授权。
       
    }
}
/**
 * 配置用户签名服务 主要是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个方法进行重写,完成自定义配置。

@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密码支持多种加密方式,但是不推荐使用不加密的密码使用,我们在项目中使用方便,可以改为之前的不加密的方式。

@Bean
PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
    // return new BCryptPasswordEncoder();
}

项目运行

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

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

权限颗粒度细化

  • 权限配置策略

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

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

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

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

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

  • 修改相关配置类

    @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,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。(如下图)

    img

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

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

    image-20210721101133252

修改项目动态权限

添加项目依赖
        <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>
配置数据库
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///security
spring.datasource.username=root
spring.datasource.password=123456
编写实体类

image-20230416173634545

获取登录人权限

@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

public interface SysUserService extends UserDetailsService {
}

Service.impl.SysUserServiceImpl

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

SysUserService

public interface SysUserService extends UserDetailsService {
     UserAuthDTO findAuthsByUserAccount(String account);
}

UserAuthDTO

@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

@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

@Mapper
public interface SysUserMapper {
    UserAuthDTO findAuthsByUserAccount(String account);
}
​

数据库查询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查询的形式

   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

image-20230416145006322

UserMapper.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

@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

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

@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

public interface SysAuthService {
    List<SysAuth> findAll();
​
}

SysAuthServiceImpl

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

SysAuthMapper

@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>
  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

White-Camellia

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值