1·介绍
Spring Security是为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架(包含: 认证 , 授权两个方面)。它提供了完整的安全性解决方案,可以在Web请求级别和方法调用级别处理身份认证和授权充分利用了Spring IOC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能。
2·入门教程
2.1 工程搭建
pom.xml 创建测试工程并引入依赖 ;
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/>
</parent><dependencies>
<!-- web起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springBoot整合Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
B: 引导类
@SpringBootApplication
public class MySecurityApplication {
public static void main(String[] args) {
SpringApplication.run(MySecurityApplication.class,args);
}
}
C:Controller
@RestController
public class UserController {
@GetMapping("/hello")
public String hello(){
return "hello security";
}
@GetMapping("/say")
public String say(){
return "say security";
}
@GetMapping("/register")
public String register(){
return "register security";
}
}
D:测试:访问http://localhost:8080/hello
会自动拦截,并跳转到登录页面(SpringSecurity提供),登录之后才可以访问; 而登录的用户名和密码都是SpringSecurity中内置的默认的用户名密码, 用户名为user , 密码为控制台输出的一段随机数;
填写账号密码即可登录。此处密码为默认密码,需要自己按需修改
# 我们也可在配置文件中配置用户名和密码,实际开发中密码不应明文配置
spring.security.user.name=user
spring.security.user.password=6666
2.2 认证配置
【1】自定义合法登录用户信息
如果我们想指定系统的访问用户名及密码, 可以通过配置的形式声明 , 声明一个 UserDetailsService 类型的Bean。
@Configuration
@EnableWebSecurity//开启web安全设置生效
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 构建认证服务,并将对象注入spring IOC容器,用户登录时,会调用该服务进行用户合法信息认证
* @return
*/
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
//构建用户
UserDetails u1 = User
.withUsername("itcast")
.password("{noop}123456") //{noop}意味着密码不以密文形式出现
.authorities("P1", "ROLE_ADMIN").build(); // p1,role_admin 皆为权限类型
UserDetails u2 = User
.withUsername("itheima")
.password("{noop}123456")
.authorities("O1", "ROLE_SELLER").build();
inMemoryUserDetailsManager.createUser(u1);
inMemoryUserDetailsManager.createUser(u2);
return inMemoryUserDetailsManager;
}
}
2.3授权配置
1). 编码方式
@Configuration
@EnableWebSecurity//用于启用全局的方法安全性。
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
inMemoryUserDetailsManager.createUser(User.withUsername("itcast").password("{noop}123456").authorities("P1","ROLE_ADMIN").build());
inMemoryUserDetailsManager.createUser(User.withUsername("itheima").password("{noop}123456").authorities("O1","ROLE_SELLER").build());
return inMemoryUserDetailsManager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.and()
.logout()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/register").permitAll() //不登录即可访问
.antMatchers("/hello").hasAuthority("P1") //具有P1权限才可以访问
.antMatchers("/say").hasRole("SELLER") //具有SELLER 角色才可以访问
.anyRequest().authenticated(); //其他的登录之后就可以访问
}
}
TIPS:
1·CSRF(Cross-site request forgery)跨站请求伪造,也被称为"One Click Attack"或者 Session Riding,通常缩写为 CSRF 或者 XSRF,是一种对网站的恶意利用。
2·Spring Security 提供了几个注解,如 @PreAuthorize
, @PostAuthorize
, @Secured
等,这些注解可以应用于方法上,以定义方法调用前后的安全性要求。
@PreAuthorize
: 在方法调用前进行权限检查。@PostAuthorize
: 在方法调用后进行权限检查,通常用于返回值的过滤。@Secured
: 用于简单的基于角色的安全性检查。
2). 注解方式
在控制方法/URL的权限时, 可以通过配置类中配置的方式进行控制, 也可以使用 注解 @PreAuthorize 来进行控制,
@GetMapping("/hello")
@PreAuthorize("hasAuthority('P5')")
public String hello(){
return "hello security";
}@GetMapping("/say")
@PreAuthorize("hasRole('SELLER')")
public String say(){
return "say security";
}
3)问题所在
A. 密码采用的是明文的,不安全 ;
B. 用户名/密码直接通过程序硬编码,不够灵活 ;
因此,考虑使用密码加密,让敏感数据变得安全及灵活
2.4 密码加密
2.4.1 可逆加密算法:加密后, 密文可以反向解密得到密码原文;
1). 对称加密
指加密和解密使用相同密钥的加密算法。
优点: 对称加密算法的优点是算法公开、计算量小、加密速度快、加密效率高。 缺点: 没有非对称加密安全。
常见的对称加密算法:DES、3DES、DESX、Blowfish、RC4、RC5、RC6和AES
2). 非对称加密
指加密和解密使用不同密钥的加密算法,也称为公私钥加密。假设两个用户要加密交换数据,双方交换公钥,使用时一方用对方的公钥加密,另一方即可用自己的私钥解密。
加密和解密:
-
私钥加密,持有私钥或公钥才可以解密
-
公钥加密,持有私钥才可解密
优点: 非对称加密与对称加密相比,其安全性更好; 缺点: 非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密。
简单理解:A向B发送数据,需要用到B的公钥进行加密,加密后的密文发送给B,B使用自己的私钥即可解密。 安全性更高,但是速度慢。
2.4.2 不可逆加密算法
一旦加密就不能反向解密得到密码原文 。通常用于密码数据加密。
常见的不可逆加密算法有: MD5 、SHA、HMAC
2.3.3 MD5与Bcrypt
1).MD5
MD5是比较常见的加密算法,广泛的应用于软件开发中的密码加密,通过MD5生成的密文,是无法解密得到明文密码的。但是现在在大数据背景下,很多的网站通过大数据可以将简单的MD5加密的密码破解。例如通过彩虹表暴力破解。
可以在用户注册时,限制用户输入密码的长度及复杂度,从而增加破解难度。
2). Bcrypt
用户表的密码通常使用 MD5 等不可逆算法加密后存储,为防止彩虹表破解,会先使用一个特定的字符串(如域名)加密,然后再使用一个随机的 salt(盐值)加密。 特定字符串是程序代码中固定的,salt 是每个密码单独随机,一般给用户表加一个字段单独存储,比较麻烦。
BCrypt 算法将 salt 随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理 salt 问题。
验证程序:
~加密密码
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
for (int i = 0; i < 10; i++) {
System.out.println(bCryptPasswordEncoder.encode("123456"));
}
~得到结果:
$2a$10$C6YynRFeJsSy7D/kg3d30OWnuwko7KQIEK5JrX0mWND.vuz2TqwpK
$2a$10$aSJfxH2oBtopFMbkMJ.PQ.sbSBXJH9g.9bv1mCyte/BtcU9VTs7lG
$2a$10$nVoB.eV5Uhc9FNUC36Pn0OosGh7aKlp7Sjfxaiml8NCSJ6PX1q6.m
$2a$10$2RM3mRNjz1LoZ5eeLdj.Hu15vlWIIj2zJC09vwTevBlIi5rjJStam
$2a$10$5bTOnk9hITzJd6EJMsX47uX9UdjASrPl4sEG6GJjfZGTk9f/37Q/q
$2a$10$0.PfbDnlBBWzpsw8PBjDcOtjUnwRgbSPCmhrAg5APUWor/4eQ0VVy
$2a$10$jfpPFH0DuTENicQ6vv38BeBO5YUXolS03bk1Ti3fmCrhQmBL1hYj.
$2a$10$pxR.jhV79v1po1vbhWi8CudiLTaw.W5lpl.E/dOEodfGXCJIPrJ4i
$2a$10$MvWb5LvCojzloYX9QLA8buL2Mkci2qaiMIdIH2PzGDssHUzEU21R2
$2a$10$7HLclohKrBZHvsBLDm8U/eTqe0KP2qV4F9d6jNvP4vO0pJG4wmeQy
~验证密码
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
boolean matches =
bCryptPasswordEncoder.matches("123456", "$2a$10$c2sZT/LtM1ExWfZjO0yIPeTGSqMSlX7oi.SvliMbeZpT9Y4qIBDue");
System.out.println(matches);//返回值为true, 则代表验证通过; 反之, 验证不通过
2.5 程序完善
2.5.1 密码加密处理
在配置类 SecurityConfig中配置Bean:
//配置密码加密器 ;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
2.5.2 动态查询用户
创建简易数据库
create database security_demo default charset=utf8mb4;
use security_demo;CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL,
`password` varchar(100) DEFAULT NULL,
`roles` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;INSERT INTO `tb_user` VALUES (1, 'itcast', '$2a$10$f43iK9zKD9unmgLao1jqI.VluZ.Rr/XijizVEA73HeOu9xswaUBXC', 'ROLE_ADMIN,P1');
INSERT INTO `tb_user` VALUES (2, 'itheima', '$2a$10$f43iK9zKD9unmgLao1jqI.VluZ.Rr/XijizVEA73HeOu9xswaUBXC', 'ROLE_SELLER,O1');
2.5.3 导入依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency><dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency
2.5.4 application.yml文件
# 应用名称
spring.application.name=security_test
# 应用服务 WEB 访问端口
server.port=8080# 配置用户名和密码
#spring.security.user.name=user
#spring.security.user.password=6666#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mapper/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=com.itheima.security.pojo# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接地址
spring.datasource.url=jdbc:mysql://192.168.188.130:3306/security_demo?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
TIPS: 关于实体类与Mapper类,可以考虑使用MyBatisX插件来实现
2.5.6实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TbUser implements Serializable {
private Integer id;
private String username;
private String password;
private String roles;private static final long serialVersionUID = 1L;
}
2.5.7 mapper
@Mapper
public interface TbUserMapper {int deleteByPrimaryKey(Long id);
int insert(TbUser record);
int insertSelective(TbUser record);
TbUser selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(TbUser record);
int updateByPrimaryKey(TbUser record);
TbUser findByUserName(@Param("userName") String userName);}
xml:篇幅有限省略。
2.5.8 自定义需要的实现类
package com.itheima.security.config;
import com.itheima.security.mapper.TbUserMapper;
import com.itheima.security.pojo.TbUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;import java.util.List;
@Component
public class UserDetailsServiceImpl implements UserDetailsService {@Autowired
private TbUserMapper tbUserMapper;@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
TbUser user = tbUserMapper.findByUserName(userName);
if (user==null) {
throw new UsernameNotFoundException("用户不存在");
}
//构建认证明细对象
//获取用户权限
List<GrantedAuthority> list = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles());
User user1 = new User(user.getUsername(),user.getPassword(),list);
return user1;
}
}
该方法只适用于权限以逗号分隔的
AuthorityUtils.commaSeparatedStringToAuthorityList():
这是一个来自Spring Security的工具方法,用于将逗号分隔的字符串转换为GrantedAuthority
对象的列表。GrantedAuthority
是Spring Security中表示权限或角色的接口。