1. 简介
一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权 (Authorization)两个部分,这两点也是 Spring Security 重要核心功能。
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问 该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认 证过程。通俗点说就是系统认为用户是否能登录。
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户 所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以 进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的 权限。通俗点讲就是系统判断用户是否有权限去做某些事情。
2. 基本原理
SpringSecurity 本质是一个过滤器链:
从启动是可以获取到过滤器链:
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFil
ter
org.springframework.security.web.context.SecurityContextPersistenceFilter
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
org.springframework.security.web.session.SessionManagementFilter
org.springframework.security.web.access.ExceptionTranslationFilter
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
代码底层流程:重点看三个过滤器:
-
FilterSecurityInterceptor:是一个方法级的权限过滤器, 基本位于过滤链的最底部。
super.beforeInvocation(fi) 表示查看之前的 filter 是否通过。
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());表示真正的调用后台的服务。 -
ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常
-
UsernamePasswordAuthenticationFilter :对/login 的 POST 请求做拦截,校验表单中用户 名,密码。
2.1 UserDetailsService 讲解
● 当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中 账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。
● 如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可。接口定义如下:
- 返回值 UserDetails
这个类是系统默认的用户“主体” 。
// 表示获取登录用户所有权限
Collection<? extends GrantedAuthority> getAuthorities();
// 表示获取密码
String getPassword();
// 表示获取用户名
String getUsername();
// 表示判断账户是否过期
boolean isAccountNonExpired();
// 表示判断账户是否被锁定
boolean isAccountNonLocked();
// 表示凭证{密码}是否过期
boolean isCredentialsNonExpired();
// 表示当前用户是否可用
boolean isEnabled();
以下是 UserDetails 实现类
以后我们只需要使用 User 这个实体类即可!
2. 方法参数 username
表示用户名。此值是客户端表单传递过来的数据。默认情况下必须叫 username,否则无 法接收。
2.2 PasswordEncoder 接口讲解
// 表示把参数按照特定的解析规则进行解析
String encode(CharSequence rawPassword);
/** 表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹
配,则返回 true;如果不匹配,则返回 false。
第一个参数表示需要被解析的密码。第二个参数表示存储的密码。*/
boolean matches(CharSequence rawPassword, String encodedPassword);
/** 表示如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回
false。默认返回 false。*/
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
接口实现类
BCryptPasswordEncoder 是 Spring Security 官方推荐的密码解析器,平时多使用这个解析器。 BCryptPasswordEncoder 是对 bcrypt 强散列方法的具体实现。是基于 Hash 算法实现的单 向加密。可以通过 strength 控制加密强度,默认 10。
● 查用方法演示
@Test
public void test01(){
// 创建密码解析器
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
// 对密码进行加密
String atguigu = bCryptPasswordEncoder.encode("atguigu");
// 打印加密之后的数据
System.out.println("加密之后数据:\t"+atguigu);
//判断原字符加密后和加密之前是否匹配
boolean result = bCryptPasswordEncoder.matches("atguigu", atguigu);
// 打印比较结果
System.out.println("比较结果:\t"+result);
}
3. SpringSecurity Web 权限方案
制作登录实体类
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Override
public UserDetails loadUserByUsername(String s) throws
UsernameNotFoundException {
QueryWrapper<Users> wrapper = new QueryWrapper();
wrapper.eq("username",s);
Users users = usersMapper.selectOne(wrapper);
if(users == null) {
throw new UsernameNotFoundException("用户名不存在!");
}
System.out.println(users);
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User(users.getUsername(),
new BCryptPasswordEncoder().encode(users.getPassword()),auths);
}
}
3.1 设置未授权的请求跳转到登录页
@Override
protected void configure(HttpSecurity http) throws Exception {
// 配置认证
http.formLogin()
.loginPage("/index") // 配置哪个 url 为登录页面
.loginProcessingUrl("/login") // 设置哪个是登录的 url。
.successForwardUrl("/success") // 登录成功之后跳转到哪个 url
.failureForwardUrl("/fail");// 登录失败之后跳转到哪个 url
http.authorizeRequests()
.antMatchers("/layui/**","/index") //表示配置请求路径
.permitAll() // 指定 URL 无需保护。
.anyRequest() // 其他请求
.authenticated(); //需要认证
// 关闭 csrf
http.csrf().disable();
}
3.2 基于角色或权限进行访问控制
3.2.1 hasAuthority 方法
如果当前的主体具有指定的权限,则返回 true,否则返回 false
- 修改配置类
- 给用户登录主体赋予权限
3.2.2 hasAnyAuthority 方法
如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回 true.
与3.2.1 比较,该方法是只要拥配置的权限中的一个就可以。
3.2.3 hasRole 方法
如果用户具备给定角色就允许访问,否则出现 403。 如果当前主体具有指定的角色,则返回 true。
底层源码:
修改配置文件:
注意配置文件中不需要添加”ROLE_“,因为上述的底层代码会自动添加与之进行匹配。
给用户添加角色:
3.2.4 hasAnyRole
表示用户具备任何一个条件都可以访问 。
3.3 注解使用
3.3.1 @Secured
判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。
使用注解先要开启注解功能!
在启动类或者配置类添加如下注解:
@EnableGlobalMethodSecurity(securedEnabled=true)
在控制器方法上添加注解
@RequestMapping("testSecured")
@ResponseBody
@Secured({"ROLE_normal","ROLE_管理员"})
public String helloUser() {
return "hello,user";
}
3.3.2 @PreAuthorize
先开启注解功能:
@EnableGlobalMethodSecurity(prePostEnabled = true
@PreAuthorize:注解适合进入方法前的权限验证, @PreAuthorize 可以将登录用 户的 roles/permissions 参数传到方法中。
@RequestMapping("/preAuthorize")
@ResponseBody
@PreAuthorize("hasAnyAuthority('menu:system')")
public String preAuthorize(){
System.out.println("preAuthorize");
return "preAuthorize";
}
3.4 记住我
3.4.1 创建配置类
@Configuration
public class BrowserSecurityConfig {
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new
JdbcTokenRepositoryImpl();
// 赋值数据源
jdbcTokenRepository.setDataSource(dataSource);
// 自动创建表,第一次执行会创建,以后要执行就要删除掉!
jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
3.4.2 修改安全配置类
@Autowired
private UsersServiceImpl usersService;
@Autowired
private PersistentTokenRepository tokenRepository;
// 开启记住我功能
http.rememberMe()
.tokenRepository(tokenRepository)
.userDetailsService(usersService);
3.4.3 页面添加记住我复选框
记住我:<input type="checkbox"name="remember-me"title=“记住密码”/>
3.4.4 设置有效
默认 2 周时间。但是可以通过设置状态有效时间,即使项目重新启动下次也可以正常登录。
在配置文件中设置
3.5 用户注销
在登录页面添加一个退出连接 :
<body>
登录成功<br>
<a href="/logout">退出</a>
</body>
在配置类中添加退出映射地址 :
http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();
then. 退出之后,是无法访问需要登录时才能访问的控制器!
3.6 CSRF
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已 登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个 自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买 商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。 这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的 浏览器,却不能保证请求本身是用户自愿发出的。
从 Spring Security 4.0 开始,默认情况下会启用 CSRF 保护,以防止 CSRF 攻击应用 程序,Spring Security CSRF 会针对 PATCH,POST,PUT 和 DELETE 方法进行防护。