IDEA-Spring Security安全管理-自定义用户认证

 针对项目的安全管理,Spring家族提供了安全框架Spring Security,它是一个基于Spring生态圈的,用于提供安全访问控制解决问题的框架。
常见的安全管理功能如下:
 	1.MVC Security是Spring Boot整合Spring MVC搭建Web应用的安全管理框架
 	2.WebFlux Security是Spring Boot整合Spring WebFlux搭建Web应用的安全管理,继承了其他安全功能优点,有可能越来越流行
 	3.OAuth2是大型项目的安全管理框架,可实现第三方认证、单点登录等功能,目前SpringBoot版文还不支持该框架 
 	4.Actuator Security用于对项目的一些运行环境提供安全监控。

Spring Security使用体验

  • Spring Security的安全管理有两个重要概念,分别是Authentication(认证)和Authorization(授权)。
    • 认证即确认用户是否登录,并对用户登陆进行管控
    • 授权即确定用户所拥有的的功能权限,并对用户权限进行管控
基础环境搭建
  • 整合MCV Security安全管理功能,实现Authentication(认证)和Authorization(授权)功能
  • 文件置于templates文件夹下:有需要静态资源的朋友可以评论我发你(太多了就不放在这儿了)
    在这里插入图片描述
  • 编写Web控制类
@Controller
public class FileController {
    // 影片详情映射
    @GetMapping("/detail/{type}/{path}")
    public String toDetail(@PathVariable("type")String type,
                           @PathVariable("path")String path){
        return "detail/" + type+ "/" + path;
    }
}
至此准备工作结束,我们实现了一个传统的Web项目,有跳转等。
开启安全管理效果测试
  • 添加依赖启动器
# 一旦项目引入该依赖启动器,MVC Securityhe和WebFlux Security负责的安全功能会立即生效(后者生效的前提是项目为WebFlux Web项目)
 		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
  • 项目启动测试
    • 项目启动时会在控制台自动生成一个安全密码(security password,该密码每次启动项目都是随机生成的)

    • 启动后,Spring Security会自带一个默认的登录页面,帐号为user,密码为生成的passord
      在这里插入图片描述

    • 尽管有登陆认证,但是这种默认安全管理方式存在诸多问题。(唯一默认登录用户user、密码随机生成过于暴露、登录页面不精美等)

MVC Security安全配置介绍

  • MVC Security安全管理功能的默认安全配置是在SecurityAutoConfiguration和UserDetailsServiceAutoConfiguration中实现的。
    • SecurityAutoConfiguration会导入并自动化配置SpringBootWebSecurityConfiguration用于启动Web安全管理
    • UserDetailsServiceAutoConfiguration用于配置用户身份信息
  • WebSecurityConfigurerAdapter的主要方法:
    • configure(AuthenticationManagerBuilder auth): 定制用户认证管理员来实现用户认证
    • configure(HttpSecurity http): 定制基于HTTP请求的用户访问控制

自定义用户认证

通过自定义WebSecurityConfigurerAdapter类型的Bean组件,并重写configure(AuthenticationManagerBuilder auth)方法,可以自定义用户认证
三个常用的自定义用户认证:
	1.In-Memory Authentication(内存身份认证)
	2.JDBC Authentication(JDBC身份认证)
	3.UserDetailsService(身份认证)

内存身份认证

  • 主要用于Security安全认证体验和测试
  • 自定义WebSecurityConfigurerAdapter配置类
@EnableWebSecurity //开启Security安全支持
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
// @EnableWebSecurity 为一组合注解 = @Configuration、@Import、@EnableGlobalAuthentication
  • 使用内存进行身份认证
    • 自定义的SecurityConfig类中重写configure(AuthenticationManagerBuilder auth)方法
@EnableWebSecurity //开启Security安全支持
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //密码需要设置编码器
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        //1.使用内存用户信息,作为测试使用
        auth.inMemoryAuthentication().passwordEncoder(encoder)
                .withUser("tangou").password(encoder.encode("123456")).roles("common")
                .and()
                .withUser("张三").password(encoder.encode("123456")).roles("vip");
    }
}
  • 效果测试-通过
    • 在实际开发中,用户都是在页面注册和登录时进行认证管理的,而非在程序内部使用内存管理的方式手动控制注册用户,故该种方式无法用于实际生产。

JDBC身份认证

  • JDBC Authentication(JDBC身份认证)是通过JDBC连接数据库对已有用户身份进行认证
  • 数据准备
drop table if exists ‘t_customer’;
//创建表t_customer插入数据
create table t_customer(
	 //Security在进行用户查询时先通过username定位是否存在唯一用户的
	 id int(20) not null auto_increment,
	 username varchar(200) default null,
	 password varchar(200) default null,
	 //必须额外定义一个tinyint类型的字段,对应boolean类型,用于校验用户身份是否合法
	 valid tinyint(1) not null default '1',
	 primary key(id)
)ENGINE=InnoDB auto_increment=4 default charset=utf8;
//$2a$10$YUthO3FOocQFxBgMMke2ROV2MKqAJwHEb8VeVhgcy4oZkx5S/zTHm 对应123456通过BCryptPasswordEncoder 加密后的形式
insert into t_customer values('1','tangou','$2a$10$YUthO3FOocQFxBgMMke2ROV2MKqAJwHEb8VeVhgcy4oZkx5S/zTHm','1');
insert into t_customer values('2','张三','$2a$10$YUthO3FOocQFxBgMMke2ROV2MKqAJwHEb8VeVhgcy4oZkx5S/zTHm','1');
//创建表t_authority插入数据
drop table if exists ‘t_authority’;
create table t_authority(
	id int(20) not null auto_increment,
	authority varchar(20) default null,
	 primary key(id)
)ENGINE=InnoDB auto_increment=3 default charset=utf8;
//权限表:权限值必须带有“ROLE_”的前缀,默认的用户角色值是对应权限制去掉“ROLE_”前缀
insert into t_authority values('1','ROLE_common');
insert into t_authority values('2','ROLE_vip');
//创建表t_customer_authority并插入相关数据
create table t_customer_authority(
	id int(20) not null auto_increment,
	customer_id int(20) default null,
	authority_id int(20) default null,
	 primary key(id)
)ENGINE=InnoDB auto_increment=5 default charset=utf8;
insert into t_customer_authority values('1','1','1');
insert into t_customer_authority values('2','2','2');
  • 添加JDBC连接数据库的依赖启动器
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
  • 进行数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/springdata?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=admin
  • 使用JDBC进行身份验证
@EnableWebSecurity //开启Security安全支持
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private DataSource dataSource;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //密码需要设置编码器
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        //1.使用内存用户信息,作为测试使用
//        auth.inMemoryAuthentication().passwordEncoder(encoder)
//                .withUser("tangou").password(encoder.encode("123456")).roles("common")
//                .and()
//                .withUser("张三").password(encoder.encode("123456")).roles("vip");
        //2.使用JDBC进行身份认证
        String userSQL = "select username,password,valid from t_customer "+"where username=?";
        String authoritySQL = "select c.username,a.authority from t_customer c,"
                +"t_authority a,t_customer_authority ca where "+
                "ca.customer_id=c.id and ca.authority_id=a.id and username=?";
        auth.jdbcAuthentication().passwordEncoder(encoder)
                .dataSource(dataSource)
                .usersByUsernameQuery(userSQL)
                .authoritiesByUsernameQuery(authoritySQL);
    }
}
  • 效果测试-与注释的内存身份认证效果相同

UserDetailService身份认证

对于流量较大的项目来说,频繁地使用JDBC进行数据库查询认证不仅麻烦,而且会降低网站响应速度。
  • 核心代码如下
@Service
public class CustomerService {
    @Autowired
    private CustomerRepository customerRepository;
    @Autowired
    private AuthorityRepository authorityRepository;
    @Autowired
    private RedisTemplate redisTemplate;

    // 业务控制:使用唯一用户名查询用户信息
    public Customer getCustomer(String username){
        Customer customer=null;
        Object o = redisTemplate.opsForValue().get("customer_"+username);
        if(o!=null){
            customer=(Customer)o;
        }else {
            customer = customerRepository.findByUsername(username);
            if(customer!=null){
                redisTemplate.opsForValue().set("customer_"+username,customer);
            }
        }
        return customer;
    }
    // 业务控制:使用唯一用户名查询用户权限
    public List<Authority> getCustomerAuthority(String username){
        List<Authority> authorities=null;
        Object o = redisTemplate.opsForValue().get("authorities_"+username);
        if(o!=null){
            authorities=(List<Authority>)o;
        }else {
            authorities=authorityRepository.findAuthoritiesByUsername(username);
            if(authorities.size()>0){
                redisTemplate.opsForValue().set("authorities_"+username,authorities);
            }
        }
        return authorities;
    }
}
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private CustomerService customerService;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 通过业务方法获取用户及权限信息
        Customer customer = customerService.getCustomer(s);
        List<Authority> authorities = customerService.getCustomerAuthority(s);
        // 对用户权限进行封装
        List<SimpleGrantedAuthority> list = authorities.stream().map(authority -> new SimpleGrantedAuthority(authority.getAuthority())).collect(Collectors.toList());
        // 返回封装的UserDetails用户详情类
        if(customer!=null){
            UserDetails userDetails= new User(customer.getUsername(),customer.getPassword(),list);
            return userDetails;
        } else {
            // 如果查询的用户不存在(用户名不存在),必须抛出此异常
            throw new UsernameNotFoundException("当前用户不存在!");
        }
    }
}
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //  密码需要设置编码器
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
    	// 3、使用UserDetailsService进行身份认证
		auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
	}
  • 测试效果如上面两种
  • (测试前记得开启redis服务,有需要整个代码的可以评论)
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值