Spring security学习
1.Spring security简介
Spring security是基于Spring的安全框架。它提供全面的安全性解决方案,同时在Web请求级和方法调用级处理身份确认和授权。在Spring Framework基础上,Spring security充分利用依赖注入和面向切面编程功能,为应用系统提供声明式的安全访问控制功能。是一个轻量级的框架。它与Spring MVC有很好的集成。
1.1 Spring security 核心功能
认证
对于认证来讲,简单的来说就是判断你是谁,对于"你",可以是用户、设备、系统等。
授权(验证)
对于授权来讲,简单的来说就是判断你能干什么,也叫做权限控制,系统中你能做什么操作。
1.2 Spring security 原理
简单来讲就是基于Filter链、Servlet、AOP来实现身份认证和权限认证的功能。
2.Demo讲解
2.1 初体验
1.首先创建项目,maven项目即可,手动导入依赖
<!--指定依赖-->
<dependencies>
<!--web开发-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
2.创建启动类
@SpringBootApplication
public class FirstApplication {
public static void main(String[] args) {
SpringApplication.run(FirstApplication.class,args);
}
}
3.创建Controller
@RestController
public class HelloSecurity {
@RequestMapping("/hello")
public String hello() {
return "hello Spring security";
}
}
4.启动应用,浏览器访问http://localhost:8080/hello
在启动应用时,控制台会输出一个Spring security自动生成的临时密码,用于登录
5.这时我们可以看到,在浏览器访问后,会有一个登录页面
6.登录成功后即可看到Controller返回的字符串
这只是Spring security最简单的应用,登录页面是由Spring security默认生成的,框架在处理请求之前先用AOP做了一个拦截做的身份认证,如果认证成功才能进行请求的处理,随后我们继续往下看
2.1.1 自定义用户名和密码
需要在SpringBoot配置文件中设置登录的用户名和密码。
再次启动项目时即可使用自定义的用户名和密码,也可以看到控制台中并没有生成临时登录密码。
2.1.2 关闭自动验证功能
关闭自动验证功能后,发送请求后不再验证。
//排除security的配置,使其不启用
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class FirstApplication {
public static void main(String[] args) {
SpringApplication.run(FirstApplication.class,args);
}
}
重新启动之后即可发现不需自动验证。
2.2 使用内存中的用户信息
使用WebSecurityConfigurerAdapter
控制安全管理的内容。
继承WebSecurityConfigurerAdapter
,重写方法。实现自定义的认证信息。重写方法为该类中定义的configure(AuthenticationManagerBuilder auth)
方法。
1.新建配置类继承WebSecurityConfigurerAdapter
重写configure(AuthenticationManagerBuilder auth)
方法
//表示当前类是一个配置类(相当于是Spring的xml配置文件),在这个类中方法的返回值是java对象,这些对象是放入到Spring容器中的
@Configuration
//表示启用Spring security安全功能
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
//在方法中来配置用户和密码的信息作为登录的数据
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder pe = passwordEncoder();
auth.inMemoryAuthentication()
.withUser("zhang3")
.password(pe.encode("123456"))
.roles();
auth.inMemoryAuthentication()
.withUser("li4")
.password(pe.encode("000000"))
.roles();
auth.inMemoryAuthentication()
.withUser("admin")
.password(pe.encode("admin"))
.roles();
}
//创建密码加密类
@Bean
public PasswordEncoder passwordEncoder() {
//创建PasswordEncoder对象,实现类是加密算法
return new BCryptPasswordEncoder();
}
}
2.需注意在Spring security5版本之后需要对密码加密
2.2.1 基于内存中用户信息角色认证
基于角色Role的身份认证,同一个用户可能有不同的角色。同时可以开启对方法级别的认证。
1.在配置类上添加@EnableGlobalMethodSecurity(prePostEnabled = true)
注解
@EnableGlobalMethodSecurity:表示启用方法级别的认证
prePostEnabled:boolean 默认是false
true:表示可以使用@PreAuthorize注解和@PostAuthorize注解
2.添加角色
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//定义两个角色 normal,admin
PasswordEncoder pe = passwordEncoder();
auth.inMemoryAuthentication()
.withUser("zhang3")
.password(pe.encode("123456"))
.roles("normal");
auth.inMemoryAuthentication()
.withUser("li4")
.password(pe.encode("000000"))
.roles("normal");
auth.inMemoryAuthentication()
.withUser("admin")
.password(pe.encode("admin"))
.roles("admin","normal");
}
3.方法上添加注解@PreAuthorize
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "使用内存中的信息";
}
//指定normal 和 admin 角色都可以访问的方法
@RequestMapping("/helloUser")
@PreAuthorize(value = "hasAnyRole('admin','normal')")
public String helloUser() {
return "hello 拥有admin和normal的角色";
}
@RequestMapping("/helloAdmin")
@PreAuthorize(value = "hasAnyRole('admin')")
public String helloAdmin() {
return "hello 拥有admin的角色";
}
@RequestMapping("/helloNormal")
@PreAuthorize(value = "hasAnyRole('normal')")
public String helloNormal() {
return "hello 拥有normal的角色";
}
}
4.启动项目分别发请求查询结果
其他结果就不一一测试了,有需要的自己测试一下。
2.3 基于jdbc的用户认证
1)从数据库中获取用户的身份信息(用户名称,密码,角色)。
2)在Srping security框架对象用户信息的表示类是UserDetails
。UserDetails
是一个接口,高度抽象的用户信息类。
public interface UserDetails extends Serializable {
/**
* 角色列表,同一个账户可能会有多种角色
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
* @return the password
*/
String getPassword();
/**
* Returns the username used to authenticate the user
*/
String getUsername();
/**
* 账户是否到期
*/
boolean isAccountNonExpired();
/**
* 账户是否锁定
*/
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
3)User
类是UserDetails
接口的实现类。构造方法有三个参数,分别是UserName、Password和角色列表。
public User(String username, String password,
Collection<? extends GrantedAuthority> authorities) {
this(username, password, true, true, true, true, authorities);
}
4)我们需要向Spring security提供User对象,这个对象的数据来自数据库的查询。
5)实现UserDetailsService
接口并重写UserDetails loadUserByUsername(String username)
,在方法中获取数据库中的用户信息。
2.3.1 pom
<!--SpringBoot-->
<parent>
<artifactId>spring-boot-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.2.6.RELEASE</version>
</parent>
<!--指定依赖-->
<dependencies>
<!--web开发-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
2.3.2 enity
请手动补齐getter和setter方法
@Entity
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//用户名称
private String username;
//密码
private String password;
//角色
private String role;
}
2.3.3 service、impl
public interface UserInfoService {
UserInfo findUserInfo(String username);
}
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private UserInfoDao userInfoDao;
@Override
public UserInfo findUserInfo(String username) {
UserInfo byUsername = userInfoDao.findByUsername(username);
return byUsername;
}
}
2.3.4 dao
public interface UserInfoDao extends JpaRepository<UserInfo,Long> {
//按照username来查询数据库信息
UserInfo findByUsername(String username);
}
2.3.5 init
@Component
public class JdbcInit {
@Autowired
private UserInfoDao userInfoDao;
@PostConstruct
public void init() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
UserInfo u = new UserInfo();
u.setUsername("zhangsan");
u.setPassword(encoder.encode("000000"));
u.setRole("normal");
userInfoDao.save(u);
UserInfo info = new UserInfo();
info.setUsername("admin");
info.setPassword(encoder.encode("admin"));
info.setRole("admin");
userInfoDao.save(info);
}
}
2.3.6 provider
@Component("MyUserDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserInfoDao dao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = null;
UserInfo userInfo = null;
if (username != null) {
userInfo = dao.findByUsername(username);
if (userInfo != null) {
//创建User
List<GrantedAuthority> list = new ArrayList<>();
//角色必须以ROLE_开头
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + userInfo.getRole());
list.add(authority);
//创建User对象
user = new User(userInfo.getUsername(),userInfo.getPassword(),list);
}
}
return user;
}
}
以上代码用了jpa,有表会自动生成,没有接触过的可以稍微了解下就可使用。
整体流程是
(Controller -> service -> serviceImpl -> dao) => userInfo
(userInfo.getUsername + userInfo.getPassword + userInfo.getRoles) => user
3.基于角色的权限
3.1 认证和授权
authentication
:认证,认证访问者是谁。一个用户或者一个系统是不是当前要访问的系统中的有效用户。
authorization
:授权,访问者能做什么。
比如张三用户要访问公司的系统,系统要判断张三是不是系统中的有效用户(认证过程),在是有效用户的基础上,判断张三用户能使用系统中的什么功能(授权过程)。
3.2 RBAC是什么?
RBAC是基于角色的访问控制,在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予给用户,这样的权限设计很清楚,管理起来很方便。
其基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。这样做的好处是,不必在每次创建用户时都进行分配权限的操作,只要分配用户相应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限管理,减少系统的开销。
- 权限:能对资源的操作,比如增加、删除、修改、查看等。
- 角色:自定义的,表示权限的集合。一个角色可以有多个权限。
3.2.1 RBAC设计表
1.用户表:用户认证(登录用的表)
用户名,密码,登陆时间,是否启用等
2.角色表:定义角色信息
角色名称,角色的描述
3.用户和角色的关系表:角色和用户是多对多的关系
一个用户可以有多个角色,一个角色也可以有多个用户
4.权限表、角色和权限的关系表
角色可以有哪些权限
3.3 认证中的接口和类
3.3.1 UserDetails
UserDetails
接口,表示用户信息的
public interface UserDetails extends Serializable {
/**
* 角色列表,同一个账户可能会有多种角色
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
* @return the password
*/
String getPassword();
/**
* Returns the username used to authenticate the user
*/
String getUsername();
/**
* 账户是否到期
*/
boolean isAccountNonExpired();
/**
* 账户是否锁定
*/
boolean isAccountNonLocked();
/**
* 证书是否过期
*/
boolean isCredentialsNonExpired();
/**
* 是否启用
*/
boolean isEnabled();
}
只有上述四个返回值为boolean类型都为真时,才能正确登录账号
3.3.2 User
User
类是UserDetails
接口的实现类。
里面包含两个构造方法,传参与接口一致。
public User(String username, String password,
Collection<? extends GrantedAuthority> authorities) {
this(username, password, true, true, true, true, authorities);
}
public User(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
if (((username == null) || "".equals(username)) || (password == null)) {
throw new IllegalArgumentException(
"Cannot pass null or empty values to constructor");
}
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
}
可以自定义类实现UserDetails
接口,作为系统中的用户类。这个类可以交给Spring security使用。
3.3.3 UserDetailsService
这个接口的主要作用是获取用户的信息,得到的是UserDetails对象
,一般项目中,都需要自定义类来实现UserDetailsService接口
从数据库中获取数据,一个方法需要实现。
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
根据用户名称,获取用户信息。
也可不自定义实现类,使用Spring security提供的实现类。
3.3.4 UserDetailsManager
UserDetailsService
定义了根据用户名获取用户详情的能力,UserDetailsManager
对其功能进行了拓展。
public interface UserDetailsManager extends UserDetailsService {
/**
* Create a new user with the supplied details.
*/
void createUser(UserDetails user);
/**
* Update the specified user.
*/
void updateUser(UserDetails user);
/**
* Remove the user with the given login name from the system.
*/
void deleteUser(String username);
/**
* Modify the current user's password. This should change the user's password in the
* persistent user repository (datbase, LDAP etc).
*
* @param oldPassword current password (for re-authentication if required)
* @param newPassword the password to change to
*/
void changePassword(String oldPassword, String newPassword);
/**
* Check if a user with the supplied login name exists in the system.
*/
boolean userExists(String username);
}
- 创建用户账号 :
void createUser(UserDetails user)
- 更新用户账号 :
void updateUser(UserDetails user)
- 删除用户账号 :
void deleteUser(String username)
- 修改用户账号密码 :
void changePassword(String oldPassword, String newPassword)
- 判断用户账号是否存在 :
boolean userExists(String username)