文章目录
一、SpringSecurity 框架简介
1. 概要
2. Spring Security 与 Shiro 对比
2.1 Spring Security
官网地址:https://spring.io/projects/spring-security
2.2 SpringSecurity特点
- 对身份验证和授权的全面且可扩展的支持。
- 防止会话固定、点击劫持、跨站点请求伪造等攻击。
- 专门为Web开发而设计。
- 旧版本不能脱离Web环境使用。
- 新版本对整个框架进行了分层抽取,分成了核心模块和Web模块。单独引入核心模块就可以脱离Web环境。
- 重量级。
2.3 Shiro
2.4 Shiro特点
- 轻量级。Shiro主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。
- 通用性。
- 好处:不局限于Web环境,可以脱离Web环境使用。
- 缺陷:在Web环境下一些特定的需求需要手动编写代码定制。
2.5 小结
3. SpringSecurity项目模块和依赖
二、SpringSecurity 入门案例
1. 添加相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. 运行项目
3. 权限管理中的相关概念
(1)主体(principal)
(2)认证(authentication)
(3)授权(authorization)
4. 添加一个Controller进行访问
@Controller
public class IndexController {
@GetMapping("index")
@ResponseBody
public String index() {
return "success";
}
}
三、SpringSecurity 基本原理
1. 三个重要过滤器
(1)FilterSecurityInterceptor
(2)ExceptionTranslationFilter
(3)UsernamePasswordAuthenticationFilter
2. UserDetailsService 接口
// 加载用户特定数据的核心接口。
// 它在整个框架中用作用户 DAO,并且是 DaoAuthenticationProvider 使用的策略。
// 该接口只需要一种只读方法,这简化了对新数据访问策略的支持。
public interface UserDetailsService {
// 根据用户名定位用户。在实际实现中,搜索可能区分大小写,也可能不区分大小写,具体取决于实现实例的配置方式。
//在这种情况下,返回的 UserDetails 对象的用户名可能与实际请求的用户名不同。
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
2.1 返回值UserDetails
// 提供核心用户信息。
// 出于安全目的,Spring Security 不直接使用实现。它们只是存储用户信息,这些信息随后被封装到 Authentication 对象中。
// 这允许将非安全相关的用户信息(例如电子邮件地址、电话号码等)存储在方便的位置。
// 具体的实现必须特别注意确保为每个方法详细说明的非空合同得到执行。请参阅用户以获取参考实现(您可能希望在代码中扩展或使用)。
// 另请参阅:UserDetailsService、UserCache
public interface UserDetails extends Serializable {
// 表示获取登录用户所有权限
Collection<? extends GrantedAuthority> getAuthorities();
// 表示获取密码
String getPassword();
// 表示获取用户名
String getUsername();
// 表示判断账户是否过期
boolean isAccountNonExpired();
// 表示判断账户是否被锁定
boolean isAccountNonLocked();
// 表示凭证{密码}是否过期
boolean isCredentialsNonExpired();
// 表示当前用户是否可用
boolean isEnabled();
}
// 对 UserDetailsService 检索的核心用户信息进行建模。
// 开发人员可以直接使用这个类,子类化它,或者从头开始编写自己的 UserDetails 实现。
// equals 和 hashcode 实现仅基于 username 属性,因为其目的是查找相同的用户主体对象(例如,在用户注册表中)将匹配对象代表相同用户的位置,而不仅仅是当所有属性 (权限,例如密码)是相同的。请注意,此实现不是一成不变的。
// 它实现了 CredentialsContainer 接口,以允许在身份验证后删除密码。
// 如果您将实例存储在内存中并重用它们,这可能会导致副作用。如果是这样,请确保每次调用 UserDetailsService 时都返回一个副本。
public class User implements UserDetails, CredentialsContainer {
...
// DaoAuthenticationProvider 所需的详细信息构造用户。
// 参数:
// username - 提供给 DaoAuthenticationProvider 的用户名,默认情况下必须叫username,否则无法接收
// password - 应该提供给 DaoAuthenticationProvider 的密码
// enabled - 如果用户启用,则设置为 true
// accountNonExpired - 如果帐户未过期,则设置为 true
// credentialsNonExpired - 如果凭据设置为 true尚未过期
// accountNonLocked - 如果帐户未锁定,则设置为 true
// 权限 - 如果调用者提供正确的用户名和密码并且用户已启用,则应授予调用者的权限。不为空。
// 抛出:IllegalArgumentException – 如果空值作为参数或作为 GrantedAuthority 集合中的元素传递
public User(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities) {
Assert.isTrue(username != null && !"".equals(username) && password != null,
"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));
}
...
}
3. PasswordEncoder 接口
// 用于编码密码的服务接口。首选实现是 BCryptPasswordEncoder。
public interface PasswordEncoder {
// 对原始密码进行编码。
// 通常,一个好的编码算法应用 SHA-1 或更大的散列与 8 字节或更大的随机生成的盐相结合。
String encode(CharSequence rawPassword);
// 验证从存储中获得的编码密码与提交的原始密码在编码后是否匹配。
// 如果密码匹配,则返回 true,否则返回 false。
// 储的密码本身永远不会被解码。
//参数:
// rawPassword - 编码和匹配的原始密码
// encodedPassword - 存储中要与之比较的编码密码
// 返回:如果原始密码在编码后与存储中的编码密码匹配,则为 true
boolean matches(CharSequence rawPassword, String encodedPassword);
/**
* Returns true if the encoded password should be encoded again for better security,
* else false. The default implementation always returns false.
* @param encodedPassword the encoded password to check
* @return true if the encoded password should be encoded again for better security,
* else false.
*/
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
@Test
public void test01() {
// 创建密码解析器
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
// 对密码进行加密
String password= bCryptPasswordEncoder.encode("abc123");
// 打印加密之后的数据
System.out.println("加密之后数据:\t" + password);
//判断原字符加密后和加密之前是否匹配
boolean result = bCryptPasswordEncoder.matches("abc123", password);
// 打印比较结果
System.out.println("比较结果:\t" + result);
}
加密之后数据: $2a$10$04yKFU9ETE9JWWZ/obdeiOerJOWFURPnQBZDoQy65ZCNYPE2IWX06
比较结果: true